Links

Global Key/Value Store

The global key-value store allows workers to publish data which can then be accessed from other Simulate worker. As part of the Simulate Worker lifecycle functions are called to allow a cell to publish data as well as to register which keys a cell would like to receive data for. The Key/Value store maps a uint64_t keyto a value stored as std::vector<char>
Publishing to and receiving data from the store is implemented as part of the cell lifecycle by overriding three functions in your implementation of entity_based_user_state. These functions are called every tick allowing you to

Publish

std::unordered_map<key_type, data_type> publish_global_kv_store_data(
const aether_state_type &aether_state
)
Called every tick to retrieve a map of key-value pairs that should be published to the store. Workers subscribed to the key will receive updates values on the next tick via the global_kv_store_responses function

Query

std::vector<key_type> get_global_kv_store_queries(
const aether_state_type &aether_state
)
Called every tick to retrieve a list of keys of keys for which to retrieve values from the store. Values for the keys are returned to the cell the following tick via the global_kv_store_responses function

Receive

void global_kv_store_responses(
const aether_state_type &aether_state,
const std::unordered_map<key_type, data_type>& responses
)
Called each tick with values for any keys listed in the previous query. responses contains a map of the requested keys for which values were found.
In the above functions, aether_state passes an instance of aether::cell_state, which provides information about the current worker and cell.

Walkthrough

In the following example, let’s consider a simple predator and prey game. Assume that we have a single agent of type PREY with a known id PREY_ID of type uint64_t and all other agents of type PREDATOR are going to follow them.
The other agents must know the position of the prey if they have to follow it, even if the agents are in a different cell. Using the key-value store, the cell that has the prey in it publishes its position, enabling others to subscribe and receive updates.
Convention is to have a simulation.hh and a simulation.cc file where entity_based_user_state is implemented. This type is used to instantiate the octree manager. This step is not specific to the use use of the Global Key/Value store and should already exist for your simulation.
int main(int argc, char *argv[]) {
// ...
auto static_args = arguments.to.octree_params<octree_traits>();
static_argc.feature_flags = FAST_MODE | PHASE_BARRIERS;
auto manager = aether::build_entity_simulation_manager<user_cell_state_impl>(arguments.workers, static_args);
// ...
}
The first step is to publish the position of the player if present, we look through the entities being managed by the cell and if we identify the PREY we publish data for the entity
auto user_cell_state_impl::publish_global_kv_store_data(
const aether_state_tyoe &aether_state) ->
std::unordered_map<key_type, data_type> {
std::unordered_map<key_type, data_type> data_to_publish;
for (auto iter = store.iterate.local(); !iter.done(); ++iter) {
const agent_t *agent = iter.get_data();
if (agent->id == PREY_ID) {
uint64_t id = agent->id;
vec2f position = agent->p;
std::vector<char> agent_data = serialize_to_vector(position);
data_to_publish(id) = std::move(agent_data);
}
}
return data_to_publish;
}
The next step is for all the cells to query to the keys they're interest in. In this case we return a vector containing just the PREY_ID.
auto user_cell_state_impl::get_global_kv_store_queries(
const aether_state_type &aether_state) ->
std::vector<key_type> {
std::vector<key_type> ids_to_query_for = {PREY_ID};
return ids_to_query_for;
}
The last step is to receive the information. In this call a map of key and data is passed for the entities queried that the global store found, in this case just the entity data for the PREY_ID key would be returned
void user_cell_state_impl::global_kv_store_responses(
const aether_state_type &aether_state,
const std::unordered_map<key_type, data_type> &responses) {
auto search = responses.find(PREY_ID);
if(search != responses.end()) {
...
}
}

Best practices

Workers maintain a connection to the Global key/value store which you should assume is over TCP. As communication is handled as part of the cell lifecycle pushing and pulling a lot of information could potentially slow down your ticks.
The Global key/value store is great if you want to make some information available to many workers, but if you're targeting one specific worker, it might be worth using the Simulate messaging API instead, since it is more efficient for such cases.
The number of entities does not influence the performance of the key/value store, since the communication happens between workers and the store.