Building a toy game engine in rust (Part 1)

So I have been toying around with Rust for more than a year, on my spare time which means I never really got into it. Mostly I've been trying ideas and writing smaller proof-of-concept applications. I've been wanting to make a larger project for a while now, partly because I want to challenge myself doing a larger project in an unfamiliar language. At work I exclusively work in C++ or C#. Partly because I think doing larger projects is a great way to exposes other aspects of a programming language.

Goal

I want to make a game engine, what else would you expect from someone working with games? Apart from wanting to learn more about Rust I want explore some engine design ideas.

Design of component system

The following is a non-detailed overview of the design of the engine. None of the code here is meant to compile, consider it pseudo code.

The entity/component system is heavily inspired by Blizzards Overwatch presentation from GDC 2016. Short summary is a very thin entity defined all "things" in the world. The entity has different components. Systems are the only thing that contain logic and iterate over components in order to update the game state.

Components

Let's start with the components are simple structs describing different aspects of an object, for example spatial information:

pub struct SpatialData {
    position: Vector3,
    rotation: Vector3,
    scale: Vector3,
    velocity: Vector3,
}

We want components of the same type to be allocated linearly in memory in order to maximize cache efficiency. In order to achieve this we create a host type:

struct ComponentHostImpl<T> {
    components: Vec<Box<T>>,
}

pub trait ComponentHost {
    pub fn create_component();
    pub fn get_component();
}

Since components are owned by the component host we need some way of safely referencing components that plays nicely with Rusts borrow checker. One way is to use a handle. The handle only references the component indirectly.

pub struct ComponentHandle {
    type: TypeInfo,
    index: usize,
}

impl ComponentHandle {
    pub fn borrow() -> Ref<T>;
    pub fn borrow_mut() -> MutRef<T>;
}

Entities

Entities are instances representing things in the world. Each entity has list of references to a set of components.

struct Entity {
    components: Vec<Arc<ComponentHandle>>,
}

Systems

Systems will iterate over the components in order to update the game state. Some rules apply. A system may:

pub trait System {
    pub fn describe() -> SystemDescription;
    pub fn update(mut& self, ctx: SystemUpdateContext) -> SystemUpdateResult;
}

World

The world is what holds all this together

pub struct World {
    systems: Vec<(SystemDescription,RefCell<System>)>,
    components: Vec<RefCell<ComponentHost>>,
    entities: Vec<RefCell<Entities>>,
}

it also implements the main update logic:

impl World {
    pub fn update(mut& self, delta: f32) -> bool {
        for (description,system) in self.systems.iter() {
            let update_context = SystemUpdateContext::from(description);
            system.borrow().unwrap().update(update_context);
        }
    }
}

General application design

The application will be essentially two threads, one with which runs the component system, and one to handle the the presentation (rendering and possibly audio).

 Thread 0         |--- Update (t)---|--- Update (t+1)-----|
 Thread 1   --- Present (t-1) ---|  |--- Present (t) ---| |--- Present (t+1) --

The component system writes presentation commands to a double buffered linear allocator which is used by the dispatch thread, while the update thread is busy producing the next frame.

Obviously the two threads need to be syncronized (at the markers in the above diagram) in order to wait for whichever thread is slow producing or consuming frames.