GVar Dictionaries - theRAPTLab/gsgo GitHub Wiki

See #755 for discussion.

GVar Dictionaries

GVar Dictionaries adds the ability to define multiple static key-value pairings for character (agent) properties and to conduct test operations on the dictionary values.

  • Dictionary items are defined via an addOption method for GVars.
  • A specific GVar property's value can be assigned to one of the predefined "option" values using the setToOption method.
  • GVar property values can be tested against dictionary keys using GVar methods. e.g. ifProp character.colour equalToOption RED checks if character.colour value is equal to the dictionary value #f00 (not the key RED).
  • Assignment of Dictionary items to a another character property or feature property needs to be handled via stack operations.

Example Scripts:

// options
addProp colour string 'black'
prop character.colour addOption 'RED' '#f00'
prop character.colour addOption 'GREEN' '#0f0'
prop character.colour addOption 'BLUE' '#00f'
prop character.colour setToOption 'RED'

// test assignment
propPush colour
dbgStack
// -> should log "STACK(enum0): ['#f00']"

// test equalToOption condition
ifProp character.colour equalToOption 'RED' [[
  dbgOut 'is RED!'
]]
// -> should log ''is RED'

// test notEqualToOption condition
ifProp character.colour notEqualToOption 'GREEN' [[
  dbgOut 'is NOT GREEN!'
]]
// -> should log 'is NOT GREEN'

// assign dictionary option to a feature property
prop character.colour setToOption `BLUE`
// -> should set character.colour value to `#00f`
propPush character.colour
featPropPop Costume.colorCSS
// -> should set the Costume feature's `colorCSS` property to `#00f`

NOTE GVar dictionaries only work in the context of the current character. You cannot reference global agent properties or properties from other characters.

Methods

General Options Methods

addOption GVar method

Add a new option to the designated GVar. Note options values match the GVar type, e.g. a string GVar's option will be a string, a number GVar's option would be string.

Syntax

prop <propName> addOption <optionLabel> <optionValue>

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the options in the option methods. e.g. defining a 'RED' option label allows you to use a constant label to set a constant value, e.g. ...setToOption 'RED'
  • <optionValue> is the value that the <optionLabel> is mapped to, e.g. if the RED option label is set to the option value #f00, then setting a prop value to the option RED would set the prop to #f00.

Example

prop character.colour addOption 'RED' '#f00'

setToOption GVar method

Assign a prop value to the selected option value.

Syntax

prop <propName> setToOption <optionLabel>

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the options in the option methods. e.g. defining a 'RED' option label allows you to use a constant label to set a constant value, e.g. ...setToOption 'RED'.

Example

prop character.colour setToOption 'RED'

equalToOption GVar method

Evaluates whether a prop value matches the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> equalToOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'RED' option label allows you to see if your property value matches 'RED''s value ('#f00').

Example

ifProp character.colour equalToOption 'RED' [[ ... ]]

notEqualToOption GVar method

Evaluates whether a prop value does not match the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> notEqualToOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'RED' option label allows you to see if your property value does not match 'RED''s value ('#f00').

Example

ifProp character.colour notEqualToOption 'RED' [[ ... ]]

Number Methods

greaterThanOption number-only GVar method

Evaluates whether a prop number value is greater than the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> greaterThanOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'LOW' option label allows you to see if your property value is greater than 'LOW''s value ('10').

Example

ifProp character.colour greaterThanOption 'LOW' [[ ... ]]

greaterThanOrEqualToOption number-only GVar method

Evaluates whether a prop number value is greater than or equal to the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> greaterThanOrEqualToOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'LOW' option label allows you to see if your property value is greater than or equal to the 'LOW''s value ('10').

Example

ifProp character.colour greaterThanOrEqualToOption 'LOW' [[ ... ]]

lessThanOption number-only GVar method

Evaluates whether a prop number value is less than the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> lessThanOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'LOW' option label allows you to see if your property value is less than 'LOW''s value ('10').

Example

ifProp character.colour lessThanOption 'LOW' [[ ... ]]

lessThanOrEqualToOption number-only GVar method

