View Single Post
Seren Seren's Avatar

JCF Member

Joined: Feb 2010

Posts: 866

Seren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to all

Feb 9, 2016, 05:04 AM
Seren is offline
Reply With Quote
Enums

Introduction

There are certain problems in scripting that simply shouldn't be solved with use of data types already offered by AngelScript. Consider, for example, the following scenario: you're trying to write a script in which players can keep a single pet animal at their home. They have a choice between 3 available pets: a dog, cat, or gold fish. Thus, the script must somehow keep track of what pet the player chose. You probably have some idea for a variable that could accomplish that, but in this tutorial I hope to show you that the superior way to do that is by using enums.

First, let's see the flaws of the three solutions that might've come to your mind if you don't normally use enums or classes. Perhaps you thought declaring an integer variable would be a good solution:
Code:
int pet;
We can now decide that when pet equals 1, the species is a dog, when it's 2, it's a cat, and 3 means a fish. This solution works, but it's not ideal - as you can see, we have to memorize a couple of numbers and their meanings. We could define a few constants to deal with that, but if we add a lot more pets later on, that will be a lot of constants, and we'll have to make sure the numbers don't repeat. Moreover, say you make a mistake and assign a value of something like 4 to the variable pet. This obviously won't be detected as an error by the compiler because 4 is still a valid integer, but your code probably won't know how to handle it and it certainly won't know where the erroneous assignment occurred, and you'll have to debug it manually.

Another naive solution would be to declare pet as a string. This seems a bit more intuitive:
Code:
string pet;
Now we can assign something like "dog", or "cat", or "fish" to the variable, and we won't need to define any constants to make it look good. However, you still have to be careful with what you assign - perhaps even more than previously. Nothing will save you if you make a typo in one of the strings or momentarily forget whether your convention was to type "goldfish", "gold fish", or perhaps "gold_fish". The compiler will not report an error, but your code will not work as you intended. Furthermore, strings are a quite costly data type to operate on, and not supported in switch-case statements, which is a very inconvenient fact for our cause.

Finally, you may have considered Booleans. Of course one Boolean won't do, so you declare three:
Code:
bool dog;
bool cat;
bool fish;
The problems here may not be immediately obvious, but this is actually the worst of the three commonly misused solutions. First off, each time you change the pet, you have to write to all of the variables, setting one to true and the rest to false. This is simple enough if you only have 3 available pets, but once you expand to 20, your code not only starts to look like a mess, but also becomes the least efficient of the discussed solutions. Not to mention that each time you add a new pet, you have to go through your code, find each place where the pet can be changed, and add a new variable to be set to false. Additionally, this is again one of the cases where the compiler won't report an error if you forget to set something to false, but you ought to find out something is wrong when there are apparently two pet variables set to true at the same time.

As we found, all of the presented solutions have some flaws. In particular, none of them will report an error if we attempt to assign a value that we don't consider valid.

Enums

Enumerated types, enumerations, or enums for short, are a construct designed specifically to solve this sort of issues. By registering an enum, you create a new, special data type. Variables of this type will be only allowed to take values defined by the enum. Specifically, to address our pet animal problem, we could define the following enum:
Code:
enum pet_type {dog, cat, fish}
Now, pet_type is registered as a new data type whose instances can only have values of dog, cat, and fish. It's important to understand that by writing this line, we haven't actually defined any variable. That line is only a definition of a type. To define a variable, we would write the following:
Code:
pet_type pet;
Looks strange? Perhaps, but it shouldn't. It's the same as any other variable definition - type name followed by variable name. The only difference between a definition like that and something like int pet; is that now the type is one you made yourself. In fact, after pet_type was defined as an enum, it became a type as any other, and can now be used in any other expression where built-in types such as int could be used, for example as a return type or argument in function definitions:
Code:
pet_type getPetOtherThan(pet_type animal) {
	// ...
}
Let's see what we can do with this new variable. As it turns out, not a whole lot. We can assign it a new value (e.g. pet = cat;), we can compare it to other values (e.g. pet == fish), and we can very neatly use it in switch-case statements:
Code:
switch (pet) {
	case dog:
		// do something with dog
		break;
	case cat:
		// do something with cat
		break;
	case fish:
		// do something with fish
		break;
}
But that's about it. And for our purposes, it's exactly what we want. For example, we really want the line pet = 1; to cause an error, because, let's face it, numbers are not pets, and if we wrote that, we must've screwed up. Code such as pet += 2; simply makes no sense, and thus it's completely reasonable that it won't compile (as opposed to what would happen if we used an int instead). Really, all it makes sense to do with a variable like this is set it and read it, and it's not a limitation of the type that this is all that's allowed, it's what we truly expected from it.

Conversions

It may interest you how enum type variables are stored internally. After all, you were taught that it's all ones and zeroes inside your computer and now suddenly you are allowed to assign a cat to your variable. In fact, on the inside, variables declared as enum types are the same as integers, and possible values, such as dog, cat, and fish, are simply integer constants for 0, 1, and 2.

