Managing/Creating World/Entity/Components for Editor

41 views
Skip to first unread message

Christopher Sosa

unread,
Oct 10, 2015, 1:40:36 PM10/10/15
to Helium Project
Since the Helium's editor is currently too incomplete and barely loads something ( i think it the editor is an mere mockup, hehe) i was looking of how can create worlds, slices, components into the my editor, since i have an thought of using MyComponent* pComp = new MyComponent(); is not correct and could fail in any way, the markdown documentation mentions something about the component pool which i have to deal but in the example project i can't see any way to use it correctly. Some clues can be helpful thanks!.

Philip Degarmo

unread,
Oct 13, 2015, 12:48:49 AM10/13/15
to helium...@googlegroups.com
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


On Saturday, October 10, 2015, Christopher Sosa <cristop...@gmail.com> wrote:
Since the Helium's editor is currently too incomplete and barely loads something ( i think it the editor is an mere mockup, hehe) i was looking of how can create worlds, slices, components into the my editor, since i have an thought of using MyComponent* pComp = new MyComponent(); is not correct and could fail in any way, the markdown documentation mentions something about the component pool which i have to deal but in the example project i can't see any way to use it correctly. Some clues can be helpful thanks!.

--
You received this message because you are subscribed to the Google Groups "Helium Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email to heliumprojec...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Christopher Sosa

unread,
Oct 13, 2015, 7:47:02 PM10/13/15
to Helium Project
Thanks you for those details about your component system is really useful and will save it for the future uses i can do.
Regarding my question seems reasonable and a bit straightforward but another issue is component definitions and component definition sets, there's an way to create it programatically because i want to create an "Definition Builder" and can be assigned easily to the selected component by the user, and later save that definition to the disk, together with the component data too. Many thanks again!.
Reply all
Reply to author
Forward
0 new messages