Evaluates whether a prop number value is less than or equal to the selected option value. This is generally used with ifProp

Syntax

ifProp <propName> lessThanOrEqualToOption <optionLabel> [[ ... ]]

where

  • <propName> is the name of a property object reference. e.g. could be character.propname or agent.propname. or simply propname
  • <optionLabel> is the name of the option. Use this to refer to the option value in the option methods. e.g. testing a property value against a 'LOW' option label allows you to see if your property value is less than or equal to the 'LOW''s value ('10').

Example

ifProp character.colour lessThanOrEqualToOption 'LOW' [[ ... ]]

Use Case

There are three basic needs:

  1. Define multiple Dictionary Items -- a list of items each consisting of a key that is assigned to a value.
  2. Be able to set a character property to a dictionary item by simply selecting the key from a list (rather than having to type it).
  3. Be able to test whether a character property matches a dictionary item key (rather than matching it to the dictionary value).

Four example use cases:

  1. Simple CONSTANT key with no value-- Assign an entityType property to a character designating it a PRODUCER, CONSUMER, or DECOMPOSER, and use a property operation test to trigger behaviors based on the selected entityType. No value is used.
  2. Assignment to a "numeric" value -- Assign a energyLevel property where LOW is 0, MEDIUM is 5, and HIGH is 10, and use a property operation test to trigger behaviors based on numeric thresholds, e.g. do something if energyLevel is higher than LOW.
  3. Assignment to a "string" value -- Assign a entityState property to a character designating it HUNGRY, CONTENT, FULL, or DEAD, displaying the selected value during exectution, e.g. HUNGRY would be displayed as the value Hungry.
  4. Assignment to a "string" value and use a Feature property to trigger special operations -- Assign a colour to a character property where RED = "#f00", GREEN = "#0f0", and BLUE = "#00f"and use the value to set a costume color.

Implementation History

  1. Original Request
  2. Trying Constants
  3. Hacking in Options + UI

1. Original Request

The original request was to provide a way to define constant-like declarations to use as part of GEMSCRIPT. For example:

  • Organism type: producer, consumer, decomposer
  • Energy level low (1), medium (5), high (10)
  • Fish state: hungry, content, full, dead

2. Trying Constants

After conferring with Sri, we felt that a cleaner implementation of "options" would be to emulate the notion of constants. The requested features were less "enums" and more like "dictionaries". We suggested the use of the keywords addConstants and constantPush as a way to implement dictionaries. The implementation was fairly complex but essentially introduced a new set of agent properties called constants to go along with the existing properties and methods properties. constants essentially replicated the functionality of properties as a parallel subsystem.

While this implementation does work, the problem was that GEMSCRIPT can only handle assignments and tests to a simple javascript string, number, or boolean. We could not support assignments or tests to objects that are properties of agents or feature properties. e.g. you cannot use prop character.colour setTo RED because the setTo method only supports the assignment of pure strings, not RED which would be a constant object. e.g. you cannot use ifProp character.colour equal RED for the same reason -- the equal method can only work with a simple string, not RED which is a constant object. The only way to assign values was to use stack operations. While this implementation "works", it is confusing for students and because of the complexity does not lend itself to "Let's try this" comment instructions.

See discussion of constants implementation

3. Hacking in Options + UI

The third approach was to implement a hybrid solution. This involves a two part solution.

First, it adds the ability to add and use "options" to existing GVars like class-sm-string, class-sm-number, and class-sm-boolean. By adding options directly to GVars, we can introduce GVar-specific options methods for use within the scope of the GVar. This allows us to do assignments and run tests on options. This also allows the simulation engine to compile and run code that references agent property-specific options during assignments and tests without fundamentally altering how the simulation engine works.

Second, although we can introduce "options" methods to GVars that support the simulation engine, using the Script Wizard to construct GEMSCRIPT for "options" manipulation is not as straightforward. The Script Wizard uses a complex system that parses script text into tokens that are then in turn used to construct the wizard UI. The system is designed to work with standard keywords and syntax. But "options" do not quite adhere to the way keywords are handled, as a result, it has no way of constructing a list of user-defined "options" for the user to select -- we can generate lists of properties that have been added via code (e.g. user-defined character properties), but not the derivative methods (where the "options" are defined).

