Role: System & Engine Programmer
Role: System & Engine Programmer
Description
Titan Engine
Custom Engine in development since 09/2024.
The sections below describes some highlighted features developed by me.
Read files with Clang to generate code for the program.
This tool helps with buttons and Scripting tool.
When starting up a level all meshes needed to be loaded. This was time consuming as other components did not need meshes to be loaded to run. To fix this we started to create multiple threads that would work on different tasks. Two systems that benefited the most from this were: SceneLoader and ModelLoader.
Scene Loader: Reads a scene binary file and batches each component type and starts a thread that would process the data and create the component related to the data and adds it to the Titan Engine component system.
Model Loader: A request for a model is added to the model loader as a task. The model loader will process the oldest task and start working on creating a model. If it completes a task it will set the result of the task and pick the next task on the list. At the end of frame the requester will check if the model has finished loaded and take it.
Our performance were at 5 FPS when during debug mode when playing a level. The problem were we drew each object separately. To increase performance it was decided to batch all object that shared the same data. To accomplice this a Transform buffer containing all transforms in each batch were created, then a manager that sorts our rendering objects that shared data. Each frame the transform buffer is filled with all objects, then each batch meta data is added to the batch data for what index in the transform is used. This allowed us to run on 60+ FPS in debug mode.
As Memory is allocated and released it was difficult to manage all pointers to these memory's (Smart pointers did not give us enough control).
Assets are called on from different components it's unsure if there will be copy's or releasing the asset too many times. To manage this Asset Managers with a AssetPointer were used to manage memory of assets. Asset Manager contains the assets associated with the asset type, each asset has a count were if it reaches 0 it's released. AssetPointer refers to a asset in the Asset Manager when a AssetPointer is created or destroyed the count is asset Manager is modified.
As components and GameObject move, removed and changed into another object some pointers could refer to invalid or wrong data. To manage this Titan Engine uses GamePointer<T> that refers to a SlotMap, and stores a Index and a generation, these are used for finding the data index and if it has the right version. The SlotMap will keep each object aligned for less cash missing, and stores data for retrieving objects.
For development and ease of development it was decided to use component system inspired by Unity Engine's component system.
GameObject contains components attached to it, serves as a list of components that the developers can interact with to add and get components. Similarly if the gameobject is destroyed it will clean up it's components. Changing the gameobjects state to enable or disable causes all components to be effected.
Components inherit from a base class that adds the foundation for an component, the developers can then inherit from components and add onto it without worrying about the underling system. Components state change calls OnEnable or OnDisable and will determine if they are updated.
To manage these each unique type is stored inside their own list to be aligned next to each other, and manage any search, get and add logic.
During development the deleting of components are unreliable as other components may refer to them, the system will mark gameobjects and components as destroyed but wont clear the memory until the end of the frame, this allowed for less crashes as the data can be obtained in the current update loop.
As multiple objects can change position, rotation and/or scale it was decided to use a Transform component. Every component has a reference to the transform that's attached to gameobject it's attached.
Transform controls local position, rotation and scale. A transform can communicate with other transforms that is connected through it's hierarchy, these are parent and children. Parent represents the relationship between two transforms were one is attached to the other, while children represent transforms that are attached to a transform.
When retrieving world data the transform will iterate through each parent in it's hierarchy to calculate it. This process may cost more then return a value, however when updating a transform a call to it's children to update world data is not necessary, as the data will calculate it only when requested.
Transform has a special relationship with GameObject as gameobject will use transform to change states of all gameobjects and components in the hierarchy from itself and down the hierarchy towards its children. As such deactivating a gameobject will deactivate the hierarchy for all gameobjects and components in its hierarchy.
To save a scene a binary and a Json file is filled with data that is sorted into a Class List were the list contains every object that was saved, the end of the list, and the list size. Each object stores the data that it needs in Titan engine. The binary file is used for loading the scene into our engine and the Json file is used for debugging.
Here is some code in Unity that we use for our editor.
In Titan scene file is loaded and for each list in the file a thread is given a shared stream to the scene file that refers to the list associated with the given Object (Such as Component and GameObject).
Each thread goes through three steps: Read File, Initialize and Communicate.
Read File: Extract data from file stream.
Initialize: Create Objects and set data.
Communicate: Set object state depending on hierarchy state.
After these stages was completed it waits for a input to Open or discard the scene.
Discard: Wont implement any data into the system and discard all data and join the threads.
Open will run two steps: Finalize and Awake.
Finalize: Connect components and gameObject to each other, set their states.
Awake: All components has a function called Awake that works as a controlled constructor.
The steps are used to make sure all the objects are at the same step to make sure the data retrieved is available and valid. For other reason were for performance issues as retrieving data from objects after Finalize caused cache misses turning our loading of a scene from 30 sec to 5 sec.
Here is some code for BoxColliders: