Developing Cauda Engine

Cauda Engine has been an incredibly fun project, but it hasn't come without hitches and adjustments.
The engine started life as an amalgamation of two existing smaller projects from Keamu and myself, blending a rendering and voxel focused engine with an engine backend with what was essentially a command shell debugging environment. The two projects were put together after receiving the prompt for 'Tumult' which tasked us with creating a game with rope based physics tails and tight same screen gameplay, to which we saw fit to delve further into our passions of custom tech.

The Backend

Cauda's backend was written as a "Layer Stack" which allowed us to separate execution and order our pipeline. The setup for the layer stack is ordered so that the InputLayer always collects input events first, passing on to the EditorLayer (and / or GameLayer) where all our events are handled such as player input, updating editor panels, handling scene loading, updating physics, etc. The RenderLayer and ImGuiLayer are set up as "Overlay" layers, always happening at the end of the layer stack, which can have other regular layers pushed or popped out of it using iterators. These two layers do what you would expect and render the game world to the viewport, then draw the GUI over the top.

This system worked beatifully until we attempted more runtime events down the track, where we ran into a sneaky but impactful problem - we had two update loops. Through integrating Flecs and its world.progress() function, we were calling update for the layer stack and then inside the Editor/Game Layer the Flecs pipeline was running. The issue with this we didn't forsee was that we had written timing reliant code for scripts and other items that were expecting to be updated at the beginning or end of a frame (especially Jolt body creation/destruction) which resulted in a lot of time spent fixing crashes. We eventually swapped our system around, holding onto the layer classes and refactoring our core pipeline to focus on Flecs' existing pipeline. The layers became essentially controller classes, handling their corresponding modules (i.e. RenderLayer handles calling RendererModule and other related items, GameLayer handles the PhysicsModule and CharacterModule, etc).

Serialisation, Scenes, and Prefabs

One of the earliest pieces I wrote for Cauda was our serialisation. This mainly consisted of simple file I/O and the use of log files, and extended into JSON serialisation through Flecs.

For our scenes we are serialising a "Root" entity which adopts any entity with a transform component in scene, and then recursively we serialise entities and their children allowing us to load and unload scenes at runtime (never underestimate how useful this is!). The inbuilt JSON serialisation also became massively useful in allowing us to create prefabs from entities, utilising their reflection data to make easily replicateable entities. The time up until I had worked out the kinks and our internal name caching (Flecs doesn't allow for two identically named entities) was an unruly and painful period, but once it was working seamlessly it greatly increased our iteration speeds and allowed us to prototype levels and objects lightning fast.

Serialisation wasn't without its quirks however and taught us a lot about memory ownership - in specific with strings. The ability to serialise std::strings or even just const char* almost stopped us from being able to pull off this project. We spent the better half of a week digging into Flecs documentation (to no avail at the time), eventually necessitating asking for help from the Flecs discord server, to which Sander Mertens the creator of Flecs (lovingly dubbed Mr.Flecs by our team) pointed out the very obvious but glazed over custom serialisers and existing string and vector serialisation examples. With this newfound knowledge and cofidence boost that we had all the pieces we needed, we delved into custom serialisers which also aided in the set up of our Grimoire scripts and their reflection data.

Translating into Tools

My main focus during the development of Cauda was on tooling. I've always enjoyed making QOL changes and creating tools so this was by far my favourite job, bringing our designers into the alien landscape that is a custom engine. The first piece of tooling as any half decent engine has is a log file system of which I followed suit and implemented, but my first proper piece of tooling was the History Stack which I could not live without again. We spent the first 2 weeks of Cauda moving items around with no sense of undo/redo, no safety net in the case of accidentally setting something's y position to 3000 instead of 30, and we were habitually hitting ctrl + z and wondering why nothing would happen wich made this an obviously massive gain.

The next major piece of tooling I made besides some inspector windows for components and smaller QOL features was the Prefab system. I massively underestimated so many things that are taken for granted once they exist and this was by far the biggest one. The use of Flecs' entities and existing prefab structure made this so much easier to implement but there were still so many edge cases that had to be handled, which lead to an engine wide refactor of our serialisation. Before prefabs the entire scene was being serialised into a json file, and since every entity was a child of the scene root this was free and made sense, but the nagging incapability of major scene distinctions and lack of control made me dig deep into more serialisation to recreate the saving function to serialise only the entities under the "Root" entity and checking for prefab tags. This wasn't a short process either as it came with so many naming convention conflicts and a not so small amount of massaging and moulding to have the prefabs working sensibly, but once it was in it felt so good. This was a make or break piece and I'm incredibly glad I was able to get it in because it saved us a sisyphean effort of recreating entities and objects per scene.