The workaround is to do two things:

  1. Add an extra compile loop that is used purely for pulling out the list of "options" that have been defined for the blueprint, and...
  2. Inject a custom UI into the standard Script Wizard UI to render and allow selection of an "option" from the list of possible "options" (emulating the approach taken with the Comment Styles selection interface).

This way the code data remains pure, can run on the standard simulation engine, and wizard hacks are confined to the purely visual rendering side.

LIMITATIONS with this approach

  • Cross-agent options do not work. e.g. you can't define an option for the global agent and use it with the Fish agent. Similarly, a Fish agent's option is completely separate from a Algae agent's options.

  • We also cannot completely get around the need for some stack operations when working with GVar Dictionaries. E.g. if we're working with a character prop and you need to reference a featProp, you can only do cross-type assignments using a stack operation. e.g.

// 1. set a character prop
prop character.colour setToOption 'RED'
// 2. this is illegal because 'RED' is defined in the context of the character prop, the featProp
~featProp Costume.colorHex setToOption 'RED'~
// 3. instead, use a stack operation to do a cross-type assignment
propPush character.colour
featPropPop Costume.colorCSS
// -> assigns the value of character.colour option 'RED' (value = "#f00") to the Costume.colorCSS property

Technical Aside

[In which Ben tries to document different approaches to the problem and serve as reminders of how the system works...feel free to skip.]

Bundle the option symbols as part of normal compile

