-
Notifications
You must be signed in to change notification settings - Fork 12
Modules
The beating heart of the engine. As performance is critical for any VR application I decided to create Brane Engine using an entity-component system. Entity Component Systems have been shown to significantly reduce the occurrence of cache misses on CPUs with smaller caches, drastically increasing speed, and that's not even taking into account the use of SIMD operations. While planning out the engine I decided that I wanted it to be able to jit compile scripts to have them be as efficient as possible. This means that users also need to be able to define their own components after compile time. This is not currently supported by any major ECS libraries, so I had to create my own. Fortunately, I seem to have done a decent job and it's not incredibly slow. The system uses an archetype paradigm, with paged chunks for memory storage, and a custom build reflection system for creating components at runtime.
In tests, there is one labeled ForEachParellelTest, this test serves a dual purpose. One is to make sure multithreaded for-each calls are working as intended. Second, to get a rough benchmark of speed. All it does is create a specified number of entitles, add a single component to each that contains a uint64 and then measure how long it takes to iterate over all the entities and set that variable. After some recent optimizations, these are the current benchmarks for speed.
Processor: Ryzen 7 3700X
-----------------------------
Debug mode
=============================
Number of entities: 200,000
Single thread Foreach: 44ms
Multithreaded Foreach: 6ms
-----------------------------
Release mode
=============================
Number of entities: 20,000,000
Single thread Foreach: 1009ms
Multithreaded Foreach: 130ms
I tried to keep bumping up the number of entities in the release build until it hit about 1 second for the non-multithreaded Foreach, and we can see that the system is able to iterate over about 20 million entities a second. In a controlled environment with basically no processing happening in the actual loop so take it with a grain of salt.
In the future, I would like to look into the possibility of using a raw function pointer instead of std::function to avoid its overhead, especially when we start jit compiling scripts.
In Brane Engine, Assets are everything. The only reason for this engine to exist rather than using a different one is its ability to asynchronously asset stream (and in the future, jit compile scripts). All assets are represented by an ID consisting of the server they originate from, and a hexadecimal number representing an unsigned 64-bit int. For example: localhost/0000000000000F5F. The idea behind this is that this allows assets to be pulled from any server, not just one at a time. Basically every single module here ties back to assets somehow. User-generated components are defined in ComponentAssets, Meshes are stored in MeshAssets, etc.
Another core part of the asset system is assemblies. These are very similar to Unity's prefabs. They define a list of assets that they are dependent upon and a list of serialized entities that can be deserialized and injected into the entity component system.
There is also the (not yet unimplemented) chunk system. Chunks will include the functionality of assemblies but also represent locations in games. Each chunk also will store its relationship with other chunks. They will have a border defined by a simple mesh, that whenever crossed will trigger the loading or releasing of the appropriate LOD for the surrounding chunks.
The graphics system is built on top of Vulkan to allow us the highest possible optimization potential, but also to let us do cool things like mesh and texture streaming.
Yet to be implemented
The most important feature of the engine, networking. Brane Engine's networking module is built upon asio, this is because of its built in multithreading support, and general ease of use. Because of this, all networking runs on it's own separate thread, allowing us to download assets in the background with almost no impact upon the users experience. The module works through two main types of communication. Requests and Streams. Requests work in a very similar way to http requests, you send a message with a label that allows the correct server callback to be triggered, and then when a response is received your callback is triggered. Streams on the other hand allow you to send a continuous "stream" of messages to a specific callback. You first use a request to negotiate a stream ID, and then you can set up a listener that will funnel all the messages from that stream to a specified callback. This is more useful for things like mesh/texture streaming. In the future UDP support is also planned for more time sensitive communications such as voice chat and state syncing.
A set of helper functions that quite honestly could just be a namespace, but it's a class since we might want to extend it to support caching files in ram in the future... Long shot but possible.
The Brane Surfer, the way to enter the wonderous worlds that shall eventually be created with this engine. Currently just loads a single model and does nothing fancy
Where the bulk of the work has been done so far. This server build stores assets, user data, permissions for users and assets, as well as the structure of worlds themselves. Basically the cloud build folder of the game, as well as a database.
The main way to interact with the asset server, the goal is that you will be able to fully build and manage worlds from this application.
This is what stores the runtime state of worlds, runs voice chat, handles rollback networking, etc.
A set of functions in a namespace that ties everything together. The runtime makes sure that everything is properly setup, run, and disposed of. It also keeps a list of all current modules and keeps track of the timeline. It is also responsible for the main run loop and triggering the timeline to run.
A set of random classes that don't really belong to a module so they're all dumped here.
A thread safe queue class that's only used for connection message passing. It could probably be moved to the networking module...
At the moment this only contains the Stopwatch class that I use for profiling stuff in tests, but will probably be expanded into it's own common module to keep track of time related things.
The functions for converting to and from hexadecimal, currently used for asset IDs.
This is a set of classes that we use to pass any form of serialized data with, a very core and essential set of classes, but not belonging to any one module.
A combination of the shared and recursive mutex classes. It's recursive and shareable. Essential to the Chunk class and ECS thread safety.
A header file that defines a preprocessor that allows us to allocate vectors on the stack instead of the heap... Currently used in the ECS system where templates would usually stack allocate stuff, but we can't because runtime. Complete overengineering, whoever came up with this code is dumb. Don't use this.
A vector that ensures that once an object has been added to it, it's index will not change. It also has deletion and insertion functions that make sure that deleted indexes get reused.
The thread pool for brane engine. Similar to the Unity jobs system, but much more prone to errors. Use anytime you want easy multithreading. It's automatically initialized by the runtime so you should be able to interact with it at any time through it's static methods.
All this does is load the config.json file at startup, and then allow you to grab it through the static json() method at any time. You can also use the static save() function to save the config if you've made changes.
Woefully underused module. The testing module is a separate build target using gtest that if extended right will reduce the amount of bugs in our program.