Announcement

Collapse
No announcement yet.

Lua "Constant" Trick

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Lua "Constant" Trick

    I just discovered something rather interesting (that, in retrospect, should have occurred to me a long time ago) that I thought others might want to know.

    This applies to a module which contains values which you want to share publicly but, while sharing them, you don't want them altered by the client (because it would affect the internal workings of your module). In most programming languages, this is where a contant comes in handy. The problem is that in Lua (unless you're using 5.4+), there are no contants...or are there?

    Why This Trick Works
    In lua, withinin a script, if there are two variables of the same name, one local and one global, lua will prefer the local.

    How This Trick Works
    By setting a local variable in your module then placing it in global scope with the same name, you are effetively (with regards to your script's scope) creating a constant (even though the global version of it can be altered). The global version is basically a mirror of the local variable which can be affected by the local but cannot,in turn, affect it. Even though the global value can be altered by the client, the local is unaffected. This means that if you check input values of global functions against the variable name, only the local value is checked.


    Example:

    myscript.lua
    PHP Code:
    --set it in the global environment
    DEFAULT = 0;

    --
    create the local version of the global variable
    local 
    DEFAULT =DEFAULT;0;

    --
    check the user's input vs the actual variable value
    function checkDefault(nInput)
        print("Yout Input: "..tostring(nInput).." | Original variable value: "..tostring(DEFAULT))
    end 
    externalscript.lua

    PHP Code:
    --require the module
    require(myscript.lua)

    --
    firstcheck the values without trying to alter the DEFAULT variable
    checkDefault
    (DEFAULT);

    --
    now, try to alter it
    DEFAULT = 456;

    --
    check it again
    checkDefault
    (DEFAULT); 
    Last edited by Centauri Soldier; 09-22-2021, 02:27 AM.
    https://github.com/CentauriSoldier

  • #2
    Hi CentauriSoldier
    shoud it be "checkValues(DEFAULT)" instead of "checkDefault(DEFAULT)"?


    David

    Comment


    • #3
      When I look at this code in Lua 5.1 bytecode, I can't see DEFAULT being exported to the global scope in the myscript.lua: https://luac.nl/s/4edea155a68bc18e9b878ab1
      Do you mean to set it globally by writing
      Code:
      _G.DEFAULT = DEFAULT;
      in the original?

      Code:
      --mirror it in the global environment
      DEFAULT = DEFAULT;
      effectively copies slot 0 to slot 0, which has been assigned a name for a local variable.
      Code:
      MOVE 0 0
      Bas Groothedde
      Imagine Programming :: Blog

      AMS8 Plugins
      IMXLH Compiler

      Comment


      • #4
        Originally posted by daviz View Post
        Hi CentauriSoldier
        shoud it be "checkValues(DEFAULT)" instead of "checkDefault(DEFAULT)"?


        David
        Yes, I did, thanks for correcting that.

        When I look at this code in Lua 5.1 bytecode, I can't see DEFAULT being exported to the global scope in the myscript.lua
        Odd, it works when I do it lol. I wonder what is different.
        https://github.com/CentauriSoldier

        Comment


        • #5
          Okay, I figured it out. I had the declaration order wrong. It should be global declaration and then local delcaration. Obviously this trick is limited to numbers, booleans and other passed-by-value variables.

          Here's test code (you need to download CoG to test it or just require pot.lua).

          Code:
          --[[*
          @authors Centauri Soldier
          @copyright Public Domain
          @description
          <h2>pot</h2>
          <p>A logical potentiometer object. The client can set minimum and maximum values for the object, as well as rate of increase/decrease.
          Note: 'increase' and 'decrease' are logical terms referring to motion along a line based on the current direction. E.g., If a pot is
          alternating and descening, 'increase' would cause the positional value to be absolutely reduced, while 'decrease' would have the opposite
          affect.
          By default, values are clamped at min and max; however, if the object is set to be revolving (or alternating), any values which exceed the minimum or maximum
          boundaries, are carried over. For example, imagine a pot is set to have a min value of 0 and a max of 100. Then, imagine its position is set to 120.
          If revolving, it would have a final positional value of 19; if alternating it would have a final positional value of 80 and, if neither, its final positional
          value would be 100.</p>
          @license <p>The Unlicense<br>
          <br>
          @moduleid pot
          @version 1.2
          @versionhistory
          <ul>
          <li>
          <b>1.3</b>
          <br>
          <p>Added serialization and deserialization methods.</p>
          </li>
          <li>
          <b>1.2</b>
          <br>
          <p>Fixed a bug in the revolution mechanism.</p>
          <p>Added the ability which allows the potentiometer to be continuous in a revolving or alternating manner.</p>
          </li>
          <li>
          <b>1.1</b>
          <br>
          <p>Added the option for the potentiometer to be continuous in a revolving manner.</p>
          </li>
          <li>
          <b>1.0</b>
          <br>
          <p>Created the module.</p>
          </li>
          </ul>
          @website https://github.com/CentauriSoldier
          *]]
          local tPots = {};
          local pot;
          
          --make these publicy available
          POT_CONTINUITY_NONE = 0;
          POT_CONTINUITY_REVOLVE = 1;
          POT_CONTINUITY_ALT = 2;
          
          --now localize them
          local POT_CONTINUITY_NONE = POT_CONTINUITY_NONE;
          local POT_CONTINUITY_REVOLVE = POT_CONTINUITY_REVOLVE;
          local POT_CONTINUITY_ALT = POT_CONTINUITY_ALT;
          
          
          
          local class = class;
          local serialize = serialize;
          local deserialize = deserialize;
          local math = math;
          
          
          local function continuityIsValid(nVal)
          print("printing the local value of POT_CONTINUITY_NONE: "..POT_CONTINUITY_NONE)
          return type(nVal) == "number" and
          (nVal == POT_CONTINUITY_NONE or
          nVal == POT_CONTINUITY_REVOLVE or
          nVal == POT_CONTINUITY_ALT);
          end
          
          
          local function clampMin(oPot)
          
          if (oPot.min >= oPot.max) then
          oPot.min = oPot.max - 1;
          end
          
          end
          
          local function clampMax(oPot)
          
          if (oPot.max <= oPot.min) then
          oPot.max = oPot.min + 1;
          end
          
          end
          
          --this is a placeholder so clampPosMin can call clampPosMax
          local function clampPosMax()end
          
          local function clampPosMin(oPot)
          
          if (oPot.pos < oPot.min) then
          
          if (oPot.continuity == POT_CONTINUITY_REVOLVE) then
          oPot.pos = oPot.min + math.abs(oPot.max - math.abs(-oPot.pos + 1));
          clampPosMin(oPot);
          
          elseif (oPot.continuity == POT_CONTINUITY_ALT) then
          oPot.pos = oPot.min + (oPot.min - oPot.pos);
          oPot.toggleAlternator = true;
          clampPosMax(oPot);
          
          else
          oPot.pos = oPot.min;
          
          end
          
          else
          
          --check if the alternator needs toggled
          if (oPot.toggleAlternator) then
          oPot.alternator = oPot.alternator * -1;
          oPot.toggleAlternator = false;
          end
          
          end
          
          end
          
          local function clampPosMax(oPot)
          
          if (oPot.pos > oPot.max) then
          
          if (oPot.continuity == POT_CONTINUITY_REVOLVE) then
          oPot.pos = oPot.pos - math.abs(oPot.max - oPot.min + 1);
          clampPosMax(oPot);
          
          elseif (oPot.continuity == POT_CONTINUITY_ALT) then
          oPot.pos = oPot.max - (oPot.pos - oPot.max);
          oPot.toggleAlternator = true;
          clampPosMin(oPot);
          
          else
          oPot.pos = oPot.max;
          
          end
          
          else
          
          --check if the alternator needs toggled
          if (oPot.toggleAlternator) then
          oPot.alternator = oPot.alternator * -1;
          oPot.toggleAlternator = false;
          end
          
          end
          
          end
          
          local function clampRate(oPot)
          local nVariance = oPot.max - oPot.min;
          
          if (math.abs(oPot.rate) > math.abs(nVariance)) then
          oPot.rate = nVariance;
          end
          
          end
          
          
          
          pot = class "pot" {
          
          __construct = function(this, nMin, nMax, nPos, nRate, nContinuity)
          tPots[this] = {
          alternator = 1,
          continuity = continuityIsValid(nContinuity) and nContinuity or POT_CONTINUITY_NONE,
          min = 0,
          max = 100,
          pos = 0,
          toggleAlternator = false,
          rate = 1,
          };
          
          local oPot = tPots[this];
          
          --set the min
          if (type(nMin) == "number") then
          oPot.min = nMin;
          end
          
          --set the max
          if (type(nMax) == "number") then
          oPot.max = nMax;
          clampMax(oPot);
          end
          
          --set the position
          if (type(nPos) == "number") then
          oPot.pos = nPos;
          clampPosMin(oPot);
          clampPosMax(oPot);
          end
          
          --set the rate
          if (type(nRate) == "number") then
          oPot.rate = nRate;
          clampRate(oPot);
          end
          
          end,
          
          
          adjust = function(this, nValue)
          local oPot = tPots[this];
          local nAmount = oPot.rate;
          
          --allow correct input
          if (type(nValue) == "number") then
          nAmount = nValue;
          end
          
          --set the value
          oPot.pos = oPot.pos + nAmount;
          
          --clamp it
          clampPosMin(oPot);
          clampPosMax(oPot);
          
          return this;
          end,
          
          
          decrease = function(this, nTimes)
          local oPot = tPots[this];
          local nCount = 1;
          
          if (type(nTimes) == "number") then
          nCount = nTimes;
          end
          
          --set the value
          oPot.pos = oPot.pos - oPot.rate * nCount * oPot.alternator;
          
          --clamp it
          if (oPot.continuity == POT_CONTINUITY_ALT) then
          clampPosMax(oPot);
          end
          
          clampPosMin(oPot);
          
          return this;
          end,
          
          deserialize = function(this, sData)
          local oPot = tPots[this];
          local tData = deserialize.table(sData);
          
          oPot.alternator = tData.alternator;
          oPot.continuity = tData.continuity;
          oPot.min = tData.min;
          oPot.max = tData.max;
          oPot.pos = tData.pos;
          oPot.toggleAlternator = tData.toggleAlternator;
          oPot.rate = tData.rate;
          
          return this;
          end,
          
          destroy = function(this)
          table.remove(tPots, this);
          this = nil;
          end,
          
          getMax = function(this)
          return tPots[this].max;
          end,
          
          getMin = function(this)
          return tPots[this].min;
          end,
          
          getPos = function(this)
          return tPots[this].pos;
          end,
          
          getRate = function(this)
          return tPots[this].rate;
          end,
          
          getContinuity = function(this)
          return tPots[this].continuity;
          end,
          
          increase = function(this, nTimes)
          local oPot = tPots[this];
          local nCount = 1;
          
          if (type(nTimes) == "number") then
          nCount = nTimes;
          end
          
          --set the value
          oPot.pos = oPot.pos + oPot.rate * nCount * oPot.alternator;
          
          --clamp it
          if (oPot.continuity == POT_CONTINUITY_ALT) then
          clampPosMin(oPot);
          end
          
          clampPosMax(oPot);
          
          return this;
          end,
          
          isAlternating = function(this)
          return tPots[this].continuity == POT_CONTINUITY_ALT;
          end,
          
          isAscending = function(this)
          return (
          (tPots[this].continuity == POT_CONTINUITY_REVOLVE or
          tPots[this].continuity == POT_CONTINUITY_ALT) and
          tPots[this].alternator == 1
          );
          end,
          
          isDescending = function(this)
          return (
          (tPots[this].continuity == POT_CONTINUITY_REVOLVE or
          tPots[this].continuity == POT_CONTINUITY_ALT) and
          tPots[this].alternator == -1
          );
          end,
          
          isRevolving = function(this)
          return tPots[this].revolving == POT_CONTINUITY_REVOLVE;
          end,
          
          --[[!
          @desc Serializes the object's data.
          @func pot.serialize
          @module pot
          @param bDefer boolean Whether or not to return a table of data to be serialized instead of a serialize string (if deferring serializtion to another object).
          @ret sData StringOrTable The data returned as a serialized table (string) or a table is the defer option is set to true.
          !]]
          serialize = function(this, bDefer)
          local oPot = tPots[this];
          local tData = {
          alternator = oPot.alternator,
          continuity = oPot.continuity,
          min = oPot.min,
          max = oPot.max,
          pos = oPot.pos,
          toggleAlternator = oPot.toggleAlternator,
          rate = oPot.rate,
          };
          
          if (not bDefer) then
          tData = serialize.table(tData);
          end
          
          return tData;
          end,
          
          setMax = function(this, nValue)
          local oPot = tPots[this];
          
          if (type(nValue) == "number") then
          oPot.max = nValue;
          clampMax(oPot);
          clampPosMax(oPot)
          end
          
          return this;
          end,
          
          setMin = function(this, nValue)
          local oPot = tPots[this];
          
          if (type(nValue) == "number") then
          oPot.min = nValue;
          clampMin(oPot);
          clampPosMin(oPot)
          end
          
          return this;
          end,
          
          setPos = function(this, nValue)
          local oPot = tPots[this];
          
          if (type(nValue) == "number") then
          oPot.pos = nValue;
          clampPosMin(oPot);
          clampPosMax(oPot);
          end
          
          return this;
          end,
          
          setRate = function(this, nValue)
          local oPot = tPots[this];
          
          if (type(nValue) == "number") then
          oPot.rate = math.abs(nValue);
          clampRate(oPot);
          end
          
          return this;
          end,
          
          setContinuity = function(this, nContinuity)
          local oPot = tPots[this];
          oPot.continuity = continuityIsValid(nContinuity) and nContinuity or oPot.continuity;
          print("Continuity Set to :"..oPot.continuity.." from input value of: "..nContinuity);
          return this;
          end,
          
          };
          
          return pot;
          Code:
          --package.path = package.path..";LuaEx\\?.lua;..\\?.lua;?.lua";
          package.path = package.path..";CoG\\?.lua";
          require("init");
          
          
          
          local t = pot(0, 100, 0, 1, 0);
          print("Continuity is currently at: "..t:getContinuity())
          
          print("The global value of POT_CONTINUITY_NONE is: "..POT_CONTINUITY_NONE);
          POT_CONTINUITY_NONE = 66;
          print("Changing the global value of POT_CONTINUITY_NONE to: "..POT_CONTINUITY_NONE);
          
          print("Attempting to set the continuity to: "..POT_CONTINUITY_NONE)
          t:setContinuity(POT_CONTINUITY_NONE);
          
          for x = 1, 101 do
          t:increase();
          end
          
          print("Continuity now at: "..t:getContinuity())

          Here's the results of my test.

          Code:
          printing the local value of POT_CONTINUITY_NONE: 0
          Continuity is currently at: 0
          The global value of POT_CONTINUITY_NONE is: 0
          Changing the global value of POT_CONTINUITY_NONE to: 66
          Attempting to set the continuity to: 66
          printing the local value of POT_CONTINUITY_NONE: 0
          Continuity Set to :0 from input value of: 66
          Continuity now at: 0
          
          [Finished in 0.307s]
          Last edited by Centauri Soldier; 09-22-2021, 02:33 AM.
          https://github.com/CentauriSoldier

          Comment


          • #6
            Originally posted by Centauri Soldier View Post
            Okay, I figured it out. I had the declaration order wrong. It should be global declaration and then local delcaration. Obviously this trick is limited to numbers, booleans and other passed-by-value variables.
            I figured you were going for that, this technique is called 'Variable Shadowing' [1] and is usually warned for by i.e. clang and gcc as it prevents access to certain variables. For example, in C++ this might occur if a method's argument name is the same as a attribute name of the class. In Lua it's a useful technique in your use-case. The bytecode generated with the right order: https://luac.nl/s/4f4dd8254f8bc18e9b878ab1 - I should note that the MOVE 0 0 instruction I mentioned earlier is only for specifying upvalues for the closure so it made no sense to mention it on my part. I forgot Lua 5.1 did it that way

            It's basically the same technique we use when we want to localize a variable to make its lookup faster:
            Code:
            local print, type = print, type;
            for i = 1, 10000 do
              print(type(i));
            end
            Here we shadow _G.print and _G.type with local variables print and type. Local access or upvalue access is faster than a table lookup, but only slightly.

            [1] https://en.wikipedia.org/wiki/Variable_shadowing
            Bas Groothedde
            Imagine Programming :: Blog

            AMS8 Plugins
            IMXLH Compiler

            Comment

            Working...
            X