Architecture
This document describes LifeGrid’s internal architecture and module relationships.
High-Level Overview
┌──────────────────────────────────────────────────┐
│ Entry Points │
│ main.py (GUI) cli.py (CLI) api/app.py (API) │
└──────┬──────────────┬──────────────┬─────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────┐
│ Core Engine Layer │
│ Simulator ←→ SimulatorConfig │
│ │ │
│ ├── UndoManager (state history) │
│ ├── BoundaryMode (wrap/fixed/reflect) │
│ └── CellularAutomaton (abstract base) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Automata Implementations │
│ ConwayGameOfLife │ HighLife │ Wireworld │
│ BriansBrain │ LangtonsAnt │ Generations │
│ ImmigrationGame │ RainbowGame │ HexagonalGoL │
│ LifeLikeAutomaton (custom B/S rules) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Support Modules │
│ ExportManager │ PatternManager │
│ PluginManager │ ConfigManager (AppConfig) │
│ ThemeManager │ StatisticsCollector │
│ EnhancedStatistics │ PatternAnalyzer │
│ RLEParser/Encoder │ CellAgeTracker │
│ HeatmapGenerator │ SymmetryAnalyzer │
│ RuleDiscovery │ GPUSimulator │
└──────────────────────────────────────────────────┘
Module Breakdown
Entry Points
Module |
Purpose |
|---|---|
|
Launches the Tkinter GUI via |
|
Headless CLI with argparse. Creates a |
|
FastAPI server with REST endpoints and WebSocket streams |
Core (src/core/)
Module |
Key Classes |
Responsibility |
|---|---|---|
|
|
Orchestrates automaton stepping, undo/redo, metrics, callbacks |
|
|
Dataclass holding grid size, speed, mode, rule sets, feature flags |
|
|
Bounded stack of grid snapshots (default 100) |
|
|
Edge-handling strategies |
|
Utility functions |
Shared helpers |
The Simulator owns a CellularAutomaton instance and an UndoManager. On each step() call it:
Pushes the current grid to the undo stack.
Calls
automaton.step().Records metrics (generation, population, density).
Fires the on-step callback.
Automata (src/automata/)
All automata inherit from CellularAutomaton (defined in base.py) which provides:
step()— advance one generationget_grid() -> np.ndarray— return the current gridset_cell(x, y, value)— set a cell valuereset()— clear the gridboundary: str— edge mode ("wrap"|"fixed"|"reflect"), default"wrap"
All convolution-based automata use core.boundary.convolve_with_boundary() (wrapping scipy) instead of calling scipy.signal.convolve2d directly. This routes every neighbour count through the correct boundary handler.
LifeLikeAutomaton additionally caches the Moore-neighbourhood kernel as a class-level constant (_KERNEL) to avoid re-allocation on every step.
LangtonsAnt exposes get_population_grid() which returns the raw cell grid without the ant-marker overlay (used for population statistics).
LifeLikeAutomaton accepts arbitrary B/S rules via parse_bs(), making it the backbone of custom-rule support and the Rule Explorer.
GUI (src/gui/)
Module |
Purpose |
|---|---|
|
|
|
Canvas drawing: cells, grid lines, overlays. Uses a PIL/Pillow fast-path ( |
|
UI builder: creates widgets, binds callbacks |
|
GUI constants: |
|
|
|
|
|
|
|
Additional feature widgets |
|
Enhanced rendering utilities |
|
Procedural icon generation |
|
Layout management helpers |
|
Modern UI components (styled buttons, frames) |
|
Visual refinements |
The GUI uses Tkinter’s grid geometry manager throughout. The main layout is:
root
└── content_frame (grid)
├── toolbar (row 0, sticky EW)
├── sidebar (row 1, column 0)
├── canvas (row 1, column 1)
├── timeline (row 2, columnspan 2)
├── graph (row 3, columnspan 2)
└── statusbar (row 4, columnspan 2)
API (src/api/)
Module |
Purpose |
|---|---|
|
FastAPI application with session CRUD, stepping, state retrieval, pattern loading, WebSocket streaming |
|
|
Sessions are stored in a module-level dict keyed by UUID. The collaborative endpoint creates sessions on demand.
Advanced Analytics (src/advanced/)
Module |
Key Classes |
Purpose |
|---|---|---|
|
|
Per-generation metrics collection via |
|
|
Entropy, complexity, fractal dimension, connected components, cluster stats, symmetry, centre of mass, radial distribution (static-method API) |
|
|
Bounding box, period detection, displacement detection |
|
|
Favourites/history backed by JSON, tag-based search, similarity matching. Add patterns with |
|
|
Run-Length Encoded I/O. |
|
|
Vectorised |
|
|
Track cell age, birth/death history |
|
|
Activity/age heatmaps, symmetry detection and scoring |
Performance (src/performance/)
Module |
Key Classes |
Purpose |
|---|---|---|
|
|
CuPy-accelerated Life-like simulation with NumPy fallback |
|
Benchmarking utilities |
Performance measurement and reporting |
Plugin System (src/plugin_system.py)
PluginManager scans a directory for .py files, imports them, finds AutomatonPlugin subclasses, and registers them. Plugins are discovered at GUI startup from the plugins/ directory.
Export & Persistence (src/export_manager.py, src/autosave_manager.py)
ExportManager handles all output formats (PNG, GIF, MP4, WebM, JSON). It accumulates frames for animation export and supports 4 colour themes (light, dark, blue, warm).
Key method signatures:
em.export_png(grid, filepath, cell_size=8) # grid is first arg
em.export_gif(filepath, cell_size=8, duration=100)
em.export_json(filepath, grid, metadata=None)
em.export_video(filepath, cell_size=8, fps=10, codec="mp4")
AutoSaveManager runs a background thread that calls a user-supplied callback and writes the return value as JSON to a configurable directory. It is started with am.start() and stopped cleanly with am.stop().
# Default: saves to ./autosave/ every 300 seconds, keeps 5 backups
am = AutoSaveManager(save_dir="autosave", interval=300, max_backups=5)
am.set_save_callback(lambda: {"generation": sim.generation, "grid": sim.get_grid().tolist()})
am.start()
Configuration
Module |
Class |
Storage |
|---|---|---|
|
|
In-memory dataclass |
|
|
Persisted to |
Data Flow
GUI Simulation Loop
User clicks Start
→ AutomatonApp sets running = True
→ Tkinter after() callback fires every (101 - speed) ms
→ Simulator.step()
→ UndoManager.push_state(grid)
→ CellularAutomaton.step() ← uses convolve_with_boundary()
→ Metrics recorded
→ on_step callback fires
→ Canvas re-rendered (PIL fast-path if Pillow available + grid lines off)
→ Timeline updated
→ PopulationGraph updated
→ Breakpoints checked
→ AutoSaveManager fires on its own background thread (every 60s)
CLI Pipeline
argparse → build SimulatorConfig → Simulator.initialize()
→ loop N steps, collecting frames
→ ExportManager.export_*(frames, output_path)
API Request Flow
HTTP POST /session → create Simulator → store in sessions dict → return UUID
HTTP POST /session/{id}/step → lookup Simulator → step() → return generation
WS /session/{id}/stream → lookup Simulator → loop: step + send JSON state
WS /collab/{id} → lookup or create CollaborativeSession → broadcast mutations