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

    ...
}

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

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

math.pi

Translated of M_PI in C++

math.e

Translated of M_E in C++

math.inf

Translated of INFINITY in C++

math.nan

Translated of NAN in C++

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

Char

Would be char type template argument

Float

Would be float type template argument

Double

Would be double type template argument

Int

Would be int type template argument (the same as Int32)

UInt

Would be unsigned int type template argument (the same as UInt32)

Int8

Would be int_8 type template argument

UInt8

Would be uint_8 type template argument

Int16

Would be int_16 type template argument

UInt16

Would be uint_16 type template argument

Int32

Would be int_32 type template argument

UInt32

Would be uint_32 type template argument

Int64

Would be int_64 type template argument

UInt64

Would be uint_64 type template argument

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

int

Python built in int type which will translate transpile to a C++ int

float

Python built in float type which will translate transpile to a C++ float

numpy.byte

A numpy byte type which will transpile to a C++ char

numpy.ubyte

A numpy ubyte type which will transpile to a C++ unsigned char

numpy.short

A numpy short type which will transpile to a C++ short

numpy.ushort

A numpy ushort type which will transpile to a C++ unsigned short

numpy.intc

A numpy intc type which will transpile to a C++ int

numpy.uintc

A numpy uintc type which will transpile to a C++ unsigned int

numpy.uint

A numpy uint type which will transpile to a C++ unsigned int

numpy.longlong

A numpy longlong type which will transpile to a C++ long long

numpy.ulonglong

A numpy ulonglong type which will transpile to a C++ unsigned long long

numpy.half

A numpy half type which will transpile to a C++ half

numpy.single

A numpy single type which will transpile to a C++ float

numpy.double

A numpy double type which will transpile to a C++ double

numpy.longdouble

A numpy longdouble type which will transpile to a C++ long double (not currently supported in device code but left for completeness)

numpy.bool_

A numpy bool_ type which will transpile to a C++ bool

numpy.bool8

A numpy bool8 type which will transpile to a C++ bool

numpy.int_

A numpy int_ type which will transpile to a C++ long

numpy.int8

A numpy int8 type which will transpile to a C++ int8_t

numpy.int16

A numpy int16 type which will transpile to a C++ int16_t

numpy.int32

A numpy int32 type which will transpile to a C++ int32_t

numpy.int64

A numpy int64 type which will transpile to a C++ int64_t

numpy.intp

A numpy intp type which will transpile to a C++ intptr_t

numpy.uint_

A numpy uint_ type which will transpile to a C++ long

numpy.uint8

A numpy uint8 type which will transpile to a C++ uint8_t

numpy.uint16

A numpy uint16 type which will transpile to a C++ uint16_t

numpy.uint32

A numpy uint32 type which will transpile to a C++ uint32_t

numpy.uint64

A numpy uint64 type which will transpile to a C++ uint64_t

numpy.uintp

A numpy uintp type which will transpile to a C++ uintptr_t

numpy.float_

A numpy float_ type which will transpile to a C++ float

numpy.float16

A numpy float16 type which will transpile to a C++ half

numpy.float32

A numpy float32 type which will transpile to a C++ float

numpy.float64

A numpy float64 type which will transpile to a C++ double

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

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