This document describes how to create a simple agent-based application in which agents are exchanging basic messages inside a sub-space. Before reading this document, it is recommended reading the SARL Language Reference.
The elements that are explained in this tutorial are:
The source code related to this tutorial may be found in the GitHub of the SARL demos.
The principle of the application is the following:
PingAgent agent is sending a Ping message to all agents into the sub-space.PongAgent agent is receiving the Ping message, and replies with a Pong message to the sender of the Ping message.PingAgent agent is receiving a Pong message and replies to the sender of the Pong with a new Ping message.These messages contain an integer number that indicates the number of the event.

First, the Ping and Pong events must be defined.
The Ping is an event that contains the index of the event. This index indicates
at which position the event is located in the sequence of sent Ping event.
The index attribute is a value, for making it unmodifiable after its initialization.
For setting the value of the index value, it is mandatory to define a constructor.
The Pong is an event that contains the index of the Ping event for which the
Pong event is created.
The index attribute is also a value, and it must be set in a constructor.
event Pong {
val index : int
}
The second step of this tutorial is the definition of the agent that is waiting
for Ping events, and replying Ping events.
The initial definition of the pong agent is:
agent PongAgent {
}
Because the agents are interacting into a sub-space, they must join this sub-space at start-up.
The sub-space is located in the default context. For creating or joining it, we must use the
getOrCreateSpaceWithSpec function. This function searches for a space, which was
created with the given specification. If there is no space, the space identifier is used
for creating a new space.
After retrieving the instance of the space, it is mandatory to register the agent for
receiving the events. The spaces of type OpenEventSpaceSpecification provides
the registerStrongParticipant function. It takes the event listener of the agent (provided by
the Behaviors capacity).
agent PongAgent {
uses DefaultContextInteractions, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
}
The pong agent needs to handle the Ping events. For that, a “behavior unit” must be defined in the
agent. According to the Agent Reference,
the on keyword followed by the name of the event permits to define a handler of events.
This handler will be invoked by the runtime environment each time the agent is
receiving a Ping event.
agent PongAgent {
uses DefaultContextInteractions, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
on Ping {
}
}
Now, it is time to define how the pong agent is replying with a Ping message.
First, sending an event in the default space must be done with a built-in capacity:
DefaultContextInteractions. This capacity provides a collection of functions that
enable the agent to interact with the default context, and its default space.
For using the capacity, it is recommended declaring it with the uses keyword.
This keyword permits the agent to directly call the functions of the capacity as if
they were defined as actions in the agent.
The DefaultContextInteractions capacity provides the function emit(Event) for
sending an event in the default space of the default context.
The ExternalContextAccess capacity provides the function emit(EventSpace, Event) for
sending an event in the given space.
This latest function is used for sending the events.
The Ping event must be built with an index value as argument. This argument
is the index stored in the Ping event. For accessing the occurrence of the
Ping event, you must use the special keyword occurrence.
In the following example, the Pong event is built with the index argument
stored in the received Ping event.
agent PongAgent {
uses DefaultContextInteractions, ExternalContextAccess, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
on Ping {
var evt = new Pong( occurrence.index )
^space.emit( evt )
}
}
In the previous code, the event is emitted to all the agents belonging to the default space, including the pong agent.
For restricting the receiver of the Pong event to the initial sender of the
Ping event, you must define a scope for the Pong event.
The DefaultContextInteractions capacity provides the function emit(Event, Scope<Address>)
for sending an event with a specific scope.
In the following code, we select the receiver of an event based on its address within the space.
It permits to restrict to the initial sender of the Ping event: [ it == occurrence.source ]
agent PongAgent {
uses DefaultContextInteractions, ExternalContextAccess, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
on Ping {
var evt = new Pong( occurrence.index )
^space.emit(evt) [ it == occurrence.source ]
}
}
The third step of this tutorial is the definition of the agent that is sending Ping
events, and waiting for Pong events.
The initial definition of the ping agent is:
agent PingAgent {
uses DefaultContextInteractions, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
}
The ping agent needs to handle the Pong events. For that, a “behavior unit” must be
defined in the agent.
agent PingAgent {
uses DefaultContextInteractions, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
on Pong {
}
}
When the ping agent is receiving a Pong event, it re-sends a
Ping event to the sender of the Pong event.
This new Ping event has an index greater than the one of the
Pong event.
The receiving of the Ping event is restricted to the sender of the
Pong event.
agent PingAgent {
uses DefaultContextInteractions, ExternalContextAccess, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
}
on Pong {
var evt = new Ping( occurrence.index + 1 )
^space.emit(evt) [ it == occurrence.source ]
}
}
For starting the exchanges among the agents, it is mandatory to send a first occurrence
of the Ping event.
This emit is done when the ping agent is started, i.e. when the agent is
receiving the Initialize event.
agent PingAgent {
uses DefaultContextInteractions, ExternalContextAccess, Behaviors
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
var evt = new Ping(0)
^space.emit( evt )
}
on Pong {
var evt = new Ping( occurrence.index + 1 )
^space.emit(evt) [ it == occurrence.source ]
}
}
The previous code has a major problem: if there is no pong agent launched
when the ping agent is sending the first Ping event, the application
will reach a deadlock, even if the pong agent is launched later.
For solving this problem, the ping agent must wait for sending the initial
Ping event until the pong agent is belonging to the default space.
The concrete implementation is based on the Schedules capacity, which provides
a collection of functions for creating and launching asynchronous tasks.
In the following code, a task is created with the name waiting_for_partner.
This task is executed every second with the every function (given by the Schedules
capacity). The code between the brackets contains the statements
that will be periodically executed.
In this periodically executed code, the agent is testing if it is the only
one agent belonging to the default space. If not, the agent is sending the initial
Ping event, and stopping the periodic task.
agent PingAgent {
uses DefaultContextInteractions, ExternalContextAccess, Behaviors, Schedules
var ^space : OpenEventSpace
on Initialize {
^space = defaultContext.getOrCreateSpaceWithSpec(
typeof(OpenEventSpaceSpecification),
occurrence.parameters.get(0) as UUID)
^space.registerStrongParticipant(asEventListener())
val task = task("waiting_for_partner")
task.every(1000) [
if (defaultSpace.numberOfStrongParticipants > 1) {
var evt = new Ping(0)
^space.emit( evt )
task.cancel
}
]
}
on Pong {
var evt = new Ping( occurrence.index + 1 )
^space.emit(evt) [ it == occurrence.source ]
}
}
The fourth step of this tutorial is the definition of the launching process. In the rest of this section, we discuss the use of the Janus runtime environment for running the agents. The Janus platform is designed to launch a single agent at start-up. Then, this launched agent must spawn the other agents in the system.
The principle is to launch a single instance of Janus, and run all the agents inside.
Because of the design of the Janus platform, we must define an agent that will launch
the other agents. This agent is named BootAgent. It is defined below.
The boot agent uses the Lifecycle capacity for launching agents in the default context.
This capacity provides the function spawn(Class<? extends Agent>) for launching an
agent of the given type. When the boot agent has launched the two expected agents,
it is killing itself. This is done with the killMe function, which is provided
by the Lifecycle capacity too.
agent BootAgent {
uses Lifecycle
on Initialize {
spawn( PongAgent )
spawn( PingAgent )
killMe
}
}
Copyright © 2014-2025 SARL.io, the Original Authors and Main Authors.
Documentation text and medias are licensed under the Creative Common CC-BY-SA-4.0; you may not use this file except in compliance with CC-BY-SA-4.0. You may obtain a copy of CC-BY-4.0.
Examples of SARL code are licensed under the Apache License, Version 2.0; you may not use this file except in compliance with the Apache License. You may obtain a copy of the Apache License.
You are free to reproduce the content of this page on copyleft websites such as Wikipedia.
Generated with the translator docs.generator 0.15.1.