One possible approach to streamline the "options" implementation: If we could bundle the "options" definitions as part of the tokens used to render the wizard, then we do not need a extra separate compile cycle.

  • How does addProps figure out the list of newly defined props? addProps defines new prop names that are listed when editing a prop script line. So in wizard slot editor, the interpreted bundles do contain a list of the prop names as keys to the prop object (e.g. props: { colour, scale, ...}).
  • How are the bundles constructed? Bundles are constructed during the compile process. script-compiler calls:
    • script-compiler.CompileBlueprint, which breaks the blueprint into single lines in
    • script-compiler.CompileStatement, which breaks down into
    • script-compiler.DecodeStatement, which in turn is broken down into tokens in
    • script-compiler.DecodeToken, which returns the single token.
    • When adding a new prop, we deconstruct the statement into four tokens (e.g. addProp colour string 'black': a. keyword: addProp b. propName: colour c. GVar type: string d. GVar arg: black
    • During compile for addProp, the fourth token for "b. propName" is added to an array of newly defined property names.
    • Later, when processing a statement that references the prop keyword, we look up the list of previously defined propNames to provide a list of options. This also requires processing 4 tokens (e.g. prop character.colour setToOption 'RED'): a. keyword: prop b. propName: character.colour c. GVar method: setToOption d. GVar method argument: RED
    • WHERE ARE THESE propNames stored and retrieved from? In order to show the list of available predefined propNames when defining the second propName slot, class-symbol-interpreter has to look up the available propNames.
      • Bundler adds symbols by retrieving all the symbols for each keyword (e.g. each keyword has a symbolize function that generates all the available symbols). This is fed to...
      • ...script-bundler.AddSymbols constructs the features, and props (and constants if we keep that) lists from the symbol data generated by the bundler. Everything else is derived from that as symbolData.
      • ...which eventually leads to...
      • ...EditSymbol_Block.f_renderchoices generates the list of options from symbolData
      • ...which is constructed from symbol-utilities.DecodeSymbolViewData generates lists based on props (and constants if we keep that).
    • If we wanted to add propOptions to the list, it would need to happen here in script-bundler.AddSymbols. We would probably either:
      • a) figure out how to shove it into props, or
      • b) figure out how to shove it in as constants (if we decide to implement constants)
      • c) add a new symbol set propOptions or something like that?
    • This means we either add the symbolization in the prop keyword (if we're adding symbols to the addOption keyword), which is awkward, because we would then be diving down to the next level to process prop methods in order to symbolize values...
    • ...or we introduce a new addConstant or addPropOption keyword that allows us to symbolize the keywords separately. BUT, then we would lose the ability to use prop methods to reference stored dictionary values (since they no longer a part of the prop object)?

SO...we either have a nicely constructed menu of symbols that cannot be compiled, or we have a compilable script but a wizard UI that is not able to generate the list of symbols.

  • This is where things get even more complicated. In order to provide a list of options (RED, GREEN, BLUE) for the 4th token "d. GVar method argument", we need to look up the options that have been defined for the second token "2. propName". THIS IS WHERE using a constant is potentially easier? e.g. rather than having to look up the propName, and then look up the option stuffed into the propName, we can use looking up addConstant keyword the same way that addProp works. But again, we have nice UI, but the code can't compile because we can't reference the option's value.

CONCLUSION: Although it's less than ideal, the "simple" solution is to use the hybrid approach: a. define options as part of the GVar b. during wizard construction, scrub the script text to pull out "options" that have been defined in the code c. inject a menu selection UI into the normal wizard to allow selection of the "option" d. during code compile and execution, the "option" references the value of the "option" (e.g. #f00) rather than the "label" (e.g. RED).


To Test

  1. git checkout dev-bl/enum
  2. npm run gem
  3. Edit a blueprint, adding the following lines to any blueprint:
# BLUEPRINT enum
# TAG isCharControllable true
# TAG isPozyxControllable false
# TAG isPTrackControllable false

# PROGRAM INIT
addFeature Costume
featProp character.Costume.costumeName setTo 'AQ_algae.png'

// STRING OPTIONS
addProp colour string 'black'
prop character.colour addOption 'RED' '#f00'
prop character.colour addOption 'GREEN' '#0f0'
prop character.colour addOption 'BLUE' '#00f'
prop character.colour setToOption 'RED'

// test assignment
propPush colour
dbgStack
// -> should log '#f00'

// test equalToOption condition
ifProp character.colour equalToOption 'RED' [[
  dbgOut '1 is RED!'
]]
// -> should log ''is RED'

// test equalToOption condition
ifProp character.colour notEqualToOption 'GREEN' [[
  dbgOut '2 is NOT GREEN!'
]]
// -> should log 'is NOT GREEN'



// NUMBER OPTIONS
addProp hungerLevel number 0
prop character.hungerLevel addOption 'LOW' 0
prop character.hungerLevel addOption 'MED' 50
prop character.hungerLevel addOption 'HIGH' 100
prop character.hungerLevel setToOption 'MED'
ifProp character.hungerLevel equalToOption 'MED' [[
  dbgOut '0 Fish hunger is not MED!'
]]
ifProp character.hungerLevel notEqualToOption 'HIGH' [[
  dbgOut '1 Fish hunger is not HIGH!'
]]
ifProp character.hungerLevel greaterThanOption 'LOW' [[
  dbgOut '2 Fish hunger is greater than LOW'
]]
ifProp character.hungerLevel greaterThanOrEqualToOption 'MED' [[
  dbgOut '3 Fish hunger is greater or equal to MED'
]]
ifProp character.hungerLevel lessThanOption 'HIGH' [[
  dbgOut '4 Fish hunger is greater than HIGH'
]]
ifProp character.hungerLevel lessThanOrEqualToOption 'MED' [[
  dbgOut '5 Fish hunger is greater than MED'
]]



// BOOLEAN OPTIONS
addProp isDead boolean false
prop character.isDead addOption 'DEAD' true
prop character.isDead addOption 'ALIVE' false
ifProp character.isDead equalToOption 'ALIVE' [[
  dbgOut '1 Fish isDead is ALIVE!'
]]
ifProp character.isDead notEqualToOption 'DEAD' [[
  dbgOut '2 Fish isDead is not DEAD!'
]]
  1. The web console on "Main" should show the following output: screenshot_1320

This should demonstrate:

  • adding a new option
  • setting a property to an option
  • testing an option's assignment

⚠️ **GitHub.com Fallback** ⚠️