Now, I know what you're thinking, at the beginning of this tutorial I told you that using an int would be bad and registering int constants would be tiresome, and now we're doing exactly that except in a more complicated way, and you're right that there would be no difference in the effect perceived by a person running our script. The reason we use enums is that we are humans. We make errors in our code, and enums let us know about them immediately. We can read organized code more easily, and enums provide organization. We have second thoughts, and enums let us quickly expand or modify code. Enums will not extend what you can do with scripts, but they make your life as a script writer easier.

Why is knowing the internal form of enums useful? Well, turns out it's not entirely internal and you can actually do some operations involving both enums and integers. For example, I told you that the expression pet += 2; is invalid. However, pet + 2 would in fact be a valid integer. In this expression, the variable pet is first converted to an integer, for example cat would be converted to 1, and then it is added to 2, giving 3 in the result. This value cannot be assigned back to pet, because it is an integer, but it can be assigned to an integer variable. It may seem strange that this conversion is allowed, but it finds use in some instances. Indeed, enums can be defined in such a way that specific identifiers are given specific integer values. For example:
Code:
enum Quantity {one = 1, two, dozen = 12, lots = 128}
By using the equals ('=') sign, you assign the integer constant on the right to the identifier on the left. If no value is assigned, as in case of two in our example, the value used is the value of the previous term plus 1. If no value is given to the initial term, it is given the value of 0, as happened to dog in our first enum.

This feature is sometimes used to simply define a large number of related integer constants in a row, but this can be considered abuse of the enum keyword, because if you don't need the additional functionality provided by enums; you could just use a bunch of const int definitions. As for valid use cases, it mostly comes in useful if you intend to serialize1 enum types, that is, save them into a file or send over the network. The latter is unlikely to ever be an issue in JJ2+ because the game ensures that the client and the server are running the same version of the script, however files are a completely realistic concern. Consider for example that you had saved a file that stores information about a player's pet. Then you worked on the script further and extended it so that different dog and cat breeds are available. You updated the pet_type enum accordingly
Code:
enum pet_type {
	golden_retriever,
	german_shepherd,
	corgi,
	
	scottish_fold,
	maine_coon,
	ragdoll,
	
	gold_fish
}
However, there's a problem. Many players already have save files from the old version of the script and if they load it in this new script, their pets become dogs. That's because values 0, 1, and 2 saved in their files all correspond to dog breeds in this new enum. We could rearrange the enum, but then we would have to sacrifice the organization by species. Instead, we solve it by giving explicit values to the identifiers:
Code:
enum pet_type {
	golden_retriever = 0,
	german_shepherd  = 3,
	corgi            = 4,
	
	scottish_fold    = 1,
	maine_coon       = 5,
	ragdoll          = 6,
	
	gold_fish        = 2
}
Notice that if you choose to do this, you are the one responsible for making sure all integer values are different, because repeated values in enums are allowed and won't be reported as errors or even warnings. Explicit values are often given to identifiers of serialized enums even when they're in the right order, to show clearly that the values must be maintained through different versions of the script:
Code:
enum pet_type {
	dog  = 0,
	cat  = 1,
	fish = 2
}
There's one more thing you're going to need to store enum values in a file, and that is conversion from integer back to an enum type. As stated from the beginning, usually attempts to assign integers to an enum type variable will be met with an error message, which is the desired behavior. However, when necessary, it's possible to convert an integer using explicit conversion, i.e.
Code:
pet = pet_type(2);
Remember not to overuse it because this operation is unsafe. AngelScript does not clearly specify what happens if you attempt to convert an integer that does not have a corresponding enum constant, but it suggests that the operation will not cause an error, and the result will be a pet value that is not one of the valid values specified by the enum. As such, it's best to make sure the integer is in the right range before converting it. As in most cases, the explicit conversion means the programmer takes responsibility for making sure this conversion makes sense. If you're writing a script library for others to use, you may also want to take into account that due to this functionality, enum values are not guaranteed to contain a valid enum constant and you should always include a default case in your functions for when that happens.

Summary

Enums are a language feature designed to make code more organized and readable, and errors easier to detect. Enums should be used wherever a variable can have several possible states that can't be reasonably described with numbers (5 is not an animal, 12 is not a piece of furniture). Variables of enum type support assignment, comparison for equality, and usage in switch-case statements. They can also be implicitly converted to integers, and integers can be explicitly converted back, however the latter operation is unsafe. When variables of enum type are to be converted to integers or back, it's best to assign the used constants the desired value in the enum definition.

Useful link:
AngelScript documentation of enums
__________________

I am an official JJ2+ programmer and this has been an official JJ2+ statement.

Last edited by Sir Ementaler; Jun 6, 2016 at 10:03 AM.