Minty23185Fresh
February 28th, 2018, 22:16
Issue
Local script, those .lua files referenced in XML windowclasses, templates and controls, because of code layering, are difficult to override without wholesale function or even complete script file replacement. Having to replace whole functions or files bloats extensions, may make the use of multiple extensions by users impossible and can cause code maintenance to be more difficult or time consuming than necessary. The greatest single contribution to this difficulty is function and variable scope.
Solution
To mitigate the issue of local (modular or file level) variable scope, get and set functions, at the ruleset level, can be employed to expose the variables to other code layers. This methodology helps mitigate the issue of functions executing in an extension layer rather than the ruleset layer. By using a side-by-side existence for functions rather than function replacement code duplication can be substantially reduced.
Methodology
An agent or interface is used to host the get and set functions. The agent can and probably should reside in its own extension, a code set exposed to modification by all developers. This allows multiple extension employment by users without demanding the use and loading of a particular extension, other than the agent.
Implementation
Agent
The agent, or interface, is a wholesale replacement of specific ruleset code. This typically involves the XML object (windowclass, template, etc.) that references the .lua script affected by a developer's extension as well as the .lua file itself. The portion of the XML file containing the target object is copied, verbatim, to a like-named file in the agent and referenced in the extension's extension.xml file. The only change is the reference within the object to its .lua script file. Instead of referring to the ruleset LUA script, the XML will reference an exact copy of the ruleset .lua file residing in the agent. The .lua file, as just stated, is an exact copy of the ruleset file, unmodified except for one get function and one set function. The two functions provide a single conduit of variable exposure from the agent script to and from extensions employing the agent.
Functions
Using side-by-side function existence, at the global level, was discussed here (https://www.fantasygrounds.com/forums/showthread.php?39930-How-to-Avoid-or-Mitigate-Extension-Conflicts-(or-Collisions)). In essence, whenever the developer needs to modify a ruleset function, rather than just copying the function to the developer's extension, then modifying it as a replacement for the ruleset function, the developer should leave the ruleset function in place but rename it as a helper function. Then the ruleset function is made to point to the extension's like-named function. A minimal amount of code is added to the extension's function to augment the ruleset code. The extension's function calls the ruleset (helper) function either before or after its own code execution. When properly implemented this methodology allows multiple extensions to be developed by independent developers without regard to the contents of other extensions nor which ones the user decides to employ. As each extension is loaded it sets aside any other extension's functionality as a helper and substitutes it own. At runtime all extension functionality is performed one after the other via the side-by-side helpers.
Discussion
Conventions
1) A directory structure within the Agent should mirror that of the ruleset. So an .xml file from the ruleset's "common" folder should reside in a "common" folder within the Agent. A copied .lua file in the "common/scripts" folder of the ruleset should be placed in the same named Agent subfolder. This helps later developer's know where files came from and if a file that they need to work with already exists within an Agent.
2) Though not necessary, I'd suggest that XML and LUA file names in the Agent be prefaced with "ERA_" (Extension-Ruleset Agent). This will help developers recognize the agent files, those that are minimally modified, from their own extension files.
3) Helper functions should be named the same as the ruleset function they are replacing with a prefix unique to the developer's extension and a suffix of "_helper". Example: if one is developing the "MINT extension one might use: "MINT_theFunction_helper( )". This allows multiple helpers to reside side-by-side, as in EXT1_funct_helper, EXT2_funct_helper, EXT3_funct_helper.
4) Given the helper naming convention, one might consider naming the other functions in their extension similarly: MINT_updateDisplay( ), MINT_roll( ), MINT_getRandomNumber( ).
5) The added get and set functions in the Agent LUA file contain a root name of "Super", as in getSuperVar( ) and setSuperVar( ) as a simple indication of where those functions live, in the Agent, the "super" layer to all subsequent extension layers.
6) Be sure to pay attention to <loadorder> in the extension.xml files. Agent should load before extensions (have lower load order number).
Agent
At a minimum the agent will have three code files: the extension.xml, an .xml file containing the affected template, windowclass or control, and a .lua file.
The pertinent code within the extension.xml file would reference the XML object's file, something like this for a template_buttons.xml file in the ruleset (CBA stands for "Common Buttons Agent"):
<base>
<includefile source="common/CBA_template_buttons.xml" />
</base>
The template that is being augmented, is the only object that need be in the CBA_template_buttons.xml file. The template should be an exact copy of the template in the ruleset. And then a single change, the script reference. The Agent's reference, should point to the like-named .lua file in the Agent.
<root>
<template name="button_iconcycler">
<genericcontrol>
<anchored height="20" />
<script file="common/scripts/CBA_button_iconcycler.lua" />
</genericcontrol>
</template>
</root>
And the referenced CBA_button_iconcycler.lua file would be an exact copy of the ruleset LUA file with two functions added to the bottom of the file, a "getter" and a "setter":
local sString;
local nInteger;
-- all of the functions would be here......
function getSuperVar(sVarName)
if sVarName == "sString" then
return sString;
elseif sVarName == "nInteger" then
return nInteger;
else
-- something here to deal with unhandled argument values
end
end
function setSuperVar(sVarName, aVarValue)
if sVarName == "sString" then
sString = aVarValue;
elseif sVarName == "nInteger" then
nInteger = aVarValue;
else
-- something here to deal with unhandled argument values
end
end
LUA Functions
Side-by-side existence of LUA script, the functions that need augmenting is accomplished in the extension's onOnit( ) function in the .lua file. Recall, the ruleset (actually the Agent's) function is renamed as a helper. Then the ruleset's function name is made to point to the extension's like-named function. The extension's function calls the helper, to maintain all the ruleset functionality, without a bunch of duplicate code, and executes the augmentation code it contains:
function onInit()
-- get these in place before initializing super
super.EIC1_synchData_helper = super.synchData;
super.synchData = EIC1_synchData;
super.onInit();
end
function EIC1_synchData()
-- some augmentation might happen here, then call the helper
super.EIC1_synchData_helper();
-- and or some augmentation might happen here (too)
end
(Continued in next post)
Local script, those .lua files referenced in XML windowclasses, templates and controls, because of code layering, are difficult to override without wholesale function or even complete script file replacement. Having to replace whole functions or files bloats extensions, may make the use of multiple extensions by users impossible and can cause code maintenance to be more difficult or time consuming than necessary. The greatest single contribution to this difficulty is function and variable scope.
Solution
To mitigate the issue of local (modular or file level) variable scope, get and set functions, at the ruleset level, can be employed to expose the variables to other code layers. This methodology helps mitigate the issue of functions executing in an extension layer rather than the ruleset layer. By using a side-by-side existence for functions rather than function replacement code duplication can be substantially reduced.
Methodology
An agent or interface is used to host the get and set functions. The agent can and probably should reside in its own extension, a code set exposed to modification by all developers. This allows multiple extension employment by users without demanding the use and loading of a particular extension, other than the agent.
Implementation
Agent
The agent, or interface, is a wholesale replacement of specific ruleset code. This typically involves the XML object (windowclass, template, etc.) that references the .lua script affected by a developer's extension as well as the .lua file itself. The portion of the XML file containing the target object is copied, verbatim, to a like-named file in the agent and referenced in the extension's extension.xml file. The only change is the reference within the object to its .lua script file. Instead of referring to the ruleset LUA script, the XML will reference an exact copy of the ruleset .lua file residing in the agent. The .lua file, as just stated, is an exact copy of the ruleset file, unmodified except for one get function and one set function. The two functions provide a single conduit of variable exposure from the agent script to and from extensions employing the agent.
Functions
Using side-by-side function existence, at the global level, was discussed here (https://www.fantasygrounds.com/forums/showthread.php?39930-How-to-Avoid-or-Mitigate-Extension-Conflicts-(or-Collisions)). In essence, whenever the developer needs to modify a ruleset function, rather than just copying the function to the developer's extension, then modifying it as a replacement for the ruleset function, the developer should leave the ruleset function in place but rename it as a helper function. Then the ruleset function is made to point to the extension's like-named function. A minimal amount of code is added to the extension's function to augment the ruleset code. The extension's function calls the ruleset (helper) function either before or after its own code execution. When properly implemented this methodology allows multiple extensions to be developed by independent developers without regard to the contents of other extensions nor which ones the user decides to employ. As each extension is loaded it sets aside any other extension's functionality as a helper and substitutes it own. At runtime all extension functionality is performed one after the other via the side-by-side helpers.
Discussion
Conventions
1) A directory structure within the Agent should mirror that of the ruleset. So an .xml file from the ruleset's "common" folder should reside in a "common" folder within the Agent. A copied .lua file in the "common/scripts" folder of the ruleset should be placed in the same named Agent subfolder. This helps later developer's know where files came from and if a file that they need to work with already exists within an Agent.
2) Though not necessary, I'd suggest that XML and LUA file names in the Agent be prefaced with "ERA_" (Extension-Ruleset Agent). This will help developers recognize the agent files, those that are minimally modified, from their own extension files.
3) Helper functions should be named the same as the ruleset function they are replacing with a prefix unique to the developer's extension and a suffix of "_helper". Example: if one is developing the "MINT extension one might use: "MINT_theFunction_helper( )". This allows multiple helpers to reside side-by-side, as in EXT1_funct_helper, EXT2_funct_helper, EXT3_funct_helper.
4) Given the helper naming convention, one might consider naming the other functions in their extension similarly: MINT_updateDisplay( ), MINT_roll( ), MINT_getRandomNumber( ).
5) The added get and set functions in the Agent LUA file contain a root name of "Super", as in getSuperVar( ) and setSuperVar( ) as a simple indication of where those functions live, in the Agent, the "super" layer to all subsequent extension layers.
6) Be sure to pay attention to <loadorder> in the extension.xml files. Agent should load before extensions (have lower load order number).
Agent
At a minimum the agent will have three code files: the extension.xml, an .xml file containing the affected template, windowclass or control, and a .lua file.
The pertinent code within the extension.xml file would reference the XML object's file, something like this for a template_buttons.xml file in the ruleset (CBA stands for "Common Buttons Agent"):
<base>
<includefile source="common/CBA_template_buttons.xml" />
</base>
The template that is being augmented, is the only object that need be in the CBA_template_buttons.xml file. The template should be an exact copy of the template in the ruleset. And then a single change, the script reference. The Agent's reference, should point to the like-named .lua file in the Agent.
<root>
<template name="button_iconcycler">
<genericcontrol>
<anchored height="20" />
<script file="common/scripts/CBA_button_iconcycler.lua" />
</genericcontrol>
</template>
</root>
And the referenced CBA_button_iconcycler.lua file would be an exact copy of the ruleset LUA file with two functions added to the bottom of the file, a "getter" and a "setter":
local sString;
local nInteger;
-- all of the functions would be here......
function getSuperVar(sVarName)
if sVarName == "sString" then
return sString;
elseif sVarName == "nInteger" then
return nInteger;
else
-- something here to deal with unhandled argument values
end
end
function setSuperVar(sVarName, aVarValue)
if sVarName == "sString" then
sString = aVarValue;
elseif sVarName == "nInteger" then
nInteger = aVarValue;
else
-- something here to deal with unhandled argument values
end
end
LUA Functions
Side-by-side existence of LUA script, the functions that need augmenting is accomplished in the extension's onOnit( ) function in the .lua file. Recall, the ruleset (actually the Agent's) function is renamed as a helper. Then the ruleset's function name is made to point to the extension's like-named function. The extension's function calls the helper, to maintain all the ruleset functionality, without a bunch of duplicate code, and executes the augmentation code it contains:
function onInit()
-- get these in place before initializing super
super.EIC1_synchData_helper = super.synchData;
super.synchData = EIC1_synchData;
super.onInit();
end
function EIC1_synchData()
-- some augmentation might happen here, then call the helper
super.EIC1_synchData_helper();
-- and or some augmentation might happen here (too)
end
(Continued in next post)