Closed phylll closed 2 years ago
Started some work in https://github.com/phylll/mychs-macro-magic/tree/dev/mgd%2Freusable-code-libraries
Here are the precautionary coding standards I've come up with in implementing three such library snippets so far in this branch:
"Library snippets" are prefixed with lib
in Roll20 storage (abilities or global macros) and internal names (see below), and ideally stored in a character sheet of their own, separate from individual scripts (I'm using MacroSheetLibrary
).
Any script using library snippets sets a variable called CURRENT
to identify itself in a way that helps to understand which part of the code is being executed in a debugging situation: debug chat: ${CURRENT} foo bar
Any library snippet begins and ends with temporarily shadowing CURRENT
with its own identifier, while storing the previous value in PARENT
, so that both variables together tell us which library snippet is being executed as part of which parent script at any moment.
This makes for this skeleton code for a new library snippet:
!rem // begin MMM fragment
!mmm set PARENT = CURRENT
!mmm set CURRENT = "libFlushExchange 1.0.0 (2021-12-24)"
!mmm
[...]
!mmm
!mmm set CURRENT = PARENT
!mmm set PARENT = undef
!rem // end MMM fragment
_
and all data exchange between library code and script code should be handling using such prefixed variables, to avoid confusion with "local" script variables and "local" library snippet variables. Only exception is access to globally available data such as the MMM Midgard data exchange sheet, for which there is a global library snippet that initializes and validates access to the sheet and provides a global set of variables prefixed by m3mgd
.When the defense
script calls the data table output snippet stored in libDefenseDataTable
, it looks like this now -- a bit cumbersome but very clear, and allowing for necessary conversations where the script and the library snippet treat variables in slightly different ways, as with the two instances of 0/1
vs. false/true
below:
!rem // Output data table using data table execution code from library
!mmm set CURRENT = scriptVersion
!mmm set _GMSilentMode = (cGMSilentMode == 1)
!mmm set _ownID = cOwnID
!mmm set _weaponLabel = cWeaponLabel
!mmm set _armorPiercing = (armorPiercing == 1)
!mmm set _defenseSuccess = defenseSuccess
!mmm set _defenseResult = defenseResult
!mmm set _prvEndurance = endurance
!mmm set _maxEndurance = m3mgdExchange.(cEnduranceAttr).max
!mmm set _newEndurance = newEndurance
!mmm set _prvHealth = health
!mmm set _maxHealth = maxHealth
!mmm set _newHealth = newHealth
!mmm set _timeToDie = timeToDie
%{MacroSheetLibrary|libDefenseDataTable}
Variables used only inside a library snippet, i.e. variables that we would like to be treated as local to that snippet, should be prefixed by two underscores: __myLocalCounter
. Seemed logical and all-but-impossible to use by accident in a script only to find it overwritten by some library snippet, even if it makes writing library snippets a bit of a pain since it is pretty easy to mix up single-underscore-prefixed variables with double-underscore-prefixed-variables. Best to use quite different names for these two groups.
While self-documenting code isn't quite possible, this convention for comments at the beginning of every library snippet helps me keep track of variables logically considered "imported" to and "exported" from snippets:
!rem // libFlushExchange: delete everything from the exchange data structure
!rem //
!rem // Expects ("imports") variables:
!rem // CURRENT
!rem //
!rem // Sets ("exports") variables:
!rem // _m3mgdExchangeAttributesFlushed Number of attributes set to "" in both their current and max values
!rem //
!rem // begin MMM fragment
!mmm set PARENT = CURRENT
!mmm set CURRENT = "libFlushExchange 1.0.0 (2021-12-24)"
[...]
This topic makes for some intriguing holiday reading :)
I've felt inspired to think about MMM support for custom functions. Shouldn't be too difficult to implement. Syntax is more interesting to ponder.
Define function – scoped to the script that contains the definition, like variables:
!mmm function foo(bar, baz)
!mmm chat: Foo, my ${bar} is ${baz}'d!
!mmm return sender.(bar & "_" & baz)
!mmm end function
Call function – like any other:
!mmm do foo("ammo", "use")
!mmm set remaining = foo("life", "challenge")
That's the baseline. What else?
Notes from michael-buschbeck#160's current state of implementation:
script
variable (e.g. script.myOwnID
).I'm planning to make it so that functions defined outside a script
block are stored per-player in the API sandbox session, and all scripts run by that player can access them. A library macro would then simply be a bunch of !mmm function
blocks, and it'd need to be run only once per session (or after an update).
https://github.com/michael-buschbeck/mychs-macro-magic/issues/160 makes this issue redundant, closing.
There is too much complicated code being stored in various copies across scripts, which leads to annoying and avoidable bugs. Roll20's chat syntax makes it easy to parcel out MMM code between different global macros or character sheet abilities:
Which may or may not, depending on the contents of the ability
bigBadWolfCodeSnippet
in the character sheetBigNastyCodeLibrary
, produce output like this:The narrative continuity problem points toward the key drawback: there is no protected namespace or anything similar, no guarantee that either the code in the very pedestrian "code library" receives exactly the variables it expects, nor can the main script be sure that its variables will be left untouched or touched only in expected ways.
Still, some basic validation of key variables is easy to implement for both library snippets and main code elements after a library snippet was called. Some sane coding practices and supporting conventions for easier debugging may help make this a feasible option and leverage its benefits for reusable standard code.