A year ago, I started to create a framework in Golang for developing 3-dimensional real-time applications (for example, a game) using pseudo-functional concepts instead of the object-oriented style that is commonly used. It was a cool learning project that got put aside, but after thinking about it for the last eight months I wanted to pick it up and share some thoughts on how it will be designed in the future.
An application framework often provides a number of systems as well as a method to connect these systems. Traditionally in games, these systems follow an architectural pattern called Entity-Component-System (ECS).
Areas of Concern
The project will have the following major components:
- Architecture: provides utilities to maintain application state over time
- Rendering engine: displays visual output on a user’s screen
- Audio engine: plays sounds for the user
- Rules engine: allows developers to create common triggers that will send actions over time
- Artificial intelligence engine: provides utilities to allow entities to make complex decisions
- Scripting engine: an interface so users can add custom behaviour to an application
These components are distinct and mutually exclusive; they can be composed together to implement the application logic.
The architecture system is the part of the framework that is commonly handled by an ECS library.
The architecture system will be inspired by Redux and how it handles state, which means that it will follow the core three principles of Redux. These principles are to ensure that:
The architecture system will be inspired by Redux and how it handles state, which means that it will follow the three principles of Redux. These principles are to ensure that:
- there is a single source of truth,
- state is read-only, and
- changes are made with pure functions.
According to these principles, the developer will emit stateless objects called actions to update state. These actions will be processed by a pure function called a reducer, which will update the application's state. The reducer will accept the current state and an action as parameters, and will return the new state object.
Since creating a large function to handle all of the application logic is impractical, multiple pure functions with the same signature will be combined to create the core reducing functionality.
Entities (stand-alone objects within the application) will be created by combining components, some of which will be connected to the global state. Components will define which parts of the application state they need, and will have methods that dispatch actions on certain events.
Notably, components will not have local state. Components will be updated when changes to application state happen that affect the parts of state they are subscribed to.
For example, to change an object's 3-dimensional coordinates, an action could be emitted that will have an object-identifier and the new coordinates. A reducer will then modify the object’s coordinates in the global state, and the component that is subscribed to changes on that ID will update, triggering a re-render.
Render and Audio Systems
These systems will integrate with OpenGL to provide the basic functionality of rendering and playing sounds. They will largely consist of functions to modify the rendering pipeline.
This needs to handle:
- importing 3D objects (meshes)
- creating particle systems
- creating lights
- rendering meshes
- performing specific mathematical operations (transformations)
- creating and modifying shaders
- playing sounds
Rules System (Physics)
This will provide an interface to add various properties to an object, which will be acted upon in each frame. These properties will largely be used to define the physical properties and movement of objects, but could also be used to create logic around other updating systems like plant growth. This will involve sending large amounts of actions to the application state object, so the sending and modification of state will need to be very efficient.
The other components of the framework will be considered when the need arises, but until then they are peripheral.
This is likely a very bad idea, since Golang is not designed for the demands of a real-time rendering framework. The project is mainly a fun proof-of-concept and learning initiative. Many of the decisions outlined in this document may change as development on this project continues, as I have written very little code to enforce these ideas; however, I look forward to seeing where it takes me. You can follow my progress on Github.
Subscribe to Tristan Potter
Get the latest posts delivered right to your inbox