[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Of Scripts, functions, threads & application integration...
- From: RLake@...
- Date: Sun, 25 Jan 2004 05:24:57 +0000
> One way to fix this would be to require each
script to have a unique 
> name. For example, if a script file is called "foo.lua",
then it might 
> define a function called "foo_getData1" instead of just
plain 
> "getData1". That way each script has its own version of
getData1, and 
> your app just needs to call the correct one depending on which script
is 
> being used at the time.
Another way to solve this is for each script to create
a table of
functions/methods. You can do this directly, or you
can manipulate
the environment table for the compiled script. I think
the first
solution is cleaner, and it is certainly easier to
write, but it
makes the scripts a little wordier.
Since you mentioned threads, I figured you might end
up with
re-entrant tools, so I have tried to allow for that
possibility.
It costs very little.
The protocol here is that a script file (or, if you
like, a tool
definition) *returns* a function whose return value
is the tool
represented as a table of tool-actions. Every time
this function is
called, it returns a new tool.
Now, there are two choices: the tools can be one-off's,
sort of
like objects, or they can be recyclable. Most of the
time, the
one-off implementation will be the best: in that case,
the function
returned by the script file is effectively the class
"new" operation
so there is no need to define "init". So
we can start with that
approach.
//// Warning: it is late and I didn't trying compiling
any of this
-- Private class members are possible simply by using
-- closures. But you have to be careful to implement
-- some sort of synchronisation system if you are
using
-- (OS-level) pre-emptive threading with mutable state.
-- This example just uses a constant, so there is
no problem
local hiddenInflation = 0.05
-- If you have naive script authors, the simplest
explanation
-- for the following two lines might just be "do
it like this":
return function(init_arg1, init_arg2)
  local tool = {}
  -- Now here we can put private instance methods.
There is
  -- no synchronisation issue as long as an instance
of the
  -- tool runs in a single thread
  local count, sum = 0, 0
 
  -- Now we can define the tool entry points,
again using
  -- a stylised syntax
  -- (I would have thought "receiveData"
a better name)
  function tool.getData(d)
    count, sum = count + 1, sum + d
  end
  -- The boss thinks that some data points are
more equal
  -- than others
  function tool.getBiasedData(d)
    count, sum = count + 20, sum + 20 *
d
  end
  -- done does not actually reset the tool, it
just returns
  -- the value. In this case, you could go on
adding data,
  -- but that is probably not good design unless
it is
  -- documented. Note that this tool is optimistic;
the boss
  -- might like that, too.
  function tool.done()
    print("The average of the data
set is " .. (sum / count) * (1 + hiddenInflation))
  end
  -- Now, the stylised end: we actually have
to return the
  -- object
  return tool
end
------------------------------------------
OK, now we need to load this tool file and stash it
somewhere.
To minimise the C code, we can do the grunt work in
Lua
---------- file runtool.lua
-- Change this to the tool suffix you prefer
local SUFFIX = ".tool"
-- Memoise is an incredibly useful function, so I
will repeat
-- it here. I load it in my standard library.
local function memoise(fn)
  return setmetatable({}, {
    __index = function(t, k)
      local rv = fn(k); t[k] = rv;
return rv
    end
  }
end
-- here is a cheap-o split function which simply skips
-- empty fields
local function split(delim, str)
  local fields = {n = 0}
  string.gsub(str, "([^"..delim.."]+)",
function(fld) table.insert(fields, fld) end)
  return fields
end
-- There must be a million versions of this one out
there, too.
local function mapreplace(fn, vec)
  for i = 1, getn(vec) do
    vec[i] = fn(vec[i])
  end
end
-- Should probably try harder to validate paths; all
this does
-- is get rid of leading and trailing spaces, and
make sure that
-- there is a / at the end and no // in the middle
local function improve(path)
  path = gsub(path, "^%s*(.-)%s*$",
"%1")
  return string.gsub(path, "/+", "/")
end
local path = mapreplace(improve, split(":",
os.getenv "TOOLPATH"))
 
-- given a toolname, find the first file which exists
-- and then try to compile it. If the compilation
-- fails or the file doesn't exist, generate an error.
-- otherwise, run the script and return the resulting
-- tooltable
local function pathsearch(tool)
  for i = 1, getn(path) do
    local fn = path[i] .. tool .. SUFFIX
    local f = io.open(fn, "r")
    if f then
      f:close()
      local loader = assert(loadfile(fn))
      return loader()
    end
  end
  -- If we fall through:
  error("Could not find tool '"..tool.."'")
end
-- and finally, we put all the pieces together in
a global
-- table:
TOOL = memoise(pathsearch)
-------------------------------------------------------------
Now, the C part is trivial, particularly if there
is no
initialisation argument. This function leaves the
tooltable
on the top of the stack: (or dies trying)
void tool_get(lua_State *L, const char *toolname)
{
  lua_getglobal(L, "TOOL");
  lua_pushstring(L, toolname);
  lua_gettable(L, -2);
}
This function feeds a value into the tool on the top
of the stack. If you do this in a tight loop, you
should
just look up the function once, but I'll leave that
as
an exercise. Also, some error checking would be a
good idea.
void tool_put(lua_State *L, lua_Number val) {
  lua_pushliteral(L, "getData");
  lua_gettable(L, -2);
  lua_pushnumber(L, val);
  lua_call(L, 1, 0);
}
This function calls the tool's done method and cleans
up:
void tool_done(lua_State *L) {
  lua_pushliteral(L, "done");
  lua_gettable(L, -2);
  lua_call(L, 0, 0);
  lua_pop(L, 1);
}