Skip to main content

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.

Python 3.10+ Version License: MIT Tests


โœจ 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

pykworldsim-3.0.0.tar.gz (36.2 kB view details)

Uploaded Source

Built Distribution

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

pykworldsim-3.0.0-py3-none-any.whl (36.7 kB view details)

Uploaded Python 3

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

Hashes for pykworldsim-3.0.0.tar.gz
Algorithm Hash digest
SHA256 30e31755798007267089e4c132f0965acc8e2e6c81585b38d9f95603df413324
MD5 6b24c68e3688e2aef98c0c158bb76050
BLAKE2b-256 8a053e75e8c7816000be7d92636e6d27ca9e21c556458cb0a7560fb95f3bbbf8

See more details on using hashes here.

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

Hashes for pykworldsim-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 010e07af0b0b095d08e37ec10292c52aa9e14532bffc31529786e9bb485efeb5
MD5 f704b69b443a430da5c93c0600439523
BLAKE2b-256 0fbfc4245c060560b052f5710391c661f82b0cc216942718819b04a9a9d933fd

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