{"id":254,"date":"2025-12-05T05:18:15","date_gmt":"2025-12-05T05:18:15","guid":{"rendered":"https:\/\/leah-lindner.com\/blog\/?p=254"},"modified":"2025-12-05T15:15:05","modified_gmt":"2025-12-05T15:15:05","slug":"e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it","status":"publish","type":"post","link":"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/","title":{"rendered":"E.T.Engines Archetype Based Entity-Component-System &#8211; Part 1: How to use it"},"content":{"rendered":"\n<p>To illustrate the way in which I program, I figured it would be interesting to show how I wrote the Entity-Component-System (ECS) <a href=\"https:\/\/github.com\/Illation\/ETEngine\/tree\/master\/Engine\/source\/EtFramework\/ECS\" data-type=\"link\" data-id=\"https:\/\/https:\/\/github.com\/Illation\/ETEngine\/tree\/master\/Engine\/source\/EtFramework\/ECSgithub.com\/Illation\/ETEngine\/tree\/master\/Engine\/source\/EtFramework\/ECS\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>for ETEngine<\/strong><\/a>. In this first part I will go over the basics of what it is and how we can work with it. In the next part I will go into the nitty gritty details of how it was implemented.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is an Entity Component System?<\/h2>\n\n\n\n<p>Entity Component Systems are a design pattern that formalises the relationship between an Entity in a games Scene, it&#8217;s Properties (Data \/ Components) and the functionality that can be applied to them (Systems). <\/p>\n\n\n\n<p>ECS follows the principles of Data Oriented Design, in which Data Structures (the Components) are defined separately from the logic (Systems). Entities are simple identifiers, with which we can find all the components which hold the data that makes up this entity, which are stored separately. Systems then modify the data in components in a way that&#8217;s agnostic to specific entities.<\/p>\n\n\n\n<p>This design pattern is an alternative to other common aproaches game engines use for structuring scene data, like for example Scene Actors (in which all properties and functionality are members of an Actor, often implemented using lots of inheritance), or the nowadays somewhat ubiquitous Entity-Component model that both Unity and Unreal implement in their own way (Components contain both data and functionality, and entities may or may not do so too). Both of these approaches follow a more OOP based approach, and while this may seem a bit more intuitive to work with, there are several benefits to using an ECSs based architecture instead:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The separation of concerns makes ECS very easy to modify. Components can be mixed and matched freely for each entity without needing to rewrite anything<\/li>\n\n\n\n<li>Components can easily be added and removed from entities at runtime, without needing to worry about breaking complex relationships. Systems will automatically start and stop affecting entities with the relevant components<\/li>\n\n\n\n<li>ECS based architecture is highly scalable because it uses composition instead of complex inheritance structures, which makes it easy to add new features as one doesn&#8217;t need to worry about breaking existing code or respecting strange edge cases that are distributed across a large code base<\/li>\n\n\n\n<li>Last but not least, using ECS can have big performance benefits, especially when handling scenes with large numbers of entities. This is because data can be structured in a way that makes iteration very cache friendly. They also enable mostly automating parallel execution, because systems can strictly define mutability and execution sequencing<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-medium\"><img decoding=\"async\" data-attachment-id=\"264\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/actormodeluml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML.jpg\" data-orig-size=\"708,449\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ActorModelUML\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;Actor Model&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML-300x190.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML.jpg\" data-id=\"264\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/plugins\/images-lazyload-and-slideshow\/blank_250x250.gif\" file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML-300x190.jpg\" alt=\"Actor Model UML Diagram\" class=\"wp-image-264 ls_lazyimg\"\/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"190\" data-attachment-id=\"264\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/actormodeluml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML.jpg\" data-orig-size=\"708,449\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ActorModelUML\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;Actor Model&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML-300x190.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML.jpg\" data-id=\"264\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML-300x190.jpg\" alt=\"Actor Model UML Diagram\" class=\"wp-image-264\" srcset=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML-300x190.jpg 300w, https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/ActorModelUML.jpg 708w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/noscript><figcaption class=\"wp-element-caption\">Actor Model<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-medium\"><img decoding=\"async\" data-attachment-id=\"266\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/entitycomponentuml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML.jpg\" data-orig-size=\"879,340\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"EntityComponentUML\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-300x116.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML.jpg\" data-id=\"266\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/plugins\/images-lazyload-and-slideshow\/blank_250x250.gif\" file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-300x116.jpg\" alt=\"\" class=\"wp-image-266 ls_lazyimg\"\/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"116\" data-attachment-id=\"266\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/entitycomponentuml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML.jpg\" data-orig-size=\"879,340\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"EntityComponentUML\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-300x116.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML.jpg\" data-id=\"266\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-300x116.jpg\" alt=\"\" class=\"wp-image-266\" srcset=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-300x116.jpg 300w, https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML-768x297.jpg 768w, https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EntityComponentUML.jpg 879w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/noscript><figcaption class=\"wp-element-caption\">Entity Component model<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-medium\"><img decoding=\"async\" data-attachment-id=\"265\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/ecsuml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML.jpg\" data-orig-size=\"661,602\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"EcsUML\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML-300x273.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML.jpg\" data-id=\"265\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/plugins\/images-lazyload-and-slideshow\/blank_250x250.gif\" file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML-300x273.jpg\" alt=\"\" class=\"wp-image-265 ls_lazyimg\"\/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"273\" data-attachment-id=\"265\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/ecsuml\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML.jpg\" data-orig-size=\"661,602\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"EcsUML\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML-300x273.jpg\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML.jpg\" data-id=\"265\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML-300x273.jpg\" alt=\"\" class=\"wp-image-265\" srcset=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML-300x273.jpg 300w, https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/EcsUML.jpg 661w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/noscript><figcaption class=\"wp-element-caption\">ECS<\/figcaption><\/figure>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Example of a feature implemented using ECS in ETEngine<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" data-attachment-id=\"268\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/spawnsystem\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" data-orig-size=\"540,304\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"SpawnSystem\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem-300x169.gif\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/plugins\/images-lazyload-and-slideshow\/blank_1x1.gif\" file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" alt=\"\" class=\"wp-image-268 ls_lazyimg\" style=\"width:814px;height:auto\"\/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"304\" data-attachment-id=\"268\" data-permalink=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/spawnsystem\/\" data-orig-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" data-orig-size=\"540,304\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"SpawnSystem\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem-300x169.gif\" data-large-file=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" src=\"https:\/\/leah-lindner.com\/blog\/wp-content\/uploads\/2025\/12\/SpawnSystem.gif\" alt=\"\" class=\"wp-image-268\" style=\"width:814px;height:auto\"\/><\/noscript><\/figure>\n\n\n\n<p>One of the the features in my engines demo is a spawn system, which is used to shoot balls with rigid body physics from the camera into the scene. Here is how this system is defined:<\/p>\n\n\n\n<!--more-->\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------------------\n\/\/ SpawnSystem\n\/\/\n\/\/ Spawns objects upon user input\n\/\/\nclass SpawnSystem final : public fw::System&lt;SpawnSystem, SpawnSystemView>\n{\npublic:\n    SpawnSystem();\n\n    void Process(fw::ComponentRange&lt;SpawnSystemView>&amp; range) override;\n};<\/pre>\n\n\n\n<p>There are three aspects we&#8217;re going to have a closer look at:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Spawn system declares it&#8217;s dependencies in it&#8217;s constructor<\/li>\n\n\n\n<li>The Spawn system works with something called <code>SpawnSystemView<\/code> (a type of <code>ComponentView<\/code>)<\/li>\n\n\n\n<li>The Spawn system has a process function in which the main functionality is implemented<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Dependencies and Execution Flow<\/h3>\n\n\n\n<p>In order to ensure a correct execution order, systems declare their dependencies and dependents in their constructor:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/--------------------\n\/\/ SpawnSystem::c-tor\n\/\/\n\/\/ system dependencies\n\/\/\nSpawnSystem::SpawnSystem()\n{\n    DeclareDependencies&lt;fw::TransformSystem::Compute>(); \/\/ update after transforms have been updated so we spawn from the most recent spawner position\n    DeclareDependents&lt;fw::RigidBodySystem>(); \/\/ update before rigid bodies so bullet simulates our spheres as soon as they are added\n}<\/pre>\n\n\n\n<p>Dependents are any systems that need to run after our system, where as dependencies are systems that need to run before our system because it generates information we depend on. The engines ECS automatically calculates the order in which systems execute based on this information. This is handled by the so called <code>EcsController<\/code>, which handles all top level concerns for dealing with an ECS. In order for it to start running the spawn system, all we have to do is register it:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void MainFramework::OnSystemInit()\n{\n    fw::EcsController&amp; ecs = fw::UnifiedScene::Instance().GetEcs();\n\n    ecs.RegisterSystem&lt;FreeCameraSystem>();\n    ecs.RegisterSystem&lt;SpawnSystem>(); \/\/ the order here doesn't particularly matter as all systems will be sorted according to their dependencies after the fact\n    ecs.RegisterSystem&lt;LightControlSystem>();\n    ecs.RegisterSystem&lt;SwirlyLightSystem>();\n    ecs.RegisterSystem&lt;CelestialBodySystem>();\n    ecs.RegisterSystem&lt;PlaylistSystem>();\n    ecs.RegisterSystem&lt;DemoUISystem>();\n}<\/pre>\n\n\n\n<p>If we at any point wanted to disable the spawn behaviour, all we&#8217;d need to do is unregister the Spawn System:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ecs.UnregisterSystem&lt;SpawnSystem>();<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Component Views<\/h3>\n\n\n\n<p>Component views provide a way to access component data in a cache friendly way while iterating entities relevant to a system, while also declaring if we need read or write access to run the system (which is important for multi-threading). Here&#8217;s what this looks like for our spawn system:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------------------\n\/\/ SpawnSystemView\n\/\/\n\/\/ ECS access pattern for spawn behavior\n\/\/\nstruct SpawnSystemView final : public fw::ComponentView\n{\n    SpawnSystemView() : fw::ComponentView()\n    {\n        Declare(spawner);\n        Declare(transform);\n    }\n\n    WriteAccess&lt;SpawnComponent> spawner;\n    ReadAccess&lt;fw::TransformComponent> transform;\n};<\/pre>\n\n\n\n<p>We see this view allows us to read from transform components (but not write, their access is const), and write to spawn components (implicitly also read from them of course). Note we don&#8217;t need to know that this spawner is attached to the camera so our spawn system view doesn&#8217;t include a camera component. This way if we wanted to for example create a fountain in our world it could reuse the same components and systems we use here too.<\/p>\n\n\n\n<p>Lets have a look at what these components look like:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------------------------\n\/\/ SpawnComponent\n\/\/\n\/\/ Component containing data to spawn entities with model and rigid body components\n\/\/\nstruct SpawnComponent final\n{\n    \/\/ definitions\n    \/\/-------------\n    ECS_DECLARE_COMPONENT\n\n    \/\/ construct destruct\n    \/\/--------------------\npublic:\n    SpawnComponent(AssetPtr&lt;render::MeshData> const meshPtr,\n        AssetPtr&lt;render::I_Material> const materialPtr,\n        float const s, \n        btCollisionShape* const shape, \n        float const shapeMass, \n        float const interv, \n        float const imp);\n    ~SpawnComponent() = default;\n\n    float interval = 0.f;\n    float cooldown = 0.f;\n    float impulse = 0.f;\n\n    \/\/ hold the assets so that they are loaded in already when spawning\n    AssetPtr&lt;render::MeshData> mesh;\n    AssetPtr&lt;render::I_Material> material;\n    float scale = 1.f;\n\n    btCollisionShape* collisionShape = nullptr;\n    float mass = 1.f;\n};<\/pre>\n\n\n\n<p>As we can see, it entirely contains data, some of it relating to its spawn behavior, and some of it containing information about what to actually spawn.<\/p>\n\n\n\n<p>I will spare you the details of transform components, but essentially it contains a bunch of transform data, accessed through basic getter and setter functions (but no complex behavior). If you want to see the details you can find it <a href=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Engine\/source\/EtFramework\/Components\/TransformComponent.h\" data-type=\"link\" data-id=\"httpshttps:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Engine\/source\/EtFramework\/Components\/TransformComponent.hgithub.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Engine\/source\/EtFramework\/Components\/TransformComponent.h\" target=\"_blank\" rel=\"noreferrer noopener\">HERE<\/a>.<\/p>\n\n\n\n<p>Our spawn system will automatically operate on all entities that have both a spawn component and a transform component.<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Beyond read and write, here are a few more advanced access patterns we can define in our Component View (which are not required for this example but good to know about)&#8230;<\/summary>\n<ul class=\"wp-block-list\">\n<li><code>EntityRead&lt;ComponentType&gt; <\/code>While for performance reasons it&#8217;s best to avoid it. sometimes it&#8217;s necessary to read component data from another entity for a system to function, and this accessor makes this possible. To use it one supplies an entity ID, and the accessor will provide a pointer to the relevant component data: <\/li>\n\n\n\n<li><code>ParentRead&lt;ComponentType&gt; <\/code>This allows the system to read component data from a parent entity. E.T.Engines ECS supports scene hierarchy (parent child relationships between entities), so this allows us to read data from a parents component. A good example is the transform system accessing a parents transform to update the childs coordinates accordingly.<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">EntityRead&lt;MyComp> extComp;\nT_EntityId extEntity;\nMyComp const* const comp = extComp[extEntity];<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We can also create a System which requires an entity to have a component, but doesn&#8217;t actually need to read or write any data from this component using the <code>Include <\/code>function. This can be useful to limit the amount of entities that a system runs on. For example here is a view for a <a href=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Projects\/Demo\/source\/Runtime\/FreeCamera.h#L56\" target=\"_blank\" rel=\"noreferrer noopener\">system <\/a>that operates only on cameras, but never actually needs to read or write data from a camera component:<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------------------\n\/\/ FreeCameraSystemView\n\/\/\n\/\/ ECS access pattern for editor camera behavior\n\/\/\nstruct FreeCameraSystemView final : public fw::ComponentView\n{\n\tFreeCameraSystemView() : fw::ComponentView()\n\t{\n\t\tDeclare(camera);\n\t\tDeclare(transform);\n\t\tInclude&lt;fw::CameraComponent>();\n\t}\n\n\tWriteAccess&lt;FreeCameraComponent> camera;\n\tWriteAccess&lt;fw::TransformComponent> transform;\n};<\/pre>\n<\/details>\n\n\n\n<h3 class=\"wp-block-heading\">Processing Systems<\/h3>\n\n\n\n<p>Every system implements a Process function. This is where the magic happens, systems use this opportunity to iterate over all entities with the correct combination of components as defined by their <code>ComponentView<\/code> and execute the behavior the system is intended for.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void Process(fw::ComponentRange&lt;SpawnSystemView>&amp; range) override;<\/pre>\n\n\n\n<p>As you can see, the function provides a so called <code>ComponentRange<\/code>, which provides the means to iterate across relevant entities. Let&#8217;s have a look at our spawn systems <code>Process <\/code>function and then break down what&#8217;s happening.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/------------------------------\n\/\/ SpawnSystem::Process\n\/\/\n\/\/ Spawn upon user input\n\/\/\nvoid SpawnSystem::Process(fw::ComponentRange&lt;SpawnSystemView>&amp; range)\n{\n    \/\/ Skip execution unless we have the required user input\n    if (!(core::InputManager::GetInstance()->GetMouseButton(E_MouseButton::Right) >= E_KeyState::Down))\n    {\n        return; \/\/ nothing needs to be spawned\n    }\n\n    \/\/ common variables\n    fw::EcsCommandBuffer&amp; cb = GetCommandBuffer();\n    float const dt = core::ContextManager::GetInstance()->GetActiveContext()->time->DeltaTime();\n\n    \/\/ spawn stuff\n    for (SpawnSystemView&amp; view : range)\n    {\n        view.spawner->cooldown -= dt;\n        if (view.spawner->cooldown &lt;= 0.f)\n        {\n            view.spawner->cooldown = view.spawner->interval;\n\n            fw::T_EntityId const spawned = cb.AddEntity();\n\n            vec3 const&amp; dir = view.transform->GetForward();\n\n            fw::TransformComponent tf;\n            tf.SetPosition(view.transform->GetWorldPosition() + dir * view.spawner->scale);\n            tf.SetScale(vec3(view.spawner->scale));\n\n            fw::RigidBodyComponent rb(true, view.spawner->mass, view.spawner->collisionShape);\n\n            fw::ModelComponent model(view.spawner->mesh, view.spawner->material);\n            \n            cb.AddComponents(spawned, tf, model, rb);\n\n            vec3 const impulse = dir * view.spawner->impulse;\n            cb.OnMerge(spawned, fw::EcsCommandBuffer::T_OnMergeFn([impulse](fw::EcsController&amp; ecs, fw::T_EntityId const entity)\n                {\n                    ecs.GetComponent&lt;fw::RigidBodyComponent>(entity).ApplyImpulse(impulse);\n                }));\n        }\n    }\n}<\/pre>\n\n\n\n<p>Before we start actually iterating, we get an opportunity to do some housekeeping, like computing variables that are consistent for all entities. Let&#8217;s skip to the for loop, as what comes before will be self evident.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">for (SpawnSystemView&amp; view : range)\n{\n    view.spawner->cooldown -= dt;\n    if (view.spawner->cooldown &lt;= 0.f)\n    {\n        view.spawner->cooldown = view.spawner->interval;\n\n        \/\/ spawn stuff\n    }\n}<\/pre>\n\n\n\n<p>When iterating over a component range, for each relevant entity we get a reference to one of the component views we declared for our system. This allows us to access component data through the <code>operator-&gt;<\/code> of our accessors (<code>ReadAccess&lt;TComponent&gt;<\/code> provides a const ref and <code>WriteAccess&lt;TComponent&gt;<\/code> provides a non-const ref).<\/p>\n\n\n\n<p>We declared WriteAccess&lt;SpawnComponent&gt; spawner, which allows us to modify the cooldown member and read the interval member of SpawnComponent. By doing so we ensure a more reasonable spawn rate for the entities.<\/p>\n\n\n\n<p>The next block of code deals with actually spawning a new entity:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fw::T_EntityId const spawned = cb.AddEntity();<\/pre>\n\n\n\n<p>This is orchestrated through a command buffer, which we accessed before entering the for loop, using <code>EcsCommandBuffer::AddEntity()<\/code> and <code>EcsCommandBuffer::AddComponents(entity, ...)<\/code>. While the <code>EcsController<\/code> has all the required functions for adding entities and components, we can&#8217;t actually use those while iterating over entities in our component range, because adding them immediately may cause undefined behavior due to how the memory gets restructured when entities get added.<\/p>\n\n\n\n<p>So the command buffer allows us to tell it what changes we want to make (Like adding or removing entities or components), and then stores them until it can execute them later when we&#8217;re done iterating all the entities. Note that command bufferss are only necessary for systems that make structural changes to our ECS, most systems only modify the data in components that already exist, and therefore don&#8217;t need a command buffer.<\/p>\n\n\n\n<p>In our case we start by adding a new entity. Note that all we get back is  <code>T_EntityId<\/code>, remember with ECS an entity is just an identifier, and all the data lives in components. Our new entity doesn&#8217;t actually contain any data yet, so let&#8217;s give it some:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">vec3 const&amp; dir = view.transform->GetForward();\n\nfw::TransformComponent tf;\ntf.SetPosition(view.transform->GetWorldPosition() + dir * view.spawner->scale);\ntf.SetScale(vec3(view.spawner->scale));\n\nfw::RigidBodyComponent rb(true, view.spawner->mass, view.spawner->collisionShape);\n\nfw::ModelComponent model(view.spawner->mesh, view.spawner->material);\n\ncb.AddComponents(spawned, tf, model, rb);<\/pre>\n\n\n\n<p>First we create a <code>TransformComponent<\/code>, <code>RigidBodyComponent<\/code> and <code>ModelComponent<\/code>, which we initialize with data from the spawner entity that we can access through our <code>ComponentView<\/code>. Then, we tell the command buffer to add those components to the entity we had it make in the previous step.<\/p>\n\n\n\n<p>That&#8217;s all that&#8217;s needed to create a new entity, once we&#8217;re done updating all our spawners, the command buffer will tell the ECS to add this entity, and going forward this new entity will be affected by all systems that need transform, rigid body and\/or model components.<\/p>\n\n\n\n<p>There&#8217;s one last thing we do here though. Once the entity has been created, we want to give it a little push so it goes flying off into the sunset. We have to wait until it actually exists though, and so we use the command buffers <code>OnMerge() <\/code>callback function to do this as soon as all it&#8217;s commands have been executed:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">vec3 const impulse = dir * view.spawner->impulse;\ncb.OnMerge(spawned, fw::EcsCommandBuffer::T_OnMergeFn([impulse](fw::EcsController&amp; ecs, fw::T_EntityId const entity)\n    {\n        ecs.GetComponent&lt;fw::RigidBodyComponent>(entity).ApplyImpulse(impulse);\n    }));<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">More examples<\/h2>\n\n\n\n<p>And with this we have touched on most of the fundamental concepts required to write features with E.T.Engine&#8217;s ECS. If you&#8217;re interested in why structuring your code like this can lead to some big performance gains, hold on tight until I write the next part about how some of this works under the hood&#8230;<\/p>\n\n\n\n<p>I realize some of the workflow might seem a little aesthetically contrived if you&#8217;re used to how things are done in Unreal or Unity, however the quirks mostly start and end with what you&#8217;ve already seen in this post. After using this approach to scene managment for a bit I&#8217;ve really come to enjoy how well this allows the user to keep all of the code related to implementating a feature logically contained in one place. If you have ever worked on projects with a large codebase you will have learnt the joys of discovering 30 little modifications you have to make to tangentially related systems all across the codebase just to add one small feature &#8211; this seems to be much rarer with this architecture.<\/p>\n\n\n\n<p>Here are some examples of how other systems are implemented<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Projects\/Demo\/source\/Runtime\/SwirlyLightSystem.h\" data-type=\"link\" data-id=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Projects\/Demo\/source\/Runtime\/SwirlyLightSystem.h\"><strong>SwirlyLightSystem<\/strong><\/a>: Moves lights about in a scene like fireflies<\/li>\n\n\n\n<li><strong><a href=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Projects\/Demo\/source\/Runtime\/PlaylistSystem.cpp\" data-type=\"link\" data-id=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Projects\/Demo\/source\/Runtime\/PlaylistSystem.cpp\">PlaylistSystem<\/a><\/strong>: Allows users to control a playlist on an audio source<\/li>\n\n\n\n<li><strong><a href=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Engine\/source\/EtFramework\/Systems\/TransformSystem.h\" data-type=\"link\" data-id=\"https:\/\/github.com\/Illation\/ETEngine\/blob\/f446488b6d57148547b2c5809341c8d98f380ef4\/Engine\/source\/EtFramework\/Systems\/TransformSystem.h\">TransformSystem<\/a><\/strong>: Updates transform components according to scene hierarchy (this is a unique [to my knowledge] feature of my ECS implementation that bakes entity hierarchy into how it works, watch out for my next post to see more about how it works).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">ECS and Scene Serialization<\/h2>\n\n\n\n<p>E.T.Engine&#8217;s ECS can be automatically serialized and deserialized to binary or human readable formats using the engines reflection system (built on top of <a href=\"https:\/\/www.rttr.org\/\" data-type=\"link\" data-id=\"https:\/\/www.rttr.org\/\">RTTR<\/a>). This is also the process during which a components variables can be intialized, should any such thing be required. Here is an excerpt from a serialized SceneDescriptor that describes one of the spawner entities we looked at before:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"id\": 0,\n  \"components\": [\n    {\n      \"transform component\": {\n        \"position\": [ 0.0, 2.0, -10.0 ],\n        \"rotation\": [ 0.0, 0.0, 0.0, 1.0 ],\n        \"scale\": [ 1.0, 1.0, 1.0 ]\n      }\n    },\n    {\n      \"camera component\": {\n        \"is perspective\": true,\n        \"field of view\": 45.0,\n        \"ortho size\": 25.0,\n        \"near plane\": 0.1,\n        \"far plane\": 1000.0\n      }\n    },\n    {\n      \"audio listener component\": {\n        \"gain\": 1.0\n      }\n    },\n    {\n      \"spawn comp desc\": {\n        \"mesh\": \"Models\/sphere.etmc\",\n        \"material\": \"Materials\/MI_TexPBR_Ball.json\",\n        \"scale\": 0.2,\n        \"shape\": {\n          \"sphere collider shape\": {\n            \"radius\": 0.2\n          }\n        },\n        \"mass\": 3.0,\n        \"interval\": 0.2,\n        \"impulse\": 30.0\n      }\n    }\n  ],\n  \"children\": []\n}<\/pre>\n\n\n\n<p>This entity is composed of a transform, a camera, an audio listener and a spawn component. Except what you actually see here are ComponentDescriptors, or even more accurately implementations of them. No, the irony that I ended up using inheritance to serialize components in an ECS doesn&#8217;t escape me. The abstraction this makes saving and loading very simple, and importantly we do not use this inheritance at runtime, only for loading and saving.<\/p>\n\n\n\n<p>In E.T.Engine, ComponentDescriptors create Components for the ECS to use at runtime. For example, here is the spawn components descriptor:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ SpawnComponent.h\n\n\/\/---------------------------------\n\/\/ SpawnComponentDesc\n\/\/\n\/\/ Descriptor for serialization and deserialization of spawn components\n\/\/\nclass SpawnComponentDesc final : public fw::ComponentDescriptor&lt;SpawnComponent>\n{\n    \/\/ definitions\n    \/\/-------------\n    RTTR_ENABLE(ComponentDescriptor&lt;SpawnComponent>)\n    DECLARE_FORCED_LINKING()\n\n    \/\/ construct destruct\n    \/\/--------------------\npublic:\n    SpawnComponentDesc() : ComponentDescriptor&lt;SpawnComponent>() {}\n\n    SpawnComponentDesc&amp; operator=(SpawnComponentDesc const&amp; other);\n    SpawnComponentDesc(SpawnComponentDesc const&amp; other);\n    ~SpawnComponentDesc();\n\n    \/\/ ComponentDescriptor interface\n    \/\/-------------------------------\n    SpawnComponent* MakeData() override;\n\n    \/\/ Data\n    \/\/\/\/\/\/\/\n\n    AssetPtr&lt;render::MeshData> mesh;\n    AssetPtr&lt;render::I_Material> material;\n    float scale = 1.f;\n\n    fw::CollisionShape* shape = nullptr; \/\/ This should be updated to use the engines smart pointer system\n    float mass = 1.f;\n\n    float interval = 1.f;\n    float impulse = 0.f;\n};\n\n\n\/\/ SpawnComponent.cpp\n\n\/\/ reflection\n\/\/------------\n\nRTTR_REGISTRATION\n{\n    rttr::registration::class_&lt;SpawnComponent>(\"spawn component\");\n\n    BEGIN_REGISTER_CLASS(SpawnComponentDesc, \"spawn comp desc\")\n        .property(\"mesh\", &amp;SpawnComponentDesc::mesh)\n        .property(\"material\", &amp;SpawnComponentDesc::material)\n        .property(\"scale\", &amp;SpawnComponentDesc::scale)\n        .property(\"shape\", &amp;SpawnComponentDesc::shape)\n        .property(\"mass\", &amp;SpawnComponentDesc::mass)\n        .property(\"interval\", &amp;SpawnComponentDesc::interval)\n        .property(\"impulse\", &amp;SpawnComponentDesc::impulse)\n    END_REGISTER_CLASS_POLYMORPHIC(SpawnComponentDesc, fw::I_ComponentDescriptor);\n}\nDEFINE_FORCED_LINKING(SpawnComponentDesc) \/\/ force the linker to include this unit\n\n\/\/ constructor and assignment implementations are omitted for simplicity\n\n\/\/------------------------------\n\/\/ SpawnComponentDesc::MakeData\n\/\/\n\/\/ Create a spawn component from a descriptor\n\/\/\nSpawnComponent* SpawnComponentDesc::MakeData()\n{\n    return new SpawnComponent(mesh, material, scale, shape->MakeBulletCollisionShape(), mass, interval, impulse);\n}<\/pre>\n\n\n\n<p>The macros <code>RTTR_ENABLE(ComponentDescriptor&lt;SpawnComponent&gt;)<\/code> and the code following <code>RTTR_REGISTRATION<\/code> is what is needed to tell the engine how to automatically serialize and deserialize this component descriptor.<\/p>\n\n\n\n<p>Also note that <code>SpawnComponentDesc <\/code>is a <code>ComponentDescriptor&lt;SpawnComponent&gt;<\/code>, and implements <code>MakeData()<\/code>, which creates a new <code>SpawnComponent<\/code>. During the call to <code>MakeData()<\/code>, we take the opportunity to get the Physics Engine to create a collision shape. Effectively this allows us to use the MakeData() function to do something similar to what happens in Unreal engine when <code>BeginPlay()<\/code> is called on an actor.<\/p>\n\n\n\n<p>Not every component needs this kind of initialization. Using a SimpleComponentDescriptor, ECS components can be their own descriptors:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ .h\n\n\/\/---------------------------------\n\/\/ AudioListenerComponent\n\/\/\n\/\/ Descriptor for serialization and deserialization of sprite components\n\/\/\nclass AudioListenerComponent final : public SimpleComponentDescriptor\n{\n    \/\/ definitions\n    \/\/-------------\n    ECS_DECLARE_COMPONENT\n    RTTR_ENABLE(SimpleComponentDescriptor) \/\/ for serialization\n    DECLARE_FORCED_LINKING()\n\n    friend class AudioListenerSystem;\n\n    \/\/ construct destruct\n    \/\/--------------------\npublic:\n    AudioListenerComponent(float const gain = 1.f) : m_Gain(gain) {}\n    ~AudioListenerComponent() {}\n\n    \/\/ accessors\n    \/\/-----------\n    float GetGain() const { return m_Gain; }\n\n    \/\/ modifiers\n    \/\/-----------\n    void SetGain(float const val) { m_Gain = val; }\n\n    \/\/ Data\n    \/\/\/\/\/\/\/\n\nprivate:\n    vec3 m_PrevPos;\n    float m_Gain = 1.f;\n};\n\n\/\/ .cpp\n\n\/\/ reflection\n\/\/------------\n\nRTTR_REGISTRATION\n{\n    BEGIN_REGISTER_CLASS(AudioListenerComponent, \"audio listener component\")\n        .property(\"gain\", &amp;AudioListenerComponent::GetGain, &amp;AudioListenerComponent::SetGain)\n    END_REGISTER_CLASS_POLYMORPHIC(AudioListenerComponent, I_ComponentDescriptor);\n}\nDEFINE_FORCED_LINKING(AudioListenerComponent) \/\/ force the linker to include this unit\n\nECS_REGISTER_COMPONENT(AudioListenerComponent);\nECS_REGISTER_COMPONENT(ActiveAudioListenerComponent);\n<\/pre>\n\n\n\n<p>On the flip side for some additional initialization functionality, component descriptors can implement <code>OnScenePostLoad() <\/code>in order to execute functionality after the component has been loaded and added to the scene, like in the following example, where a GUI Canvas Component creates it&#8217;s draw context after it can grab data from a linked entity containing a camera component:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ .h\n\nclass GuiCanvasComponent final : public SimpleComponentDescriptor\n{\n    \/\/ definitions\n    \/\/-------------\n    ECS_DECLARE_COMPONENT\n    RTTR_ENABLE(SimpleComponentDescriptor) \/\/ for serialization\n\n    \/\/ init deinit\n    \/\/-------------\n\n    static void OnComponentAdded(EcsController&amp; controller, GuiCanvasComponent&amp; component, T_EntityId const entity);\n    static void OnComponentRemoved(EcsController&amp; controller, GuiCanvasComponent&amp; component, T_EntityId const entity);\n\n    \/\/ interface\n    \/\/-----------\n    bool CallScenePostLoad() const override { return true; }\n    void OnScenePostLoad(EcsController&amp; ecs, T_EntityId const id) override;\n\nprivate:\n\n    \/\/ Some other data members and functions\n\n    EntityLink m_Camera; \/\/ for screenspace canvases defines the viewport to render to, for worldspace canvases defines where the events are coming from\n};\n\n\/\/ .cpp\n\n\/\/-------------------------------------\n\/\/ GuiCanvasComponent::OnScenePostLoad\n\/\/\nvoid GuiCanvasComponent::OnScenePostLoad(EcsController&amp; ecs, T_EntityId const id)\n{\n    ET_UNUSED(id);\n    if ((m_RenderMode == E_RenderMode::ScreenSpaceOverlay) &amp;&amp; (m_Id == core::INVALID_SLOT_ID))\n    {\n        ET_ASSERT(m_Camera.GetId() != INVALID_ENTITY_ID);\n        ET_ASSERT(ecs.HasComponent&lt;CameraComponent>(m_Camera.GetId()));\n        CameraComponent const&amp; camera = ecs.GetComponent&lt;CameraComponent>(m_Camera.GetId());\n        if (camera.GetViewport() != nullptr)\n        {\n            GuiExtension&amp; guiExt = *UnifiedScene::Instance().GetGuiExtension();\n\n            m_Id = guiExt.CreateContext(camera.GetViewport());\n            guiExt.SetLoadedDocument(m_Id, m_GuiDocumentId);\n            m_DataModel = guiExt.GetDataModel(m_Id);\n            guiExt.SetContextActive(m_Id, m_IsActive);\n        }\n    }\n}<\/pre>\n\n\n\n<p>Of interest here is <code>m_Camera<\/code>, which is an <code>EntityLink<\/code>. <code>EntityLink<\/code>s are what&#8217;s used to reflect entity IDs, because they get patched up after the scene is loaded to contain the correct entity ID. <\/p>\n\n\n\n<p>And then lastly also note the static callback functions <code>OnComponentAdded <\/code>and <code>OnComponentRemoved<\/code>. These allow the <code>GuiCanvasComponent <\/code>to execute some functionality right after they get added or removed from the ECS, by registering those functions with the <code>EcsController<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/---------------\n\/\/ EcsController\n\/\/\n\/\/ Full context for an entity component system\n\/\/\nclass EcsController final\n{\n    \/\/ [...]\n\n    \/\/ component events\n    template&lt;typename TComponentType>\n    T_CompEventId RegisterOnComponentAdded(T_CompEventFn&lt;TComponentType>&amp; fn);\n    template&lt;typename TComponentType>\n    T_CompEventId RegisterOnComponentRemoved(T_CompEventFn&lt;TComponentType>&amp; fn);\n    template&lt;typename TComponentType>\n    void UnregisterComponentEvent(T_CompEventId&amp; callbackId);\n\n    \/\/ entity events\n    T_EntityEventId RegisterOnEntityAdded(T_EntityEventFn&amp; fn);\n    T_EntityEventId RegisterOnEntityRemoved(T_EntityEventFn&amp; fn);\n    void UnregisterEntityEvent(T_EntityEventId&amp; callbackId);\n\n    \/\/ [...]\n};<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The end<\/h2>\n\n\n\n<p>That&#8217;s it for today. If you have any questions feel free to comment or write me an email and I&#8217;ll try my best to provide clarity. If you&#8217;re interested in how this all actually works, keep your eyes peeled for part 2!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>To illustrate the way in which I program, I figured it would be interesting to show how I wrote the Entity-Component-System (ECS) for ETEngine. In this first part I will go over the basics of what it is and how we can work with it. In the next part I will go into the nitty &hellip; <a href=\"https:\/\/leah-lindner.com\/blog\/2025\/12\/05\/e-t-engines-archetype-based-entity-component-system-part-1-how-to-use-it\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;E.T.Engines Archetype Based Entity-Component-System &#8211; Part 1: How to use it&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[56],"tags":[16,54,55,53],"class_list":["post-254","post","type-post","status-publish","format-standard","hentry","category-programming","tag-c","tag-ecs","tag-entity-component-system","tag-etengine"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/posts\/254","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/comments?post=254"}],"version-history":[{"count":9,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/posts\/254\/revisions"}],"predecessor-version":[{"id":270,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/posts\/254\/revisions\/270"}],"wp:attachment":[{"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/media?parent=254"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/categories?post=254"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/leah-lindner.com\/blog\/wp-json\/wp\/v2\/tags?post=254"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}