dynamic enum framework.. (compile time..)

Started by
9 comments, last by Zahlman 13 years, 8 months ago
Hey there..

Apologies if the title of ths thread isnt actually what im looking for.. its the best i could do to describie it in 3 words..

Basically i have a system that i want to make reusable across multiple projects. Part of that system allows users to query / access functionlity based on certain 'types' or 'categories' managed by the system, the types being determined by the needs of this particular project.

As a complete example.. (so dont look too much into the specifics of these types its purely for demonstration!) imagine that this system offered query functionality based on general 'entity categories'. So a user could query for data regarding 'hostile' / 'friendly' / 'neutral' entities.

Now in a single non-reusable system... the simple way would be to provide an enum within the sytem e.g.

enum E_EntityCategory{     E_EntityCategory_None = -1,     E_EntityCategory_Hostile,     E_EntityCategory_Neutral,     E_EntityCategory_Friendly,     //... any other types...     E_EntityCategory_Max};



The system functionality would just expect these.. as input... and could preallocate memory / resize containers appropriately. simple.

But then we have project 2... It also wants to use the system... but its entity categories are entirely different...

Now the project could be split up.. so we just rewrite the enum file based on the category needs of the current project. But what if we have one codebase... you cant really have both enums exist... ( i mean you could.. with some #ifdef project1.. etc magic.. but ugh) unless..

We give the enums different names e.g. E_projNameEntityCategory.. and then template the system to take the enum to use as an input parameter. But this just seems.. messy..? Plus theres no simple way in the system to refer to the 'max count' of these template enums for container sizing etc.

Ideally..

It'd be great if i could just grab the system and call

System.RegisterCategoryType( someCategoryOption1 )System.RegisterCategoryType( someCategoryOption2 )System.RegisterCategoryType( someCategoryOption3 )System.RegisterCategoryType( someCategoryOption4 )


BUT.. that'd mean i couldnt dynamically size containers in the system implementation to the max category size (And other such setup steps) until all the register stuff was called. And then things become horribly order dependent. That just represents more complexity for the end user. yuck.

Ideally i'd like something similar (i.e. a registration system) but that was done at compile time... Perhaps using some template wizardy (e.g. incrementing some count var each time a template type is instantiated).


e.g.
template< typename T >
RegisterCategoryType< T >
{
// increment some compile time counter
// manipulate type obj if necessary (e.g. setting its 'Id' to current count)
}



This would then allow the max count + ordering to be determined, containers could be preallocated to the correct sizes etc, and the actual system code could remain user friendly. This would require some object representing a type to be passed into the register func though (passed as a template param i mean... not an actual parameter).

Any ideas?

Alot of this probably makes no sense right now.. too early in the morning!.



EDIT: this is in C++ :)

[Edited by - NovaBlack on August 22, 2010 7:25:43 AM]
Advertisement
Is this what you're after?

Project A:
enum ProjectATypes{   FOO,   BAR,   // ...   PROJECT_A_TYPE_COUNT,};

Project B (depends on Project A):
enum ProjectBTypes{   ALPHA = PROJECT_A_TYPE_COUNT,   BETA,   // ...   PROJECT_B_TYPE_COUNT,};

Otherwise I'm a little confused as to exactly what you're looking for. Perhaps a more specific usage case would help?
Sorry :) my ramblings are a little confusing!!

its more like

Project A:

[SOURCE]enum ProjectATypes{   A_FOO,   A_BAR,   // ...   PROJECT_A_TYPE_COUNT,};



Project B (independent of Project A):

[SOURCE]enum ProjectBTypes{   B_FOO,   B_BAR,   // ...   PROJECT_B_TYPE_COUNT,};




but both project A and project B make use of the same system (e.g. a system written independently and included through say a library). This system needs some way of knowing about the types in the current project (so the system could be using project A's types, or Project B's types or Project N's types).

Essentially i need a nice way to allow any project (A , B or N etc) to 'plug-in' the types into the indpendent system. Then internally it can store things in terms of these types.

Hope that makes more sense.
If the 'system' you mention needs to know about specific types in project A, then by definition your system depends on project A. If this project also depends on the 'system', you have a circular dependency between two projects which indicates a design flaw in your project hierarchy. The solution is to design this system so that it can operate opaquely on data from higher-level libraries (i.e. 'int' instead of specific enumeration types), but without knowing exactly what this system does it's difficult to provide more detailed advice.
You know, posting what language this is about would help a lot... it looks like C++ but there might be other languages with same enum syntax as C++.
Thanks for the reply :) much appreciated!!

sorry, im working in C++!! (forgot to mention that .. oops!)



Yeah i totally see what your saying...sorry about my examples, its hard to talk about specifics when i cant 100% freely talk about specifics lol ;)

Hmm... perhaps it would be easier if i re-approached the problem. (As you said its not good if my system depends on the project, i definitely dont want that).

Essentially, at a high level, my system needs to read in some *data blobs* from whichever application (say a game) its being used in, on level load. This data will always be read in in the same basic format, that the system understands, and is independant of the application (i.e. my system just worries about reading in C_DataBlob objects) . All users of this system will provide this data to the system in the same format. So at least we only have a one way dependency there.

However, each data blob that is read in, need to be assigned a type. The *specific* types that can be assigned to each data blob, are project dependent. E.g. imagine 2 different game genres. An fps may have data blobs with types like 'hazard' 'slowTerrain' etc, whereas an rpg game may have completely different types like 'trap' or 'Anti-Magic-zone'. This is where the talk of enums came from, as game side at least, enums would probably be used to distinguish between types.

It is these *specific* types that differ between projects. The concept , at an abstract level of a C_DataBlob having a 'type' does not differ.

Now, going forward with what you are saying, it should be the case that the system just knows about this generic concept of a 'type' e.g. using an int, and doesn't worry about the project specifics. Which would be great. The problem is that this system then needs to provide functionality to easily allow users to filter for data by type. To do this i was hoping to store data in such a way, that accessing by type would be trivial. E.g. if a particular game had 10 types, my system could simply store a container holding 10 objects indexed by numeric type to provide access to all data of a particular type.

Now i guess the 'easy' way to keep the system totally idependent, would be just to read these data blob types as a generic 'int', count the different types as they are read in, and then voila, you have the number of types. Users of course could then pass enums through ( the system function signatures writen to just take type ints ) and access the correct data. I guess i was hoping there was some way to avoid this 'runtime' cost of analysing the incoming data, and resizing containers appropriately etc. hmm.

[Edited by - NovaBlack on August 22, 2010 7:23:37 AM]
Sure, this is possible. I actually am working on something slightly similar for a component-based entity/game framework. All it requires is some template work, basic inheritance, the typeid() operator and a few static variables.

First, if you've never heard of either pluggable factories (or, more specifically, the really cool trick used for autoregistry) or the curiously-recurring template pattern, I suggest reading the posted links. The pluggable factory technique is the core trick you're going to need, all remaining bits are basically ease-of-use tricks.

Next, I suggest creating a 'registrar helper' object that maintains all information about your type system. At minimum, I've found a method to retrieve the type name as a string and some sort of numerical type ID useful. Essentially what you're going to be doing with this is creating a static instance of one of these for each type you want to register and using C++'s static object initialization idioms to have this be handled automatically. For reference, see the 'm_registerThis' thing in the linked pluggable factory article.

Here's where things get cool. Fundamentally what we want to do now is come up with a way to actually achieve that 'one static registry thing per object type,' and I find a CRTP variant to be the most elegant solution here. This class is ultimately very basic, and more or less just serves as a vehicle to attach this registry helper without also cluttering up the class with inherited member variables, etc. You should be able to get away with something like the following:
template<typename ImplementingType>class RegisteredObjectTypeBase{public:	// These helper functions more or less just call into the 	// equivalent functions in the registrar helper, cutting down on typing.	static ObjectTypeID	GetTypeID();	static const char*	GetTypeName();protected:	// Obviously, swap out for your own type.	static TypeRegistrarHelper	_typeRegistrarHelper;};


Since you mentioned using something like this for serialization, you may want to include interface declarations for serialize()/deserialize() methods, though this may be better off placed in a separate abstract class that the aforementioned CRTP helper object derives from-- making an interface like that behave is a pain in the ass with templated types. Now all a user has to do in order to make an object visible to your system is have it inherit from this CRTP class, like so:
class VisibleTypeExample : public RegisteredObjectTypeBase<VisibleTypeExample>{	// Insert methods here};

Presto! But wait, there's more!

I don't think I mentioned the specific method(s) by which you can have the compiler automagically fill in the name of the type, but this, too, isn't rocket science-- enter typeid(), as in the following:
template<typename ImplementingType>ComponentRegistrarHelper RegisteredObjectTypeBase<ImplementingType>::_typeRegistrarHelper( typeid(ImplementingType).name() );
(this can be placed in the CRTP base class header file and assumes a constructor for the helper object is defined, taking a single const char* as an argument.)

The second cool trick the CRTP does here is to allow you access to the C++ type that you want to register. The type_info structure that typeid() returns contains a useful member, 'name,' that contains the name of the type. Unfortunately for us, however, the specific content of this is compiler-dependent and almost always not what we're after. That being said, there is a way to turn it into something practical:
#if( ET_COMPILER_IS_MSVC )#	include <cstring>#elif( ET_COMPILER_IS_GCC )#	include <cxxabi.h>#endifconst char* UndecorateName( const char* src ){#if( ET_COMPILER_IS_MSVC )	// MSVC decorates the name with both the storage type and any namespaces, so we have to prune that	// Since scope is delimited with '::', searching for the last instance of the ':' character will	// remove *all* scope qualifiers	const char* namePos = strrchr( src, ':' );	// Alternately, we might be in the global namespace-- thus, we just need to remove the 'class'	// keyword and beginning whitespace	namePos = (NULL == namePos) ? strrchr( src, ' ' ) : namePos;	return namePos+1;#elif( ET_COMPILER_IS_GCC )	// GCC actually has a pretty standardized way of doing this, but we need to free the result	// Fortunately, we save the final name anyways so this is a trivial problem	int status;	return abi::__cxa_demangle( src, 0, 0, &status );#endif}

If building on GCC, you will also need to free() the resulting character string, as mentioned in the comments. Operator delete[] will not work. Since I mentioned that storing the name was a good idea and something I do anyways, you can simply pop this in the type registrar helper's destructor and have everything work fine.

From here, you're free to add any sort of additional indexing information into the type registry, etc. and/or implement additional serialization functionality, as per the linked article on pluggable factories. As it's entirely possible that I missed something here, please feel free to ask questions if there's something you don't understand or if you run into implementation issues. I'm actually quite fond of how nicely this came together for me :)

EDIT: Realized I forgot to include a few needed headers in the undecorate function, so that's fixed now.
EDIT 2: Also clarified how typeid() fits into the whole shebang.
clb: At the end of 2012, the positions of jupiter, saturn, mercury, and deimos are aligned so as to cause a denormalized flush-to-zero bug when computing earth's gravitational force, slinging it to the sun.
Quote:Essentially i need a nice way to allow any project (A , B or N etc) to 'plug-in' the types into the indpendent system. Then internally it can store things in terms of these types.


Suppose that you did a system like that. After that, how are you going to use that? Let me be clear with an example:

enum projA { FOO, BAR, ..., ETC };enum projB { AFOO, ABAR, ... ,AETC };void doSomething(int enumNum) {  /*what will be there. If FOO and AFOO starts from the same position (0) then what will be the difference? If not, are you going to add each of these enum members into a switch statement or something?*/}
@ Kasya

Hey hope this explains it more...

essentially im writing a reusable c++ system that depends on a client side enum. This enum will differ based on the project using the system.

[source lang = "cpp"]// declared in the projects filesenum projA { FOO, BAR, ..., PROJ_A_MAX  };    // say PROJ_A_MAX  == 10 i.e. game 1, an rpg,  has 10 different entity typesenum projB { BFOO, BBAR, ... , PROJ_B_MAX };  // say PROJ_B_MAX  == 20 i.e. game 2, an fps, has 20 different entity types// declared in my system's files// Container for data blobs  of different typesC_DataBlob dataBlobArray[MAX_NUM]; // where MAX_NUM is either PROJ_A_MAX || PROJ_B_MAX depending on which project is using the system.... void doSomething(int enumNum) {/*     Foo and BFoo do start from the same position (0)    However doSomething(int enumNum) doesn't want to care wether enum projA     or enum projB is being used. ( NOTE that only one is ever used at a time,     if proj A is running the proj A enum is used, if B is running enum B is     used )    The system just knows *some* type is being     used that starts at 0 and has a limited count.     At runtime The data blobs read in and placed in dataBlobArray need to match     up by index e.g. if project A is using the system and a data blob with a     type field of FOO is read in, it needs to go in index 0 of data blob array.     Alternatively if project B is using the system for another game.. and a     data blob of type BFOO is read in, it also needs to go in index 0 of data       blob array...     And herein lies the problem.. I dont want the system to specify the       *specific* enum used. It just wants to know 'an' enum has      been used that can be mapped to an integer range, and how to map it.        I.e. essentially its like a system where a project specific enum can be     'plugged in'. Ideally i'd like this to be a compile time thing to.. almost     like auto generating an enum system side that matches.      Essentially i want a solution whereby the specific types can be defined      game side.. but the system knows enough to  know how many    types there are and can store incoming data blobs say.. sorted by type..     i.e. it can read an incoming data blob and know that say BBAR maps to     index 1 etc*/}




@InvalidPointer

Thanks!!! i'll take a look at those articles and see if its what im after. Thanks for all the code samples too, looks awesome. Scary at first glance :P but im sure if i read it enough ill get it. Been a while since ive written much templated code.

[Edited by - NovaBlack on August 22, 2010 1:40:19 PM]
Infact the more i think about it..

mapping the incoming data isnt even a problem.. if a data blob comes in purely with an integer as its type.. thats the mapping done! (e.g. data blob with integer type of '7' goes into index 7 in the data blob array.. (well 6.. since we start at zero)

then when somebody wants data of a certain game side type.. all the system cares is that say.. the client wants (int)(E_GameSideType7)... eg. type 7. Job done. the system doesnt care about the specific types..

I guess the only problem was purely how to let the system know the max number of types the current project is using (which is only known game side).. so it can size everything at startup.. rather than pushing back crap and resizing stuff at runtime. (ideally knowing this at compile time would be best)

EDIT: still reading your stuff InvalidPointer! :)

This topic is closed to new replies.

Advertisement