benmoran56 / esper

An ECS (Entity Component System) for Python
MIT License
537 stars 67 forks source link

Add processor phases feature #91

Open acinis opened 10 months ago

acinis commented 10 months ago
acinis commented 10 months ago

Hi,

(Sorry if PRs without prior issue are not welcome here.)

This PR adds phases to world processing feature. It allows to define custom flags via enum.IntFlag, and choose which group of processors will be run.

It's usefull eg. when we have some logic systems that will be called at higher frequency than rendering system.

Note: I chose name phase cos:

metaphist commented 10 months ago

A small usage example would be cool. I have a vague idea of skipping certain processors, but not 100% sure of a use case.

acinis commented 10 months ago
# Example: Fixed update time step but with variable rendering

class Phase(enum.IntFlag):
    PHYSICS = enum.auto()
    RENDERING = enum.auto()

# TIME_PER_UPDATE is our fixed delta time
TIME_PER_UPDATE = 16 # Just an example, 16ms is about 60 FPS
acc = 0  # accumulator - how much time we are behind with simulation

while True:

    elapsed = get_elapsed_time()
    acc += elapsed

    handle_input()

    # Inner catch-up loop
    # When eg. user PC can't do so much processing in certain in-game situation
    # and we are behind with simulation, this loop will run few times...
    while acc >= TIME_PER_UPDATE:
        world.process_phase(Phase.PHYSICS, TIME_PER_UPDATE)
        acc -= TIME_PER_UPDATE

    # ... but rendering is run just once, so we lost few rendered frames,
    # but game world is still intact (eg. no bullet will fly through the wall).
    world.process_phase(Phase.RENDERING, TIME_PER_UPDATE)

I feel like this example is too minimal, but I hope it's clear how processing phases will be useful.

Also think about various supporting systems like caching, achievements, etc. These systems can be run at lower frequencies, but currently calling world.process() will run all systems at once.

So another example will be (with all systems at fixed time step):

Also, in some (most?) ECS implementations systems are run manually, so we have full control how and when our systems will run. In this PR, everything will work as before. But if someone needs more control, there are additional methods for running only certain processing phase.

Felecarpp commented 10 months ago

Is it like using multiple World instances (or multiple contexts in v3) but separate processors can share data ? So there will be far fewer use cases for multiple contexts.

acinis commented 10 months ago

Well, I think it's not like contexts. From https://github.com/benmoran56/esper/blob/esper3/esper/__init__.py

Each World is a dedicated context, and does not share Entities, Components, etc.

Phases will share everything, it's just a way to run some processors conditionally (less frequently or based on user setting, etc).

I think phases will play nicely with multiple worlds/contexts.

PS. Maybe I just choose bad name for phases.

benmoran56 commented 9 months ago

Sorry for the delay. Now that 3.0 is out and has gotten some testing, lets follow up on this. It's an interesting idea, and I can see how it can be useful.

I'm also not sure if phase is the best name for this. I can certainly understand the difficulty in choosing names :laughing: To me, stage and phase sound like it's always related to frequency. This might be a common case, but how about something more generic, like tag? Perhaps that's not any better :thinking: