leudz / shipyard

Entity Component System focused on usability and flexibility.
Other
750 stars 45 forks source link

How does the current field in ViewMut struct change? What's the flow? #194

Closed PudgeKim closed 2 months ago

PudgeKim commented 8 months ago

My shipyard version: 0.6.2

I have four systems and I make it into one workload.

fn main_workload() -> Workload {
    (
        add_entities,
        system1,
        system2,
        system3,
    ).into_workload()
}

fn add_entities(
    mut entities: EntitiesViewMut,
    mut first_components: ViewMut<FirstComponent>,
    mut second_components: ViewMut<SecondComponent>,
    mut third_component: ViewMut<ThirdComponent>,
) {
    entities.add_entity((&mut first_components, &mut second_components, &mut third_component), (FirstComponent(0), SecondComponent(0), ThirdComponent(0)));
}

When I run world.run_workload(main_workload).unwrap(); I found that the storage.current seems weird.

impl<T: Component> AddEntity for &mut ViewMut<'_, T> {
    type Component = T;

    #[inline]
    fn add_entity(storage: &mut Self, entity: EntityId, component: Self::Component) {
        storage
            .sparse_set
            .insert(entity, component, storage.current);
    }
}

when add_entities system runs, storage.current is 1. when system1 runs, storage.current is 5. I think it's because 1 + 4 (total system count) = 5.

I want to know your purpose. so..

  1. What's the advantage or purpose of it?
  2. Which code changes storage.current? storage.current is changed every time each system is run, and I want to know the flow.
leudz commented 4 months ago

🤔 I don't know why storage.current is 5 but I can walk you through the flow.

current is used to assign a timestamp to tracking events, like insertions, modifications,... in these arrays: https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/sparse_set/mod.rs#L49-L52

The goal is to be able to filter these events so systems don't get multiple times the same events. Each system should see all tracking events but they should only see them once.

current is basically like the unix epoch time, it's a counter that increases with time. But shipyard doesn't know anything about time, it can run on systems that don't provide any way to know time. So instead it counts borrows. It can be a system, World::borrow, World::get,... anytime components are accessed. And it tries to only increase the counter once when multiple views are borrowed in one go to avoid before/after issues.

I wrote about why timestamps improve things compared to bool in this post. And there is an example of a workload that couldn't work without timestamps.


About the code itself. The counter lives here, it's twice the same counter, shared with an Arc: https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/world.rs#L26 https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/all_storages/mod.rs#L39

It's initialized with 1: https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/world.rs#L35

It's a private field and the only way to access it is through get_current. Every time it's accessed it increases: https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/world.rs#L1037-L1040

Workloads for example access it here: https://github.com/leudz/shipyard/blob/966f360ec1adddc09bbb826470736e6aaa0f5bfb/src/scheduler/into_workload_system.rs#L145

PudgeKim commented 2 months ago

@leudz Sorry My first question was weird.

Here's my other question..!

fn add_first_component(
    mut first_components: ViewMut<FirstComponent>,
) { .. }

fn add_second_and_third_component(
    mut second_components: ViewMut<SecondComponent>,
    mut third_components: ViewMut<ThirdComponent>,
) { .. }

when add_first_component system runs, the current field of ViewMut<FirstComponent> is 2. maybe 1(initial value) + 1 = 2.

when add_second_and_third_component system runs, the current field of ViewMut<SecondComponent> and ViewMut<ThirdComponent> are 3.

I thought it would be 2 because the components are different. My expectation: 1(initial value of ViewMut<SecondComponent>) + 1 = 2, 1(initial value of ViewMut<ThirdComponent>) + 1 = 2 What's the purpose of it? Could you please explain about it?

and what's the difference between counter field in AllStorage struct and current field in View/ViewMut struct?

leudz commented 2 months ago

current in View/ViewMut is a copy of the value of counter in World/AllStorages at the time of the system start. There is only a single global value, it's not a counter per component.

counter starts at 1, then add_first_component copy its value for all its views. counter is incremented. Then add_second_and_third_component does the same, it copies the value 2 for all its views and counter is incremented.