Skip to content

Commit

Permalink
Temporarily roll back integrating and aliasing the dataclass decorator.
Browse files Browse the repository at this point in the history
  • Loading branch information
benmoran56 committed Jun 28, 2021
1 parent 78b9135 commit e3d57bb
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 67 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
dist: xenial
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "nightly"
- "pypy3"

Expand Down
62 changes: 12 additions & 50 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,18 @@

Esper
=====
**Esper is a lightweight Entity System for Python, with a focus on performance.**
**Esper is a lightweight Entity System module for Python, with a focus on performance.**

Esper is an MIT licensed Entity System, or, Entity Component System (ECS).
The design is based on the Entity System concepts outlined by Adam Martin in his blog at
http://t-machine.org/, and others. Efforts were made to keep it as lightweight and performant
as possible.

There is a fairly accurate writeup describing Entity Systems in this Wikipedia article:
The following Wikipedia article provides a summary of the ECS pattern:
https://en.wikipedia.org/wiki/Entity_component_system

API documentation is hosted at ReadTheDocs: https://esper.readthedocs.io

Inspired by Sean Fisk's **ecs** https://github.com/seanfisk/ecs,
and Marcus von Appen's **ebs** https://bitbucket.org/marcusva/python-utils.


What's New
----------
**1.2** - Calls to `super()` are no longer necessary in your Processor subclasses.
This should eliminate a fair amount of boilerplate. The README has also been updated
with more usage examples. All methods should now have at least one example. And finally,
wheels are now uploaded to PyPi. This should help with packaging systems that only support
wheels. Addresses issue #38.

**1.0.0** - Esper is now using simple lru_caching internally by default. The cache is currently
flushed when adding or deleting Entities or Components from the World, Component queries
are much faster otherwise. This will likely be improved in a Future version. In addition
to caching, Esper now supports passing kwargs to Processors. Continuous Integration testing
is now being done for Python 3.7.

**0.9.9** - The big change in this release is that esper has been condensed into a single
file: `esper.py`. This will make it simple to just drop into your project folder,
without cluttering your project with additional folders that didn't really need to
exist. You can still install it from PyPi via pip if you wish, but it's easy enough
to just ship with your project (and of course the license allows for this).

**0.9.8** - This release contains a new timer that can be enabled to profile Processor execution
time. Simply pass the "timed=True" parameter to the World on instantiation, and a new
World.process_times dictionary will be available. This contains the total execution time
of each Processor in milliseconds, and can be logged, printed, or displayed on screen as
is useful. It's useful to see a quick profile of which processors are using the most cpu
time, without fully profiling your game. This release also contains some consolidations
and cleanups for the benchmarks.

**0.9.7** - By default, entities are now lazily deleted. When calling *World.delete_entity(entity_id)*,
Entities are now placed into a queue to be deleted at the beginning of the next call
to World.process(). This means it is now safe to delete entities even while iterating
over components in your processors. This should allow for cleaner Processor classes, by
removing the need to manually track and delete "dead" Entities after iteration. If you
do wish to delete an Entity immediately, simply pass the new optional *immediate=True*
argument. Ie: *self.world.delete_entity(entity, immediate=True)*.


1) Compatibility
----------------
Expand All @@ -69,8 +29,8 @@ interpreter. Continuous Integration (automated testing) is done for both CPython

2) Installation
---------------
No installation is necessary. Esper is a tiny library with no dependencies. Simply copy
*esper.py* into the top level of your project folder, and *import esper*.
No installation is necessary. Esper is a single-file module with no dependencies.
Simply copy *esper.py* into your project folder, and *import esper*.

If you prefer, Esper is also available on PyPI for easy installation via pip.

Expand All @@ -90,24 +50,26 @@ your application is running.

Entities are simple integer IDs (1, 2, 3, 4, etc.).
Entities are "created", but they are generally not used directly. Instead, they are
simply used as IDs in the internal Component database, to track collections of Components.
simply used as IDs in the internal Component database to track collections of Components.
Creating an Entity is done with the World.create_entity() method.


* Components

Components are defined as simple Python classes. In keeping with a pure Entity System
design philosophy, they should not contain any logic. They might have initialization
code, but no processing logic whatsoever. A simple Component might look like::
code, but no processing logic whatsoever. A simple Component can be defined as::

class Position:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y

Esper exports Python's *dataclasses.dataclass*
(https://docs.python.org/3/library/dataclasses.html#module-dataclasses) decorator as
*esper.component*, which can be used to reduce boilerplate::
In addition, the excellent `dataclass` decorator is available in Python 3.7+.
https://docs.python.org/3/library/dataclasses.html#module-dataclasses
This decorator is handy to simplify the creation of Component definitions::

from dataclasses import dataclass as component

@component
class Position:
Expand Down Expand Up @@ -306,7 +268,7 @@ you want to first check if an Entity has a Component, get it if so, then operate
write it this way::

if self.world.has_component(ent, Stun):
stun = self.world.get_component(ent, Stun)
stun = self.world.component_for_entity(ent, Stun)
stun.duration -= dt

The above code works fine, but the *try_component* method is more concise and slightly faster.
Expand Down
10 changes: 10 additions & 0 deletions RELEASE_NOTES
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
esper 1.5
=========
Maintenance release

Changes
-------
- Update documentation with notes about dataclass decorator usage.
- Add Python 3.9 to Continuous Integration testing.


esper 1.4
=========
Maintenance release
Expand Down
8 changes: 3 additions & 5 deletions esper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import time as _time

from dataclasses import dataclass as component

from functools import lru_cache as _lru_cache

from typing import Any as _Any
Expand All @@ -13,7 +11,7 @@
from typing import TypeVar as _TypeVar


version = '1.4'
version = '1.5'

_C = _TypeVar('_C')
_P = _TypeVar('_P')
Expand Down Expand Up @@ -119,8 +117,8 @@ def create_entity(self, *components: _C) -> int:
self._next_entity_id += 1

# TODO: duplicate add_component code here for performance
for component in components:
self.add_component(self._next_entity_id, component)
for cmp in components:
self.add_component(self._next_entity_id, cmp)

return self._next_entity_id

Expand Down
5 changes: 3 additions & 2 deletions examples/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
# -*- coding: utf-8 -*-

import gc
import pickle
import sys
import time
import optparse

from esper import component, World
from dataclasses import dataclass as component

from esper import World

######################
# Commandline options:
Expand Down
5 changes: 4 additions & 1 deletion examples/benchmark_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import sys
import time
import optparse
from esper import component, Processor, World

from dataclasses import dataclass as component

from esper import Processor, World

try:
from matplotlib import pyplot
Expand Down
5 changes: 4 additions & 1 deletion examples/headless_example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import time
from esper import component, Processor, World

from dataclasses import dataclass as component

from esper import Processor, World


##################################
Expand Down
4 changes: 1 addition & 3 deletions examples/pygame_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pygame

import esper


Expand Down
3 changes: 0 additions & 3 deletions examples/pyglet_example_batch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pyglet
from pyglet.gl import *
from pyglet.window import key
Expand Down

0 comments on commit e3d57bb

Please sign in to comment.