Defining Agent Functions
Agent Functions can be specified as C++ functions, built at compile time when using the C++ API, or they can be specified as Run-Time Compiled (RTC) function strings when using the both the C++ and Python APIs. Although agent functions are technically CUDA device code the FLAME GPU API abstracts this and no CUDA syntax is required to script behaviour. The same limitations apply to agent functions as to CUDA C++, the most significant restriction being standard libraries are not supported unless specified otherwise (see CUDA documentation for more details). C++ agent functions are distinguished within the examples in this guide by using the name Agent C++ so that it is clear that this is a subset of supported C++.
An experimental feature for Python allows the specification of native agent functions in Python. The Python can then be transpiled, a process of translating the Python syntax to equivalent C++, at runtime. A limited subset of Python is supported which is restricted to Python features that can be easily transpiled to a C++ equivalent at runtime for compilation. For example Python functionality like generator expressions, arrays, dictionaries, etc, which do not have an obvious C++ equivalent are not permitted and will raise an exception. The subset of supported Python is referred to as Agent Python within this guide.
C++ Compile Time Agent Functions
A C++ agent function is can be defined for compilation using the FLAMEGPU_AGENT_FUNCTION
macro.
This takes three arguments: a unique name identifying the function, an input message communication strategy, and an output message communication strategy.
We will discuss messages in more detail later, so for now don’t worry about the second and third parameters. flamegpu::MessageNone
is specified when not requiring message input or output, so this is used.
Similarly, agent functions should return flamegpu::ALIVE
by default, agent death is explained in a later section of this chapter.
For compile time (i.e. non-RTC functions), when using the C++ API, the FLAMEGPU_AGENT_FUNCTION
macro can be used to declare and define the agent function, which can then be associated with the AgentDescription
object using the newFunction()
method.
// Define an agent function called agent_fn1 - specified ahead of main function
FLAMEGPU_AGENT_FUNCTION(agent_fn1, flamegpu::MessageNone, flamegpu::MessageNone) {
// Behaviour goes here
return flamegpu::ALIVE;
}
int main() {
// ...
// Attach a function called agent_fn1, defined by the symbol agent_fn1 to the AgentDescription object agent.
flamegpu::AgentFunctionDescription agent_fn1_description = agent.newFunction("agent_fn1", agent_fn1);
// ...
}
C++ and Python Runtime Compiled Agent Functions
Run-time compiled (RTC) C++ style agent functions follow the same syntax as for C++ compile time agent functions with the exception that the function must be defined in a string and associated with the AgentDescription
using the newRTCFunction()
method.
Runtime C++ style compiled functions can be used with both the the C++ and Python APIs.
const char* agent_fn1_source = R"###(
// Define an agent function called agent_fn1 - specified ahead of main function
FLAMEGPU_AGENT_FUNCTION(agent_fn1, flamegpu::MessageNone, flamegpu::MessageNone) {
// Behaviour goes here
}
)###";
int main() {
...
// Attach a function called agent_fn1, defined in the string variable agent_fn1_source to the AgentDescription object agent.
flamegpu::AgentFunctionDescription agent_fn1_description = agent.newRTCFunction("agent_fn1", agent_fn1_source);
...
}
# Define an agent function called agent_fn1
agent_fn1_source = r"""
FLAMEGPU_AGENT_FUNCTION(agent_fn1, MessageNone, MessageNone) {
# Behaviour goes here
}
"""
...
# Attach a function called agent_fn1 to an agent represented by the AgentDescription agent
# The AgentFunctionDescription is stored in the agent_fn1_description variable
agent_fn1_description = agent.newRTCFunction("agent_fn1", agent_fn1_source);
...
Note
If you wish to store RTC agent functions in separate files newRTCFunction()
can be replaced with newRTCFunctionFile()
, instead passing the path to the agent function’s source file (either absolute or relative to the working directory at runtime). This will allow them to be developed in a text editor with C++ syntax highlighting.
Note
RTC agent functions support #include
statements, allowing seperate header files to be used. By default the only include path used is the working directory. The default include path can be overridden by setting the FLAMEGPU_RTC_INCLUDE_DIRS
environment variable, multiple paths can be specified using the operating system’s normal PATH delimiter (Linux :
, Windows ;
).
To optimise the loading of RTC agent functions, they are only compiled when changes are detected, with compiled agent functions being cached both in memory during execution and to the operating system’s on-disk temporary directory between executions. The utility util::clearRTCDiskCache()
can be used to clear the on-disk cache.
The on-disk cache may grow over time if you are using many different versions of flame gpu and compiling many different agent functions (e.g. running the test suites). If you wish to purge the on-disk cache this can be achieved via the below command.
#include "flamegpu/util/cleanup.h"
flamegpu::util::clearRTCDiskCache();
from pyflamegpu import *
pyflamegpu.clearRTCDiskCache()
FLAME GPU Python Agent Functions
Python agent functions are required to have the @pyflamegpu.agent_function
decorator and must specify two arguments, the message_in
and message_out
variables (although the names can be changed), both of which must use a type annotation of a supported message type prefixed with the Python FLAME GPU module name pyflamegpu.
.
#Define an agent function called agent_fn1
@pyflamegpu.agent_function
def agent_fn1(message_in: pyflamegpu.MessageNone, message_out: pyflamegpu.MessageNone):
# Behaviour goes here
pass
...
# Transpile the Python agent function to equivalent C++ (and transpile errors with raise an exception at this stage)
agent_fn1_translated = pyflamegpu.codegen.translate(agent_fn1)
# Attach Python function called agent_fn1 to an agent represented by the AgentDescription agent (the function will be compiled at runtime as C++)
agent.newRTCFunction("agent_fn1", agent_fn1_translated)
...
Supported Function Calls
The Python transpiler supports function calls that have a C++ equivalent in the will check function calls in the API. The API singleton is available as pyflamegpu
which is equivalent to the FLAMEGPU
object in C++. If non existent functions are called on API objects then this will raise an error during translation. Calling of correctly specified device functions is also supported as well as calls to a small number of python built in functions. E.g. abs()
, int()
, and float()
. Any calls to math library functions are directly translated to C++ equivalents. E.g. Math.sinf()
will result in a call to sinf()
in C++. The majority of python math functions directly translate to their C++ counterparts.
Supported Math Constants
The following mathematics constants are supported;
Python |
Description |
---|---|
|
Translated of |
|
Translated of |
|
Translated of |
|
Translated of |
Typed API Functions
Calls to many FLAME GPU API functions are typed using template arguments in C++. In the Python equivalents the type is specified by calling a type instantiated version of the function using a suffix. The following type suffix are supported;
Type Suffix |
C++ Equivalent type |
---|---|
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
|
Would be |
In each case the type is appended to the end of the API function. E.g. getVariableInt("name")
in Python would be getVariable<"int">("name")
in C++, and getVariableFloat("name")
would be getVariable<"float">("name")
in C++
FLAME GPU Device Functions
If you wish to define regular functions which can be called from agent functions, you can use the FLAMEGPU_DEVICE_FUNCTION
macro (in C++), or the @pyflamegpu.device_function
decorator for Python. Python device functions require type annotations for arguments and the function return type using supported types.
Type |
Description |
---|---|
|
Python built in |
|
Python built in |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
|
A numpy |
Any runtime compiled agent functions must include the definition of any device functions in the agent function string. These can not be shared between agent functions (as each has a unique string definition).
Python agent device functions must be contained in the source file of any agent functions which call them. All device functions are automatically discovered and included during the transpilation process.
// Define a function for adding two integers which can be called inside agent functions.
FLAMEGPU_DEVICE_FUNCTION int add(int a, int b) {
return a + b;
}
# Define a function for adding two integers which can be called inside agent functions.
@pyflamegpu.device_function
def add(a: int, b: int) -> int :
return a + b
FLAME GPU Host Device Functions
If you wish to define regular functions which can be called from within agent and host functions, you can use the FLAMEGPU_HOST_DEVICE_FUNCTION
macro.
Host Device functions are not currently supported by the Python agent function format.
// Define a function for subtracting two integers which can be called inside agent functions, or in host code
FLAMEGPU_HOST_DEVICE_FUNCTION int subtract(int a, int b) {
return a - b;
}