ooecs 0.0.1

Object-oriented entity-component-system.
Documentation

maintenance: actively developed

ooecs

Highly non-traditional entity-component-system implementation where logic is encapsulated in object-oriented systems.

While components remain lean and data-focused, systems are treated as first-class objects that can maintain their own internal state across ticks.

It is designed for cases, where simple "linear" systems are not good enough.

Example: mimicry of traditional ECS

First, define components data:

pub struct Position {
    pub x: i16,
    pub y: i16,
}

pub struct Velocity {
    pub x: i16,
    pub y: i16,
}

Next, create World and register components:

let world = &mut World::new();
let position = Component::new::<Position>(world);
let velocity = Component::new::<Velocity>(world);
let mobs = Component::new::<List>(world);

The mobs component is needed for organizing entities in linear list.

Lets create some test entities and attach components to them:

let player = Entity::new(world);
let mob = Entity::new(world);

player.add_component(world, position, Position { x: 0, y: 0 });
player.add_component(world, velocity, Velocity { x: 1, y: 0 });
List::init(player, world, mobs);

mob.add_component(world, position, Position { x: 10, y: 0 });
mob.add_component(world, velocity, Velocity { x: -1, y: 0 });
List::init(mob, world, mobs);

Put entities in common mobs list:

List::add(player, mob, world, mobs);

Create Movement system using basic-oop library:

import! { pub movement:
    use [obj basic_oop::obj];
    use ooecs::{Entity, Component, World};
}

#[class_unsafe(inherits_Obj)]
pub struct Movement {
    mobs: Component,
    position: Component,
    velocity: Component,
    #[non_virt]
    run: fn(start: Entity, world: &mut World),
}

impl Movement {
    pub fn new(
        mobs: Component,
        position: Component,
        velocity: Component,
    ) -> Rc<dyn IsMovement> {
        Rc::new(unsafe { Self::new_raw(
            mobs,
            position,
            velocity,
            MOVEMENT_VTABLE.as_ptr()
        ) })
    }

    pub unsafe fn new_raw(
        mobs: Component,
        position: Component,
        velocity: Component,
        vtable: Vtable,
    ) -> Self {
        Movement {
            obj: unsafe { Obj::new_raw(vtable) },
            mobs,
            position,
            velocity,
        }
    }

    pub fn run_impl(this: &Rc<dyn IsMovement>, start: Entity, world: &mut World) {
        let movement = this.movement();
        let mut entity = start;
        loop {
            let (vx, vy) = {
                let velocity = entity.component::<Velocity>(world, movement.velocity).unwrap();
                (velocity.x, velocity.y)
            };
            {
                let mut position = entity.component::<Position>(world, movement.position).unwrap();
                position.x += vx;
                position.y += vy;
            }
            entity = entity.component::<List>(world, movement.mobs).unwrap().next;
            if entity == start { break; }
        }
    }
}

Now we can run the system:

let movement = Movement::new(mobs, position, velocity);
movement.run(player, world);

Finally, do some cleanup:

position.drop_component::<Position>(world);
velocity.drop_component::<Velocity>(world);
mobs.drop_component::<List>(world);

Full code:

#![feature(macro_metavar_expr_concat)]

mod game {
    use basic_oop::{Vtable, import, class_unsafe};
    use ooecs::list::List;
    use std::rc::Rc;

    pub struct Position {
        pub x: i16,
        pub y: i16,
    }

    pub struct Velocity {
        pub x: i16,
        pub y: i16,
    }

    import! { pub movement:
        use [obj basic_oop::obj];
        use ooecs::{Entity, Component, World};
    }

    #[class_unsafe(inherits_Obj)]
    pub struct Movement {
        mobs: Component,
        position: Component,
        velocity: Component,
        #[non_virt]
        run: fn(start: Entity, world: &mut World),
    }

    impl Movement {
        pub fn new(
            mobs: Component,
            position: Component,
            velocity: Component,
        ) -> Rc<dyn IsMovement> {
            Rc::new(unsafe { Self::new_raw(
                mobs,
                position,
                velocity,
                MOVEMENT_VTABLE.as_ptr()
            ) })
        }

        pub unsafe fn new_raw(
            mobs: Component,
            position: Component,
            velocity: Component,
            vtable: Vtable,
        ) -> Self {
            Movement {
                obj: unsafe { Obj::new_raw(vtable) },
                mobs,
                position,
                velocity,
            }
        }

        pub fn run_impl(this: &Rc<dyn IsMovement>, start: Entity, world: &mut World) {
            let movement = this.movement();
            let mut entity = start;
            loop {
                let (vx, vy) = {
                    let velocity = entity.component::<Velocity>(world, movement.velocity).unwrap();
                    (velocity.x, velocity.y)
                };
                {
                    let mut position = entity.component::<Position>(world, movement.position).unwrap();
                    position.x += vx;
                    position.y += vy;
                }
                entity = entity.component::<List>(world, movement.mobs).unwrap().next;
                if entity == start { break; }
            }
        }
    }
}

use game::*;
use ooecs::{Entity, Component, World};
use ooecs::list::List;

fn main() {
    let world = &mut World::new();
    let position = Component::new::<Position>(world);
    let velocity = Component::new::<Velocity>(world);
    let mobs = Component::new::<List>(world);

    let player = Entity::new(world);
    let mob = Entity::new(world);

    player.add_component(world, position, Position { x: 0, y: 0 });
    player.add_component(world, velocity, Velocity { x: 1, y: 0 });
    List::init(player, world, mobs);

    mob.add_component(world, position, Position { x: 10, y: 0 });
    mob.add_component(world, velocity, Velocity { x: -1, y: 0 });
    List::init(mob, world, mobs);
    List::add(player, mob, world, mobs);

    let movement = Movement::new(mobs, position, velocity);
    movement.run(player, world);

    position.drop_component::<Position>(world);
    velocity.drop_component::<Velocity>(world);
    mobs.drop_component::<List>(world);
}