More about the main loop - makingthematrix/gailibrary GitHub Wiki

The simplest way to use GAI is to populate it with cells at the beginning of each iteration, and at the end to read these computed values which interest us (eg. „move_towards”, „turn_towards”, and „shoot”), and destroy the cells. But this is not the only possible way. Another approach is to create cells corresponding to objects in the game (eg. The player and the NPCs) only at the beginning of the scene (eg. When the player approaches the room), and then only update their values before each iteration. We can roughly divide the cell’s values into three types:

  1. Values set by the external code in order to provide GAI with data to reason about.
  2. Internal values, carried from one iteration to another.
  3. Results, which are read by the external code, but GAI does not need them for further computations. Sometimes results can also be internal values, used in the next iteration.

In this mode the cells might of course still be deleted, eg. when the NPC dies. It’s also possible to add new cells gradually: either from the external code, or one of the functions may request creating a new cell at the end of the iteration. Anyway, without constantly creating and destroying cells we have:

  1. faster code
  2. the ability to keep data in cells between iterations without needing the user to read it and set it each time.

So, in our example, the user has to update the position, the rotation, the health and the ammo of the player and NPCs, and at the end she reads the decisions: „shoot”, „move_towards”, and „turn_towards”. Note that „move_towards” is not the new position, and „turn_towards” is not the new rotation. It’s up to the game engine how much of the movement is done between two consecutive iterations – usually very little. It depends mainly on the time interval between the iterations, and actually it is a hint to that we don’t have to call GAI very often. Once per second should probably be enough for a shooter game. So, the main GAI loop could run in a separate thread and go to sleep for a specified time after each iteration. The user may update the cells’ input values from its own thread (they’re only read, not set, so it’s safe). Or there can be a special update functions, specyfing where the init values come from, called by GAI at the beginning of each iteration. Then GAI performs the computations and at the end of the iteration turns the list of results into cells’ values. All cells’ values can be read by external code at any time.

If we’re sure the cells are not going to be destroyed at the end of an interation, we may let them set values in one another. Again, this is not an idiomatic cellular automata way of doing things – C.A. may only gather data from other cells and set its own values – but it may help us avoid lazy vals in certain situations. For example, we can write „player_in_room” as follows:

„player_in_room”: { npc => npc(„player_warning_received”) || npc(„player_visible”) }

„player_visible”: { npc =>
  let oos = GAI.graph(„outofsight”)
  let result = oos.edge( oos.node_of(npc.id), oos.node_of(„player”) )
  if (result) npc.refs.foreach { _.set(„player_warning_received”, true) }
  result
}

When one NPC sees the player, it warns all others. They will receive the warning on the next iteration.

But the player may leave the room – what then? „player_in_room” should be updated once in a while, even after it’s set to true. A special notation may be useful to ease setting an ephemeral value. If instead of setting the value in the standard value we will use:

npc.setForTime(„player_warning_received”, true, seconds(5))

it will be expanded into:

npc.set(„player_warning_received”, true)
npc.set(„player_warning_received$timestamp”, now() + seconds(5))

During the init function at the beginning of each iteration GAI will look for values ending with $timestamp and if now() is bigger than their value, both X and X$timestamp will be removed.