---------------------------------------------------------------------- -- Springs -- Serialization with Pluto for Rings ---------------------------------------------------------------------- -- -- Copyright (c) 2008, Fabien Fleutot . -- -- This software is released under the MIT Licence, see licence.txt -- for details. -- ---------------------------------------------------------------------- -- -- This module is an improved version of -- Lua Rings : -- Lua Rings lets users create independant Lua states, and provides -- limited communication means (sending commands as strings, -- receiving as results strings, numbers, booleans). -- Springs uses Pluto to -- let both states communicate arbitrary data, by serializing them -- as strings. -- -- This module is designed to avoid touching Rings and Pluto, in order -- to ease maintenance. This requires a bit of monkey-patching here -- and there. -- -- API -- --- -- -- * new states are created with 'springs.new()' as before -- -- * method :dostring() still works as usual -- -- * method :pcall(f, arg1, ..., argn) works as standard function -- pcall(), except that execution occurs in the sub-state. -- Moreover, 'f' can also be a string, rather tahn a function. -- If it's a string, this string must eval to a function in -- the substate's context. This allows to pass standard functions -- easily. For instance: -- > r:pcall('table.concat', {'a', 'b', 'c'}, ',') -- -- * method :call() is similar to :pcall(), except that in case of -- error, it actually throws the error in the sender's context. -- Therefore, it doesn't return a success status as does pcall(). -- For instance: -- > assert('xxx' == r:call('string.rep', 'x', 3)) -- -- Springs requires modules Rings and Pluto to be accessible to -- require() in order to work. -- ---------------------------------------------------------------------- require 'pluto' require 'rings' ---------------------------------------------------------------------- -- Make the name match the module, grab state __index metamethod. -- We need to use the debug() API, since there is a __metatable -- metamethod to prevent metatable retrieval. -- Change the __NAME__ if you want to rename the module! ---------------------------------------------------------------------- local __NAME__ = 'springs' local rings = rings local ring_index = debug.getregistry()['state metatable'].__index getfenv()[__NAME__] = rings ---------------------------------------------------------------------- -- Permanent tables for Pluto's persist() and unpersist() functions. -- Unused for now. ---------------------------------------------------------------------- rings.p_perms = { } rings.u_perms = { } ---------------------------------------------------------------------- -- For springs to work, the newly created state must load springs, so -- that it has the 'pcall_receive' function needed to answer :call() -- and :pcall(). ---------------------------------------------------------------------- local original_rings_new = rings.new function rings.new () local r = original_rings_new () r:dostring (string.format ("require %q", __NAME__)) return r end ---------------------------------------------------------------------- -- Serialize, send the request to the child state, -- deserialize and return the result ---------------------------------------------------------------------- function ring_index:pcall (f, ...) local type_f = type(f) if type_f ~= 'string' and type_f ~= 'function' then error "Springs can only call functions and strings" end ------------------------------------------------------------------- -- pack and send msg, get response msg ------------------------------------------------------------------- local data = { f, ... } local msg_snd = pluto.persist (rings.p_perms, data) local st, msg_rcv = self:dostring (string.format ("return rings.pcall_receive %q", msg_snd)) ------------------------------------------------------------------- -- Upon success, unpack and return results. -- Upon failure, msg_rcv is an error message ------------------------------------------------------------------- if st then return unpack(pluto.unpersist (rings.u_perms, msg_rcv)) else return st, msg_rcv end end ---------------------------------------------------------------------- -- Similar to pcall(), but if the result is an error, this error -- is actually thrown *in the sender's context*. ---------------------------------------------------------------------- function ring_index:call (f, ...) local results = { self:pcall(f, ...) } if results[1] then return select(2, unpack(results)) else error(results[2]) end end ---------------------------------------------------------------------- -- Receive a request from the master state, deserialize it, do it, -- serialize the result. -- Format of the message, once unpersisted: -- * either a function and its arguments; -- * or a string, which must eval to a function, and its arguments ---------------------------------------------------------------------- function rings.pcall_receive (rcv_msg) local result local data = pluto.unpersist (rings.u_perms, rcv_msg) assert (type(data)=='table', "illegal springs message") ------------------------------------------------------------------- -- If the function is a string, the string is evaluated in -- the context of the sub-state. This is an easy way to -- pass reference to global functions. ------------------------------------------------------------------- if type(data[1]) == 'string' then local f, msg = loadstring ('return '..data[1]) if not f then result = { false, msg } else local status status, f = pcall(f) if f then data[1] = f else result = { false, f } end end end ------------------------------------------------------------------- -- result might already have been set by a failure to load or eval -- a string passed as first argument. ------------------------------------------------------------------- if not result then result = { pcall (unpack (data)) } end ------------------------------------------------------------------- -- Format of the result: a table, with as first element a boolean -- indicating the success status (true ==> the evaluation went -- successfully), then all the results of the evaluation. ------------------------------------------------------------------- return pluto.persist (rings.p_perms, result) end