Keyword Cookbook - theRAPTLab/gsgo GitHub Wiki

Created Feb 05 2021

when engine details

GEMSTEP Interactions are operations that apply to a set of agents. A test function is used to filter agents into 'passed' and 'failed' arrays. Currently we support:

  • Single Interaction: a test is aplplied to all instances of a named BlueprintType
  • Paired Interaction; all agents of two BlueprintType instances are tested against each other

Interactions are scripted using the when keyword, which looks like this:

// Paired Interaction
# PROGRAM UPDATE
when Bee touches Flower 10 [[
  prop Bee.collectedPollen add 1
]]

This is parsed into the following parameters for the when keyword:

  • A as "Bee"
  • testName as "touches", the name of a pre-written testing function
  • B as "Flower"
  • ...args additional arguments to pass to the testing function (in this case, 10)
  • consequent as script inside the [[ ]] block for all agent pairs that pass the test

The when keyword implements interactions in both compilation and runtime phases

When the when keyword is encountered during compilation, the parameters are used to construct a ConditionCache Key. The purpose of ConditionCacheKey is to run the test just once per gameloop, rather than allowing every instance to run the test multiple times. At compile time, the ConditionCacheKey is stored in a dictionary entry with all the arguments needed to run the test, and the cachekey is then available for generating the runtime function that will perform the actions on all passing agents.

// inside when.compile() for paired interaction
const [kw, A, testName, ...args] = unit;
const consq = args.pop();
// save test to run during CONDITIONS_UPDATE
const key = RegisterPairInteraction([A, testName, B, ...args]);
// return function to run during AGENT_UPDATE
return (agent,state)=>{
  const passed = GetInteractionResults( key );
  passed.forEach(pair=>{
    const [aa, bb] = pair;
    const ctx = { [A]:aa, [B]:bb }
    agent.exec(conseq,ctx,...args);
  }
}

TODO there's a bug in when that does not pass...args because it is dependent on counting the number of unit elements. Variable arg length messes this up!!!

The gist of this code is to:

  • register the ConditionCacheKey key at compile time
  • read all the results and run the consequent on them at runtime

During the game loop:

  • during CONDITION, all the CONDITION_CACHE tests are executed, which updates the passed array for all the tests.
  • during UPDATE, the function returned by when loads the cached results and runs the consequent on all of them

Argument Expansion

GEMSTEP defines additional ScriptText types beyond string, number, and boolean:

  • expressions are Javacript-style expressions, written in ScriptText as {{ expressionhere }}
  • blocks are ScriptText blocks delimited by a trailing [[ and terminated by a ]] on a line by itself
  • objrefs are identifiers that are dotted (e.g. agent.x) for referencing internal properties

There are two transformations:

  • during compile, the text is converted to an array of primitives and the type expresion, block, objref.
    • expressions are converted into an "AST",
    • blocks are converted recursively and stored in an array type.
    • Objref are converted into a list of properties that can be used to "drill into" an object
  • during runtime, expressions and objrefs need to be computed on-the-fly, since they refer to objects in the system that are constantly changing. These values can not be determined during compile time.
    • expressions have already been converted into an AST, so evaluate(ast,context) is called
    • objrefs are counted, and swizzled to provide the parameters to drill into a referenced agent instance. For example, agent.x is swizzled into agent.getProp('x').value

In summary:

  • COMPILING: text tokens converted into arguments. These are either primitive values (number, string, boolean) and extended type (expression, block, objref)
  • RUNTIME: primitive values are passed as-is to the runtime TOpcode(s), and expressions/objrefs are evaluated before being used

The Compiler and Runtime Engine use different function to do argument expansion:

  • TRANSPILER does expansion from tokens into blocks, expression, and objref as it processes all the lines of the ScriptText. This conversion is handled automatically by the engine.
  • EXPR-EVALUATOR does argument expansion via the utilty method EvalUnitArgs() and EvalArg() , which must be called explicitly from the keyword's compile() function if expects to handle extended types.
⚠️ **GitHub.com Fallback** ⚠️