Skip to content

Latest commit

 

History

History
218 lines (165 loc) · 6.89 KB

File metadata and controls

218 lines (165 loc) · 6.89 KB

server.py Public API Reference

This document describes the function and methods of agent-model simulation server (currently named combined_server.py). It servers as an intermediate between simulation code (simulation.py, imported as module) and a client accessing the simulation state via the network (http, websocket).


World

The simulation is loaded as a module named World:

from simulation import World

The following methods must exists in World (even if just as a dummy method returning null if not applicable)

###create() (classmethod)

@classmethod
World.create(**kwargs) -> World

Primary entry point for creating a simulation world.

Accepts an opaque config dict from the server. Only this method knows what parameters are valid and what they mean. The server passes the config through without inspection.

step()

Advance simulation one step.

mark_clean()

Clear dirty tracking (call after sending delta to clients).

get_viewport(center, radius)

Get all entities (agents and map cells) within a viewport region.

count_agents() -> Tuple

Server passes array without unpacking:

counts = world.count_agents()
return {'counts': list(counts), 'total': sum(counts)}

Sent by HTTP /stats endpoint and in periodic status broatcasts in stream.

world.report_statistics() -> dict

Generic simulation state statistis (not interpreted by server)

report_params() -> dict (classmethod)

Returns simulation parameters for external reporting. Used by: HTTP /params endpoint. Simulation decides what to expose.

set_param(name, value) -> dict

Set a simulation parameter at runtime. Returns dict:

{'status': 'ok', 'name': 'prey.move_cost', 'old_value': 0.1, 'new_value': 0.5}
{'status': 'error', 'message': 'Unknown parameter: foo', 'valid_params': [...]}
{'status': 'error', 'message': 'Invalid value for prey.move_cost: ...'}

Used by: HTTP /set_param endpoint.

world.get_full_state() -> dict (unused?)

Send description of full simulation state (for clients connecting to running simulation).

get_dirty_ids() -> dict

Get IDs of changed agents since last mark_clean(). Preferred for streaming.

{
    'tick': int,
    'spawned_ids': List[int],    # New agents
    'updated_ids': List[int],    # Modified agents (excluding spawned)
    'despawned_ids': List[int]   # Removed agents
}

Used by: Streaming server delta distribution

mark_clean()

Clear dirty tracking. Call after processing delta.

get_full_state() -> dict

Complete world snapshot for initial sync.

{
    'tick': int,
    'width': int,
    'height': int,
    'agents': List[dict],        # to_display_dict() for each
    'cells': List[dict],         # Food cells (type=-1)
    'total_entities': int
}

Used by: WebSocket subscribe (initial snapshot)

inspect_agent(agent_id) -> Optional[dict]

Get full state of a specific agent.

Used by: HTTP /inspect/<id>, WebSocket inspect command

is_in_viewport(position, center, radius) -> bool (static method)

Check if a position is within a viewport region.

World.is_in_viewport((5, 5), (0, 0), 10)   # True - within radius
World.is_in_viewport((15, 5), (0, 0), 10)  # False - outside radius

Server uses this for viewport filtering without knowing topology details.

Used by: Streaming buffer, HTTP viewport handler

get_viewport(center, radius) -> dict

Get all agents within a viewport region.

{
    'tick': int,
    'center': List[float],  # Center position as list
    'radius': float,
    'agents': List[dict],   # to_display_dict() for each agent in viewport
    'count': int            # Number of agents
}

This is the simulation's implementation of viewport queries. The server calls this without knowing topology details.

Used by: HTTP /viewport endpoint

describe_map(self) -> List[List[int]]

Get static map data for initial transmission to client.

###World parameters world.tick Current simulation time.

world.halted, world.halt_reason Flag to report if simulation has halted, string with reason for halting.

world.width, world.height These are there for convenience, so the client can request them. Not needed for server functionality, can also return null.

Agents

World contains an agents array that can be accessed by agent id:

agent = world.agents.get(aid)

agent.id Unique integer to identify specific agent.

agent.position Reports agent "position". Server is agnostic as to format or content, can also return null if agents don't hvae "positions".

agent.to_display_dict() "Phenotype" dictionary of agents streamed over websocket. Server is agnostic as to format or content of this data.

run_simulation(...)

Simulation should also have standalone CLI entry point for testing. Not used by server.

run_simulation(
    width: int = 100,
    height: int = 100,
    initial_prey: int = 1000,
    initial_predators: int = 15,
    max_ticks: int = 500,
    seed: Optional[int] = None
)

Server

The server is completely simulation-agnostic. It:

  • Only imports World (not AgentType or any constants)
  • Does not inspect config parameters - passes them as opaque dict
  • Treats agent counts as opaque arrays
  • Reads topology agent.position, world.width, world.height only to transmit to client, does not "look inside".
  • Delegates viewport logic to World.is_in_viewport() and World.get_viewport()

HTTP Endpoints (port 5000): POST /init - Initialize world POST /start - Start simulation POST /stop - Stop simulation POST /step - Single step(s) POST /set_param - Set simulation parameter GET /stats - Get statistics GET /params - Get parameters GET /viewport - Get agents in viewport (center, r) GET /inspect/ - Get full agent state

Viewport URL format: /viewport?center=50_50&r=20 ("center" is a single string to be parsed by get_viewport method) /viewport?cx=50&cy=50&r=20 (legacy with explicit 2d position format)

WebSocket (port 8765): -> subscribe - Start receiving streaming updates -> unsubscribe - Stop receiving updates -> viewport - Set viewport filter for WS stream -> viewport_clear - Clear viewport filter <- snapshot - Full state (on subscribe) <- delta - Streaming updates (constant rate) <- stats - Periodic statistics

WS Streaming: {ws.DEFAULT_AGENTS_PER_MESSAGE} agents every {ws.DEFAULT_STREAM_INTERVAL_MS}ms = {1000/ws.DEFAULT_STREAM_INTERVAL_MS * ws.DEFAULT_AGENTS_PER_MESSAGE:.0f} agent updates/sec


Thread Safety Notes

  • world.agents is a Python dict - individual get() calls are thread-safe
  • Iteration (for agent in agents.values()) is NOT safe during modification
  • Server uses list(agents.keys()) snapshot pattern for safe iteration
  • step() modifies agents dict - must not be called concurrently with reads