-
Notifications
You must be signed in to change notification settings - Fork 67
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
API Design : Why so much OOP ? #87
Comments
This is an interesting idea, and I'm interested in seeing the performance implications of switching to this approach. I'm sure there will be some improvement, for the reasons you've specified, but I wonder how much of the time is actually spent there as opposed to just iteration. It will be interested to see. I would suggest making an The loss of easy modularity would be a big negative for me personally, since I tend to create one World per game scene. esper.init_world('world one')
esper.init_world('world two')
esper.switch_world('world one') Also, it would be easy enough to allow |
related to benmoran56#87 esper2.Processor is a process function esper2.enter assign world methods to be esper2 functions test_esper2.py to test new API implementation benchmark_api.py to compare API runtime
I made a first draft here. About implementation, I didn't use About performance implication, because World methods the same and are called directly, all the performance gap (out of About the loss of easy modularity, World methods can be reimplemented by user as before. Therefore, the esper2 API now looks like this : import esper2
class Position:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
class Velocity:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
def process_movement():
for ent, (vel, pos) in esper2.get_components(Velocity, Position):
pos.x += vel.x
pos.y += vel.y
esper2.enter(esper2.World())
esper2.add_processor(process_movement)
player = esper2.create_entity(Velocity(x=0.9, y=1.2), Position(x=5, y=5))
esper2.process() # repeat |
I think the only way to get any performance difference is if you manually make all of the methods into module level functions. It shouldn't be too difficult - just remove self everywhere, and put the World attributes at the top as global variables. _processors = []
_next_entity_id = 0
_components = {}
etc, etc I was also thinking about context switching. There could be a default context (with some UUID). A context is really just a collection of the various data objects (as listed above). If a new context is made/switched to, the old context can be stored in a dict: |
@benmoran56 |
Yes, that's what I was thinking. It's worth a try just to confirm. |
Good News ! I get greet scores. $ python -m examples.benchmark_api
esper1 query components, 2000 Entities: 5.735848 ms
esper2 query components, 2000 Entities: 5.481931 ms
score 22.635247453946775
$ python -m examples.benchmark_api
esper1 query components, 2000 Entities: 5.359582 ms
esper2 query components, 2000 Entities: 5.001317 ms
score 34.57849502145058
$ python -m examples.benchmark_api
esper1 query components, 2000 Entities: 5.480789 ms
esper2 query components, 2000 Entities: 5.162471 ms
score 29.907925480148688
$ python -m examples.benchmark_api
esper1 query components, 2000 Entities: 5.704820 ms
esper2 query components, 2000 Entities: 5.355262 ms
score 31.6053965630032
$ python -m examples.benchmark_api
esper1 query components, 2000 Entities: 5.422279 ms
esper2 query components, 2000 Entities: 5.155304 ms
score 25.239634343133325 And naturally $ git diff --stat esper2.py
esper2.py | 626 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------------
1 file changed, 257 insertions(+), 369 deletions(-) |
related to benmoran56#87 (comment) World attributes are now module variables World methods are now module functions
Those are compelling results. The numbers are not huge, but still valuable considering the scope of this library. I also just noticed that the |
Ok, I will do this now.
An event handler will already be world specific if the component type required by |
Same answer about processors, why not shared processors between worlds ? We could also call them directly from the main loop. It should be simpler and faster. "Explicit is better than implicit." |
From the README.md
Considering this, I suggest remove |
Forgot about the branch limitation. I just made a new
Sure, event handlers don't necessarily have anything to do with Components. The main usage is usually Processors using events to signal things between themselves. For instance I have a MapProcessor that emits an event with the new map size. Other Processors, such as CollisionProcessor, and MovementProcessor, might be interested to know about the new map change.
I'm not sure what you're asking here. These two questions seem to be asking for opposite things. Could you elaborate?
The Event system was added due to heavy demand. Many ECS libraries include something similar, so it was hard to argue against. Users can of course use another event library if they want, and are encouraged to do so if they want more functionality. I have personally started using the |
Ok, I will push to this branch. I use mainly the pure ECS functions from About event handlers and processor calls, their behavior already relies on context-dependent functions like In the case of processors, the simplest way to do this looks very good to me : direct calls to process functions in the main loop. So, processors can easily be grouped in functions. User can write processor specific arguments. It replaces the hard to maintains priority system by a readable suite of function calls. |
This is not necessarily always the case. Advanced types of processors may have different state, and handle events differently depending on how they are instantiated. For example, something like The current way around this is to manually remove event handlers when switching Scenes, but it's tedious. Making events part of the context will simplify this.
I can see the appeal of this, and of course the existing |
Thanks to replied to all these answers. I'm now writing a pull request. |
Thank for all of your work on this. I merged in the pull request, and will test it with my own projects over the next few days. There might be one or two small changes I would like to make, but overall I think it's looking pretty good. |
I have made some changes & fixed some bugs, and have currently started testing with my personal projects. A few extra functions were needed to fit my workflow. So far it seems to be working as expected. |
Does the new API allow for having what would be two worlds active at the same time? I'm experimenting with having a world for the UI and a world for the game state. It's a turn based game, and it feels simpler to have the UI and game state tick (call |
You are true. Having two contexts running at the same time with the new API requires at least two context_switch per loop turn. However, in a turn based game, the performance cost of the switch_context should not be penalizing. I think it is simpler to implement GUI elements using classic class based programing than using ECS. |
It would look something like this: esper.switch_world('gui')
call_code()
esper.switch_world('myworld1')
call_code() The performance impact of switching contexts should be negligible. |
After lots of testing with my own projects with the alpha, I've release v3.0. |
The today esper API is class based programming. I think they are a unreasonable use of classes in the case of a python lightweight library. This API looks like this (from README.md).
The new API I suggest to write looks like this
Here is how I analyze the benefits and loss.
Readability
In
process
implementation, they are no unnecessaryclass X
lines; they are no repeatedself.world
.The world instance is module-side, they are no world attribute in the App-level object or in global variables.
Performance
esper implementation become straightforward.
Less calls to the dot operator in process implementation and even less using
from esper import get_components
.contextvar.get
at the start of every esper function must have a performance cost.Need to write a benchmark to avoid taking a step back (first PR).
Modularity
It is harder to redefine World and Processor methods before use them to alter their behaviors. Because esper is a lightweight library, a developer with specific needs can implement a new function for this, so it is not so embarrassing.
Reusability
Using contextvars from the python standard library allows to use several World instances at the same time.
The text was updated successfully, but these errors were encountered: