Dev Question: Standard Enums

Hi again,

I have one other question. I would like to standardize our approach to enums within the API, for example, openfl.display.CapsStyle. There are a few approaches we could use:

A standard enum

enum CapsStyle {
	
	NONE;
	ROUND;
	SQUARE;
	
}

Public static values

class CapsStyle {
    
    public static inline var NONE = "none";
    public static inline var ROUND = "round";
    public static inline var SQUARE = "square";
    
}

An abstract enum

@:enum abstract CapsStyle(String) from String to String {
    
    var NONE = "none";
    var ROUND = "round";
    var SQUARE = "square";
    
}

I have seen it done all of these ways before.

A standard enum is light, fast, and simple. The only downside is that it does not convert to or from a String, and is technically not how it is implemented in Flash Player. The “fake enum” construct is used in core Haxe definitions for Flash to create enums that behave like Haxe enums, but really are not.

Using public static values solves the “is a String” issue, but removes the ability to nicely switch over the value. To me, it seems crude, and doesn’t take advantage of Haxe as well as it should.

The abstract enum is a bit heavier than a standard enum, if only because it uses String values instead of a basic Int value, but this could be solved. The real strength of an abstract enum is that you can use it as a String (like you would in Flash Player) but also switch over it as a Haxe enum. It’s what “fake enum” should have been all along.

One downside, though, to an abstract enum is that you cannot extend it (which may be fine in these cases) but also cannot reflect against it, so no Type.enumIndex or Reflect.field

I would like to pick an implementation and standardize all of our core enums to work the same way. What seems like the best path?

Thanks again :slight_smile:

I would say Public static values.

For me its always about trying to align to Flash’s API.
this makes porting of code and AS3 programmers a-lot easier…

2 Likes

I ran into the very same issue today and ended up using the public statics approach due to my usage of Flash Player. However, I think the last one, the abstract enum, would be nicer - usually we don’t need to extend these enums: at least on my case I usually create my own with the new values and keep using the existing ones as needed.

I have to agree with @scianid !!
same reason as here as well.

1 Like

For maximum insanity: Use a macro to generate something like this!

@:enum abstract CapsStyle(String) from String to String {

  var NONE = "none";
  var ROUND = "round";
  var SQUARE = "square";

}

class CapsStyleValues {

   public static inline var NONE = CapsStyle.NONE;
   public static inline var ROUND = CapsStyle.ROUND;
   public static inline var SQUARE = CapsStyle.SQUARE;

}

Probably not worth the trouble :stuck_out_tongue:

I think that is what the abstract does for you, no?

I agree with you. Although the enum approach is much “better”, but the static properties approach is much more compatible.

If we go with public static values, you are allowed to do this code:

var style = CapsStyle.ROUND;

…and if it is inlined, you may also switch like this:

switch (style) {
    
    case CapsStyle.ROUND:
    default:
    
}

However, you lose some of the benefits of enums. For example:

switch (style) {
    
    case ROUND:
    
}

In an enum, we are allowed to skip the prefix (optionally), this code will also throw an error that you are missing cases for some of the styles. If you match styles, you do not need the default: as you would with a standard String switch.

Now, if we went with an abstract, we could theoretically merge both benefits:

var style = CapsStyle.ROUND;
trace (style); // "round"

switch (style) {
    
    case ROUND:
    case SQUARE:
    case NONE:

}

Theoretically, the abstract enum is similar to the public static inline var, but better. If we do not inline on a class, then we could use reflection, but then you could not switch on the values at all. I think being able to use a switch statement is pretty important?

I’m heavily biased towards abstract enums since they’re so cool. If basing the enum on String is an issue, do you think it would be performant to base it on Int and then @:to @:from to get it to String when necessary?

1 Like

I thought about that, I think only if you typed it to Dynamic then did Std.string it would be an issue, but I’m not sure that’s really something people would commonly do. I wondered if an Int would perform better on C++ than String, which might cause allocation/garbage collection

Something like this?

http://pastebin.com/A8CBQAHh

2 Likes

An abstract enum gives the same benefits, and more. The values act like public static vars if you want to use them that way, but you also have all of the syntax sugar that comes with enums.

@singmajesty Yeah, exactly. I’m not familiar with cpp, so I’m not sure about those switches as far as String allocs. But how often does a MouseEvent or CapStyle need to be a String, outside of tracing the value of something?

It needs to be a string when you are porting as3 code that was written as a literal string.
Or maybe it was assigned to a class variable that is typed to string.

This will make porting complicated. Though i get why abstracting it can also be a good solution.

Im neutral between string and abstract solutions. Enum for me just seems not consistnce with as3 .

Okay guys,

I’ve implemented them as abstract enums for right now, all our tests are passing so far.

Many of the values on C++, Neko or HTML5 used standard Haxe enums, which meant that Std.toString or using a string literal would have failed.

