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

Add processor phases feature #91

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

acinis
Copy link

@acinis acinis commented Aug 15, 2023

  • imports: Union and IntFlag
  • Processor.phases instance variable: allows IntFlag and bare int for default ~0
  • World.add_processor method: new optional arg _phases
  • World._process method: new arg _phases
  • World.process method: call to _process with ~0 (all flags set - all phases on)
  • World.process_phase method: new method, like process, but accepts phases arg

- imports: Union and IntFlag
- Processor.phases instance variable: allows IntFlag and bare int for default ~0
- World.add_processor method: new optional arg _phases
- World._process method: new arg _phases
- World.process method: call to _process with ~0 (all flags set - all phases on)
- World.process_phase method: new method, like process, but accepts phases arg
@acinis
Copy link
Author

acinis commented Aug 15, 2023

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:

  • group seems to me like entity grouping
  • batch is used in eg. pyglet and OpenGL
  • stage is used in libgdx

@acinis acinis marked this pull request as draft August 22, 2023 10:45
@metaphist
Copy link

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
Copy link
Author

acinis commented Sep 11, 2023

# 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):

  • physics - 120Hz
  • rendering - 60Hz
  • achievements - 1Hz

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
Copy link
Contributor

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
Copy link
Author

acinis commented Sep 11, 2023

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
Copy link
Owner

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 😆
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 🤔

Copy link
Owner

@benmoran56 benmoran56 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basic concept is good.
I would say that it's better to replicate the process method, rather than sharing code. This will avoid the minor performance penalty for those who are not using this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants