Defining Messages (Communication)
Communication between agents in FLAME GPU is handled through messages. Messages contain variables which are used to transmit information between agents. Agents may output a single message from an agent function, subsequent agent functions can then be configured to access messages of the same type according to the message’s communication strategy.
Communication Strategies
Defining a message type in FLAME GPU requires selection of a communication strategy and specification of the data the message will contain. FLAME GPU 2 comes with several built-in communication strategies, described below. It can also be extended to support bespoke messaging types. For guidance on this see the header file include/flamegpu/runtime/messaging.h
. For each message type, the communication strategy defines how the messages will be accessed.
Strategy |
Symbol |
Description |
---|---|---|
Brute Force |
Access all messages |
|
Bucket |
Access all messages with a specified integer key |
|
Spatial 2D |
Access all messages within a radius in 2D continuous space |
|
Spatial 3D |
Access all messages within a radius in 3D continuous space |
|
Array 1D |
Directly access messages via a 1 dimensional array |
|
Array 2D |
Directly access messages via a 2 dimensional array |
|
Array 3D |
Directly access messages via a 3 dimensional array |
Defining a New Message Type
A new message type can be defined using one of the above symbols, two examples are shown below:
// Create a new message called "brute_force" which uses the brute force communication strategy
flamegpu::MessageBruteForce::Description bf_message = model.newMessage<flamegpu::MessageBruteForce>("brute_force");
// Create a new message called "spatial_3D" which uses the Spatial 3D communication strategy
flamegpu::MessageSpatial3D::Description s3d_message = model.newMessage<flamegpu::MessageSpatial3D>("spatial_3D");
# Create a new message called "brute_force" which uses the brute force communication strategy
bf_message = model.newMessageBruteForce("brute_force")
# Create a new message called "spatial_3D" which uses the Spatial 3D communication strategy
s3d_message = model.newMessageSpatial3D("spatial_3D")
Variables that the message should contain can then be defined using the newVariable
method of the message’s respective Description
object.
This function works similarly to that previously introduced for defining agent variables, however message variables cannot have default values specified:
// Add a variable of type "int" with name "foo" to the "bf_message" type
bf_message.newVariable<int>("foo");
// Add a variable of type "float" with name "bar" to the "bf_message" type
bf_message.newVariable<float>("bar");
# Add a variable of type "int" with name "foo" to the "bf_message" type
bf_message.newVariableInt("foo")
# Add a variable of type "float" with name "bar" to the "bf_message" type
bf_message.newVariableFloat("bar")
Note
Variable names must not begin with _
, this is reserved for internal variables.
By default messages within FLAME GPU 2 are not persistent, message lists are cleared at the end of each simulation step. If persistent messages are desired, so that message lists hold messages until messages are next output to the message list in a subsequent simulation step, this can be enabled for specific message types via setPersistent()
on the message’s respective Description
object.
// Enable message persistence for the "bf_message" type
bf_message.setPersistent(true);
# Enable message persistence for the "bf_message" type
bf_message.setPersistent(True)
If multiple agent functions write to a message list during the same simulation step, the second agent function will append to the message list (or overwrite/combine in the case of array message types).
By default, FLAME GPU 2 expects that every agent within an agent function will always output a message if the message is marked for output. However optional message output can be enabled, per agent function, using AgentFunctionDescription::setMessageOutputOptional()
. See Agent Communication - Sending Messages for a full usage example.
Brute Force Specialisation
MessageBruteForce
is the simplest message type to utilise, as such MessageBruteForce::Description
does not have any additional options which must be configured.
However for any large agent population, having all-to-all communication is prohibitively expensive so using better suited message specialisations is recommended where feasible.
Bucket Specialisation
Bucket messages work similarly to the data structure known as a multimap. Messages are assigned an integer key (in a predefined range) when output. When an agent requests messages, it then specifies a key, all the messages assigned this key are returned.
When defining a bucket message, a MessageBucket::Description
is returned.
The Bucket keys are a sequential set of integers, between a configurable lower and upper bound, using the setUpperBound()
and setLowerBound()
or setBounds()
methods on the BucketMessage::Description
class.
// Set an upper bound of bucket keys to 12 for the "message" MessageBucket::Description instance.
message.setUpperBound(12);
// Set the lower bound to 2, this will default to 0 if not provided
message.setLowerBound(2);
// Or set them both at the same time
message.setBounds(2, 12);
# Set an upper bound of bucket keys to 12 for the "message" MessageBucket::Description instance.
message.setUpperBound(12)
# Set the lower bound to 2, this will default to 0 if not provided
message.setLowerBound(2)
# Or set them both at the same time
message.setBounds(2, 12)
Bucket messages are automatically assigned a hidden int
variable _key
representing the key of a given message, this cannot be accessed via regular variable methods and has dedicated methods which are introduced in the relevant later sections regarding message input and output.
Spatial Specialisation
Spatial messages operate by decomposing the 2D or 3D environment into a discrete grid of bins. Messages may be emitted outside the bounds of the specified 3D environment, however significant quantities of messages output out of bounds will harm performance. When an agent requests messages, it then specifies a search origin. Messages from bins within the interaction radius of the search origin are then returned. As such, it’s possible for messages outside of the interaction radius to be returned, and distances to each message must be calculated by the agent.
The discrete grid which support spatial messaging is abstracted from users, when defining spatial messages either a MessageSpatial2D::Description
or MessageSpatial3D::Description
will be returned. This is used to configure the environment bounds and interaction radius.
The following is an example of configuring the specialisations for a MessageSpatial2D
message:
// Specify the minimum coordinate of the environment as (0.0, 0.0)
message.setMin(0.0f, 0.0f);
// Specify the maximum coordinate of the environment as (100.0, 100.0)
message.setMax(100.0f, 100.0f);
// Specify the interaction radius as 1.0
message.setRadius(1.0f);
# Specify the minimum coordinate of the environment as (0.0, 0.0)
message.setMin(0, 0)
# Specify the maximum coordinate of the environment as (100.0, 100.0)
message.setMax(100, 100)
# Specify the interaction radius as 1.0
message.setRadius(1)
The setMin()
and setMax()
of MessageSpatial3D::Description
instead take 3 arguments.
Spatial messages are automatically assigned float
location variables with the names x
, y
(and z
). These are used by FLAME GPU internally to sort messages and handle localised accesses, so must be used when outputting messages.
Note
The spatial message radius must be a factor of the environment dimensions if messages will be accessed via the wrapped iterator.
Array Specialisation
Array messages work similarly to an array. When an array message type is defined, it’s dimensions must be specified. Agents can then output a message to a single unique element within the array.
Multiple agents must not output messages to the same element, if FLAMEGPU_SEATBELTS
error checking is enabled this will be detected and an exception raised.
Elements which do not have a message output will return 0
for all variables, similar to if an agent does not set all variables of a message it outputs.
When defining array messages either a MessageArray::Description
, MessageArray2D::Description
or MessageArray3D::Description
will be returned. This should be used to configure dimensions.
The following is an example of configuring the specialisations for each of the 3 array message types:
// Specify the length of the MessageArray as [100000]
message_1D.setLength(100000);
// Specify the dimensions of the MessageArray2D as [100][100]
message_2D.setDimensions(100, 100);
// Specify the dimensions of the MessageArray3D as [50][50][10]
message_3D.setDimensions(50, 50, 10);
# Specify the length of the MessageArray as [100000]
message_1D.setLength(100000)
# Specify the dimensions of the MessageArray2D as [100][100]
message_2D.setDimensions(100, 100)
# Specify the dimensions of the MessageArray3D as [50][50][10]
message_3D.setDimensions(50, 50, 10)
Array messages are all automatically assigned a hidden int
variable ___INDEX
representing the index assigned to an output message, this cannot be accessed via regular variable methods and has a dedicated method which is introduced in the later section regarding message output.