1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
-- Lua implementation of LEAP (LLSD Event API Plugin) protocol
--
-- This module supports Lua scripts run by the Second Life viewer.
--
-- LEAP protocol passes LLSD objects, converted to/from Lua tables, in both
-- directions. A typical LLSD object is a map containing keys 'pump' and
-- 'data'.
--
-- The viewer's Lua post_to(pump, data) function posts 'data' to the
-- LLEventPump 'pump'. This is typically used to engage an LLEventAPI method.
--
-- Similarly, the viewer gives each Lua script its own LLEventPump with a
-- unique name. That name is returned by get_event_pumps(). Every event
-- received on that LLEventPump is queued for retrieval by get_event_next(),
-- which returns (pump, data): the name of the LLEventPump on which the event
-- was received and the received event data. When the queue is empty,
-- get_event_next() blocks the calling Lua script until the next event is
-- received.
local leap = {}
-- _reply: string name of reply LLEventPump. Any events the viewer posts to
-- this pump will be queued for get_event_next(). We usually specify it as the
-- reply pump for requests to internal viewer services.
-- _command: string name of command LLEventPump. post_to(_command, ...)
-- engages LLLeapListener operations such as listening on a specified other
-- LLEventPump, etc.
leap._reply, leap._command = get_event_pumps()
-- Dict of features added to the LEAP protocol since baseline implementation.
-- Before engaging a new feature that might break an older viewer, we can
-- check for the presence of that feature key. This table is solely about the
-- LEAP protocol itself, the way we communicate with the viewer. To discover
-- whether a given listener exists, or supports a particular operation, use
-- _command's "getAPI" operation.
-- For Lua, _command's "getFeatures" operation suffices?
-- leap._features = {}
-- Each outstanding request() or generate() call has a corresponding
-- WaitForReqid object (later in this module) to handle the
-- response(s). If an incoming event contains an echoed ["reqid"] key,
-- we can look up the appropriate WaitForReqid object more efficiently
-- in a dict than by tossing such objects into the usual waitfors list.
-- Note: the ["reqid"] must be unique, otherwise we could end up
-- replacing an earlier WaitForReqid object in self.pending with a
-- later one. That means that no incoming event will ever be given to
-- the old WaitForReqid object. Any coroutine waiting on the discarded
-- WaitForReqid object would therefore wait forever.
leap._pending = {}
-- Our consumer will instantiate some number of WaitFor subclass objects.
-- As these are traversed in descending priority order, we must keep
-- them in a list.
leap._waitfors = {}
-- It has been suggested that we should use UUIDs as ["reqid"] values,
-- since UUIDs are guaranteed unique. However, as the "namespace" for
-- ["reqid"] values is our very own _reply pump, we can get away with
-- an integer.
leap._reqid = 0
-- get the name of the reply pump
function leap.replypump()
return leap._reply
end
-- get the name of the command pump
function leap.cmdpump()
return leap._command
end
-- Fire and forget. Send the specified request LLSD, expecting no reply.
-- In fact, should the request produce an eventual reply, it will be
-- treated as an unsolicited event.
--
-- See also request(), generate().
function leap.send(pump, data, reqid)
local data = data
if type(data) == 'table' then
data = table.clone(data)
data['reply'] = leap._reply
if reqid ~= nil then
data['reqid'] = reqid
end
end
post_to(pump, data)
end
-- Send the specified request LLSD, expecting exactly one reply. Block
-- the calling coroutine until we receive that reply.
--
-- Every request() (or generate()) LLSD block we send will get stamped
-- with a distinct ["reqid"] value. The requested event API must echo the
-- same ["reqid"] field in each reply associated with that request. This way
-- we can correctly dispatch interleaved replies from different requests.
--
-- If the desired event API doesn't support the ["reqid"] echo convention,
-- you should use send() instead -- since request() or generate() would
-- wait forever for a reply stamped with that ["reqid"] -- and intercept
-- any replies using WaitFor.
--
-- Unless the request data already contains a ["reply"] key, we insert
-- reply=self.replypump to try to ensure that the expected reply will be
-- returned over the socket.
function leap.request(pump, data)
local reqid = leap._requestSetup(pump, data)
local ok, response = pcall(leap._pending[reqid].wait)
-- kill off temporary WaitForReqid object, even if error
leap._pending[reqid] = nil
if ok then
return response
else
error(response)
end
end
-- common setup code shared by request() and generate()
function leap._requestSetup(pump, data)
-- invent a new, unique reqid
local reqid = leap._reqid
leap._reqid += 1
-- Instantiate a new WaitForReqid object. The priority is irrelevant
-- because, unlike the WaitFor base class, WaitForReqid does not
-- self-register on our leap._waitfors list. Instead, capture the new
-- WaitForReqid object in leap._pending so _dispatch() can find it.
leap._pending[reqid] = WaitForReqid.new(reqid)
-- Pass reqid to send() to stamp it into (a copy of) the request data.
leap.send(pump, data, reqid)
return reqid
end
-- Send the specified request LLSD, expecting an arbitrary number of replies.
-- Each one is yielded on receipt. If you omit checklast, this is an infinite
-- generator; it's up to the caller to recognize when the last reply has been
-- received, and stop resuming for more.
--
-- If you pass checklast=<callable accepting(event)>, each response event is
-- passed to that callable (after the yield). When the callable returns
-- True, the generator terminates in the usual way.
--
-- See request() remarks about ["reqid"].
function leap.generate(pump, data, checklast)
-- Invent a new, unique reqid. Arrange to handle incoming events
-- bearing that reqid. Stamp the outbound request with that reqid, and
-- send it.
local reqid = leap._requestSetup(pump, data)
local ok, response
repeat
ok, response = pcall(leap._pending[reqid].wait)
if not ok then
break
end
coroutine.yield(response)
until checklast and checklast(response)
-- If we break the above loop, whether or not due to error, clean up.
leap._pending[reqid] = nil
if not ok then
error(response)
end
end
-- Kick off response processing. The calling script must create and resume one
-- or more coroutines to perform viewer requests using send(), request() or
-- generate() before calling this function to handle responses.
--
-- While waiting for responses from the viewer, the C++ coroutine running the
-- calling Lua script is blocked: no other Lua coroutine is running.
function leap.process()
local ok, pump, data
while true do
ok, pump, data = pcall(get_event_next)
if not ok then
break
end
leap._dispatch(pump, data)
end
-- we're done: clean up all pending coroutines
for i, waitfor in pairs(leap._pending) do
waitfor._exception(pump)
end
for i, waitfor in pairs(leap._waitfors) do
waitfor._exception(pump)
end
end
-- Route incoming (pump, data) event to the appropriate waiting coroutine.
function leap._dispatch(pump, data)
local reqid = data['reqid']
-- if the response has no 'reqid', it's not from request() or generate()
if reqid == nil then
return leap._unsolicited(pump, data)
end
-- have reqid; do we have a WaitForReqid?
local waitfor = leap._pending[reqid]
if waitfor == nil then
return leap._unsolicited(pump, data)
end
-- found the right WaitForReqid object, let it handle the event
data['reqid'] = nil
waitfor._handle(pump, data)
end
-- Handle an incoming (pump, data) event with no recognizable ['reqid']
function leap._unsolicited(pump, data)
-- we maintain waitfors in descending priority order, so the first waitfor
-- to claim this event is the one with the highest priority
for i, waitfor in pairs(leap._waitfors) do
if waitfor._handle(pump, data) then
return
end
end
print_debug('_unsolicited(', pump, ', ', data, ') discarding unclaimed event')
end
-- called by WaitFor.enable()
function leap._registerWaitFor(waitfor)
table.insert(leap._waitfors, waitfor)
-- keep waitfors sorted in descending order of specified priority
table.sort(leap._waitfors,
function (lhs, rhs) return lhs.priority > rhs.priority end)
end
-- called by WaitFor.disable()
function leap._unregisterWaitFor(waitfor)
for i, w in pairs(leap._waitfors) do
if w == waitfor then
leap._waitfors[i] = nil
break
end
end
end
-- ******************************************************************************
-- WaitFor and friends
-- ******************************************************************************
-- An unsolicited event is handled by the highest-priority WaitFor subclass
-- object willing to accept it. If no such object is found, the unsolicited
-- event is discarded.
--
-- - First, instantiate a WaitFor subclass object to register its interest in
-- some incoming event(s). WaitFor instances are self-registering; merely
-- instantiating the object suffices.
-- - Any coroutine may call a given WaitFor object's wait() method. This blocks
-- the calling coroutine until a suitable event arrives.
-- - WaitFor's constructor accepts a float priority. Every incoming event
-- (other than those claimed by request() or generate()) is passed to each
-- extant WaitFor.filter() method in descending priority order. The first
-- such filter() to return nontrivial data claims that event.
-- - At that point, the blocked wait() call on that WaitFor object returns the
-- item returned by filter().
-- - WaitFor contains a queue. Multiple arriving events claimed by that WaitFor
-- object's filter() method are added to the queue. Naturally, until the
-- queue is empty, calling wait() immediately returns the front entry.
--
-- It's reasonable to instantiate a WaitFor subclass whose filter() method
-- unconditionally returns the incoming event, and whose priority places it
-- last in the list. This object will enqueue every unsolicited event left
-- unclaimed by other WaitFor subclass objects.
--
-- It's not strictly necessary to associate a WaitFor object with exactly one
-- coroutine. You might have multiple "worker" coroutines drawing from the same
-- WaitFor object, useful if the work being done per event might itself involve
-- "blocking" operations. Or a given coroutine might sample a number of WaitFor
-- objects in round-robin fashion... etc. etc. Nonetheless, it's
-- straightforward to designate one coroutine for each WaitFor object.
leap.WaitFor = {}
function leap.WaitFor:new()
obj = setmetatable({}, self)
self.__index = self
self._first = 0
self._last = -1
self._queue = {}
return obj
end
-- Check if the queue is empty
function Queue:IsEmpty()
return self._first > self._last
end
return leap
|