Systems

Systems contain the actual code that process components. A system can be created using the #[system] macro:

#![allow(unused)]
fn main() {
use dynec::system;

#[system]
fn hello_world() {
    println!("Hello world!");
}
}

After the #[system] macro is applied, hello_world becomes a unit struct with the associated functions hello_world::call() and hello_world.build(). call calls the original function directly, while build() creates a system descriptor that can be passed to a world builder.

We can package this system into a "bundle":

#![allow(unused)]
fn main() {
use dynec::world;

pub struct MyBundle;

impl world::Bundle for Bundle {
    fn register(&mut self, builder: &mut world::Builder) {
        builder.schedule(hello_world.build());
        // schedule more systems here
    }
}
}

Then users can add the bundle into their world:

#![allow(unused)]
fn main() {
let mut world = dynec::new([
    Box::new(MyBundle),
    // add more bundles here
]);
}

Alternatively, in unit tests, the system_test! macro can be used:

#![allow(unused)]
fn main() {
let mut world = dynec::system_test!(
    hello_world.build();
);
}

Calling world.execute() would execute the world once. Run this in your program main loop:

#![allow(unused)]
fn main() {
event_loop.run(|| {
    world.execute(&dynec::tracer::Noop);
})
}

Ticking

Since dynec is just a platform-agnostic ECS framework, it does not integrate with any GUI or scheduler frameworks to execute the main loop. Usually it is executed at the same rate as the world simulation, screen rendering or turns (for turn-based games), depending on your requirements.

It is advisable to keep latency-sensitive operations out of the main loop, i.e. do not process them directly with the Dynec scheduler so that the world tick rate does not become a necessary latency bottleneck. Dynec systems are designed for ticked simulation, not event handling; event handlers may interact with the ticked world through non-blocking channels.