Skip to content

Journal

Karn Kaul edited this page Sep 11, 2019 · 1 revision

Background

Perhaps unsurprisingly, Little Engine was not a planned effort, but rather an organic and haphazard process, having gone through several rounds of overhaul/refactoring already. In fact the start of this project can be traced back to early 2018, when I was still brushing up on modern C++, and wanted to try something beyond exercises and "practice". An attempt at a fixed point primitive type implementation seemed challenging and tightly-scoped enough, so I went ahead with it. Upon its success, the next obvious step was to create a 2D Vector2 class using this, and finally a gameplay-level Transform class that encapsulated all the data needed to specify an object in 2D space, including optional parent/child transforms.

I encountered the first hard road block here, which I still haven't resolved, and that is of using std for sqrt, sin, cos, etc. The simplest way out would be compile-time reference tables; I might get to it some day. So I decided to change things up a bit and try to use whatever I had so far in some kind of visual feedback, and thus I decided to embark on a project using SFML and my newfangled data structures, despite SFML already providing throughly-tested versions of almost all of them built in. (I choose to justify those choices today by the fact that there is still no simple, in-built solution for fixed point.)

Genesis

I created a stupid simple event loop, drew a few shapes and sprites, and successfully moved them around in my very own world space! Several bugs popped up with Transform hierarchies (curse you, rotation), but were soon taken care of.

Dealing with multiple SFML base types as primitives soon became a bother and thus I wrapped all of them into one monolithic SFPrimitive class, enabling myself to evolve to a factory pattern later. I had a very basic game engine up and running: dynamically spawnable entities in a custom world space.

Having never dealt with custom engines before - only Unreal and Unity - I was rather impatient to get the gameplay layers up and running, and thus went straight into deciding what kind of architecture I'd like. I started with a fat Entity class that could be derived from for custom behaviour, keeping in mind that I'd like to plug in a fat Component system to it later.

The First Engine "Systems"

Despite my enthusiasm I was very quickly shot back down to the engine level, as I did not even have a mechanism for handling input. Since I had a fixed-time slice game loop, and didn't want to have input pass throughs interfere with that orchestration, I opted for a "snapshot" approach, where each set of game Tick cycles corresponds to one single input callback. The first version of EngineInput had complicated registration mechanisms to triage the firing of callbacks, but now it simply passes the whole Input Frame in the callback, and also exits the callback stack if a callee returns true.

Great, now I had centralised input and entity manipulation: I could build my first "controller" and finally achieve real-time interaction! However, nothing on screen could interact with anything else on it yet, so the next item on the agenda was a simple collision detection system, so I devised an O(n^2) detector that simply iterates over every pair of Colliders and uses a visitor pattern to determine collision.

User Interface

I spent the next few months developing a Rect2 and "anchor" based UITransform, a base UIElement capable of drawing shapes and text, UIWidget: an interactive element, and UIContext: a collection of widgets and elements, with rudimentary 2D navigation among the interactables. Using this UI framework it was pretty simple to create standard buttons, checkboxes, dialogue boxes, etc.

Exploiting the UI framework, I quickly hacked together a "console" where I could type simple commands to be parsed and executed. (I still regret hacking it and am slowly cleaning it up; at least one DebugConsole.cpp has now been split into half a dozen).

Some time was spent in implementing the Asset system and EngineRepository, though at the time it did I/O synchronously and did not support zip archives or manifests.

Although I neither had the knowledge/expertise nor the systems underneath to support it, I created an amateur's particle system, and for the first time, faced bottlenecks in execution. This led me to develop the Profiler and further improve the console.

Multi Threading

Having professional programming experience only as a gameplay programmer, not only was I very much a sequential programmer, my experience with threads was purely academic. But, especially after gifting myself a 144Hz monitor, I realised that I was (a) effectively running on a Pentium, and (b) rendering way below my display's refresh rate. I didn't want to unnecessarily crank up the tick rate, but without it, all the frames drawn interim were identical. Thus began my long and painful research into game-engine level multi threading, and ways to decouple updates from rendering.

In the end I decided on a hybrid between a job system and dedicated threads, and using a double buffer mechanism to store two copies of state for the renderer to interpolate between.

The first implementation created copies of SFPrimitive every swap, which proved to be a major bottleneck with large counts, and thus the entire system was recently overhauled to only copy SFRenderStates, and have both the game and render threads access (different memory regions of) the same SFPrimitive.

The repository was then expanded to exploit the job system for loading assets; a serialisation class GData implemented, and using it, asset manifests. Soon after, PhysicsFS was integrated into the engine and support for loading assets via archives was added. Eventually, filesystem asset loading was disabled entirely in SHIPPING builds, and a rudimentary binary TextAsset developed to support that. At the time of writing, the engine loads all assets specified in Manifest.minified through GameAssets.cooked before loading the first World, and other gameplay entities can async load/decompress individual assets/whole manifests at any time.

Vertex Arrays and Quads

// TODO

v0.2: Asset Cooker and Application Resources (Icon + Version)

// TODO

v0.4: CMake

// TODO

Clone this wiki locally