Components are the main building blocks in Cogs. Entities in the scene/model are not classic "fat" entities (like for example the node kits used in Inventor) but are composed of one or more components, adding data and/or behavior to the entity.
Most components define an aspect of the entity combining data and behavior in specific ways. The behavior is in many cases moved to a Component System, handling all instances of a given type of components.
Read the design document chapter below first if you are new to Components.
The Cogs Bridge functions used by Cogs Demo/Cogs.Net/Cogs.js applications will correctly set a field and mark only the given field changed using Component.setFieldChanged(..) and trigger an engine update. The Bridge functions does not check if the field actually changes value. It is up to the application to do this checking if saving a rendering update is important.
Applications using Cogs Components directly will often just call Component.setChanged(), e.g. mark all fields changed (most components/systems does not implement individual field change checking).
All component updates goes via the owning component system. The systems are sorted by priority.
Default behavior of Component change handling in a system is:
prepareUpdate(context) => All components: call component.resetCarryChanged();
update(context) => Handle updates for all components.
postUpdate(context) => All components: call component.resetChanged();
The resetCarryChanged() - update() - resetChanged() sequence ensures that each component will know if any fields are changed in update() and that the component is not changed at next redraw.
A component or its owning System should check if any fields changed using Component.hasChanged() and only do further processing if anything changed.
Checking individual fields is done using Component.hasFieldChanged(FieldId) (Cache the fieldIds as field ID lookup is slow).
If the component reads data asynchronously it should mark a field changed and trigger an engine update when data arrives.
So the logic should be for non-trivial updates: detect change from flag (supposed to be super-cheap), and if you have a change, check for actual change.
Components should not unset changed flags, definitely not unset other component's flags. That might introduce false negatives.
Sometimes a component may need to change other components. The normal way to do this is to set a field value and call Component.setFieldChanged(..).
Location of other component:
A. The component is in a system rendered Earlier. A new engine update must be triggered.
B. The component is in the same system. If the Components are DynamicComponents, relying on update order is error phrone - triggering engine update recommended. In other Systems the system can correctly do cross-component updates (if implemented).
C. The component is in a system not yet rendered. Updated is handled when component updated. No need for second pass.
The safe and easy way is to always trigger an engine update, but this may waste resources.
It is possible to use Component.setChangedTransient() or Component.setFieldChangedTransient(..). These changes are reset when frame rendering is completed and requires expert knowledge to avoid loosing updates.
This is relevant for components storing data used by other components in the Entity, possibly also in another entity.
A safe way of handling this is to use Component.setGeneration()/getGeneration().
Designs involving components and entities are often referred to as Entity Component Systems or just Entity Systems.
There are many different ways of solving the actual implementation issues in such systems, many of which are described and discussed in the following literature.
Traditionally we have used and defined a lot of object or entity models and hierarchies like the following example:
class Entity
{
int id;
virtual void update() {}
virtual void render() {}
};
class SceneEntity : public Entity
{
vec3 position;
quat rotation;
void render()
{
auto t = CalculateTransform(position, rotation);
ApplyTransform(t);
}
};
class Mesh
{
VertexBuffer vb;
void render()
{
base.render();
SetVertexBuffer(vb);
Draw();
}
};
Swap out entity with node, and scene entity with shape, the design is still much of the same. This design has its merits, especially when it comes to try to model the world in terms programmers can easily reason about. The limits of traditional inheritance hierarchies first start to really show themselves when working with non-trivial example code and larger codebases. When it is no longer apparent where in the inheritance chain functionality should go, the case for component based architectures makes itself clear.
Take for example the task of adding animation support to the Mesh entity, with an updateAnimation method taking a delta time parameter:
virtual void updateAnimation(float deltaTime) {}
Many times, the approach for solving this in a future-proof manner will be to add an AnimatableEntity base class and derive from that:
class AnimatableEntity : public SceneEntity
{
int frame;
virtual void updateAnimation(float deltaTime) {}
};
class Mesh : public AnimatableEntity
{
...
void updateAnimation(float deltaTime)
{
UpdateVertexBufferBlendShapes(vb, deltaTime);
}
};
If we then need a mesh type without animation support we're still stuck with all the functionality from AnimatableEntity if we do not want to duplicate code.
Approaching this problem with component based architecture in mind we could design the same problem as such:
class Component
{
virtual void update();
...
};
class SceneComponent : public Component
{
vec3 position;
quat rotation;
void update()
{
auto t = CalculateTransform(position, rotation);
ApplyTransform(t);
}
}
class MeshComponent : public Component
{
VertexBuffer vb;
void update()
{
SetVertexBuffer(vb);
Draw();
}
};
class AnimationComponent : public Component
{
int frame;
void update()
{
auto meshComponent = getComponent();
if (meshComponent && meshComponent.vb.hasFrames()) {
UpdateVertexBufferBlendShapes(vb, deltaTime);
}
}
};
class Entity
{
vector components;
void update()
{
for (auto c : components) {
c->update();
}
}
};
void createOurEntities()
{
Entity nonAnimated;
nonAnimated.push_back(new SceneComponent());
nonAnimated.push_back(new MeshComponent());
Entity animated;
animated.push_back(new SceneComponent());
animated.push_back(new MeshComponent());
animated.push_back(new AnimationComponent());
}
The above code makes it obvious that at least for simple cases, the component based design carries a little more boilerplate. But it should also be obvious that entity composition can happen dynamically and data-driven instead of explicitly.
When working with components it is common to have component managers or systems to handle each different type of components. This gives us the possibility to for example handle all instances of the same component type in the same update method or similar construct. As in most cases, dealing with collections of data instead of individual instances opens us up to both parallelization and other optimizations. In some cases it can also make the code much clearer and more maintainable, since clear areas of responsibility can be established for most systems.
When using an entity system one would typically remove the manual allocation of components like above and let systems handle this:
template
class ComponentSystem
{
vector components;
ComponentType * createComponent()
{
auto c = new ComponentType();
components.push_back(c);
return c;
}
virtual void update() {};
};
One would also create systems for the different types of components like so:
// We removed the virtual update from earlier.
class SceneComponent
{
vec3 position;
quat rotation;
};
class SceneSystem : public ComponentSystem
{
vector transforms;
void update()
{
transforms.resize(components.size());
size_t index = 0;
for (auto c : components) {
transforms[index++] = CalculateTransform(c.position, c.rotation);
}
}
};
This could make it possible to allocate components from the different systems and compose them together as an entity like so:
...
void createOurEntities()
{
Entity nonAnimated;
nonAnimated.push_back(sceneSystem.createComponent());
nonAnimated.push_back(meshSystem.createComponent());
Entity animated;
animated.push_back(sceneSystem.createComponent());
animated.push_back(meshSystem.createComponent());
animated.push_back(animationSystem.createComponent());
}
Note that any update methods on the entity itself and the typical core components can now be moved to the subsystems which can then work on all allocated components at the same time.
There are several omissions in the code above as to not obfuscate design issues behind implementation details. Look to the Cogs.Core source code for actual usage of this pattern.
When looking at the fact that systems are able to control all allocated instances of a single component type, we can take advantage of this and take the design one step further by groping all data in preallocated arrays of components. This makes it possible to do really fast allocation and de-allocation of components. In addition the components will be allocated in contiguous memory, making optimizing for cache access much easier.
template
class ComponentSystem
{
vector pool;
size_t next;
ComponentSystem() : pool(MAX_COMPONENTS), next(0) {}
ComponentType * createComponent()
{
return &pool[next++];
}
};