I've been making some pretty massive changes to the GameEngine in SVN. I'm basically going through step by step and merging pieces from the core of my independent similar project and the lessons learned from that. So in a semi-random fashion here's a break down of what I've changed or have in progress.
__________________________________________
I’ve modified the event system to use an ‘EventManager’ class that handles the registration and identification of various events. In order to remove the current requirement that event classes be part of the engine core, I’ve changed ‘GameEvent’ into a simple class that contains its type identifier and a reference to a simple stream type class. When the event is handled, the components have read/write access to the stream.
The obvious problem with using a stream for passing the data around is knowing what’s in the data. Programmatically there is no means to know this, though I’ve considered adding a datatype identifier to each piece of data, so aside from a name the Event Manager can also take a description of the event as well as a ‘data profile’ that can be dumped to a text, xml, or html file to document the events currently in use.
Code:
class GameEvent
{
public:
GameEvent(unsigned short id, GameComponent* sender) : id_(id), m_sender(sender), data_(TBinArc()) {}
GameEvent(unsigned short id, TBinArc& data, GameComponent* sender) : id_(id), data_(data), m_sender(sender) {}
~GameEvent() {}
const GameEvent& operator= (const GameEvent& other) {
id_ = other.id_;
data_ = other.data_;
m_sender = other.m_sender;
return *this;
}
GameEvent(const GameEvent& other) : id_(other.id_), data_(other.data_), m_sender(other.m_sender) {
}
const unsigned short id() const {return id_;}
TBinArc& data() {return data_;}
const GameComponent* sender() const {return m_sender;}
protected:
unsigned short id_;
private:
TBinArc& data_;
const GameComponent* m_sender;
};
I also whipped up a very plain macro syntax for plugins to use to register events and/or obtain their Ids for their manager’s to store as static members (static members being preferred over using the event manager’s getEventID(std::string) function to avoid a hash function followed by a map search. Events cannot have clashing names. Attempts to re-register an existing event will return the existing event id, meaning you can register every single event a plug-in needs without scrambling anything, assuming you are using it as all the others expect.
Code:
class EventManager
{
public:
EventManager();
EventID registerEvent(const std::string& aEventName, const std::string& aEventDesc = std::string(), const std::string& aDataDesc = std::string());
EventID getEventID(const std::string& aEventName);
std::string getEventName(EventID aEvent);
void dump(const std::string& aFileName);
size_t eventCount() {return events_.size();}
private:
struct EventData {
EventData(const std::string& aName, const std::string& aDesc = std::string(), const std::string& aData = std::string()) :
name_(aName), desc_(aDesc), data_(aData)
{}
const std::string name_;
const std::string desc_;
const std::string data_;
};
EventID nextId_;
std::map<EventID, EventData> events_;
std::map<unsigned long, EventID> nameIds_;
};
Code:
#define EVENT_DEC(aEvent) static unsigned short aEvent;
#define BEGIN_EVENT_MAP(aClass) define E_MAP aClass \
include <GameEngine/GameModules.h> \
include <GameEngine/GameWorld.h>
#define EVENT_SET(aEvent) unsigned short E_MAP ## :: ## aEvent = GameModules::gameWorld()->getEventID( #aEvent );
#define EVENT_REG(aEvent, aDesc, aData) unsigned short E_MAP ## :: ## aEvent = GameModules::gameWorld()->registerEvent( #aEvent , aDesc, aData );
#define END_EVENT_MAP() undefine E_MAP
#define GET_EVENT(aEvent) E_MAP ## :: ## aEvent
Other changes include overhauling the scene format and implementing more thorough (de)serialization, to either a D.O.M. that can be written to a human readable file (XML and a C-like syntax are implemented), or any other parser/writer that can cope with a tree that could be added, or a binary stream. Any GameObject should be able to write itself out to an XML or w/e string through the API that could be dumped to a file or read into an editing tool as well as able to reinitialize itself and components from a string. Lastly, on serialization, components (and the GameObject) should be able to dump their XML requirements and possibilities (to the best of their ability to be intelligible). Serialization and deserialization is mostly there, just need to go through the components. Of course, this means that the configuration now comes from elsewhere.
Code:
<Scene>
<Meta>
<Name>Super Duper Scene of Awesomeness</Name>
<Brief>One liner goes here</Brief>
<Description>Insert some descriptive text here.</Description>
</Meta>
<Resources>
<Res file=""/>
<Res file="" cache="true" preload="false"/>
<Res file="" cache="true" preload="true"/>
</Resources>
<Content>
<Constant>
<Group name="Trees"> <!-- Meaningless, intended to aid dev for partitioning stuff -->
<GameObject name="">
<Modules>
<Graphic resource=""/>
<Physics res="" />
</Modules>
<Properties>
<Vec3f name="position" value="1.09, 1.028, 12.0" />
</Properties>
<Data>
<Int name="boxes" value="23" />
<Int name="boxes" value="23" />
<Int name="boxes" value="23" />
<Int name="boxes" value="23" />
</Data>
</GameObject>
<Reference file="" />
</Group>
</Constant>
<Persistant>
<!-- Objects and such that aren't constant go here -->
<!-- Optionally feed a seperate persistance file to the parser
to recover saved state data -->
</Persistant>
</Content>
</Scene>
The "meaningless" comment about groups can be ignored. Today I just set everything up to be able to serialize just a group as well as just read in a group, e.g. the ground level for paging.
A property registrar as a part of the GameObject, to which the object and any components can register members for braindead serialization, which also should be available through the API so that tool components like property grids can display and alter them. Here the component changes to no longer initialize itself when it loads its data, but does such when the entity decides to initialize its components. This way alot of the data can come straight from the dynamic and data set and property map, making setting up a component for serialization a jiff. Lastly on the properties and data issue, they're using OpenMP macros when setting and grabbing data for some thread safety.
A resource management class that can cope with caching and background loading to which GameObjects can subscribe to for notification that a resource is ready (<- todo) and conceals the differences between actual file based resources and archived (PAK-type) resources. Other than the packaged resource it's basically done as a core component manager that is automatically added (so that it can run during the thread loop to deal with some tasks like slowly releasing the cache and lazy loading).
Lastly every component should include a function to return the events that it is concerned about (yes, check event could be used but it typically does some checking and other processing too) which the GameObject can access and return through the API. Again, this is more of a tool support function but could be useful to components that may want to know if anyone is handling an event (e.g. a ScriptComponent may want to do some developer checking and say
“uhhh, you told me to do this on this event, but that event is never going to occur”Things still unimplemented and in the bag of tricks include event recording (undo/redo/playback)