Structure of RobotSwarmSimulator#
Let’s take a look at the different components of RobotSwarmSimulator.
World Map#
The world contains all the objects that will be simulated.
The population is the collection of agents in the world.
An agent is an object that exists in the world and has volition.
A population often has multiple agents, each of
which can have a different type, controller, or set of sensors.
spawners can create new agents and
add them to the population.
A world can have one or more metrics which reduce the state
of the world. They can describe the behavior of the agents and are useful for
quantifying or training global behaviors.
A world can have subscribers which allow user-defined
hooks to run each step() of the simulation.
Initialization Order#
The initialization system machinery typically starts with the World_from_config() function.
Then, the world object runs its __init__() method.
If using the simulate module, the simulation initializes the world by calling world.setup().
Here is the order in which the initialization system runs:
-
First, the simulation initializes the world.
World.setupThe world then runs
setup(), which creates the following:World.populationAgent.__init__()The world then creates agents from agent configs in its
config.agentslist, and back-references to theworldare added to these agents. These agents are then appended to the world’spopulationlist.Upon initialization, each agent also initializes its controller and sensors from its config, and back-references to the agent are passed to them.
Agent.controllerController.__init__()Agent.sensorsSensor.__init__()
World.spawnersSpawner.__init__()Spawners are created from spawner configs and appended to the world’s
config.spawnerslist.Note
Spawners are
step()ed once during the world’sWorld.setup()method.World.objectsWorldObject.__init__()A similar process to the population initialization is carried out for world objects.
World.metricsmetrics.__init__Lastly, any metrics in the world’s
config.metricslist are created and appended to the world’smetricslist.World.spawnersSpawner.step()By default,
step()is called on all spawners in the world’sspawnerslist. This is done so that spawners have a chance to run before the firststep()of the simulation, but this can be disabled with thestep_spawners=Falseargument toWorld.setup().
Hint
Keep in mind that the initialization system operates on the configs, but you can side-step it entirely by creating the objects in code yourself!
Simulation Loop#
The simulator runs on a single thread. Let’s take a look at the execution order inside the simulation loop. On each tick of the simulation, the following happens:
The swarmsim.world.simulate.main() function runs the main simulation loop.
If the simulation is not in headless mode, input events are handled first.
The simulator then asks the world object to step() itself.
Outputting to the screen can be slow, so when the user requests the simulation to speedup,
step() will be called multiple times, skipping event handling and draw() calls.
For each spawner in World.spawners
spawner.step() Each spawner performs its step() method at this point.
Spawners have a mark_for_deletion flag to remove them from the list of World.spawners.
If a spawner has oneshot=True, then it will be marked for deletion after its first step().
For each agent in World.population:
Agent.step() Each agent performs its step() method at this point.
Its step() method may call step() on its controller
or sensors.
Here’s the execution order for MazeAgent.step():
Controller.get_actions()The controller returns actions to take based on the Agent.sensors.
The agent carries out the actions from the controller,
moving according to its agent-specific dynamics.
For each sensor in Agent.sensors:
Sensor.step() The agent updates its sensors so that the new current_state
will be available on the next step().
For each object in World.objects:
WorldObject.step() A world object is based on the Agent class, so its step()
method may also call step() on its controller
or sensors, if it has any.
For each metric in World.metrics:
AbstractMetric.calculate() Following the step() , each metric takes an
observation of the world’s state and performs its calculations, storing the results in its
value_history attribute.
If the simulation is not operating headlessly, then the simulator calls draw() on the world object.
Step rate is not tied to FPS!
draw() is not guaranteed to be called once per step()! It is called
even when the simulation is paused.
When the simulation is sped up, some draw() calls will be skipped.
draw There is currently no layering system, so the first things that are drawn are the bottom-most ‘layer’
and succeeding draw() calls draw on top.
First, the screen is cleared. Then, draw() is called:
For each object in World.objects:
Object.draw() For each agent in World.population:
Agent.draw() As an example, see StaticAgent.draw().