[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);
}