Accessing the Environment
As detailed in the earlier chapter detailing the defining of environmental properties, there are two types of environment property which can be interacted with in host functions. Unlike agent functions, host functions have full mutable access to both forms of environment property.
The HostEnvironment
instance can be accessed in host functions via FLAMEGPU->environment
.
Environment Properties
Host functions can both read and update environment properties using setProperty()
and getProperty()
respectively.
Unlike agent functions, host functions are able to access environment property arrays in a single transaction, rather than individually accessing each element. Otherwise, the syntax matches that found in agent functions.
Environmental properties are accessed, using HostEnvironment
, as follows:
FLAMEGPU_HOST_FUNCTION(ExampleHostFn) {
// Get the value of scalar environment property 'scalar_f' and store it in local variable 'scalar_f'
float scalar_f = FLAMEGPU->environment.getProperty<float>("scalar_f");
// Set the value of the scalar environment property 'scalar_f'
FLAMEGPU->environment.setProperty<float>("scalar_f", scalar_f + 1.0f);
// Get the value of array environment property 'array_i3' and store it in local variable 'array_i3'
std::array<int, 3> array_i3 = FLAMEGPU->environment.getProperty<int, 3>("array_i3");
// Set the value of the array environment property 'array_i3'
FLAMEGPU->environment.setProperty<int, 3>("array_i3", std::array<int, 3>{0, 0, 0});
// Get the value of the 2nd element of the array environment property 'array_u4'
unsigned int array_u4_1 = FLAMEGPU->environment.getProperty<unsigned int>("array_u4", 1);
// Set the value of the 3rd element of the array environment property 'array_u4'
FLAMEGPU->environment.setProperty<unsigned int>("array_u4", 2, array_u4_1 + 2u);
}
class ExampleHostFn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Get the value of scalar environment property 'scalar_f' and store it in local variable 'scalar_f'
scalar_f = FLAMEGPU.environment.getPropertyFloat("scalar_f")
# Set the value of the scalar environment property 'scalar_f'
FLAMEGPU.environment.setPropertyFloat("scalar_f", scalar_f + 1.0)
# Get the value of array environment property 'array_i3' and store it in local variable 'array_i3'
array_i3 = FLAMEGPU.environment.getPropertyArrayInt("array_i3")
# Set the value of the array environment property 'array_i3'
FLAMEGPU.environment.setPropertyArrayInt("array_i3", [0, 0, 0])
# Get the value of the 2nd element of the array environment property 'array_u4'
array_u4_1 = FLAMEGPU.environment.getPropertyUInt("array_u4", 1)
# Set the value of the 3rd element of the array environment property 'array_u4'
FLAMEGPU.environment.setPropertyUInt("array_u4", 2, array_u4_1 + 2)
Environment Macro Properties
Similar to regular environment properties, macro environment properties can be read and updated within host functions.
Environmental macro properties can be read via the returned HostMacroProperty
, as follows:
// Define an host function called read_env_hostfn
FLAMEGPU_HOST_FUNCTION(read_env_hostfn) {
// Retrieve the environment macro property foo of type float
const float foo = FLAMEGPU->environment.getMacroProperty<float>("foo");
// Retrieve the environment macro property bar of type int array[3][3][3]
auto bar = FLAMEGPU->environment.getMacroProperty<int, 3, 3, 3>("bar");
const int bar_1_1_1 = bar[1][1][1];
}
# Define an host function called read_env_hostfn
class read_env_hostfn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Retrieve the environment macro property foo of type float
foo = FLAMEGPU->environment.getMacroPropertyFloat("foo");
# Retrieve the environment macro property bar of type int array[3][3][3]
bar = FLAMEGPU.environment.getMacroPropertyInt("bar");
bar_1_1_1 = bar[1][1][1];
Macro properties in host functions are designed to behave as closely to their representative data type as possible. So most assignment and arithmetic operations should behave as expected.
Python has several exceptions to this rule:
The assignment operator is only available when it maps to
__setitem__(index, val)
(e.g.foo[0] = 10
)The increment/decrement operators are not available, as they cannot be overridden.
Below are several examples of how environment macro properties can be updated in host functions:
// Define an host function called write_env_hostfn
FLAMEGPU_HOST_FUNCTION(write_env_hostfn) {
// Retrieve the environment macro property foo of type float
auto foo = FLAMEGPU->environment.getMacroProperty<float>("foo");
// Retrieve the environment macro property bar of type int array[3][3][3]
auto bar = FLAMEGPU->environment.getMacroProperty<int, 3, 3, 3>("bar");
// Update some of the values
foo = 12.0f;
bar[0][0][0]+= 1;
bar[0][1][0] = 5;
++bar[0][0][2];
}
# Define an host function called write_env_hostfn
class write_env_hostfn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Retrieve the environment macro property foo of type float
foo = FLAMEGPU->environment.getMacroPropertyFloat("foo");
# Retrieve the environment macro property bar of type int array[3][3][3]
bar = FLAMEGPU.environment.getMacroPropertyInt("bar");
# Update some of the values
# foo = 12.0; is not allowed
foo.set(12.0);
foo[0] = 12.0; # This is the same as calling set()
bar[0][0][0]+= 1;
bar[0][1][0] = 5;
bar[0][0][2]+= 1; # Python does not allow the increment operator to be overridden
Warning
Be careful when using HostMacroProperty
via the C++ API. When you retrieve an element e.g. bar[0][0][0]
(from the example above), it is of type HostMacroProperty
not int
. Therefore you cannot pass it directly to functions which take generic arguments such as printf()
, as it will be interpreted incorrectly. You must either store it in a variable of the correct type which you instead pass, or explicitly cast it to the correct type when passing it e.g. (int)bar[0][0][0]
or static_cast<int>(bar[0][0][0])
.
Macro Property File Input/Output
Environment macro properties are best suited for large datasets. For this reason it may be necessary to initialise them from file. As such, the HostEnvironment
provides methods for importing and exporting macro properties. Unlike model state export, these operate on a single property. The additional .bin
(binary) file format is supported.
// Define an host function called macro_prop_io_hostfn
FLAMEGPU_HOST_FUNCTION(macro_prop_io_hostfn) {
// Export the macro property
FLAMEGPU->environment.exportMacroProperty("macro_float_3_3_3", "out.bin");
// Import a macro property
FLAMEGPU->environment.importMacroProperty("macro_float_3_3_3", "in.json");
}
# Define an host function called macro_prop_io_hostfn
class macro_prop_io_hostfn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Export the macro property
FLAMEGPU.environment.exportMacroProperty("macro_float_3_3_3", "out.bin");
# Import a macro property
FLAMEGPU.environment.importMacroProperty("macro_float_3_3_3", "in.json");
Environment Directed Graph
The environment directed graph can be initialised within host functions, defining the connectivity and initialising any properties stored within.
The host API allows vertices and edges to be managed via a map/dictionary interface, where the ID is used to access a vertex, or source and destination vertex IDs to access an edge.
Vertex IDs are unsigned integers, however the value 0 is reserved so cannot be assigned. Vertex IDs are not required to be contiguous, however they are stored sparsely such that two vertices with IDs 1 and 1000001 will require an index of length 1000000. It may be possible to run out of memory if IDs are too sparsely distributed.
// Define an host function called directed_graph_hostfn
FLAMEGPU_HOST_FUNCTION(directed_graph_hostfn) {
// Fetch a handle to the directed graph
HostEnvironmentDirectedGraph fgraph = FLAMEGPU->environment.getDirectedGraph("fgraph");
// Declare the number of vertices and edges
fgraph.setVertexCount(5);
fgraph.setEdgeCount(5);
// Initialise the vertices
HostEnvironmentDirectedGraph::VertexMap vertices = graph.vertices();
for (int i = 1; i <= 5; ++i) {
// Create (or fetch) vertex with ID i
HostEnvironmentDirectedGraph::VertexMap::Vertex vertex = vertices[i];
vertex.setProperty<float, 2>("bar", {0.0f, 10.0f});
}
// Initialise the edges
HostEnvironmentDirectedGraph::EdgeMap edges = graph.edges();
for (int i = 1; i <= 5; ++i) {
// Create (or fetch) edge with specified source/dest vertex IDs
HostEnvironmentDirectedGraph::EdgeMap::Edge edge = edges[{i, ((i + 1)%5) + 1}];
edge.setProperty<int>("foo", 12);
}
}
# Define an host function called directed_graph_hostfn
class directed_graph_hostfn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Fetch a handle to the directed graph
fgraph = FLAMEGPU->environment.getDirectedGraph("fgraph")
# Declare the number of vertices and edges
fgraph.setVertexCount(5)
fgraph.setEdgeCount(5)
# Initialise the vertices
vertices = graph.vertices()
for i in range(1, 6):
# Create (or fetch) vertex with ID i
vertex = vertices[i]
vertex.setPropertyPropertyArrayFloat("bar", [0, 10])
# Initialise the edges
edges = graph.edges()
for i in range(1, 6):
# Create (or fetch) edge with specified source/dest vertex IDs
edge = edges[i, ((i + 1)%5) + 1]
edge.setPropertyInt("foo", 12)
Directed Graph File Input/Output
HostEnvironmentDirectedGraph
provides importGraph()
and exportGraph()
to import and export the graph respectively using a common JSON format.
// Define an host function called directed_graph_hostfn
FLAMEGPU_HOST_FUNCTION(directed_graph_hostfn) {
// Fetch a handle to the directed graph
HostEnvironmentDirectedGraph fgraph = FLAMEGPU->environment.getDirectedGraph("fgraph");
// Export the graph
fgraph.exportGraph("out.json");
// Import a different graph
fgraph.importGraph("in.json");
}
# Define an host function called directed_graph_hostfn
class directed_graph_hostfn(pyflamegpu.HostFunction):
def run(self,FLAMEGPU):
# Fetch a handle to the directed graph
fgraph = FLAMEGPU->environment.getDirectedGraph("fgraph")
# Export the graph
fgraph.exportGraph("out.json");
# Import a different graph
fgraph.importGraph("in.json");
An example of this format is shown below:
{
"nodes": [
{
"id": "1",
"bar": [
12.0,
22.0
]
},
{
"id": "2",
"bar": [
13.0,
23.0
]
}
],
"links": [
{
"source": "1",
"target": "2",
"foo": 21
},
{
"source": "2",
"target": "1",
"foo": 22
}
]
}