{"id":1,"date":"2021-07-27T06:49:25","date_gmt":"2021-07-27T06:49:25","guid":{"rendered":"https:\/\/voxely.net\/blog\/?p=1"},"modified":"2021-07-28T07:46:48","modified_gmt":"2021-07-28T07:46:48","slug":"object-oriented-entity-component-system-design","status":"publish","type":"post","link":"https:\/\/voxely.net\/blog\/object-oriented-entity-component-system-design\/","title":{"rendered":"Object-Oriented Entity-Component-System Design"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">The Challenge<\/h2>\n\n\n\n<p>About two months ago, I decided to start developing a fresh codebase for my voxel project &#8211; something that many indie developers are probably all-too familiar with, including myself. This time around, there were three major features that I wanted to implement:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>System modularity<\/li><li>C# and C\/C++ Interop where either can be used or combined for nearly everything<\/li><li>Effortless scripting and modding extensibility<\/li><\/ul>\n\n\n\n<p>The overall goal was to address scalability issues that I&#8217;ve encountered with my previous engines. It&#8217;s pretty easy to get a Vulkan application up and running and throw some voxel stuff at it, but without a solid foundation to build systems on top of, everything sort of becomes a mess of spaghetti code and hacky interop.<\/p>\n\n\n\n<p>Actually, this problem extends beyond just engine development; it&#8217;s pretty much true of any software design. Developers need to try and anticipate the needs of future development while also making sure not to design an architecture that&#8217;s too abstract. There are lots of software design patterns and philosophies out there, but ultimately they are broad tools and you still have to figure out the best way to apply them.<\/p>\n\n\n\n<p><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"750\" height=\"563\" class=\"wp-image-23\" style=\"width: 750px;\" src=\"https:\/\/i0.wp.com\/voxely.net\/blog\/wp-content\/uploads\/2021\/07\/productivity_graph.png?resize=750%2C563&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/voxely.net\/blog\/wp-content\/uploads\/2021\/07\/productivity_graph.png?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/voxely.net\/blog\/wp-content\/uploads\/2021\/07\/productivity_graph.png?resize=300%2C225&amp;ssl=1 300w, https:\/\/i0.wp.com\/voxely.net\/blog\/wp-content\/uploads\/2021\/07\/productivity_graph.png?resize=768%2C576&amp;ssl=1 768w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/p>\n\n\n\n<p>I won&#8217;t pretend to be an expert in this field by any stretch of the imagination. I&#8217;m sure people that have degrees in computer science and years of professional experience can maybe offer better insight here. In fact, this is one of those problems that it feels the more you know, the less you know. More experience leads to more obstacles you try and prepare for, which ultimately leads to over-abstraction and less and less time writing actual functional code. But with the right design, there&#8217;s got to be a crossover where the solid foundation produces more functional code at some point, right?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Introducing the Onion Engine!<\/h2>\n\n\n\n<p>No, that&#8217;s not its actual name. Someone compared my new engine to an onion because of the numerous layers it has, and it stuck because it also makes me want to cry at times.<\/p>\n\n\n\n<p>Let&#8217;s start by defining the things every PC game engine should support:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Game window<\/li><li>Input capturing<\/li><li>Networking<\/li><li>Game loop<\/li><li>Render loop<\/li><li>ImGui (of course)<\/li><li>Logging<\/li><\/ul>\n\n\n\n<p>More or less every system that you build is an extension of one or more of these components. Players incorporate input for moving, terrain incorporates rendering, enemies incorporate the game loop to engage in combat, and so on.<\/p>\n\n\n\n<p>So the obvious step one is to incorporate these elements. An object-oriented system might then look something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public class Player : IGameObject\n{\n    public override void OnUpdate(double dt)\n    {\n    }\n\n    public override void OnRender(double dt)\n    {\n    }\n\n    \/\/ etc...\n}<\/code><\/pre>\n\n\n\n<p>For most games and simple engines, this works perfectly fine. If I was trying to get straight to developing a game, this is the method that I would use.<\/p>\n\n\n\n<p>But this is in C#. In C++, it would look pretty similar, but goal #2 was to have C# and C\/C++ interop. This means we have to write C and use function pointers, which might look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"c\" class=\"language-c\">typedef void(*game_object_update_fn_t)(struct game_object_t* object, double dt);\ntypedef void(*game_object_render_fn_t)(struct game_object_t* object, double dt);\nstruct game_object_t\n{\n    game_object_update_fn_t fn_update;\n    game_object_render_fn_t fn_render;\n};<\/code><\/pre>\n\n\n\n<p>A little more verbose, but that&#8217;s C for you. Nothing really unreasonable here for a simple game.<\/p>\n\n\n\n<p><strong>But what happens when you want to add a new system to the engine?<\/strong> Or maybe you want to attach some data to just these components like the window that we&#8217;re drawing to. You can pass parameters into the functions, but then what if someone else comes in and wants to add to those parameters? Do you then pass in a  heap-allocated list of structured parameters? What about code reusability like for networking? And perhaps another question we should be asking is <em>who&#8217;s driving?<\/em> The engine is going to have to iterate over every game object and check to see if it has a render function during the render loop even if it only updates.<\/p>\n\n\n\n<p>I can already hear the collective groan about an impending <strong>entity-component-system<\/strong> or <strong>ECS<\/strong> lecture and how CPUs weren&#8217;t built for object-oriented programming, and why you right now need to start focusing on data-oriented designs because <em>&#8220;your CPU cache will thank you.&#8221;<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Breaking Entity-Component-System&#8217;s Rules<\/h2>\n\n\n\n<p>But actually, I want to talk about how ECS and violating many of its &#8220;rules&#8221; &amp; core principles helped address the above questions. I put &#8220;rules&#8221; in quotes because there aren&#8217;t actually rules. ECS is merely a design guideline and is usually described as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>Entity:<\/em> A simple identifier that groups together components<\/li><li><em>Component:<\/em> Just some raw data that systems can operate on<\/li><li><em>System:<\/em> One or more functions that performs some operation on a specific type (or types) of component<\/li><\/ul>\n\n\n\n<p>This is great for some aspects of game development, like the usual example that gets thrown around being a <em>Position<\/em> or <em>Transformation<\/em> component. An update system could apply velocities to the position, and then the render system could fetch both sprite components and transform components simultaneously to know what to draw and where. It&#8217;s quite elegant in that respect.<\/p>\n\n\n\n<p>Here&#8217;s where somebody starts chopping up onions. What if we used the idea of ECS as a way to define our engine&#8217;s systems? I talked about the core <strong>components <\/strong>(component) that an engine has, and how some <strong>objects<\/strong> (entity) combine one or more of these components to build a <strong>system<\/strong>. The thing is, &#8220;pure&#8221; ECS designs make inter-system notoriously annoying by having to deal with message systems. Keeping functionality out of components and writing systems can also be tedious.<\/p>\n\n\n\n<p>As it turns out, using ECS where it shines but mixing in some object-oriented principles makes for a powerful software design. It can feel a bit recursive at times, which I&#8217;ll touch on in a moment, but it addresses the key problems both with an objected-oriented design and plain ECS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Changing Terminology<\/h2>\n\n\n\n<p>This part gets confusing because unlike ECS, the entity-component-system is not clear and changes based on the current perspective. An entity can become a system, and vice-versa. Let&#8217;s start by defining some terms though:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>Engine:<\/em> The host of an ECS world that creates and stores domains and contexts<\/li><li><em>Context: <\/em>A core system that creates &amp; attaches and operates on engine components<\/li><li><em>Component:<\/em> A class consisting of some optional data and callbacks that a context calls<\/li><li><em>Domain:<\/em> A collection of components, but can also become a context and create components or be a single component and act as a singleton<\/li><\/ul>\n\n\n\n<p>The part that really stands out is the <em>Domain<\/em>, which can actually be all three: an entity, component, and a system. Some might even argue that at that point it just turns into an object-oriented design, and they&#8217;d be right! That&#8217;s part of what makes it powerful and easy to use in practice. And due to the way the engine operates, it still gives us the benefits of ECS by treating it as a context or component.<\/p>\n\n\n\n<p>A domain is meant to be thought of as <strong>a collection of core systems that can be processed independently by default, or reach into other domains for extended functionality<\/strong>.<\/p>\n\n\n\n<p>For example, the Chat domain allows the users to chat with other players when in multiplayer and draws the messages to the screen. The rigid body physics domain crunches physics calculations and updates rigid body components. The player domain receives input and renders the players, and uses per-voxel collisions that it can fetch from the terrain domain.<\/p>\n\n\n\n<p>Admittedly, this is the part that makes me tear up, because it was actually really tough to understand at times. &#8220;<em>What fits into where?<\/em>&#8221; Is a question I kept asking myself along the way, but every time I took a step back and tried finding a better design, this kept making more and more sense with the 3 feature goals that were outlined in the beginning.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How it Looks in Code<\/h2>\n\n\n\n<p>Let&#8217;s jump straight into what a domain in C# looks like. For this example I&#8217;m using a camera, which has its updating processed in C++ but registry, ImGui rendering, and input handling in C#.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public unsafe partial class FPSCamera\n{\n\tprivate CameraDataT* native_data = null;\n\n\tpublic FPSCamera(CameraDataT* _native_data)\n\t{\n\t\tnative_data = _native_data;\n\t}\n\n\t\/\/ Data functions\n\tpublic ref vec3 Position =&gt; ref native_data-&gt;v_position;\n\tpublic ref vec3 Velocity =&gt; ref native_data-&gt;v_velocity;\n\tpublic ref vec3 Rotation =&gt; ref native_data-&gt;v_rot;\n}\n\npublic unsafe partial class FPSCameraSystem : DomainSystem\n{\n\tpublic FPSCamera Singleton { get; private set; }\n\tpublic IntPtr NativePointer { get; protected set; }\n\n\tprotected InputComponent InputComponent { get; private set; }\n\n\tpublic FPSCameraSystem(Engine engine) : base(engine, \"FPSCamera\")\n\t{\n\t\tNativePointer = vcore_fps_camera_domain_create(NativeID, engine.NativePointer);\n\t\tSingleton = new FPSCamera(vcore_fps_camera_get_singleton(NativePointer));\n\t}\n\n\t~FPSCameraSystem()\n\t{\n\t\tvcore_fps_camera_domain_destroy(NativePointer);\n\t}\n\n\tpublic override void AddRenderComponents(RenderContext render_context)\n\t{\n\t\tvcore_fps_camera_attach_render_components(NativePointer, render_context.NativePointer);\n\n\t\t\/\/ Imgui component\n\t\t{\n\t\t\tImGuiComponent component = (ImGuiComponent)Engine.ImGuiContext.CreateSystemComponent(this);\n\t\t\tcomponent.Render += ImGui_Render;\n\t\t\tcomponent.Register();\n\t\t\tComponents.Add(\"imgui_component\", component);\n\t\t}\n\n\t\tbase.AddRenderComponents(render_context);\n\t}\n\n\tpublic override void AddStandardComponents()\n\t{\n\t\tvcore_fps_camera_attach_standard_components(NativePointer);\n\n\t\t\/\/ Input component\n\t\t{\n\t\t\tInputComponent = (InputComponent)Engine.InputContext.CreateSystemComponent(this);\n\t\t\tInputComponent.FixedEvents.MouseDown += FixedRenderEvents_MouseDown;\n\t\t\tInputComponent.RenderEvents.MouseMove += RenderInputEvents_MouseMove;\n\t\t\tInputComponent.Register();\n\t\t\tComponents.Add(\"input_component\", InputComponent);\n\t\t}\n\n\t\tbase.AddStandardComponents();\n\t}\n\n\tprivate void RenderInputEvents_MouseMove(object sender, MouseMoveEventArgs e)\n\t{\n\t\t\/\/ Move the camera\n\t}\n\n\tprivate void FixedRenderEvents_MouseDown(object sender, MouseEventArgs e)\n\t{\n\t\tConsole.WriteLine(\"Pressed \" + e.Button.ToString() + \" from camera\");\n\t}\n\n\tprivate void ImGui_Render(object sender, EventArgs e)\n\t{\n\t\tImGui.Begin(\"Camera\");\n\t\tImGui.Text(\"Position:\\t\" + Singleton.Position.ToString());\n\t\tImGui.Text(\"Velocity:\\t\" + Singleton.Velocity.ToString());\n\t\tImGui.Text(\"Rotation:\\t\" + Singleton.Rotation.ToString());\n\t\tImGui.Text(\"-\");\n\t\tif (InputComponent != null)\n\t\t{\n\t\t\tImGui.Text(\"Mouse Pos:\\t\" + InputComponent.RenderInput.GetMouse().X + \", \" + InputComponent.RenderInput.GetMouse().Y);\n\t\t}\n\t\tImGui.End();\n\t}\n}<\/code><\/pre>\n\n\n\n<p>Of course it&#8217;s not fully complete, but you can see how easy it is to use and attach new functionality. The <em>DomainSystem<\/em> that it inherits from looks like the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public partial class DomainSystem\n{\n\tpublic Engine Engine { get; private set; }\n\tpublic uint NativeID { get; private set; }\n\tpublic int ManagedID { get; set; }\n\n\tpublic string Name { get; private set; }\n\n\tpublic Dictionary&lt;string, SystemComponent&gt; Components { get; private set; }\n\n\tpublic DomainSystem(Engine engine, string name)\n\t{\n\t\tEngine = engine;\n\t\tName = name;\n\t\tNativeID = vc_domain_create(engine.NativePointer, name);\n\t\tManagedID = engine.GetNewDomainID();\n\n\t\tComponents = new Dictionary&lt;string, SystemComponent&gt;();\n\t}\n\n\t~DomainSystem()\n\t{\n\t\tvc_domain_destroy(Engine.NativePointer, NativeID);\n\t\tEngine.UnregisterDomain(ManagedID);\n\t}\n\n\t\/\/\/ &lt;summary&gt;\n\t\/\/\/ Add standard (non-render) components here.\n\t\/\/\/ &lt;\/summary&gt;\n\tpublic virtual void AddStandardComponents()\n\t{\n\t}\n\n\t\/\/\/ &lt;summary&gt;\n\t\/\/\/ Add render components here.\n\t\/\/\/ &lt;\/summary&gt;\n\tpublic virtual void AddRenderComponents(RenderContext render_context)\n\t{\n\t}\n\n\tpublic static uint GetIDFromName(Engine engine, string name)\n\t{\n\t\treturn vc_domain_get_from_name(engine.NativePointer, name);\n\t}\n}<\/code><\/pre>\n\n\n\n<p>The actual ECS backbone is stored on the native side. I use <a href=\"https:\/\/github.com\/SanderMertens\/flecs\" data-type=\"URL\" data-id=\"https:\/\/github.com\/SanderMertens\/flecs\">https:\/\/github.com\/SanderMertens\/flecs<\/a> here because I know I couldn&#8217;t write an ECS library that&#8217;s better. Because people might wonder, I did try EnTT, but it doesn&#8217;t work across dynamic libraries.<\/p>\n\n\n\n<p>You may notice that there are separate managed and native IDs, and this is so that people can write and define systems entirely in C#. And thus, it&#8217;s important to sort of mirror the representation on the managed end as well. Also, the components need to be stored in the managed side when they wrap a native component to prevent the garbage collector from destroying them.<\/p>\n\n\n\n<p>Let&#8217;s take a look at what the camera domain looks like on the native side:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">fps_camera_domain_t::fps_camera_domain_t(vc_entity_t domain_id, voxely_engine_t* engine) : domain_t(engine, \"fps_camera\", domain_id)\n{\n\tengine-&gt;get_domain_registry().component&lt;fps_camera_component_wrapper_t&gt;();\n\n\tm_singleton = new fps_camera_t();\n\tauto entity = flecs::entity(engine-&gt;get_domain_registry(), domain_id);\n\tentity.emplace&lt;fps_camera_component_wrapper_t&gt;(m_singleton);\n}\n\nfps_camera_domain_t::~fps_camera_domain_t()\n{\n\tdelete m_singleton;\n\tm_singleton = 0;\n}\n\nvoid fps_camera_domain_t::attach_standard_components()\n{\n}\n\nvoid fps_camera_domain_t::attach_render_components(class render_context_t* render_context)\n{\n\tusing namespace std::placeholders;\n\trender_component_t* render_component = render_context-&gt;attach_component(m_domain_id);\n\trender_component-&gt;fn_pre_render[VC_NATIVE] = std::bind(&amp;fps_camera_domain_t::update, this, _1, _2, _3, _4);\n\trender_component-&gt;fn_window_resize[VC_NATIVE] = std::bind(&amp;fps_camera_domain_t::on_resize, this, _1, _2, _3, _4, _5, _6);\n}\n\n\/\/ We could update the singleton on its own or process the query in the event there are multiple cameras\nvoid fps_camera_domain_t::on_resize(voxely_engine_t* engine, render_context_t* render_context, vc_entity_t domain_id, GLFWwindow* window, unsigned int window_width, unsigned int window_height)\n{\n\tm_component_query.each([&amp;](flecs::entity entity, fps_camera_component_wrapper_t&amp; wrapper)\n\t\t{\n\t\t\tfps_camera_t* component = wrapper.data;\n\t\t\tcomponent-&gt;on_resize(window_width, window_height);\n\t\t});\n}\n\nvoid fps_camera_domain_t::update(voxely_engine_t* engine, render_context_t* render_context, vc_entity_t domain_id, double dt)\n{\n\tm_component_query.each([&amp;](flecs::entity entity, fps_camera_component_wrapper_t&amp; wrapper)\n\t\t{\n\t\t\tfps_camera_t* component = wrapper.data;\n\t\t\tcomponent-&gt;update(dt);\n\t\t});\n}\n\nfps_camera_t* fps_camera_domain_t::get_fps_camera(flecs::world&amp; domain_registry, vc_entity_t domain_id)\n{\n\tauto entity = flecs::entity(domain_registry, domain_id);\n\treturn entity.get&lt;fps_camera_component_wrapper_t&gt;()-&gt;data;\n}\n\nfps_camera_domain_t* vcore_fps_camera_domain_create(vc_entity_t domain_id, voxely_engine_t* engine)\n{\n\treturn new fps_camera_domain_t(domain_id, engine);\n}\n\nvoid vcore_fps_camera_domain_destroy(fps_camera_domain_t* domain)\n{\n\tdelete domain;\n}\n\nvoid vcore_fps_camera_attach_standard_components(fps_camera_domain_t* domain)\n{\n\tdomain-&gt;attach_standard_components();\n}\n\nvoid vcore_fps_camera_attach_render_components(fps_camera_domain_t* domain, class render_context_t* render_context)\n{\n\tdomain-&gt;attach_render_components(render_context);\n}\n\nfps_camera_t* vcore_fps_camera_get(uint32_t entity_id, voxely_engine_t* engine)\n{\n\treturn fps_camera_domain_t::get_fps_camera(engine-&gt;get_domain_registry(), (vc_entity_t)entity_id);\n}\n\nfps_camera_t* vcore_fps_camera_get_singleton(fps_camera_domain_t* domain)\n{\n\treturn domain-&gt;get_singleton();\n}<\/code><\/pre>\n\n\n\n<p>This block includes the C bindings, if you were curious what those looked like. But overall, everything again is pretty clean. The way that the domain becomes more like an object is pretty evident here. Also, I do include functionality inside <em>fps_camera_t<\/em>, which holds all of the actual camera data like its matrices. The domain could function as a static system and operate strictly on <em>fps_camera_t<\/em> components, but it includes the Singleton since in most cases there&#8217;s only ever going to be one camera and so it&#8217;s not a static system. But it could be.<\/p>\n\n\n\n<p>The main takeaway here though is this camera system was defined both in C# and C++ as an extension of core systems that were defined, but not hardcoded. In C#, the core contexts are cached for easy lookup, and the game &amp; render loops are setup here as well, but contexts and systems in general can be added and interfaced dynamically with no additional complexity.<\/p>\n\n\n\n<p>For low level systems like rendering and input, it&#8217;s a bit overkill, but with just a bit of extra effort, the camera domain can be treated the same way as a context, and thus we&#8217;ve come full circle! The engine foundation has no idea what a camera is, and yet it can be used as if it were a core component, which means other systems can access it and our modularity, managed\/native interop, and extensibility problems are <strong>solved<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Render Components<\/h2>\n\n\n\n<p>I want to briefly show what the render component looks like, and how treating component data with callbacks as a system outside of a Singleton looks.<\/p>\n\n\n\n<pre title=\"render_component.hpp\" class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">typedef void (render_component_render_fn_t)(class voxely_engine_t* engine, class render_context_t* render_context, vc_entity_t domain_id, double dt);\ntypedef void (render_component_window_resize_fn_t)(class voxely_engine_t* engine, class render_context_t* render_context, vc_entity_t domain_id, GLFWwindow* window, unsigned int window_width, unsigned int window_height);\n\nclass render_component_t : public render_derivative_t, public context_component_t\n{\npublic:\n\tVCCPPAPI render_component_t(class render_context_t* render_context);\n\tVCCPPAPI render_component_t(render_component_t&amp; rhs) = default;\n\tVCCPPAPI render_component_t(render_component_t&amp;&amp; rhs) noexcept = default;\n\n\tVCCPPAPI inline render_component_t&amp; operator=(const render_component_t&amp;) { return *this; }\n\n\tVCCPPAPI pipeline_cache_t&amp; get_pipeline_cache() noexcept;\n\nprotected:\n\tpipeline_cache_t m_pipeline_cache;\n\npublic:\n\tstd::function&lt;render_component_render_fn_t&gt; fn_pre_render[VC_NUM_FN_TYPES] = {};\n\tstd::function&lt;render_component_render_fn_t&gt; fn_render[VC_NUM_FN_TYPES] = {};\n\tstd::function&lt;render_component_render_fn_t&gt; fn_display[VC_NUM_FN_TYPES] = {};\n\tstd::function&lt;render_component_window_resize_fn_t&gt; fn_window_resize[VC_NUM_FN_TYPES] = {};\n\n\t\n};\n\ntypedef context_component_wrapper_t&lt;render_component_t&gt; render_component_wrapper_t;\n\nVCAPI void vc_render_component_register_pre_render_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn);\nVCAPI void vc_render_component_register_render_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn);\nVCAPI void vc_render_component_register_display_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn);\n\nVCAPI void vc_render_component_register_window_resize_fn(render_component_t* component, VC_FN_TYPES type, render_component_window_resize_fn_t* fn);\n\nVCAPI pipeline_cache_t* vc_render_component_get_pipeline_cache(render_component_t* component);<\/code><\/pre>\n\n\n\n<p>And then the implementation:<\/p>\n\n\n\n<pre title=\"render_component.cpp\" class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">render_component_t::render_component_t(render_context_t* render_context) : context_component_t(render_context), render_derivative_t(render_context), m_pipeline_cache(render_context)\n{\n}\n\npipeline_cache_t&amp; render_component_t::get_pipeline_cache() noexcept\n{\n\treturn m_pipeline_cache;\n}\n\nvoid vc_render_component_register_pre_render_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn)\n{\n\tcomponent-&gt;fn_pre_render[(int)type] = fn;\n}\n\nvoid  vc_render_component_register_render_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn)\n{\n\tcomponent-&gt;fn_render[(int)type] = fn;\n}\n\nvoid vc_render_component_register_display_fn(render_component_t* component, VC_FN_TYPES type, render_component_render_fn_t* fn)\n{\n\tcomponent-&gt;fn_display[(int)type] = fn;\n}\n\nvoid vc_render_component_register_window_resize_fn(render_component_t* component, VC_FN_TYPES type, render_component_window_resize_fn_t* fn)\n{\n\tcomponent-&gt;fn_window_resize[(int)type] = fn;\n}\n\npipeline_cache_t* vc_render_component_get_pipeline_cache(render_component_t* component)\n{\n\treturn &amp;component-&gt;get_pipeline_cache();\n}<\/code><\/pre>\n\n\n\n<p>So this part is pretty standard. And then the <em>render context<\/em> simply operates like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">void render_context_t::render(double dt)\n{\n\tauto&amp; command_buffer = m_vk_context-&gt;get_active_command_buffer();\n\n\t{\n\tm_component_query.each([&amp;](flecs::entity entity, render_component_wrapper_t&amp; component_wrapper)\n\t\t{\n\t\t\trender_component_t&amp; component = *component_wrapper.data;\n\t\t\t\tif (component.fn_pre_render[VC_NATIVE])\n\t\t\t\t\tcomponent.fn_pre_render[VC_NATIVE](m_engine, this, entity, dt);\n\t\t\t\tif (component.fn_pre_render[VC_MANAGED])\n\t\t\t\t\tcomponent.fn_pre_render[VC_MANAGED](m_engine, this, entity, dt);\n\t\t\t});\n\t}\n\n\t{\n\t\tm_component_query.each([&amp;](flecs::entity entity, render_component_wrapper_t&amp; component_wrapper)\n\t\t\t{\n\t\t\t\trender_component_t&amp; component = *component_wrapper.data;\n\t\t\t\tif (component.fn_render[VC_NATIVE])\n\t\t\t\t\tcomponent.fn_render[VC_NATIVE](m_engine, this, entity, dt);\n\t\t\t\tif (component.fn_render[VC_MANAGED])\n\t\t\t\t\tcomponent.fn_render[VC_MANAGED](m_engine, this, entity, dt);\n\n\t\t\t\tcomponent.get_pipeline_cache().launch_pipelines(command_buffer);\n\t\t\t});\n\t}\n}\n\nrender_component_t* render_context_t::attach_component(vc_entity_t entity_id)\n{\n\tstd::scoped_lock&lt;std::mutex&gt; lock(m_component_mutex);\n\n\tauto entity = flecs::entity(m_domain_registry, entity_id);\n\trender_component_t* component = new render_component_t(this);\n\tentity.emplace&lt;render_component_wrapper_t&gt;(component);\n\n\tcomponent-&gt;get_entity_id() = entity_id;\n\n\treturn component;\n}<\/code><\/pre>\n\n\n\n<p>One thing to address quickly is why the components have a wrapper. It&#8217;s because flecs can shift components around which invalidates any references to the components, including on the managed side. One could make the argument that you should be fetching the component from the entity every single time you want it, but not only does a managed to native function call cost about 1ns, but components could be added on a separate thread while the managed side is operating on its reference of the component and without a stable pointer, it will crash. Having the component data scattered around the heap is perfectly fine here since they involve immediate external function calls anyway.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Layering<\/h2>\n\n\n\n<p>The core <strong><em>Foundation<\/em> layer<\/strong> defines those initial game engine components and a large amount of C# bindings. It really is meant to be the foundation layer to build a solid voxel engine on top of that is ripe for game development and modding.<\/p>\n\n\n\n<p>That layer is the <strong><em>Framework<\/em> layer<\/strong>. There&#8217;s not much there yet, but it&#8217;s where the real meat of the engine will go and where most function-first engines get the ball rolling. However, I think that we&#8217;re closing in on that crossing point where it&#8217;s going to become more time-efficient having this solid foundation in place.<\/p>\n\n\n\n<p>The third layer is the <strong><em>Game<\/em> layer<\/strong>. This is where game mechanics are defined and built on top of the framework &#8211; things like specialized procedural generation, combat, inventories, etc. The <strong><em>Modding<\/em> layer<\/strong> is just above this.<\/p>\n\n\n\n<p>Lastly that just leaves the <strong><em>Launcher<\/em><\/strong>, which is the small program that initializes the .NET Core, creates a foundation engine context, finds and loads a game layer, and connects the two and launches the game. Mods will also be loaded here.<\/p>\n\n\n\n<p>Also I&#8217;m sure I&#8217;ll get these questions &#8211; I don&#8217;t use Unity\/Unreal\/&lt;xyz engine here&gt; because you&#8217;re always sacrificing something when you use someone else&#8217;s code. Unity is stuck with Mono. Unreal has very limited hardware ray tracing support. Both engines were built with the intent on developers making games with rasterized triangles. It makes more sense to me to build an engine around my end goals than to try and hack my way around an existing giant codebase that is both missing what I need and has way, way more than I need.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Closing Remarks<\/h2>\n\n\n\n<p>Overall, I&#8217;m very excited to begin integrating the old voxel systems on top of this foundation. I really do <s>hate<\/s> love C\/C++, but only for low-level stuff where I feel like I need the full control and an understanding of what happens. C# is such a pretty language and is easy to just pick up, and not to mention omega powerful and fast in .NET Core. Things like chat and the player behavior just don&#8217;t belong in C++. They aren&#8217;t performance critical and having to fake automatic garbage collection in C++ or deal with the STL is an easy way to turn content developers off.<\/p>\n\n\n\n<p>Perhaps in the next post I&#8217;ll talk about how the rendering and networking components are setup, as those are the two components that inspired this whole design and most the work went into them! Here&#8217;s a preview of how the pipeline creation works, where specialized constants, descriptor sets, resources, and shaders are all handled and mapped automagically:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">\/\/ Render component\n{\n\tRenderComponent component = (RenderComponent)render_context.CreateSystemComponent(this);\n\trender_component = component;\n\n\tRayTracedPipeline test_raytrace = new RayTracedPipeline(render_context, \"test\");\n\ttest_raytrace.AddRayGen(\"test.rgen\");\n\ttest_raytrace.AddHitGroup(\"test.rchit\", \"test.rahit\", \"test.rint\");\n\ttest_raytrace.AddMiss(\"test.rmiss\");\n\ttest_raytrace.Create();\n\ttest_raytrace.PreLaunch += (pipeline, args) =&gt;\n\t{\n\t\t((Pipeline)pipeline).LaunchSize = new ivec3(args.WindowSize.x, args.WindowSize.y, 1);\n\t};\n\tcomponent.PipelineCache.AddPipeline(test_raytrace);\n\n\tcomponent.Register();\n\tComponents.Add(\"render_component\", component);\n}<\/code><\/pre>\n\n\n\n<p>Thanks for reading.<\/p>\n\n\n\n<p>~Lin<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A discussion on how a blend of OOP and ECS turned out to be an elegant solution to system modularity, managed and native interop, and powerful modding.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[4,5,3,6],"class_list":["post-1","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-code-design","tag-ecs","tag-engine","tag-oop","entry"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/posts\/1","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/comments?post=1"}],"version-history":[{"count":29,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/posts\/1\/revisions"}],"predecessor-version":[{"id":69,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/posts\/1\/revisions\/69"}],"wp:attachment":[{"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/media?parent=1"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/categories?post=1"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/voxely.net\/blog\/wp-json\/wp\/v2\/tags?post=1"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}