1 ----------------------------------------------------------------------
2 -- Springs -- Serialization with Pluto for Rings
3 ----------------------------------------------------------------------
5 -- Copyright (c) 2008, Fabien Fleutot <metalua@gmail.com>.
7 -- This software is released under the MIT Licence, see licence.txt
10 ----------------------------------------------------------------------
12 -- This module is an improved version of
13 -- Lua Rings <http://www.keplerproject.org/rings/>:
14 -- Lua Rings lets users create independant Lua states, and provides
15 -- limited communication means (sending commands as strings,
16 -- receiving as results strings, numbers, booleans).
17 -- Springs uses Pluto <http://luaforge.net/projects/pluto/> to
18 -- let both states communicate arbitrary data, by serializing them
21 -- This module is designed to avoid touching Rings and Pluto, in order
22 -- to ease maintenance. This requires a bit of monkey-patching here
28 -- * new states are created with 'springs.new()' as before
30 -- * method :dostring() still works as usual
32 -- * method :pcall(f, arg1, ..., argn) works as standard function
33 -- pcall(), except that execution occurs in the sub-state.
34 -- Moreover, 'f' can also be a string, rather tahn a function.
35 -- If it's a string, this string must eval to a function in
36 -- the substate's context. This allows to pass standard functions
37 -- easily. For instance:
38 -- > r:pcall('table.concat', {'a', 'b', 'c'}, ',')
40 -- * method :call() is similar to :pcall(), except that in case of
41 -- error, it actually throws the error in the sender's context.
42 -- Therefore, it doesn't return a success status as does pcall().
44 -- > assert('xxx' == r:call('string.rep', 'x', 3))
46 -- Springs requires modules Rings and Pluto to be accessible to
47 -- require() in order to work.
49 ----------------------------------------------------------------------
54 ----------------------------------------------------------------------
55 -- Make the name match the module, grab state __index metamethod.
56 -- We need to use the debug() API, since there is a __metatable
57 -- metamethod to prevent metatable retrieval.
58 -- Change the __NAME__ if you want to rename the module!
59 ----------------------------------------------------------------------
60 local __NAME__ = 'springs'
62 local ring_index = debug.getregistry()['state metatable'].__index
63 getfenv()[__NAME__] = rings
65 ----------------------------------------------------------------------
66 -- Permanent tables for Pluto's persist() and unpersist() functions.
68 ----------------------------------------------------------------------
72 ----------------------------------------------------------------------
73 -- For springs to work, the newly created state must load springs, so
74 -- that it has the 'pcall_receive' function needed to answer :call()
76 ----------------------------------------------------------------------
77 local original_rings_new = rings.new
79 local r = original_rings_new ()
80 r:dostring (string.format ("require %q", __NAME__))
84 ----------------------------------------------------------------------
85 -- Serialize, send the request to the child state,
86 -- deserialize and return the result
87 ----------------------------------------------------------------------
88 function ring_index:pcall (f, ...)
90 local type_f = type(f)
91 if type_f ~= 'string' and type_f ~= 'function' then
92 error "Springs can only call functions and strings"
95 -------------------------------------------------------------------
96 -- pack and send msg, get response msg
97 -------------------------------------------------------------------
98 local data = { f, ... }
99 local msg_snd = pluto.persist (rings.p_perms, data)
101 self:dostring (string.format ("return rings.pcall_receive %q", msg_snd))
103 -------------------------------------------------------------------
104 -- Upon success, unpack and return results.
105 -- Upon failure, msg_rcv is an error message
106 -------------------------------------------------------------------
107 if st then return unpack(pluto.unpersist (rings.u_perms, msg_rcv))
108 else return st, msg_rcv end
111 ----------------------------------------------------------------------
112 -- Similar to pcall(), but if the result is an error, this error
113 -- is actually thrown *in the sender's context*.
114 ----------------------------------------------------------------------
115 function ring_index:call (f, ...)
116 local results = { self:pcall(f, ...) }
117 if results[1] then return select(2, unpack(results))
118 else error(results[2]) end
121 ----------------------------------------------------------------------
122 -- Receive a request from the master state, deserialize it, do it,
123 -- serialize the result.
124 -- Format of the message, once unpersisted:
125 -- * either a function and its arguments;
126 -- * or a string, which must eval to a function, and its arguments
127 ----------------------------------------------------------------------
128 function rings.pcall_receive (rcv_msg)
130 local data = pluto.unpersist (rings.u_perms, rcv_msg)
131 assert (type(data)=='table', "illegal springs message")
133 -------------------------------------------------------------------
134 -- If the function is a string, the string is evaluated in
135 -- the context of the sub-state. This is an easy way to
136 -- pass reference to global functions.
137 -------------------------------------------------------------------
138 if type(data[1]) == 'string' then
139 local f, msg = loadstring ('return '..data[1])
140 if not f then result = { false, msg } else
143 if f then data[1] = f else result = { false, f } end
147 -------------------------------------------------------------------
148 -- result might already have been set by a failure to load or eval
149 -- a string passed as first argument.
150 -------------------------------------------------------------------
151 if not result then result = { pcall (unpack (data)) } end
153 -------------------------------------------------------------------
154 -- Format of the result: a table, with as first element a boolean
155 -- indicating the success status (true ==> the evaluation went
156 -- successfully), then all the results of the evaluation.
157 -------------------------------------------------------------------
158 return pluto.persist (rings.p_perms, result)