The main loop - makingthematrix/gailibrary GitHub Wiki
After the setup all that is left is to feed GAI with data, run the iteration, and collect the results.
Setting the data
GAI exposes cells to the user just as they are exposed to functions running on them. With GAI::cells(cell_id: CellId)
we can get a reference to a cell, and then with cell.set(valueId: ValueId, value: Value)
we can set the value. The value will be initially stored in the results
map, as if it was a result of the previous iteration, but a the beginning of the current iteration all results will be moved to values
. We can also explicitly clear the results of the previous iteration in order to prevent any unwanted changes to the values.
Quite often cells will be 1-to-1 representations of objects on the game scene. If so, it would be nice to provide some shortcuts for creating, and then setting up cells directly from the game object they represent. They objects may for example implement traits, like GAICellCreate
and GAICellSet
, with methods which would create and set data of a cell basing on the state of the object. Then we could simply create the cell (in the setup phase) with something like GAI.cells.create(game_object)
and set the data with GAI.cells.set(game_object)
.
Running the iteration
The iteration starts with
- moving the results of the previous iteration to values
- clearing lazy vals and variables
- updating the position graph: references to agents are first removed from every node and then added again to the nodes closes to the agent's current position. Then for every cell in GAI we run all the functions with "dominion" over it, ie. able to work on that cell type. Ideally, we run them in parallel, but because of variables GAI might need to divide the functions into buckets: All functions in a bucket may be run in parallel, but buckets need to be run sequentially. If we have two buckets, A and B, and A is run first, it is because functions in B depend on variables set by functions in A.
There's another way to do it: We could run each function sequentially, but for all cells at once. I wrote more about this approach in the CUDA paragraph.
Collecting the results
This is again as simple as calling GAI::cells(cell_id).results
and using that to update the data of the game object. Additionally I could provide a trait, GAICellGet
, implemented by a game object, and then GAI::cells.update(game_object)
would do the thing.
The simplest way for the game to use GAI is to do the above three steps every time the game want to calculate decisions for its NPCs. But sometimes the decision-making process may be designed in a way which requires a few runs of GAI until we reach some sort of a stable situation, and only then the results are collected. In such case, setting the data and collecting the results are performed once at the beginning and the end, but instead of one iteration there are many. And we need to set up end conditions to stop them at some point:
- #iterations (a special case = 1)
- A statistics check (average of a value, min/max, a distribution coefficient in or out of the given interval, etc)
- an external interruption (delayed to the end of the iteration)
- We need a good statistics library
Also it would be nice to be able to create intervals between two consecutive iterations:
- A constant interval between the end of one iteration and the start of another.
- A constant time between two starts - the interval is computed basing on how much time an iteration took. May cause an exception if the iteration time is larger than the time expected between iterations.