Skip to main content

No project description provided

Project description

Peach's Entity Component System (PECS)

An Entity-Component-System (ECS) Library.

Entity

Don't think of this as an object in the traditional sense like in Object Oriented Design. An ECS pattern is a Data Oriented Design pattern. An Entity is just an identifier, think of it like a key in a map or row number in a database table.

ent = world.create_entity()

Component

This is the actual data associated with an Entity. It can take any shape or form that you wish. Typically it is kept as light as possible, think basic types (int, float, string, etc) as opposed to classes with multiple attributes. Although, it can, and does, make sense to group common data together into one class and use that as the component. For example the Position of an Entity has three elements, the X, Y, and Z coordinates. This would not make sense to keep as three different components since almost all operations on Position are going to need all three coordinates. Same for Color of an Entity, it could include Red, Green, Blue, and Alpha.

Components come in three different "flavors": Tag, Component, Relationship. All three of these "flavors" are still a Component and are all treated exactly the same. In fact, you'll never really notice the difference between them as they are being used.

Tag

This is the simplest form, it has no data except its own existance. Think of a situation where you would normally use a boolean flag to indicate something. The existence of a Tag in an Entity indicates the flag is set to True. Having Tags is useful for setting up lightweight queries for Entities.

So instead of code like this:

for item in all_items:
  if item.is_valid:
    do_something(item)

You would have something like this:

for ent, comps in world.query(["item", "is_valid"]):
  do_something(comps[0])

More efficient since all data being processed is contiguous and homogonous and less branching means less bugs.

Component

This is a basic Component in the traditional sense, it is a class that holds some data. Typically this is kept as light as possible

Relationship

This is the most powerful form of Component since it relates one piece of data to another. The most obvious use for this is graphs. It is also dangerous as it can lead to a "back-slide" into OOP and other programming paradigms.

System

This is the part of the design pattern that acts on the components.

When a System is added to a World an instance of the System is created and injected with the instance of the World that it belongs to. This allows the System to manipulate and query the World in anyway needed. Including adding/removing other Systems or itself as well as Entities and Components.

class Ping(System):
  def run(self):
    for ent, comps in self.world.query("ping"):
      print (ent, comps)
      sleep(1)
      self.world.remove_component(ent, "ping")
      self.world.add_component(ent, "pong")

world.add_system(Ping)  

Systems are added to a World and subscribed to a run event by default if no event is provided. If another event is provided then the System is subscribed to that event. Systems can subscribe to multiple events. The run event is special in that it is continuously run in a loop once the call world.start() is made. Systems can also subscribe to Components. There are three types of events for every Component: on_create, on_update, and on_delete. Intuitively, when a Component is created, updated, or deleted the corresponding event is triggered and the corresponding function call is made to any System that is subscribed to that Component.

In the following example the Ping system is subscribed to the Pong component. When a new Pong component is created a call to Ping.create() is made, passing in the newly created Pong component and the entity that it comprises. If the Ping system had defined an update() function it would also be called whenever a Pong component is updated. Likewise for delete().

class Ping(System):
  def create(self, ent, component):
    verify(component)

world.add_system(Ping, "ping")

Systems have four core functions that can be overwritten: create, update, delete, run. Each function corresponds to a subscriptable event. The run function is exclusively for the run event. This is continuously looped over from the time the world is started until it is stopped. It is possible to have a World that is executing without ever calling start(). Systems can subscribe to Component driven events. Each function is executed for the corresponding component event type. In this way a single System can behave differently depending on the type of event

Create

Useful for verification checks and other initialization tasks that need to be run after a component is created. When a System subscribes to an _on_create event for a component, this is the function that is called and the component

Update

Delete

Run

World

This is where all interaction to the ECS framework actually takes place. This ECS implementation maintains the concept of Worlds and allows multiple of them that can coexist with each other and even run simultaneously. Worlds are built prior to anything else happening, they can also be changed dynamically. The real defining thing about Worlds are Systems, as they are the behaviours of the World that act on the Components that make up the Entities.

Worlds can be cloned, this is ideal for creating reusable templates with a set of Systems. Be careful when cloning as the entirety of the World in its current state will be cloned over, this includes all Entities, Components, and Systems. Since Systems run asyncronously there is no real way to determine the state of the World. It is recommended to only load template Worlds with Systems and nothing else.

world_template = World("main_template")
world_template.add_system(Ping)
world_template.add_system(Pong)

world_1 = world_template.clone()

Ping-Pong Example

class Ping(System):
  def run(self):
    for ent, comps in self.world.query("ping"):
      print (ent, comps)
      sleep(1)
      self.world.remove_component(ent, "ping")
      self.world.add_component(ent, "pong")

class Pong(System):
  def run(self):
    for ent, comps in self.world.query("pong"):
      print (ent, comps)
      sleep(1)
      self.world.remove_component(ent, "pong")
      self.world.add_component(ent, "ping")

world_1 = World("main")
world_1.add_system(Ping)
world_1.add_system(Pong)
world_1.start()

ent_1 = world_1.create_entity()
world_1.add_component(ent_1, Component("ping")

Getting started

Installation

pip install reactives

Support

I don't get paid for this and it is just a fun experiment so it is provided as-is.

Roadmap

What will we think of next?

Contributing

This is just a fun experiment built for my own exploration, education, and entertainment so no contributions are accepted at this time. I am open to hearing about additional features, improvements, etc..

Authors and acknowledgment

Me, myself, and I

License

Beerware

Project status

Experimental

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

peach_ecs-0.0.5.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

peach_ecs-0.0.5-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

Details for the file peach_ecs-0.0.5.tar.gz.

File metadata

  • Download URL: peach_ecs-0.0.5.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for peach_ecs-0.0.5.tar.gz
Algorithm Hash digest
SHA256 18ffbe1e66141954c36e15f46f6ccd5ec5a8292ae62fa2bdccfa6491948d258a
MD5 f5afc9139918dcb889aa3113de4e4f13
BLAKE2b-256 488be5a75fc055a5659377b6eb6f2fea426cb7f31b1b718cc33840453c2e407d

See more details on using hashes here.

File details

Details for the file peach_ecs-0.0.5-py3-none-any.whl.

File metadata

  • Download URL: peach_ecs-0.0.5-py3-none-any.whl
  • Upload date:
  • Size: 8.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.3

File hashes

Hashes for peach_ecs-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 e9425fd2cd2fd021060dae5b1320b2fdb0a655d20af4b9b93fc80e0f90ad6991
MD5 c52478f18daae7031c5e9ca226448043
BLAKE2b-256 02b9ba90806044989515531547ed539224ed1aed62fd2a22b24a5bf36afcbe0f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page