NSSTC / sim-ecs

Batteries included TypeScript ECS
https://nsstc.github.io/sim-ecs/
Mozilla Public License 2.0
81 stars 12 forks source link

event system? #34

Closed smallka closed 2 years ago

smallka commented 2 years ago

refer to bevy: https://github.com/bevyengine/bevy/blob/latest/examples/ecs/event.rs

minecrawler commented 2 years ago

I wanted to get events into sim-ecs for quiet a while, and I have been thinking about ways to make them a reality. sim-ecs is not built the same way bevy_ecs works (for a number of reasons), so there are some design concerns I have and I can't find a good solution. So, let me just brainstorm:

Option 1 ReadEvent and WriteEvent are part of the regular IAccessQuery interface, but Read only triggers the query when there is an event / the query only has data when there is an event

class MyEvent {
    constructor(
        public message: string,
    ) {}
}

const query = new Query({
    event: ReadEvent(MyEvent),
    eventWriter: WriteEvent(MyEvent),
});

for (const {eventReader, eventWriter} of query.iter()) {
    console.log(event.message); // <- ref is constant for all items in query
    setTimeout(eventWriter.send(new MyEvent('Hello World')), 100); // <- ref is constant for all items in query
}

Option 2 Expose a new interface IEventSystem, which is a special System interface for handling events without touching the current Query APIs

class MyEvent {
    constructor(
        public message: string,
    ) {}
}

class MyEventSystem<T extends MyEvent> extends EventSystem<T> {
    readonly query = new Query({ counterObj: Write(Counter) });

    run(actions: ISystemActions, event: T) {
        console.log(event.message);
        actions.sendEvent(new MyEvent('Hello World'));
    }
}

Option 3 Implement an API on the world interfaces which allows manually polling events at any point

class MyEvent {
    constructor(
        public message: string,
    ) {}
}

class MySystem extends System {
    readonly query = new Query({ counterObj: Write(Counter) });

    run(actions: ISystemActions) {
        for (const event of actions.getEvents(MyEvent)) {
            console.log(event.message);
        }

        actions.sendEvent(new MyEvent('Hello World'));
    }
}

Option 4 We go ahead with stageless, centralized pipeline planning, PLUS implement a system builder, which gets rid of the class boilerplate and makes systems look like a configurable function - which would work more like bevy_ecs.


// this is fantasy code!

class MyEvent {
    constructor(
        public message: string,
    ) {}
}

const MySystem = System
    .withParams([
        new Query({
            counterObj: Read(Counter),
        }),
        EventWriter(MyEvent),
        EventReader(MyEvent),
        ReadResource(GlobalStore),
        WriteResource(SomethingEelse),
        // ...
    ])
    .build((query, eventWriter, eventReader, globalStore, somethingElse) => {
        if (eventReader.empty()) return;

        for (const event of eventReader.events()) {
            console.log(event.message);
        }

        eventWriter.send(new MyEvent('Hello World'));

        query.execute(({counterObj}) => {
            // ...
        });
    });
minecrawler commented 2 years ago

After creating a PoC with option 4, it seems to be a good move forward, so I will implement centralized pipelines, now, and after that work on stageless, System builder and finally events. Might take a while, but I'll try to work on it quickly and release it as a package in 0.5.0

minecrawler commented 2 years ago

Merged in #43 and will be part of v0.5