Production-grade ECS world simulation framework โ people, cities, relationships, and more
Project description
๐ pykworldsim v3
Production-grade ECS world simulation framework.
Simulate people, cities, relationships, jobs, goals, and events with a clean, fast, fully-tested Entity-Component-System engine.
โจ What's new in v3
| Feature | Detail |
|---|---|
| โก Inverted-index QueryEngine | O(smallest-set) lookups โ no full entity scans |
| ๐ Staged mutation queues | stage_add_component / destroy_entity safe during iteration |
| ๐ฒ Injected RNG | sim.rng = random.Random(seed) โ fully deterministic |
| ๐ก EventBus | emit() / subscribe() โ systems talk via events, not direct calls |
| ๐ System priority ordering | system.priority โ lower runs first, sorted automatically |
| ๐ช Lifecycle hooks | on_add / on_remove via EntityRegistry + EventBus |
| ๐ธ Snapshot / Replay | sim.take_snapshot("label") / sim.restore_snapshot("label") |
| ๐ Plugin registry | Register custom systems by class or dotted import path |
| โ๏ธ Config: world.size + count | count: N spawns N identical entities; world.size wired |
| ๐ฅ๏ธ CLI overrides | --steps --dt --seed --debug override config file values |
๐ฆ Installation
# From PyPI
pip install pykworldsim
# From source (dev extras)
git clone https://github.com/ka1rav6/pykworldsim
cd pykworldsim
pip install -e ".[dev]"
๐ Quick Start
Physics
from pykworldsim import World, Simulation, Position, Velocity, MovementSystem
from pykworldsim.core.systems.physics import PhysicsSystem
world = World(name="demo")
world.register_system(PhysicsSystem(gravity=9.81, bounds=(0, 0, 100, 100)))
world.register_system(MovementSystem())
e = world.create_entity()
world.add_component(e, Position(x=10.0, y=0.0))
world.add_component(e, Velocity(dx=2.0, dy=0.0))
sim = Simulation(world, seed=42)
sim.run(steps=100, dt=0.1)
print(world.get_component(e, Position))
Social world
from pykworldsim import (World, Simulation, Person, Job, Goal,
SocialSystem, EventSystem, EventComponent)
world = World(name="society")
world.register_system(SocialSystem())
world.register_system(EventSystem())
# React to goal completion via EventBus
world.events.subscribe("goal_completed",
lambda d: print(f"Goal done: {d['description']}"))
alice = world.create_entity()
world.add_component(alice, Person(name="Alice", age=28.0, happiness=65.0))
world.add_component(alice, Job(title="Engineer", salary=8000.0, satisfaction=75.0))
world.add_component(alice, Goal(description="Get promoted", priority=9.0))
sim = Simulation(world, seed=2024)
sim.run(steps=20, dt=1.0)
p = world.get_component(alice, Person)
print(f"Alice: age={p.age:.1f}, happiness={p.happiness:.1f}")
Deterministic RNG
sim = Simulation(world, seed=42)
# All randomness in systems must use sim.rng (not global random)
value = sim.rng.random() # deterministic across runs
Snapshot / Replay
sim.run(steps=50, dt=0.1)
sim.take_snapshot("checkpoint") # save state
sim.run(steps=50, dt=0.1) # advance further
sim.restore_snapshot("checkpoint") # roll back
Staged mutations (safe during iteration)
class SpawnSystem(BaseSystem):
def update(self, world, dt):
for entity, (pos,) in world.get_entities_with(Position):
if some_condition:
new_e = world.create_entity()
# Stage the component โ applied after all systems finish
world.stage_add_component(new_e, Position(x=0.0, y=0.0))
๐ฅ๏ธ CLI
# Basic run
pykworldsim run examples/config.yaml
# Override config values
pykworldsim run examples/config.yaml --steps 200 --dt 0.05 --seed 99 --debug
# Save final state
pykworldsim run examples/config.yaml --save output.json
# Inspect a saved world
pykworldsim inspect output.json
# Load a custom plugin
pykworldsim run config.yaml --plugin mypackage.systems.MySystem
# List registered plugins
pykworldsim plugins
โ๏ธ YAML / JSON Config
name: my-world
log_level: INFO
world:
size: 200.0
simulation:
steps: 100
dt: 0.1
seed: 42
systems:
- type: MovementSystem
- type: SocialSystem
params:
age_rate: 0.05
energy_decay: 0.3
- type: PhysicsSystem
params:
gravity: 9.81
bounds: [0, 0, 200, 200]
entities:
# Single entity
- position: {x: 0.0, y: 0.0}
velocity: {dx: 5.0, dy: 2.0}
# Spawn 10 identical entities
- position: {x: 50.0, y: 50.0}
velocity: {dx: 1.0, dy: 0.0}
count: 10
# Person with job + goal
- person: {name: Alice, age: 28.0, happiness: 65.0, energy: 90.0}
job: {title: Engineer, salary: 8000.0, satisfaction: 75.0}
goal: {description: "Get promoted", priority: 9.0}
position: {x: 25.0, y: 25.0}
๐ก EventBus
Systems communicate via events, never direct calls:
# Subscribe anywhere
world.events.subscribe("goal_completed", lambda d: print(d))
world.events.subscribe("entity_created", lambda d: log(d["entity"]))
world.events.subscribe("entity_destroyed", lambda d: cleanup(d["entity"]))
# Emit immediately
world.events.emit("custom_event", {"key": "value"})
# Emit deferred (safe inside system.update โ dispatched after tick)
world.events.emit_deferred("score_changed", {"delta": +10})
# Built-in events
# "entity_created" โ {"entity": Entity}
# "entity_destroyed" โ {"entity": Entity}
# "goal_completed" โ {"entity": Entity, "description": str}
๐ Plugin System
from pykworldsim.core.systems.base_system import BaseSystem
from pykworldsim.plugins import PluginRegistry
class BoundaryWrapSystem(BaseSystem):
priority: int = 15 # runs after MovementSystem(10), before SocialSystem(20)
def __init__(self, width=100.0, height=100.0):
super().__init__()
self.width = width
self.height = height
def update(self, world, dt):
for entity, (pos,) in world.get_entities_with(Position):
pos.x %= self.width
pos.y %= self.height
# Register by class
PluginRegistry.register("BoundaryWrapSystem", BoundaryWrapSystem)
# Register by dotted path (for CLI --plugin flag)
PluginRegistry.register_from_path("mypackage.systems.BoundaryWrapSystem")
๐๏ธ Architecture
pykworldsim/
โโโ core/
โ โโโ entity.py Thread-safe registry + lifecycle hooks (on_add/on_remove)
โ โโโ world.py ECS container โ staged queues, query engine, EventBus
โ โโโ simulation.py Deterministic loop โ injected RNG, snapshot/replay
โ โโโ query.py Inverted-index QueryEngine โ O(smallest-set) lookups
โ โโโ events.py EventBus โ emit/subscribe, immediate + deferred dispatch
โ โโโ components/
โ โ โโโ position.py 2-D coordinate
โ โ โโโ velocity.py 2-D velocity
โ โ โโโ person.py Individual agent
โ โ โโโ location.py Named place / city
โ โ โโโ relationship.py Directed bond
โ โ โโโ job.py Occupation
โ โ โโโ goal.py Motivation / objective
โ โ โโโ event_component.py Scheduled occurrence
โ โโโ systems/
โ โ โโโ base_system.py Abstract base โ priority + update(world, dt) contract
โ โ โโโ movement.py priority=10 โ pos += vel ร dt
โ โ โโโ physics.py priority=5 โ gravity + boundary bounce
โ โ โโโ social.py priority=20 โ ageing, energy, happiness, goals
โ โ โโโ event_system.py priority=1 โ scheduled event dispatch
โ โโโ config/
โ โโโ loader.py YAML/JSON โ World + Simulation
โโโ plugins/
โ โโโ registry.py Dynamic system registration
โโโ utils/
โ โโโ logging.py Structured logging helper
โโโ cli.py Typer CLI โ run/inspect/plugins + all overrides
ECS contract
| Concept | Role | Rule |
|---|---|---|
| Entity | Opaque integer ID | Carries no data |
| Component | @dataclass โ data only |
No logic; implements to_dict / from_dict |
| System | update(world, dt) |
Uses world.get_entities_with(...) only โ never touches world internals |
| World | Container + staged queue | Applies mutations after all systems finish |
| EventBus | Pub/sub messaging | Systems emit, never call each other directly |
System execution order (default priorities)
tick N:
EventSystem priority=1 (fire scheduled events first)
PhysicsSystem priority=5 (apply gravity/forces)
MovementSystem priority=10 (integrate velocity โ position)
SocialSystem priority=20 (age, happiness, goals)
โ _flush_staged() (apply deferred adds/removes/destroys)
โ events.flush() (dispatch deferred EventBus messages)
๐งช Tests
pip install -e ".[dev]"
pytest # all tests
pytest --cov=pykworldsim # with coverage
pytest tests/test_simulation.py -v # single file
pytest -k "test_determinism" # filter by name
Test modules:
| File | Covers |
|---|---|
test_entity.py |
Entity creation, lifecycle hooks, thread safety |
test_query.py |
QueryEngine โ add/get/has/remove/query |
test_events.py |
EventBus โ emit/subscribe/deferred/flush |
test_world.py |
World โ staged queues, priority ordering, report |
test_simulation.py |
Lifecycle, determinism, injected RNG, snapshot |
test_systems.py |
All 4 systems + system contract |
test_save_load.py |
Serialisation, ConfigLoader, edge cases |
test_plugins.py |
PluginRegistry โ register/unregister/path |
๐ License
MIT ยฉ Kairav Dutta
NOTE:
I have majorly used AI to create this. The idea and the way of implementation is original.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykworldsim-3.0.0.tar.gz.
File metadata
- Download URL: pykworldsim-3.0.0.tar.gz
- Upload date:
- Size: 36.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30e31755798007267089e4c132f0965acc8e2e6c81585b38d9f95603df413324
|
|
| MD5 |
6b24c68e3688e2aef98c0c158bb76050
|
|
| BLAKE2b-256 |
8a053e75e8c7816000be7d92636e6d27ca9e21c556458cb0a7560fb95f3bbbf8
|
File details
Details for the file pykworldsim-3.0.0-py3-none-any.whl.
File metadata
- Download URL: pykworldsim-3.0.0-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
010e07af0b0b095d08e37ec10292c52aa9e14532bffc31529786e9bb485efeb5
|
|
| MD5 |
f704b69b443a430da5c93c0600439523
|
|
| BLAKE2b-256 |
0fbfc4245c060560b052f5710391c661f82b0cc216942718819b04a9a9d933fd
|