Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run pipeline from pipeline #68

Open
minecrawler opened this issue Sep 12, 2023 · 4 comments
Open

Run pipeline from pipeline #68

minecrawler opened this issue Sep 12, 2023 · 4 comments
Labels
enhancement New feature or request
Milestone

Comments

@minecrawler
Copy link
Collaborator

For many games, it's useful to have different schedulers for systems. For example a turn-based games needs a scheduler for looping logic, like rendering, but also needs to process turns whenever a player presses a button.

There are various ways to go about this problem, but I'd like to propose the following and implement it in the near future:

It should be possible to run logic on the same (game) world, in order to circumvent syncing or expensive queries. Also, scheduling should be handled similar to data. We can already define a system schedule easily! So why not take it to the next level and define schedules for all our needs and be able to execute a schedule when we need it from within another schedule? Basically, call a method on Actions which takes a ISyncPointPrefab and executes it.

Example:

const TurnProcessorSystem = createSystem({
    actions: Actions,
    schedules: ReadResource(SchedulesRecord), // SchedulesRecord is typed as Record<string, ISyncPointPrefab>
    turnProcessingSignals: ReadEvents(TurnProcessingSignal), // may hold additional information
})
    .withRunFunction(async ({actions, schedules, turnProcessingSignals}) => {
        // If turn processing was requested - e.g. by clicking on a "End Turn" button ...
        if (turnProcessingSignals.getOne()) {
            // start processing the turn, e.g. run enemy AI
            await actions.executeSchedule(schedules.turnProcessorPipeline);
        }
    })
    .build();

Using this example, I'm not 100% yet, though, if it's a good idea to inject the processing right there, or defer it using Commands, as in

actions.commands.executeSchedule(schedules.turnProcessorPipeline);
@minecrawler minecrawler added the enhancement New feature or request label Sep 12, 2023
@dannyfritz
Copy link

dannyfritz commented Sep 13, 2023

Is there a downside to offering both options? Executing a schedule mid-schedule and scheduling a deferred schedule? I lean toward deferred if I had to choose one because executing mid-schedule provides a lot of foot-guns. But I don't really have a good enough view of the space to truly provide insight.

@minecrawler
Copy link
Collaborator Author

I've been thinking about it. The only footgun I can come up with is that a user forgets to await the schedule. That's the same footgun all systems have, since sim-ecs is built around the idea that a step is finished at a certain point, and no async tasks are still running (unmanaged) in the background.

It could be safer to defer it, because then sim-ecs implements the await, but it would mean less control over when exactly the logic is running. Hence for now, I plan to add the execute method on Actions (and on Commands if someone really wants to defer it...).

Other than that, since all system have the same kind of access to the world and should use similar (save) scheduling logic by default, they won't be riskier to execute immediately.

@dannyfritz did you have any other footguns in mind?


Parameter-wise it will be good to add the possibility to set a scheduler, in case the default one isn't the right choice.

@dannyfritz
Copy link

dannyfritz commented Sep 13, 2023

I think you're getting at the footgun I'm thinking about. But if you schedule mid-schedule, you might cause the outer query to be out-of-date when it returns to the outer schedule after modifying entities in the inner schedule.

@minecrawler
Copy link
Collaborator Author

minecrawler commented Sep 13, 2023

Modifying entities, as in adding and removing components and entities, is not immediately possible (as in "there's no API on Actions to do so"). Things which may cause any system during a step to become out-of-date can only be accessed via Commands, which defers the actual modification to a save point to do so (usually the end of a step). See https://nsstc.github.io/sim-ecs/interfaces/ISystemActions.html and https://nsstc.github.io/sim-ecs/interfaces/ICommands.html . In this case, the modification would still be deferred to the end of the step, so that the outer query stays valid.

In order to make immediate changes, it's a better architecture to keep the ECS constant and work with fields on components. This won't trigger expensive modifications and cache updates, leading to better and smoother performance. Since in JS objects are references, making changes to them immediately makes the changes available everywhere. This could be an issue if the scheduler-invoking system does not expect updates to a component. In such a case, using the Commands version of the invoker method might be the better way...

@minecrawler minecrawler added this to the 0.6.3 milestone Sep 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: In progress
Development

No branches or pull requests

2 participants