Designing an Entity Component System for interfacing with a scripting language
https://softwareengineering.stackexchange.com/questions/394291
Pergunta
I am currently building an Entity Component System (ECS) in cython in order to speed up operating on large numbers of game objects in python. In the process of building this system, I ran into the following architectural issues that I would appreciate shedding some light upon. I have read through lots of online blog posts describing ECS strategies, but most of these are geared towards C++ and do not describe interfacing such systems with a scripting language like python.
I am defining the parts of my ECS as follows for clarity, since everyone seems to have different terminology/mental frameworks for these concepts:
- Handle: This is a uint64_t that is subdivided into index, version, type, and free parts. For use in my CompMaps.
- Entity: Just a handle.
- Comp (Component): Just a handle.
- CompType: A uint8_t (derived from the Handle's "type")
- CompMap: This is a "slotmap" data structure. Memory for each component/entity can be retrieved using Handle objecsts.
- Archetype: A set of CompTypes, mapped to a uint16_t for easy access.
Since the component structs are not directly accessible in python outside of numpy arrays, and since I don't have access to C++ templating in python that is typically used for registering components, I expose all accesses of entities and components through these handles. As such, I have the following questions regarding how to safely provide access to CompTypes and Components in my ECS.
- Should each "world" in the ECS be able to define its own unique CompTypes and Archetypes?
- Pros: Allows some flexibility (i.e. there could be a UI specific set of CompTypes for a "UI world" and there would be no need to "waste" CompType int slots for these in the general gameplay world.
- Cons: Systems can't reliably operate on multiple worlds if the ints for each of the world's CompTypes mean different things!
- How should "built-in" CompTypes and Archetypes from the rest of the game engine be handled?
- Currently doing this as a global enum inside a cython module.
- Should these built-in types be auto-registered into every world upon initialization?
Please let me know if there are any parts that were unclear, and I would be happy to clarify them. Any help would be greatly appreciated.
Solução
I am answering this for completeness. The resulting API design that I went with can be best illustrated with some example user code:
from pyorama.core.app import *
from pyorama.core.system import *
from pyorama.core.world import *
class EnemySystem(System):
def init(self):
pass
def quit(self):
pass
def update(self):
COMP_GROUP = self.comp_group_types["test"]
print("Updating...", id(self.world), COMP_GROUP)
class Game(App):
def init(self):
MAX_ENTITIES = 10000
COMP_A = 123
COMP_GROUP = 0
super().init()
self.world = World()
self.world.init(MAX_ENTITIES)
self.world.register_comp_type(COMP_A, 1000, 32, b"4Q")
self.world.register_comp_group_type(COMP_GROUP, [COMP_A])
comp_a = self.world.create_comp(COMP_A)
comp_b = self.world.create_comp(COMP_A)
ent_a = self.world.create_entity()
self.world.attach_comp(ent_a, comp_a)
self.world.attach_comp(ent_a, comp_b)
enemy_system = EnemySystem()
self.world.attach_system(enemy_system, {"test": COMP_GROUP})
self.world.set_system_update_order([enemy_system])
def quit(self):
super().quit()
def update(self):
self.world.update()
game = Game(use_sleep=True, use_vsync=False, ms_per_update=1000.0/60.0)
game.run()
The solution that I came up with to map System
objects to the appropriate World
and CompGroupType
objects (what I had previously referred to as Archetypes
) was to create an attach_system
method. This method would be responsible for registering a system and supplying the correct world data. The correct components could then be located using the keys passed into the dict of CompGroupTypes.