Hey Christopher,
Here is a quick rundown on the component system. Sorry it took so long to respond. Busy weekend for me!
The component system is a bit of an experiment. You are free to use it or free to skip it. But there are some ideas in it that I think are pretty unique and worth considering, even if you decide to roll your own.
The component system is first and foremost built for performance and scalability. It is absolutely intended to handle thousands of instances of components with minimal memory usage, fast iteration, and low overhead to allocate and deallocate.
Components are pooled so that they can be stored contiguously in memory. The per-component memory overhead is 16 bytes. These two attributes allow iteration to be cache-friendly. A special smart pointer allows you to keep weak references to components.
This works with the tasking system. Here is an example:
void UpdateRotatorComponents(RotateComponent *pRotate, TransformComponent *pTransform)
{
pRotate->ApplyRotation(pTransform);
}
void Helium::UpdateRotatorComponentsTask::DefineContract( TaskContract &rContract )
{
rContract.ExecuteBefore<StandardDependencies::ProcessPhysics>();
rContract.ExecuteAfter<StandardDependencies::ReceiveInput>();
}
HELIUM_DEFINE_TASK( UpdateRotatorComponentsTask,
(ForEachWorld< QueryComponents< RotateComponent, TransformComponent, UpdateRotatorComponents > >),
TickTypes::Gameplay )
This chunk of code ensures that every frame, before physics is processed but after input is received, UpdateRotatorComponents() will be called once for every entity that has a rotator and transform component. (See ExecuteBefore, ExecuteAfter, and QueryComponents)
TickTypes::Gameplay would mark this code as important to run on server and client in a multiplayer scenario, but not when the game is paused and not in an editor. (The server/client comment is an example - we don't have multiplayer implemented but if it were, this would be how I would suggest marking logic for server only, client only, or both.)
This is an unusual way to construct this code but there are several benefits:
- It allows new functionality to be non-intrusively inserted into the engine with minimal knowledge of all the other tasks that the engine is doing
- It keeps the scope that you are accessing extremely narrow - just the components you need. This is really important as it can allow for parallel processing in the future. I hope one day to add functionality that lets you mark which components you read and write to in a task, and then the task scheduler can know what is safe to execute in parallel. (Basically read-write locks at a per-component-type level)
- A database-style query allows components to be iterated in the most efficient order. For example if there is one rotate component and a thousand transform components, the query will fetch the one rotate component and see if there is a transform component, rather than fetch a thousand transform components only to discard them all since there is just one rotate component. And you never iterate every object in the world unless a component is actually on every object.
BTW the rotate component is kind of silly. It is really only meant as a simple example.
The general design is very data-oriented. If you are not familiar with this concept, it is extremely good to keep in mind. Here are a couple starting points.
I think it is best to think of each of these tasks as a data transform. Think through the data you have, how to componentize it, then write your tasks with a contract that describes what should be done before and/or after your code. The tasking engine will make sure it happens in an order that meets your requirements, or if there is a circular dependency it will let you know.
Fun fact, while the component system was not from the code drop from the insomniac nocturnal project, it is very heavily based on the public information I was able to find about insomniac's component system.
However as far as I know the tasking/multithreading system is completely unique. I am obviously very biased but I think this non-intrusive but multi-threading-friendly markup system for logic has a lot of promise. It gives the engine all the information it needs to do the best thing with your code, and it would be straightforward to implement a visual graph of data flows that includes full debug information for every component transformation or performance data. It also keeps your logic decoupled from everything except exactly the components it works on which again is a huge win if you ever go multi-threaded. It is also a surprisingly small amount of code for all the problems it can potentially solve for you so it wouldn't take long to just read the code.
On the other hand it is very experimental. So if you decide to give it a try, I'd like to hear how it goes. And I would totally understand if you wanted to go your own route with it.
Now to answer your actual question :)
There are two ways you can create components. First, you can do it programmatically. Here is an example from PlayerComponent. Every frame it checks for if your entity was destroyed. If it was, it eventually recreates it. Part of this is creating a new player input component.
m_Avatar->Allocate<PlayerInputComponent>();
The recommended way to do this in non-trivial cases however is to use component definitions and component definition sets. See this example:
/helium/Data/ExampleGames/PhysicsDemo/Sphere.json
Here you have a transform, physics, and mesh component being placed on an object. "Position" and "Rotation" parameters are defined so that you can route construction data to components. This component set is inline on an entity definition so it is automatically applied when the object is constructed. An example of its usage is in ExampleMainWin.cpp
Helium::StrongPtr< ParameterSet_InitLocated > locatedParamSet( new ParameterSet_InitLocated() );
locatedParamSet->m_Position = Simd::Vector3::Zero;
locatedParamSet->m_Rotation = Simd::Quat::IDENTITY;
Simd::Vector3 &position = locatedParamSet->m_Position;
for (int i = 0; i < 25; ++i)
{
position = Simd::Vector3(...);
pWorld->GetRootSlice()->CreateEntity(
spSphereDefinition,
locatedParamSet.Get());
}
Using component definition sets and entity definitions allows your game to be very data-driven, keeps memory usage down by avoiding data duplication, makes it easier to build an editor for your game, and allows a path for doing runtime data reload. These were the considerations that drove the design for how entity/components work together.
I hope this gives you a good starting point!
Philip