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

MessageBruteForce

Access all messages

Bucket

MessageBucket

Access all messages with a specified integer key

Spatial 2D

MessageSpatial2D

Access all messages within a radius in 2D continuous space

Spatial 3D

MessageSpatial3D

Access all messages within a radius in 3D continuous space

Array 1D

MessageArray

Directly access messages via a 1 dimensional array

Array 2D

MessageArray2D

Directly access messages via a 2 dimensional array

Array 3D

MessageArray3D

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");

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");

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);

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);

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);

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);

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.