Keyword Cookbook - theRAPTLab/gsgo GitHub Wiki
Created Feb 05 2021
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
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 intoagent.getProp('x').value
- expressions have already been converted into an AST, so
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()
andEvalArg()
, which must be called explicitly from the keyword'scompile()
function if expects to handle extended types.