Fix real-time sync, speed, and persistence for deployed Render
Browse files- websocket.py: send full state snapshot immediately on client connect
so new browsers instantly see the current city state (Day/time/agents)
instead of a stale Day 1 6:00 until the next tick fires
- server.py: tick delay now reads SOCI_TICK_DELAY env var (default 0.5s
instead of hardcoded 2.0s) — LLM call latency naturally paces ticks
on cloud deployments; set to 0 to run as fast as LLM allows
- database.py + snapshots.py: data directory now reads SOCI_DATA_DIR env
var (default "data") so Render's persistent disk (/var/data) can be
mounted and state survives across redeploys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- src/soci/api/server.py +4 -1
- src/soci/api/websocket.py +10 -16
- src/soci/persistence/database.py +2 -1
- src/soci/persistence/snapshots.py +2 -1
src/soci/api/server.py
CHANGED
|
@@ -191,7 +191,10 @@ async def lifespan(app: FastAPI):
|
|
| 191 |
_simulation = sim
|
| 192 |
|
| 193 |
# Start background simulation
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
yield
|
| 197 |
|
|
|
|
| 191 |
_simulation = sim
|
| 192 |
|
| 193 |
# Start background simulation
|
| 194 |
+
# SOCI_TICK_DELAY: seconds to sleep between ticks (default 0.5).
|
| 195 |
+
# Set to 0 to let LLM latency pace the simulation naturally.
|
| 196 |
+
tick_delay = float(os.environ.get("SOCI_TICK_DELAY", "0.5"))
|
| 197 |
+
_sim_task = asyncio.create_task(simulation_loop(sim, db, tick_delay=tick_delay))
|
| 198 |
|
| 199 |
yield
|
| 200 |
|
src/soci/api/websocket.py
CHANGED
|
@@ -69,22 +69,16 @@ async def websocket_stream(websocket: WebSocket):
|
|
| 69 |
from soci.api.server import get_simulation
|
| 70 |
sim = get_simulation()
|
| 71 |
|
| 72 |
-
#
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
"tick": sim.clock.total_ticks,
|
| 83 |
-
"time": sim.clock.datetime_str,
|
| 84 |
-
})
|
| 85 |
-
|
| 86 |
-
# We can't easily replace the sync callback with async,
|
| 87 |
-
# so instead we poll the simulation state
|
| 88 |
last_tick = sim.clock.total_ticks
|
| 89 |
while True:
|
| 90 |
try:
|
|
|
|
| 69 |
from soci.api.server import get_simulation
|
| 70 |
sim = get_simulation()
|
| 71 |
|
| 72 |
+
# Send the full current state immediately so the client is in sync
|
| 73 |
+
# before the next tick fires (avoids "Day 1 6:00" on fresh connects).
|
| 74 |
+
state = sim.get_state_summary()
|
| 75 |
+
await manager.send_personal(websocket, {
|
| 76 |
+
"type": "tick",
|
| 77 |
+
"tick": sim.clock.total_ticks,
|
| 78 |
+
"time": sim.clock.datetime_str,
|
| 79 |
+
"state": state,
|
| 80 |
+
})
|
| 81 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
last_tick = sim.clock.total_ticks
|
| 83 |
while True:
|
| 84 |
try:
|
src/soci/persistence/database.py
CHANGED
|
@@ -12,7 +12,8 @@ import aiosqlite
|
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
|
| 15 |
-
|
|
|
|
| 16 |
DEFAULT_DB = DB_DIR / "soci.db"
|
| 17 |
|
| 18 |
SCHEMA = """
|
|
|
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
|
| 15 |
+
# SOCI_DATA_DIR env var lets you point at a persistent disk (e.g. /var/data on Render).
|
| 16 |
+
DB_DIR = Path(os.environ.get("SOCI_DATA_DIR", "data"))
|
| 17 |
DEFAULT_DB = DB_DIR / "soci.db"
|
| 18 |
|
| 19 |
SCHEMA = """
|
src/soci/persistence/snapshots.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
| 4 |
|
| 5 |
import json
|
| 6 |
import logging
|
|
|
|
| 7 |
from pathlib import Path
|
| 8 |
from typing import Optional, TYPE_CHECKING
|
| 9 |
|
|
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
|
|
| 14 |
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
-
SNAPSHOTS_DIR = Path("data") / "snapshots"
|
| 18 |
|
| 19 |
|
| 20 |
async def save_simulation(
|
|
|
|
| 4 |
|
| 5 |
import json
|
| 6 |
import logging
|
| 7 |
+
import os
|
| 8 |
from pathlib import Path
|
| 9 |
from typing import Optional, TYPE_CHECKING
|
| 10 |
|
|
|
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
+
SNAPSHOTS_DIR = Path(os.environ.get("SOCI_DATA_DIR", "data")) / "snapshots"
|
| 19 |
|
| 20 |
|
| 21 |
async def save_simulation(
|