Skip to content

EnumMap

Nightinggale edited this page Jan 22, 2022 · 1 revision

What is an EnumMap

Our custom EnumMap class follows the Java standard for EnumMaps. This means it's a variable for each possible enum value. For instance EnumMap<YieldTypes, int> has an int for each type of yield.

Our implementation is optimized for xml usage meaning for the most part the enum will have to be an enum of Types from an xml file. This means the programmer doesn't have to configure anything other than the declaration as the code sets length and stuff based on the type alone.

The code is prepared for lengths based on something other than xml files. At the time of writing, CityPlotTypes is the only one implemented, which sets itself to the number of city plots and it will change based on colony radius.

Use cases

EnumMap is an array internally and is used in cases where an array would make sense. This means read/write of variables as well as looping (though see "EnumMap vs InfoArray" below for looping). The memory allocation is automated as in memory is allocated when needed and is freed automatically in the deconstructor. This should avoid leaking memory even without considering memory. It is however possible to command an EnumMap to allocate for free memory if it's really wanted (freeing resets all data).

Usage

Like an array. Declare the EnumMap in the function or as a member function like EnumMap<type, T>. Type is used as index type unless stated otherwise in EnumMapSpecialization.h. At the time of writing it only lists CityPlotTypes for being special in the sense that it uses int indexes. In general this isn't much of an issue as it's a compile time error for mismatched types, hence errors will be detected.

EnumMap.h lists a number of functions, which can be used. The most common are get and set. Those works in all cases except if T is a class. To access classes, reference should be used using the index [] operator. Note that this will force the array to be allocated even if it's not needed and as such should likely be "guarded" by if(em.isAllocated()). The [] operator works on int types (int, unsigned short, etc) too, but not enum types and not bools. While I would generally recommend avoiding it, in cases where you know it's allocated and perhaps want to use += or similar, then it could make sense.

allocate, isAllocated and reset are used in relation to memory allocation. reset() will in fact free the memory and unallocated arrays will be treated as having the default value in all indexes.

Looping, range and constants

Each EnumMap has 3 constants named FIRST, LAST and NUM_ELEMENTS. Those can be used freely and should be used. While it can be used for range checks, isInRange(IndexType eIndex) is also always available and will do just that. Those 3 might be set with init code or they might be hardcoded at compile time. You shouldn't care about that, but if they are set at compile time, then it unlocks extra compile time optimization.

EnumMap<UnitTypes, bool> em; 
for (UnitTypes eUnit = em.FIRST; eUnit <= em.Last; ++eUnit)

That's how loops should be written. On top of benefiting from compile time hardcoding, hence compile time optimization. It also removes function calls, which is helpful for debugging. The last reason might be the most important one. FIRST and LAST might not always be what you expect. EnumMap is prepared to handle subsections of enums, like AnimalUnitTypes (not implemented) would set FIRST to the first animal and LAST to the last, effectively cutting down a lot on both loop iterations and memory used to store the data. If FIRST and LAST are used consistently then all it takes it changing the variable type in the declaration and all code will update accordingly.

EnumMap vs InfoArray

In some cases both EnumMap and InfoArray-(cpp) can be used. This is when looping an array.

Looping

EnumMap<UnitTypes, bool> em; 
for (UnitTypes eUnit = em.FIRST; eUnit <= em.Last; ++eUnit)
{
    if (em.get(eUnit))
// InfoArray<UnitTypes> iArray; 
for (int i = 0; i < iArray.getLength(); ++i)
{
    const UnitTypes eUnit = get(i);

Two different approaches to looping. The difference is that the EnumMap will check each variable for true values while the InfoArray will only trigger on true values. This means EnumMap will always have NUM_UNIT_TYPES (193 at the time of writing) iterations while InfoArray will have say 2 if only two are set. This will make InfoArray way faster, particularly in cases where most are the default value (bool=false, int = 0, YieldTypes = NO_YIELD etc)

InfoArray can however not be altered. Changing a value means converting it to an EnumMap, set the new data in the EnumMap and then convert it back into an InfoArray. This is slow. If the data changes often then it could be faster to just keep the EnumMap at all time. If the data changes rarely or never (like once every time player gains a Founding Father and not even with all of them) and at the same time is looped often, then InfoArray is to be preferred.

Random access

If interested in a specific value, like say a unit wants to look up the value for it's own UnitTypes, then EnumMap is faster because being an array it's read in constant time (which is also fast). InfoArray will have to loop through to find the value matching the type without knowing if it's even there.

Converting from InfoArray to EnumMap is somewhat fast meaning it would make sense to convert if multiple random access calls is to be made. It's converting from EnumMap to InfoArray, which is slow.

Altering data

InfoArrays are meant to be static and while technically possible to change it isn't recommended if changing it happens multiple times each turn. EnumMap is however fast when it comes to changing data.

Changing between EnumMap and InfoArray

This is simple. InfoArray has 3 functions for this called assignFrom, addTo and copyTo. Each take an EnumMap reference as argument and template types are checked for compatibility at compile time. AssignFrom(EnumMap) and copyTo(EnumMap) both copies the data while addTo(EnumMap) will add to the data in the EnumMap, effectively allowing to sum up multiple InfoArrays if needed.

Mismatching template parameters is sometimes allowed like adding InfoArray to EnumMap<YieldTypes, int> will do +1 to each index mentioned in the InfoArray.

addTo has an iChange argument (default 1), which can be set to -1 to subtract an InfoArray.