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.setup
The world then runs
setup()
, which creates the following:World.population
Agent.__init__()
The world then creates agents from agent configs in its
config.agents
list, and back-references to theworld
are added to these agents. These agents are then appended to the world’spopulation
list.Upon initialization, each agent also initializes its controller and sensors from its config, and back-references to the agent are passed to them.
Agent.controller
Controller.__init__()
Agent.sensors
Sensor.__init__()
World.spawners
Spawner.__init__()
Spawners are created from spawner configs and appended to the world’s
config.spawners
list.Note
Spawners are
step()
ed once during the world’sWorld.setup()
method.World.objects
WorldObject.__init__()
A similar process to the population initialization is carried out for world objects.
World.metrics
metrics.__init__
Lastly, any metrics in the world’s
config.metrics
list are created and appended to the world’smetrics
list.World.spawners
Spawner.step()
By default,
step()
is called on all spawners in the world’sspawners
list. 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=False
argument 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.
RectangularWorld.step()
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()
.