Skip to content

Internal Engine Layout

degenerated1123 edited this page Dec 6, 2016 · 1 revision

Internal Engine Layout

Entities and Components

REGoth uses a component-based approach. That means, that on the base level, we don't really have old fashioned objects, which are a single class, using inheritance to modify their behavior (like the original ZenGin did).

To give a quick round-up about components: Objects in the World of REGoth are called "Entities". Each entity just has a field, which denotes what further features are needed (as in enabled) for this very instance. This is where components come into play: An entity might need a position inside the world and a bounding-box (like some kind of trigger). An other entity might need a position and a static mesh to draw. Inside the entity you can then choose, which of these features you are going to use!

However, all components are actually there and ready to be used. They are just switched of when they are not needed.

They why to that is about memory managing. The fastest access of memory, is memory already inside the CPU-Cache. To keep cache misses low, you need to have your memory allocated in order, as a single continuous block, if possible. By having memory for all components for all entities, we can then say, that the entity at index i possesses all components with that index i! Ultimately an "Object" is then made out off all enabled components with the same index in their arrays!

So, we waste a bit of memory (Which is not really a problem these days, anyways) to get blazing fast speeds when iterating through all objects or just accessing many of them.

For example, you could go and create entities like this (pseudocode):

myTrigger = world.createEntity();
myTrigger.addComponent<Position>();
myTrigger.addComponent<BBox>();

addComponent would in that case just set a bit inside a bitmask, corresponding to the given component to activate.

To actually get a component of an entity in REGoth, you would write this (not pseudocode!)

World::WorldInstance& world = ...; // Some world
Handle::EntityHandle e = ...; // Any entity, imagine a rock or something

// Get static-mesh of entity 'e'
Components::StaticMeshComponent& smc = world.getEntity<Components::StaticMeshComponent>(e);
smc.doSomething();

Handles

Because dealing with pointers directly would be dangerous and keeping our memory continuous blocks would be hard if we did, REGoth uses handles for most of its objects.

A handle is returned by one of the allocators (StaticReferencedAllocator.h) you can find mainly in the WorldInstance-class. For example, a widely used type throughout REGoth is Handle::EntitiyHandle, which holds a reference to an entity as discussed above.

When requesting an object from one of the allocators, it will mark a slot inside its internal array as "used" and give you the handle to that, which is basically an index-value to this slot.

Using a second list, to which the handle actually points to (FreeList.h), we can reorder the actual memory slots just as we want and certify that there are no holes, even after deleting an object.

Dealing with invalid handles

Now, imagine you have taken a handle h, which internally points to slot n. Then, you free the slot again by "deleting" the underlying object of h. You now have an old handle. If that was a pointer, you'd now have an invalid pointer. If you then request another object, which takes the slot n, because it was marked free, you wouldn't get a crash when trying to use h again, but rather not the object you were expecting (Ie. a different NPC).

That is a problem, but luckily, there is a nice solution to that: For each slot, the generation is being tracked. If the slot gets requested, generation is bumped. If the slot gets freed again, generation is bumped. Every handle also stores the generation the slot had, when it was requested.

So if you would come with handle h, even though you already removed its object, the generations would not match and you would know that this handle is invalid, because the object has been deleted.