We also did some research on the performance impact on C++, and an abstract enum looks as if it should perform just as well. As a result, we should have consisten enum behavior on ALL targets (including Flash) and will support string values (var style:CapsStyle = "round";) now, which we did not before, while still supporting standard switching. Thank you everyone for your feedback, I think this is a win :success:

2 Likes

What I mean is, is that often enough to impact performance? (Int) is better than (String), but already being a String is faster than the @:to. If the latter only ever occurs when porting as3 code and in niche debug situations, it’s not a big deal if that loses a slight bit of performance. Versus dealing with String allocations every time an event is mentioned internally.

1 Like

Technically, the “normal” enum is the best solution on c++. You can think of it as an object that has a string (name) and index bound together. When you switch on an enum, it uses a simple function call to get the index, and then compares the indices.
Carrying around constant strings has no GC allocations for hxcpp, so that is ok. The main cost is that you must use string compares, rather than int compares/native c++ switches.
The flash target implementation of “extern enum string” is an interesting hack, but seems to work on that target.
Normal enums can easily go to indices and strings, the main problem is going from a string to the enum (or the enum index), which is the as3 case “normal” usage case. But you only need to do this when the developer calls one of the enum functions with a string. So all you really need then is an abstract class that casts from an enum or a string to a enum for each of the enum arguments. So the “real” implementation is based on enums, but if the developer passes “round”, then the abstract will create a enum via Type or a string map and pass that in instead. This gives developers an easy way the get maximum efficiency - use the enum constants, rather than simple strings. If you base your code on string constants, there is no way for the developer to use integer-based compares.

Using an abstract(Int) rather than one based on Strings, with casts to and from string, and static inline constants to allow switching is also pretty close to optimal as far as performance is concerned. I guess it would come down to how Dynamic works.

3 Likes

I think “enums” should be chosen based on the performance of the target. Perhaps try a solution that would benefit all targets in terms of performance. It’s not necessarily about trying to make it easier for Flash users to use OpenFL, just because the API is the same.

We all know Flash Player is dying, and for people making serious video games, they wouldn’t even touch that target. Video games are primarily written in C++, and so it would make more sense to use standard enums over any other form, since it is most efficient on the target most people will be making games on.

I don’t think it’s a good idea to attract ex-Flash developers purely on an API basis, the API should suit all targets not just one based on that one API decision. Consequently, I would personally choose standard enums as they are light-weight, and for serious video game developers, this is the most appropriate choice.

I agree – I see Flash Player more as a supported OpenFL target now rather than the exact image of what all targets need to reflect. It’s a good guideline for expected behavior (of course!) but we don’t want to hamstring other targets by trying to build an emulator.

Thanks to Hugh’s suggestion, I’ve moved to an abstract(Int) implementation with matching to/from String functions for conversion. This should be light with basic Int values in common use (similar to a traditional enum) but has the smarts to convert as a String in the (possibly few?) cases where someone will want that. I think this gives us the best performance, while still improving compatibility.

var style:CapsStyle = "round";

style = SQUARE;

switch (style) {
    case ROUND: trace ("it's round!");
    case SQUARE: trace ("it's square!");
    case NONE: trace ("no cap style :(");
}

trace (style);

In the ways that matter, it should (like in the above code) behave now as an enum, as a string, and as an int internally :slight_smile:

4 Likes

Hey @Hugh_Sanderson,

I tried doing an Int-based abstract, with custom @:from and @:to methods for String. The only problem (I realized) is that this is not nullable like a traditional enum. Do you think a Null<Int> abstract would still perform better than a String abstract, or perhaps would be similar to a normal enum? (or is null assignment a special case there?)

I tried to force it to allow null to be a 0 value, but I could not find a way to control abstract assignment, value = null; would throw a compile error, despite the @:from and @:op methods I tried :frowning:

Hi again everyone,

So as a follow-up, here is where we’ve been ending up.

In order to keep the same Haxe standard behavior (where enums are nullable), we have moved to using Null<Int> abstracts. Looking at the source code for both C++ and JavaScript output, I believe Null<Int> may be as or more efficient than standard Haxe enums.

I had an idea for being able to use regular Int enums (but turning value = null into value = 0 through an abstract), but (to my knowledge) this does not seem possible. I’ve opened an issue in case this can improve, or I am wrong:

In the meantime, we should have an object that behaves like a regular Haxe enum, but can convert cleanly to and from a String. This should possibly improve performance, and improve compatibility with older Flash code that may use capsStyle = "round" and so on :smile:

The only negative impact I’m aware of is Haxe serialization, older enum values will not resolve to our new ones, but this was going to be the case with any change we need. On the topic of serialization, I believe that this will be more efficient as well.

Thanks everyone :slight_smile:

1 Like