The Whole Core: At the start of our journey, we layed out the roadmap of our core libraries and introduced each of them — even though some were changed or removed later on.
Module Manager: Our module manager takes care of module startup, shutdown and update sequence. We thought it would be trivial but actually fell into a 2-hour discussion about the fundamental design of the modules...
Error Handling: We agreed on the usages of exceptions and asserts, and hesitantly brought in the Microsoft Foundation Class library to help with asserts.
Math: The first version of our Math library was quickly implemented by referencing Unity's Math API and various online 3D math resources.
String ID: We didn't think too much and brought in an open-source string ID library.
Serialization: We originally included serialization in our architecture diagram because we thought networking would need it. But the networking library we brought in already has its own! As much as we wanted to do serialization, we decided to drop it to save headaches and limit the scope.
STL Wrapper: As the development went on, we realized an STL wrapper is not necessary and would be tedious and trivial to implement, we dropped it. We still had IsettaAlias.h though, so we can write U8 instead of uint8_t every time.
Data Structures: We realized we need some custom data structures like RingBuffer for our other systems, so we implemented that. At this point, we also got ourselves into the mindset that we could implement our data strucures on-demand throughout the project.
Timer: We implemented our Timer class using C++'s built-in <chrono> library — it was as simple as that.
File System: We made a file system that's capable of async file manipulation using Microsoft's API. It was a lot of circular documentation, and knowing the right terminology was really important to figuring out what to learn.
Module Manager, Removed: ModuleManager was becoming an obvious central hub (a.k.a. choke point) for our engine's modules. Once we had introduced systems other than modules in our engine, the module manager became restrictice, so we said good bye to it and moved its functionality to Engine Loop. This also allowed us to revisit our different update loops, now named FixedUpdate and VariableUpdate.
Our Own Dynamic Array: We used std::vector extensively in our engine and discovered the need for managing its memory, so we implmented our own dynamic array class. The process revealed more problems in our memory management system!
Unit test your math library. Even if it's hard to create test cases for things like Matrix4 and Quaternion, this is crucial. If you don't unit test your math, something in the engine will definitely break.
If you have your own memory manager, be wary of creating your own standard library classes as well. We assumed that making an equivalent of the std::vector class would be straightforward since it's so ubiquitous, but there's a lot going under the hood for the commonly used classes and you'll likely do some of it incorrectly, which will propagate errors throughout your engine.
Implementing your own data structures is wise, though, because you will often need it in more than one place, and duplicating the code will likely cause problems down the road.