See https://github.com/amethyst/amethyst/wiki/ECS-Design-Crossroads
- Use "in-place modification" with "generational ID".
- Synchronize between systems using
Future
concept.
Systems are scheduled in the same order they are added/registered. Actual execution is delayed until the requested components are available. Obviously, the modified components require nothing else to be working with them at this moment.
Previous design was aimed at constraining the systems interface on the type level:
trait System {
type Inputs: ComponentList;
type Extras: ComponentList;
type Output: Component;
fn update(inputs: Self::Inputs::Data, extras: Self::Extras::ExtraData) -> Self::Output;
}
I figured there is no point in forcing the compile time interface on the systems. Instead, they can just schedule updates as free as they need:
impl System for MySystem {
fn process(&mut self, sh: &mut Schedule) {
sh.get_mut::<Position>().with(sh.get::<Speed>()).iter(|(pos, speed)| {
*pos += *speed;
});
}
}
The juice of this approach is contained within the Schedule
interface. It takes care of providing the component data and synchronizing workloads:
impl Schedule {
fn get<T>(&mut self) -> FutureData<&T> {...}
fn get_mut<T>(&mut self) -> FutureData<&mut T> {...}
}
impl FutureData<T> {
fn with<Q>(self, other: FutureData<Q>) -> FutureData<(T, Q)> {...}
fn iter<F>(self, fun: F) where F: Fn<T> {
// records a new workload
// hooks up all the signals to make it kick in after the dependencies are available
// updates the dependent futures to wait for this operation for the next systems that need them
// note: this requires the `Schedule` itself, not sure how to get it nicely
// returns immediately
}
}
Advantages:
- This is simple, both from the API point of view and the implementation. There is no need to translate compile-time information about inputs/outputs to the run-time and schedule it. This is done automatically now with signals/futures.
- More freedom for the systems. They can now access the components conditionally (thus, no need for the "optional component" concept). They can aggregate sub-systems easily, and even schedule different independent workloads.
If you special-case the 0 index to be a 'null' index, You can store it as a plain
u64
(or if you want type-safety and have nightly,Option<NonZero<u64>>
)