Cistron: an open source component-based object framework

Started by
31 comments, last by Raveler 12 years, 6 months ago
As promised almost a year ago in this thread on this forum, I now release my component-based framework as an open source project. You can find information, documentation, tutorials and a zip containing the source code on the following page: http://code.google.com/p/cistron/ Tutorial/example code using Cistron I have been using Cistron intensively over the course of this year while working on a large game project of mine (which is not ready to be announced yet), so it is definitely mature enough to be used in a real-life, demanding environment. The first strong point of Cistron of is its ease of use. I use several template tricks to hide programming complexities from the user, providing a smooth, yet powerful interface to interact with other components. The library is also extremely small and only depends on the boost library for its implementation, so it should work on any platform. Installation comes down to compiling the three source files and including Cistron.h wherever you want to use the framework. Its second strong point is its speed. The overhead of sending messages instead of calling functions directly can be reduced to near-zero. The slowdown, caused by mapping a string (which is usually used to identify a message/event) to a set of listeners can be smartly avoided. How this is done is explained in the documentation. It took me several iterations to get the framework to where it is now. It was a very long process because there is so very little information available on the internet about this type of paradigm. Also, I couldn't find any open source component-based frameworks at all, yet so many people seem to use one in game development. Therefore I think there is a huge demand for an open source project such as Cistron. If you plan on using Cistron for one of your projects, please let me know. I would like to know what it is used for. Also, feedback is hugely appreciated! [Edited by - Raveler on August 7, 2009 3:45:57 PM]
Advertisement
Quote:Example program using the component framework.


Really interesting and quite straightforward example.

How is the message system implemented internally?
[size="2"]I like the Walrus best.
Quote:The library is also extremely small and has no dependencies on other libraries (besides STL),

You forgot to mention you are also using boost.

Quote:Also, feedback is hugely appreciated!

The first header I looked at has namespace using declarations at global scope which is a really bad idea.

You are also using a singleton (which I will not comment on its use) but it is not safe. Firstly you use a hack to get the offset.
                CSingleton() {                        assert(!fSingleton);                        int offset = (int)(T*)1 - (int)(CSingleton<T>*)(T*)1;                        fSingleton = (T*)((int)this + offset);                };

Next the assert will only be hit in debug code and not release, in addition you do not define the copy constructor so it is auto generated meaning the class can be copied. These are the only two files I have looked at so far.
"You insulted me!" I did not say that in the private message Tom Sloper!
All the source code is available on the website, so you can see for yourself in detail.

Basically, I first map each string representing a particular message to a unique integer identifier. This identifier is then used to index into an STL vector (which is constant-time) containing a list of all the listeners. The magic is in the mapping to the intermediate id. Because you can do this mapping once, and then use the id directly instead of the string identifier for extremely fast message sending. If you use the identifier to send the message, there is only one function call overhead.
Quote:Original post by magic_man
You forgot to mention you are also using boost.


Doh, you're right. I took that as a given because it's so natural to use boost in a c++ project nowadays. I'll change it in the documentation tomorrow, because this is indeed a huge dependency.

Quote:
The first header I looked at has namespace using declarations at global scope which is a really bad idea.


You mean the 'using' statements? Sure, I can put those in the namespace.

Quote:
You are also using a singleton (which I will not comment on its use) but it is not safe. Firstly you use a hack to get the offset.
*** Source Snippet Removed ***
Next the assert will only be hit in debug code and not release, in addition you do not define the copy constructor so it is auto generated meaning the class can be copied. These are the only two files I have looked at so far.


Firstly, the hack is a hack, but is safe as far as I know. Secondly, asserts ARE only supposed to hit in debug mode and not in release mode. That's what they are for, so I fail to see your point. Thirdly, sure you can copy the class, it doesn't matter, the singleton is stored in a static member. Finally, I appreciate the feedback but you can also give it in a more constructive and less offensive manner. Thanks.
Why is that hack needed, anyway? I thought it was for VC6 compability, which no one uses anymore.

Second, what happens if I want two or more object managers? What if I spawn a minigame inside the real game and the minigame has its own (separate) objects?

Third, if sender is a component, why void* instead of Component*?

Forth, why void* instead of boost::any for the user data? (or is "payload" something else?)

I haven't taken a deep look at the code, these are just the first things that popped into my mind.

Edit:
Further observations:

Why pass Message by value instead of const reference?

Why have utility "register" functions that internally use a dynamic_cast (not a good idea, you might as well use a static_cast since you're not checking for NULL anyways)? Just let the user do the binding - no casting involved.
Quote:Original post by nullsquared
Why is that hack needed, anyway? I thought it was for VC6 compability, which no one uses anymore.


You're right. It's old legacy code I originally got from Game Programming Gems 1.

Quote:Second, what happens if I want two or more object managers? What if I spawn a minigame inside the real game and the minigame has its own (separate) objects?


Point taken. I'm not sure. I guess it doesn't HAVE to be a singleton, it can perfectly work without the requirement.

Quote:Third, if sender is a component, why void* instead of Component*?


Sender is Component*, where do you see differently?

Quote:Forth, why void* instead of boost::any for the user data? (or is "payload" something else?)


I'm not familiar with boost::any. I'll look into it.

Quote:Edit:
Further observations:

Why pass Message by value instead of const reference?


Good point.

Quote:Why have utility "register" functions that internally use a dynamic_cast (not a good idea, you might as well use a static_cast since you're not checking for NULL anyways)? Just let the user do the binding - no casting involved.


The utility function is there to provide a smooth interface. I had to type requestMessage("Msg", boost::bind(&A::fun, _1)) so many times, that I wrote utility functions to hide the bind from the user, changing the statement into requestMessage("Msg", &A::fun). But since the utility functions are safe anyway, you're right that I can as well remove the dynamic cast.
Well you do fail to see the point and after the following comment I have no interest in detailing them to you.
Quote:Original post by Raveler
Finally, I appreciate the feedback but you can also give it in a more constructive and less offensive manner. Thanks.


"You insulted me!" I did not say that in the private message Tom Sloper!
In addition to the null_squared's comments, this just doesn't belong in a modern C++ code base:

#define ObjectManager ObjectMgr::getSingletonPtr()

That one line manages to pollute the global namespace with a generically-named 'ObjectManager', which will over-write any other libraries 'ObjectManager' class, irrespective of scope or namespace.

While we are on the topic, 'Object' and 'ObjectManager' are terribly generic names - why did you choose to deviate from the generally accepted nomenclature of 'Entity'?

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by Raveler
Point taken. I'm not sure. I guess it doesn't HAVE to be a singleton, it can perfectly work without the requirement.


The reason why it's not desirable to have a singleton in default static context is because it makes it difficult to perform concurrent message passing, something such systems are naturally suited for. For message passing, either thread-local instances or explicit dispatchers are typically used.



As far as locks go - here's an alternative approach that allows modification of containers while they are iterated.

This topic is closed to new replies.

Advertisement