diff options
Diffstat (limited to '')
-rw-r--r-- | src/keeper.cpp | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/src/keeper.cpp b/src/keeper.cpp new file mode 100644 index 0000000..f56c50c --- /dev/null +++ b/src/keeper.cpp | |||
@@ -0,0 +1,884 @@ | |||
1 | /* | ||
2 | -- | ||
3 | -- KEEPER.CPP | ||
4 | -- | ||
5 | -- Keeper state logic | ||
6 | -- | ||
7 | -- This code is read in for each "keeper state", which are the hidden, inter- | ||
8 | -- mediate data stores used by Lanes inter-state communication objects. | ||
9 | -- | ||
10 | -- Author: Benoit Germain <bnt.germain@gmail.com> | ||
11 | -- | ||
12 | -- C implementation replacement of the original keeper.lua | ||
13 | -- | ||
14 | --[[ | ||
15 | =============================================================================== | ||
16 | |||
17 | Copyright (C) 2011-2024 Benoit Germain <bnt.germain@gmail.com> | ||
18 | |||
19 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
20 | of this software and associated documentation files (the "Software"), to deal | ||
21 | in the Software without restriction, including without limitation the rights | ||
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
23 | copies of the Software, and to permit persons to whom the Software is | ||
24 | furnished to do so, subject to the following conditions: | ||
25 | |||
26 | The above copyright notice and this permission notice shall be included in | ||
27 | all copies or substantial portions of the Software. | ||
28 | |||
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
35 | THE SOFTWARE. | ||
36 | |||
37 | =============================================================================== | ||
38 | ]]-- | ||
39 | */ | ||
40 | #include "keeper.h" | ||
41 | |||
42 | #include "compat.h" | ||
43 | #include "state.h" | ||
44 | #include "tools.h" | ||
45 | #include "uniquekey.h" | ||
46 | #include "universe.h" | ||
47 | |||
48 | #include <algorithm> | ||
49 | #include <cassert> | ||
50 | |||
51 | // ################################################################################### | ||
52 | // Keeper implementation | ||
53 | // ################################################################################### | ||
54 | |||
55 | class keeper_fifo | ||
56 | { | ||
57 | public: | ||
58 | |||
59 | int first{ 1 }; | ||
60 | int count{ 0 }; | ||
61 | int limit{ -1 }; | ||
62 | |||
63 | // a fifo full userdata has one uservalue, the table that holds the actual fifo contents | ||
64 | [[nodiscard]] static void* operator new([[maybe_unused]] size_t size_, lua_State* L) noexcept { return lua_newuserdatauv<keeper_fifo>(L, 1); } | ||
65 | // always embedded somewhere else or "in-place constructed" as a full userdata | ||
66 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
67 | static void operator delete([[maybe_unused]] void* p_, lua_State* L) { ASSERT_L(!"should never be called") }; | ||
68 | |||
69 | [[nodiscard]] static keeper_fifo* getPtr(lua_State* L, int idx_) | ||
70 | { | ||
71 | return lua_tofulluserdata<keeper_fifo>(L, idx_); | ||
72 | } | ||
73 | }; | ||
74 | |||
75 | static constexpr int CONTENTS_TABLE{ 1 }; | ||
76 | |||
77 | // ################################################################################################## | ||
78 | |||
79 | // replaces the fifo ud by its uservalue on the stack | ||
80 | [[nodiscard]] static keeper_fifo* prepare_fifo_access(lua_State* L, int idx_) | ||
81 | { | ||
82 | keeper_fifo* const fifo{ keeper_fifo::getPtr(L, idx_) }; | ||
83 | if (fifo != nullptr) | ||
84 | { | ||
85 | idx_ = lua_absindex(L, idx_); | ||
86 | STACK_GROW(L, 1); | ||
87 | // we can replace the fifo userdata in the stack without fear of it being GCed, there are other references around | ||
88 | lua_getiuservalue(L, idx_, CONTENTS_TABLE); | ||
89 | lua_replace(L, idx_); | ||
90 | } | ||
91 | return fifo; | ||
92 | } | ||
93 | |||
94 | // ################################################################################################## | ||
95 | |||
96 | // in: nothing | ||
97 | // out: { first = 1, count = 0, limit = -1} | ||
98 | [[nodiscard]] static keeper_fifo* fifo_new(lua_State* L) | ||
99 | { | ||
100 | STACK_GROW(L, 2); | ||
101 | STACK_CHECK_START_REL(L, 0); | ||
102 | keeper_fifo* const fifo{ new (L) keeper_fifo{} }; | ||
103 | STACK_CHECK(L, 1); | ||
104 | lua_newtable(L); | ||
105 | lua_setiuservalue(L, -2, CONTENTS_TABLE); | ||
106 | STACK_CHECK(L, 1); | ||
107 | return fifo; | ||
108 | } | ||
109 | |||
110 | // ################################################################################################## | ||
111 | |||
112 | // in: expect fifo ... on top of the stack | ||
113 | // out: nothing, removes all pushed values from the stack | ||
114 | static void fifo_push(lua_State* L, keeper_fifo* fifo_, int count_) | ||
115 | { | ||
116 | int const idx{ lua_gettop(L) - count_ }; | ||
117 | int const start{ fifo_->first + fifo_->count - 1 }; | ||
118 | // pop all additional arguments, storing them in the fifo | ||
119 | for (int i = count_; i >= 1; --i) | ||
120 | { | ||
121 | // store in the fifo the value at the top of the stack at the specified index, popping it from the stack | ||
122 | lua_rawseti(L, idx, start + i); | ||
123 | } | ||
124 | fifo_->count += count_; | ||
125 | } | ||
126 | |||
127 | // ################################################################################################## | ||
128 | |||
129 | // in: fifo | ||
130 | // out: ...|nothing | ||
131 | // expects exactly 1 value on the stack! | ||
132 | // currently only called with a count of 1, but this may change in the future | ||
133 | // function assumes that there is enough data in the fifo to satisfy the request | ||
134 | static void fifo_peek(lua_State* L, keeper_fifo* fifo_, int count_) | ||
135 | { | ||
136 | STACK_GROW(L, count_); | ||
137 | for (int i = 0; i < count_; ++i) | ||
138 | { | ||
139 | lua_rawgeti(L, 1, (fifo_->first + i)); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | // ################################################################################################## | ||
144 | |||
145 | // in: fifo | ||
146 | // out: remove the fifo from the stack, push as many items as required on the stack (function assumes they exist in sufficient number) | ||
147 | static void fifo_pop( lua_State* L, keeper_fifo* fifo_, int count_) | ||
148 | { | ||
149 | ASSERT_L(lua_istable(L, -1)); | ||
150 | int const fifo_idx{ lua_gettop(L) }; // ... fifotbl | ||
151 | // each iteration pushes a value on the stack! | ||
152 | STACK_GROW(L, count_ + 2); | ||
153 | // skip first item, we will push it last | ||
154 | for (int i = 1; i < count_; ++i) | ||
155 | { | ||
156 | int const at{ fifo_->first + i }; | ||
157 | // push item on the stack | ||
158 | lua_rawgeti(L, fifo_idx, at); // ... fifotbl val | ||
159 | // remove item from the fifo | ||
160 | lua_pushnil(L); // ... fifotbl val nil | ||
161 | lua_rawseti(L, fifo_idx, at); // ... fifotbl val | ||
162 | } | ||
163 | // now process first item | ||
164 | { | ||
165 | int const at{ fifo_->first }; | ||
166 | lua_rawgeti(L, fifo_idx, at); // ... fifotbl vals val | ||
167 | lua_pushnil(L); // ... fifotbl vals val nil | ||
168 | lua_rawseti(L, fifo_idx, at); // ... fifotbl vals val | ||
169 | lua_replace(L, fifo_idx); // ... vals | ||
170 | } | ||
171 | |||
172 | // avoid ever-growing indexes by resetting each time we detect the fifo is empty | ||
173 | { | ||
174 | int const new_count{ fifo_->count - count_ }; | ||
175 | fifo_->first = (new_count == 0) ? 1 : (fifo_->first + count_); | ||
176 | fifo_->count = new_count; | ||
177 | } | ||
178 | } | ||
179 | |||
180 | // ################################################################################################## | ||
181 | |||
182 | // in: linda_ud expected at stack slot idx | ||
183 | // out: fifos[ud] | ||
184 | // crc64/we of string "FIFOS_KEY" generated at http://www.nitrxgen.net/hashgen/ | ||
185 | static constexpr UniqueKey FIFOS_KEY{ 0xdce50bbc351cd465ull }; | ||
186 | static void push_table(lua_State* L, int idx_) | ||
187 | { | ||
188 | STACK_GROW(L, 5); | ||
189 | STACK_CHECK_START_REL(L, 0); | ||
190 | idx_ = lua_absindex(L, idx_); | ||
191 | FIFOS_KEY.pushValue(L); // ud fifos | ||
192 | lua_pushvalue(L, idx_); // ud fifos ud | ||
193 | lua_rawget(L, -2); // ud fifos fifos[ud] | ||
194 | STACK_CHECK(L, 2); | ||
195 | if (lua_isnil(L, -1)) | ||
196 | { | ||
197 | lua_pop(L, 1); // ud fifos | ||
198 | // add a new fifos table for this linda | ||
199 | lua_newtable(L); // ud fifos fifos[ud] | ||
200 | lua_pushvalue(L, idx_); // ud fifos fifos[ud] ud | ||
201 | lua_pushvalue(L, -2); // ud fifos fifos[ud] ud fifos[ud] | ||
202 | lua_rawset(L, -4); // ud fifos fifos[ud] | ||
203 | } | ||
204 | lua_remove(L, -2); // ud fifos[ud] | ||
205 | STACK_CHECK(L, 1); | ||
206 | } | ||
207 | |||
208 | // ################################################################################################## | ||
209 | |||
210 | int keeper_push_linda_storage(Universe* U, Dest L, void* ptr_, uintptr_t magic_) | ||
211 | { | ||
212 | Keeper* const K{ which_keeper(U->keepers, magic_) }; | ||
213 | Source const KL{ K ? K->L : nullptr }; | ||
214 | if (KL == nullptr) | ||
215 | return 0; | ||
216 | STACK_GROW(KL, 4); // KEEPER MAIN | ||
217 | STACK_CHECK_START_REL(KL, 0); | ||
218 | FIFOS_KEY.pushValue(KL); // fifos | ||
219 | lua_pushlightuserdata(KL, ptr_); // fifos ud | ||
220 | lua_rawget(KL, -2); // fifos storage | ||
221 | lua_remove(KL, -2); // storage | ||
222 | if (!lua_istable(KL, -1)) | ||
223 | { | ||
224 | lua_pop(KL, 1); // | ||
225 | STACK_CHECK(KL, 0); | ||
226 | return 0; | ||
227 | } | ||
228 | // move data from keeper to destination state | ||
229 | lua_pushnil(KL); // storage nil | ||
230 | STACK_GROW(L, 5); | ||
231 | STACK_CHECK_START_REL(L, 0); | ||
232 | lua_newtable(L); // out | ||
233 | while (lua_next(KL, -2)) // storage key fifo | ||
234 | { | ||
235 | keeper_fifo* fifo = prepare_fifo_access(KL, -1); // storage key fifotbl | ||
236 | lua_pushvalue(KL, -2); // storage key fifotbl key | ||
237 | std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key fifotbl // out key | ||
238 | STACK_CHECK(L, 2); | ||
239 | lua_newtable(L); // out key keyout | ||
240 | std::ignore = luaG_inter_move(U, KL, L, 1, LookupMode::FromKeeper); // storage key // out key keyout fifotbl | ||
241 | lua_pushinteger(L, fifo->first); // out key keyout fifotbl first | ||
242 | STACK_CHECK(L, 5); | ||
243 | lua_setfield(L, -3, "first"); // out key keyout fifotbl | ||
244 | lua_pushinteger(L, fifo->count); // out key keyout fifobtl count | ||
245 | STACK_CHECK(L, 5); | ||
246 | lua_setfield(L, -3, "count"); // out key keyout fifotbl | ||
247 | lua_pushinteger(L, fifo->limit); // out key keyout fifotbl limit | ||
248 | STACK_CHECK(L, 5); | ||
249 | lua_setfield(L, -3, "limit"); // out key keyout fifotbl | ||
250 | lua_setfield(L, -2, "fifo"); // out key keyout | ||
251 | lua_rawset(L, -3); // out | ||
252 | STACK_CHECK(L, 1); | ||
253 | } | ||
254 | STACK_CHECK(L, 1); | ||
255 | lua_pop(KL, 1); // | ||
256 | STACK_CHECK(KL, 0); | ||
257 | return 1; | ||
258 | } | ||
259 | |||
260 | // ################################################################################################## | ||
261 | |||
262 | // in: linda_ud | ||
263 | int keepercall_clear(lua_State* L) | ||
264 | { | ||
265 | STACK_GROW(L, 3); | ||
266 | STACK_CHECK_START_REL(L, 0); | ||
267 | FIFOS_KEY.pushValue(L); // ud fifos | ||
268 | lua_pushvalue(L, 1); // ud fifos ud | ||
269 | lua_pushnil(L); // ud fifos ud nil | ||
270 | lua_rawset(L, -3); // ud fifos | ||
271 | lua_pop(L, 1); // ud | ||
272 | STACK_CHECK(L, 0); | ||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | // ################################################################################################## | ||
277 | |||
278 | // in: linda_ud, key, ... | ||
279 | // out: true|false | ||
280 | int keepercall_send(lua_State* L) | ||
281 | { | ||
282 | int const n{ lua_gettop(L) - 2 }; | ||
283 | push_table(L, 1); // ud key ... fifos | ||
284 | // get the fifo associated to this key in this linda, create it if it doesn't exist | ||
285 | lua_pushvalue(L, 2); // ud key ... fifos key | ||
286 | lua_rawget(L, -2); // ud key ... fifos fifo | ||
287 | if( lua_isnil(L, -1)) | ||
288 | { | ||
289 | lua_pop(L, 1); // ud key ... fifos | ||
290 | std::ignore = fifo_new(L); // ud key ... fifos fifo | ||
291 | lua_pushvalue(L, 2); // ud key ... fifos fifo key | ||
292 | lua_pushvalue(L, -2); // ud key ... fifos fifo key fifo | ||
293 | lua_rawset(L, -4); // ud key ... fifos fifo | ||
294 | } | ||
295 | lua_remove(L, -2); // ud key ... fifo | ||
296 | keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) }; | ||
297 | if (fifo->limit >= 0 && fifo->count + n > fifo->limit) | ||
298 | { | ||
299 | lua_settop(L, 0); // | ||
300 | lua_pushboolean(L, 0); // false | ||
301 | } | ||
302 | else | ||
303 | { | ||
304 | fifo = prepare_fifo_access(L, -1); // ud fifotbl | ||
305 | lua_replace(L, 2); // ud fifotbl ... | ||
306 | fifo_push(L, fifo, n); // ud fifotbl | ||
307 | lua_settop(L, 0); // | ||
308 | lua_pushboolean(L, 1); // true | ||
309 | } | ||
310 | return 1; | ||
311 | } | ||
312 | |||
313 | // ################################################################################################## | ||
314 | |||
315 | // in: linda_ud, key [, key]? | ||
316 | // out: (key, val) or nothing | ||
317 | int keepercall_receive(lua_State* L) | ||
318 | { | ||
319 | int const top{ lua_gettop(L) }; | ||
320 | push_table(L, 1); // ud keys fifos | ||
321 | lua_replace(L, 1); // fifos keys | ||
322 | for (int i = 2; i <= top; ++i) | ||
323 | { | ||
324 | lua_pushvalue(L, i); // fifos keys key[i] | ||
325 | lua_rawget(L, 1); // fifos keys fifo | ||
326 | keeper_fifo* const fifo{ prepare_fifo_access(L, -1) }; // fifos keys fifotbl | ||
327 | if (fifo != nullptr && fifo->count > 0) | ||
328 | { | ||
329 | fifo_pop(L, fifo, 1); // fifos keys val | ||
330 | if (!lua_isnil(L, -1)) | ||
331 | { | ||
332 | lua_replace(L, 1); // val keys | ||
333 | lua_settop(L, i); // val keys key[i] | ||
334 | if (i != 2) | ||
335 | { | ||
336 | lua_replace(L, 2); // val key keys | ||
337 | lua_settop(L, 2); // val key | ||
338 | } | ||
339 | lua_insert(L, 1); // key, val | ||
340 | return 2; | ||
341 | } | ||
342 | } | ||
343 | lua_settop(L, top); // data keys | ||
344 | } | ||
345 | // nothing to receive | ||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | // ################################################################################################## | ||
350 | |||
351 | // in: linda_ud key mincount [maxcount] | ||
352 | int keepercall_receive_batched(lua_State* L) | ||
353 | { | ||
354 | int const min_count{ static_cast<int>(lua_tointeger(L, 3)) }; | ||
355 | if( min_count > 0) | ||
356 | { | ||
357 | int const max_count{ static_cast<int>(luaL_optinteger(L, 4, min_count)) }; | ||
358 | lua_settop(L, 2); // ud key | ||
359 | lua_insert(L, 1); // key ud | ||
360 | push_table(L, 2); // key ud fifos | ||
361 | lua_remove(L, 2); // key fifos | ||
362 | lua_pushvalue(L, 1); // key fifos key | ||
363 | lua_rawget(L, 2); // key fifos fifo | ||
364 | lua_remove(L, 2); // key fifo | ||
365 | keeper_fifo* const fifo{ prepare_fifo_access(L, 2) }; // key fifotbl | ||
366 | if( fifo != nullptr && fifo->count >= min_count) | ||
367 | { | ||
368 | fifo_pop(L, fifo, std::min( max_count, fifo->count)); // key ... | ||
369 | } | ||
370 | else | ||
371 | { | ||
372 | lua_settop(L, 0); // | ||
373 | } | ||
374 | return lua_gettop(L); | ||
375 | } | ||
376 | else | ||
377 | { | ||
378 | return 0; | ||
379 | } | ||
380 | } | ||
381 | |||
382 | // ################################################################################################## | ||
383 | |||
384 | // in: linda_ud key n | ||
385 | // out: true or nil | ||
386 | int keepercall_limit(lua_State* L) | ||
387 | { | ||
388 | int const limit{ static_cast<int>(lua_tointeger(L, 3)) }; | ||
389 | push_table(L, 1); // ud key n fifos | ||
390 | lua_replace(L, 1); // fifos key n | ||
391 | lua_pop(L, 1); // fifos key | ||
392 | lua_pushvalue(L, -1); // fifos key key | ||
393 | lua_rawget(L, -3); // fifos key fifo|nil | ||
394 | keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) }; | ||
395 | if (fifo == nullptr) | ||
396 | { // fifos key nil | ||
397 | lua_pop(L, 1); // fifos key | ||
398 | fifo = fifo_new(L); // fifos key fifo | ||
399 | lua_rawset(L, -3); // fifos | ||
400 | } | ||
401 | // remove any clutter on the stack | ||
402 | lua_settop(L, 0); | ||
403 | // return true if we decide that blocked threads waiting to write on that key should be awakened | ||
404 | // this is the case if we detect the key was full but it is no longer the case | ||
405 | if( | ||
406 | ((fifo->limit >= 0) && (fifo->count >= fifo->limit)) // the key was full if limited and count exceeded the previous limit | ||
407 | && ((limit < 0) || (fifo->count < limit)) // the key is not full if unlimited or count is lower than the new limit | ||
408 | ) | ||
409 | { | ||
410 | lua_pushboolean(L, 1); // true | ||
411 | } | ||
412 | // set the new limit | ||
413 | fifo->limit = limit; | ||
414 | // return 0 or 1 value | ||
415 | return lua_gettop(L); | ||
416 | } | ||
417 | |||
418 | // ################################################################################################## | ||
419 | |||
420 | // in: linda_ud key [[val] ...] | ||
421 | //out: true or nil | ||
422 | int keepercall_set(lua_State* L) | ||
423 | { | ||
424 | bool should_wake_writers{ false }; | ||
425 | STACK_GROW(L, 6); | ||
426 | |||
427 | // retrieve fifos associated with the linda | ||
428 | push_table(L, 1); // ud key [val [, ...]] fifos | ||
429 | lua_replace(L, 1); // fifos key [val [, ...]] | ||
430 | |||
431 | // make sure we have a value on the stack | ||
432 | if (lua_gettop(L) == 2) // fifos key | ||
433 | { | ||
434 | lua_pushvalue(L, -1); // fifos key key | ||
435 | lua_rawget(L, 1); // fifos key fifo|nil | ||
436 | // empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged! | ||
437 | keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) }; | ||
438 | if (fifo != nullptr) // might be nullptr if we set a nonexistent key to nil | ||
439 | { // fifos key fifo | ||
440 | if (fifo->limit < 0) // fifo limit value is the default (unlimited): we can totally remove it | ||
441 | { | ||
442 | lua_pop(L, 1); // fifos key | ||
443 | lua_pushnil(L); // fifos key nil | ||
444 | lua_rawset(L, -3); // fifos | ||
445 | } | ||
446 | else | ||
447 | { | ||
448 | // we create room if the fifo was full but it is no longer the case | ||
449 | should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit); | ||
450 | lua_remove(L, -2); // fifos fifo | ||
451 | lua_newtable(L); // fifos fifo {} | ||
452 | lua_setiuservalue(L, -2, CONTENTS_TABLE); // fifos fifo | ||
453 | fifo->first = 1; | ||
454 | fifo->count = 0; | ||
455 | } | ||
456 | } | ||
457 | } | ||
458 | else // set/replace contents stored at the specified key? | ||
459 | { | ||
460 | int const count{ lua_gettop(L) - 2 }; // number of items we want to store | ||
461 | lua_pushvalue(L, 2); // fifos key [val [, ...]] key | ||
462 | lua_rawget(L, 1); // fifos key [val [, ...]] fifo|nil | ||
463 | keeper_fifo* fifo{ keeper_fifo::getPtr(L, -1) }; | ||
464 | if( fifo == nullptr) // can be nullptr if we store a value at a new key | ||
465 | { // fifos key [val [, ...]] nil | ||
466 | // no need to wake writers in that case, because a writer can't wait on an inexistent key | ||
467 | lua_pop(L, 1); // fifos key [val [, ...]] | ||
468 | std::ignore = fifo_new(L); // fifos key [val [, ...]] fifo | ||
469 | lua_pushvalue(L, 2); // fifos key [val [, ...]] fifo key | ||
470 | lua_pushvalue(L, -2); // fifos key [val [, ...]] fifo key fifo | ||
471 | lua_rawset(L, 1); // fifos key [val [, ...]] fifo | ||
472 | } | ||
473 | else // the fifo exists, we just want to update its contents | ||
474 | { // fifos key [val [, ...]] fifo | ||
475 | // we create room if the fifo was full but it is no longer the case | ||
476 | should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit) && (count < fifo->limit); | ||
477 | // empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged! | ||
478 | lua_newtable(L); // fifos key [val [, ...]] fifo {} | ||
479 | lua_setiuservalue(L, -2, CONTENTS_TABLE); // fifos key [val [, ...]] fifo | ||
480 | fifo->first = 1; | ||
481 | fifo->count = 0; | ||
482 | } | ||
483 | fifo = prepare_fifo_access(L, -1); // fifos key [val [, ...]] fifotbl | ||
484 | // move the fifo below the values we want to store | ||
485 | lua_insert(L, 3); // fifos key fifotbl [val [, ...]] | ||
486 | fifo_push(L, fifo, count); // fifos key fifotbl | ||
487 | } | ||
488 | return should_wake_writers ? (lua_pushboolean(L, 1), 1) : 0; | ||
489 | } | ||
490 | |||
491 | // ################################################################################################## | ||
492 | |||
493 | // in: linda_ud key [count] | ||
494 | // out: at most <count> values | ||
495 | int keepercall_get(lua_State* L) | ||
496 | { | ||
497 | int count{ 1 }; | ||
498 | if (lua_gettop(L) == 3) // ud key count | ||
499 | { | ||
500 | count = static_cast<int>(lua_tointeger(L, 3)); | ||
501 | lua_pop(L, 1); // ud key | ||
502 | } | ||
503 | push_table(L, 1); // ud key fifos | ||
504 | lua_replace(L, 1); // fifos key | ||
505 | lua_rawget(L, 1); // fifos fifo | ||
506 | keeper_fifo* const fifo{ prepare_fifo_access(L, -1) }; // fifos fifotbl | ||
507 | if (fifo != nullptr && fifo->count > 0) | ||
508 | { | ||
509 | lua_remove(L, 1); // fifotbl | ||
510 | count = std::min(count, fifo->count); | ||
511 | // read <count> value off the fifo | ||
512 | fifo_peek(L, fifo, count); // fifotbl ... | ||
513 | return count; | ||
514 | } | ||
515 | // no fifo was ever registered for this key, or it is empty | ||
516 | return 0; | ||
517 | } | ||
518 | |||
519 | // ################################################################################################## | ||
520 | |||
521 | // in: linda_ud [, key [, ...]] | ||
522 | int keepercall_count(lua_State* L) | ||
523 | { | ||
524 | push_table(L, 1); // ud keys fifos | ||
525 | switch (lua_gettop(L)) | ||
526 | { | ||
527 | // no key is specified: return a table giving the count of all known keys | ||
528 | case 2: // ud fifos | ||
529 | lua_newtable(L); // ud fifos out | ||
530 | lua_replace(L, 1); // out fifos | ||
531 | lua_pushnil(L); // out fifos nil | ||
532 | while (lua_next(L, 2)) // out fifos key fifo | ||
533 | { | ||
534 | keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) }; | ||
535 | lua_pop(L, 1); // out fifos key | ||
536 | lua_pushvalue(L, -1); // out fifos key key | ||
537 | lua_pushinteger(L, fifo->count); // out fifos key key count | ||
538 | lua_rawset(L, -5); // out fifos key | ||
539 | } | ||
540 | lua_pop(L, 1); // out | ||
541 | break; | ||
542 | |||
543 | // 1 key is specified: return its count | ||
544 | case 3: // ud key fifos | ||
545 | lua_replace(L, 1); // fifos key | ||
546 | lua_rawget(L, -2); // fifos fifo|nil | ||
547 | if (lua_isnil(L, -1)) // the key is unknown | ||
548 | { // fifos nil | ||
549 | lua_remove(L, -2); // nil | ||
550 | } | ||
551 | else // the key is known | ||
552 | { // fifos fifo | ||
553 | keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) }; | ||
554 | lua_pushinteger(L, fifo->count); // fifos fifo count | ||
555 | lua_replace(L, -3); // count fifo | ||
556 | lua_pop(L, 1); // count | ||
557 | } | ||
558 | break; | ||
559 | |||
560 | // a variable number of keys is specified: return a table of their counts | ||
561 | default: // ud keys fifos | ||
562 | lua_newtable(L); // ud keys... fifos out | ||
563 | lua_replace(L, 1); // out keys... fifos | ||
564 | // shifts all keys up in the stack. potentially slow if there are a lot of them, but then it should be bearable | ||
565 | lua_insert(L, 2); // out fifos keys... | ||
566 | while (lua_gettop(L) > 2) | ||
567 | { | ||
568 | lua_pushvalue(L, -1); // out fifos keys... key | ||
569 | lua_rawget(L, 2); // out fifos keys... fifo|nil | ||
570 | keeper_fifo* const fifo{ keeper_fifo::getPtr(L, -1) }; | ||
571 | lua_pop(L, 1); // out fifos keys... | ||
572 | if (fifo != nullptr) // the key is known | ||
573 | { | ||
574 | lua_pushinteger(L, fifo->count); // out fifos keys... count | ||
575 | lua_rawset(L, 1); // out fifos keys... | ||
576 | } | ||
577 | else // the key is unknown | ||
578 | { | ||
579 | lua_pop(L, 1); // out fifos keys... | ||
580 | } | ||
581 | } // all keys are exhausted // out fifos | ||
582 | lua_pop(L, 1); // out | ||
583 | } | ||
584 | ASSERT_L(lua_gettop(L) == 1); | ||
585 | return 1; | ||
586 | } | ||
587 | |||
588 | //################################################################################### | ||
589 | // Keeper API, accessed from linda methods | ||
590 | //################################################################################### | ||
591 | |||
592 | /*---=== Keeper states ===--- | ||
593 | */ | ||
594 | |||
595 | /* | ||
596 | * Pool of keeper states | ||
597 | * | ||
598 | * Access to keeper states is locked (only one OS thread at a time) so the | ||
599 | * bigger the pool, the less chances of unnecessary waits. Lindas map to the | ||
600 | * keepers randomly, by a hash. | ||
601 | */ | ||
602 | |||
603 | // called as __gc for the keepers array userdata | ||
604 | void close_keepers(Universe* U) | ||
605 | { | ||
606 | if (U->keepers != nullptr) | ||
607 | { | ||
608 | int nbKeepers = U->keepers->nb_keepers; | ||
609 | // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it | ||
610 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists | ||
611 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | ||
612 | // which is early-outed with a U->keepers->nbKeepers null-check | ||
613 | U->keepers->nb_keepers = 0; | ||
614 | for (int i = 0; i < nbKeepers; ++i) | ||
615 | { | ||
616 | lua_State* K = U->keepers->keeper_array[i].L; | ||
617 | U->keepers->keeper_array[i].L = nullptr; | ||
618 | if (K != nullptr) | ||
619 | { | ||
620 | lua_close(K); | ||
621 | } | ||
622 | else | ||
623 | { | ||
624 | // detected partial init: destroy only the mutexes that got initialized properly | ||
625 | nbKeepers = i; | ||
626 | } | ||
627 | } | ||
628 | for (int i = 0; i < nbKeepers; ++i) | ||
629 | { | ||
630 | U->keepers->keeper_array[i].~Keeper(); | ||
631 | } | ||
632 | // free the keeper bookkeeping structure | ||
633 | U->internal_allocator.free(U->keepers, sizeof(Keepers) + (nbKeepers - 1) * sizeof(Keeper)); | ||
634 | U->keepers = nullptr; | ||
635 | } | ||
636 | } | ||
637 | |||
638 | // ################################################################################################## | ||
639 | |||
640 | /* | ||
641 | * Initialize keeper states | ||
642 | * | ||
643 | * If there is a problem, returns nullptr and pushes the error message on the stack | ||
644 | * else returns the keepers bookkeeping structure. | ||
645 | * | ||
646 | * Note: Any problems would be design flaws; the created Lua state is left | ||
647 | * unclosed, because it does not really matter. In production code, this | ||
648 | * function never fails. | ||
649 | * settings table is at position 1 on the stack | ||
650 | */ | ||
651 | void init_keepers(Universe* U, lua_State* L) | ||
652 | { | ||
653 | STACK_CHECK_START_REL(L, 0); // L K | ||
654 | lua_getfield(L, 1, "nb_keepers"); // nb_keepers | ||
655 | int const nb_keepers{ static_cast<int>(lua_tointeger(L, -1)) }; | ||
656 | lua_pop(L, 1); // | ||
657 | if (nb_keepers < 1) | ||
658 | { | ||
659 | luaL_error(L, "Bad number of keepers (%d)", nb_keepers); // doesn't return | ||
660 | } | ||
661 | STACK_CHECK(L, 0); | ||
662 | |||
663 | lua_getfield(L, 1, "keepers_gc_threshold"); // keepers_gc_threshold | ||
664 | int const keepers_gc_threshold{ static_cast<int>(lua_tointeger(L, -1)) }; | ||
665 | lua_pop(L, 1); // | ||
666 | STACK_CHECK(L, 0); | ||
667 | |||
668 | // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states | ||
669 | { | ||
670 | size_t const bytes = sizeof(Keepers) + (nb_keepers - 1) * sizeof(Keeper); | ||
671 | U->keepers = static_cast<Keepers*>(U->internal_allocator.alloc(bytes)); | ||
672 | if (U->keepers == nullptr) | ||
673 | { | ||
674 | luaL_error(L, "init_keepers() failed while creating keeper array; out of memory"); // doesn't return | ||
675 | } | ||
676 | U->keepers->Keepers::Keepers(); | ||
677 | U->keepers->gc_threshold = keepers_gc_threshold; | ||
678 | U->keepers->nb_keepers = nb_keepers; | ||
679 | |||
680 | for (int i = 0; i < nb_keepers; ++i) | ||
681 | { | ||
682 | U->keepers->keeper_array[i].Keeper::Keeper(); | ||
683 | } | ||
684 | } | ||
685 | for (int i = 0; i < nb_keepers; ++i) // keepersUD | ||
686 | { | ||
687 | // note that we will leak K if we raise an error later | ||
688 | lua_State* const K{ create_state(U, L) }; | ||
689 | if (K == nullptr) | ||
690 | { | ||
691 | luaL_error(L, "init_keepers() failed while creating keeper states; out of memory"); // doesn't return | ||
692 | } | ||
693 | |||
694 | U->keepers->keeper_array[i].L = K; | ||
695 | |||
696 | if (U->keepers->gc_threshold >= 0) | ||
697 | { | ||
698 | lua_gc(K, LUA_GCSTOP, 0); | ||
699 | } | ||
700 | |||
701 | STACK_CHECK_START_ABS(K, 0); | ||
702 | |||
703 | // copy the universe pointer in the keeper itself | ||
704 | universe_store(K, U); | ||
705 | STACK_CHECK(K, 0); | ||
706 | |||
707 | // make sure 'package' is initialized in keeper states, so that we have require() | ||
708 | // this because this is needed when transferring deep userdata object | ||
709 | luaL_requiref(K, "package", luaopen_package, 1); // package | ||
710 | lua_pop(K, 1); // | ||
711 | STACK_CHECK(K, 0); | ||
712 | serialize_require(DEBUGSPEW_PARAM_COMMA(U) K); | ||
713 | STACK_CHECK(K, 0); | ||
714 | |||
715 | // copy package.path and package.cpath from the source state | ||
716 | lua_getglobal(L, "package"); // "..." keepersUD package | ||
717 | if (!lua_isnil(L, -1)) | ||
718 | { | ||
719 | // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately | ||
720 | if (luaG_inter_copy_package(U, Source{ L }, Dest{ K }, -1, LookupMode::ToKeeper) != InterCopyResult::Success) | ||
721 | { | ||
722 | // if something went wrong, the error message is at the top of the stack | ||
723 | lua_remove(L, -2); // error_msg | ||
724 | raise_lua_error(L); | ||
725 | } | ||
726 | } | ||
727 | lua_pop(L, 1); // | ||
728 | STACK_CHECK(L, 0); | ||
729 | |||
730 | // attempt to call on_state_create(), if we have one and it is a C function | ||
731 | // (only support a C function because we can't transfer executable Lua code in keepers) | ||
732 | // will raise an error in L in case of problem | ||
733 | call_on_state_create(U, K, L, LookupMode::ToKeeper); | ||
734 | |||
735 | // to see VM name in Decoda debugger | ||
736 | lua_pushfstring(K, "Keeper #%d", i + 1); // "Keeper #n" | ||
737 | lua_setglobal(K, "decoda_name"); // | ||
738 | // create the fifos table in the keeper state | ||
739 | FIFOS_KEY.setValue(K, [](lua_State* L) { lua_newtable(L); }); | ||
740 | STACK_CHECK(K, 0); | ||
741 | } | ||
742 | STACK_CHECK(L, 0); | ||
743 | } | ||
744 | |||
745 | // ################################################################################################## | ||
746 | |||
747 | // should be called only when inside a keeper_acquire/keeper_release pair (see linda_protected_call) | ||
748 | Keeper* which_keeper(Keepers* keepers_, uintptr_t magic_) | ||
749 | { | ||
750 | int const nbKeepers{ keepers_->nb_keepers }; | ||
751 | if (nbKeepers) | ||
752 | { | ||
753 | unsigned int i = (unsigned int) ((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); | ||
754 | return &keepers_->keeper_array[i]; | ||
755 | } | ||
756 | return nullptr; | ||
757 | } | ||
758 | |||
759 | // ################################################################################################## | ||
760 | |||
761 | Keeper* keeper_acquire(Keepers* keepers_, uintptr_t magic_) | ||
762 | { | ||
763 | int const nbKeepers{ keepers_->nb_keepers }; | ||
764 | // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) | ||
765 | if (nbKeepers) | ||
766 | { | ||
767 | /* | ||
768 | * Any hashing will do that maps pointers to 0..GNbKeepers-1 | ||
769 | * consistently. | ||
770 | * | ||
771 | * Pointers are often aligned by 8 or so - ignore the low order bits | ||
772 | * have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer | ||
773 | */ | ||
774 | unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers); | ||
775 | Keeper* K = &keepers_->keeper_array[i]; | ||
776 | K->m_mutex.lock(); | ||
777 | //++ K->count; | ||
778 | return K; | ||
779 | } | ||
780 | return nullptr; | ||
781 | } | ||
782 | |||
783 | // ################################################################################################## | ||
784 | |||
785 | void keeper_release(Keeper* K) | ||
786 | { | ||
787 | //-- K->count; | ||
788 | if (K) | ||
789 | { | ||
790 | K->m_mutex.unlock(); | ||
791 | } | ||
792 | } | ||
793 | |||
794 | // ################################################################################################## | ||
795 | |||
796 | void keeper_toggle_nil_sentinels(lua_State* L, int val_i_, LookupMode const mode_) | ||
797 | { | ||
798 | int const n{ lua_gettop(L) }; | ||
799 | for (int i = val_i_; i <= n; ++i) | ||
800 | { | ||
801 | if (mode_ == LookupMode::ToKeeper) | ||
802 | { | ||
803 | if (lua_isnil(L, i)) | ||
804 | { | ||
805 | NIL_SENTINEL.pushKey(L); | ||
806 | lua_replace(L, i); | ||
807 | } | ||
808 | } | ||
809 | else | ||
810 | { | ||
811 | if (NIL_SENTINEL.equals(L, i)) | ||
812 | { | ||
813 | lua_pushnil(L); | ||
814 | lua_replace(L, i); | ||
815 | } | ||
816 | } | ||
817 | } | ||
818 | } | ||
819 | |||
820 | // ################################################################################################## | ||
821 | |||
822 | /* | ||
823 | * Call a function ('func_name') in the keeper state, and pass on the returned | ||
824 | * values to 'L'. | ||
825 | * | ||
826 | * 'linda': deep Linda pointer (used only as a unique table key, first parameter) | ||
827 | * 'starting_index': first of the rest of parameters (none if 0) | ||
828 | * | ||
829 | * Returns: number of return values (pushed to 'L') or -1 in case of error | ||
830 | */ | ||
831 | int keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, void* linda, int starting_index) | ||
832 | { | ||
833 | int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 }; | ||
834 | int const Ktos{ lua_gettop(K) }; | ||
835 | int retvals = -1; | ||
836 | |||
837 | STACK_GROW(K, 2); | ||
838 | |||
839 | PUSH_KEEPER_FUNC(K, func_); | ||
840 | |||
841 | lua_pushlightuserdata(K, linda); | ||
842 | |||
843 | if ((args == 0) || luaG_inter_copy(U, Source{ L }, Dest{ K }, args, LookupMode::ToKeeper) == InterCopyResult::Success) // L->K | ||
844 | { | ||
845 | lua_call(K, 1 + args, LUA_MULTRET); | ||
846 | retvals = lua_gettop(K) - Ktos; | ||
847 | // note that this can raise a luaL_error while the keeper state (and its mutex) is acquired | ||
848 | // this may interrupt a lane, causing the destruction of the underlying OS thread | ||
849 | // after this, another lane making use of this keeper can get an error code from the mutex-locking function | ||
850 | // when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread) | ||
851 | if ((retvals > 0) && luaG_inter_move(U, Source{ K }, Dest{ L }, retvals, LookupMode::FromKeeper) != InterCopyResult::Success) // K->L | ||
852 | { | ||
853 | retvals = -1; | ||
854 | } | ||
855 | } | ||
856 | // whatever happens, restore the stack to where it was at the origin | ||
857 | lua_settop(K, Ktos); | ||
858 | |||
859 | // don't do this for this particular function, as it is only called during Linda destruction, and we don't want to raise an error, ever | ||
860 | if (func_ != KEEPER_API(clear)) [[unlikely]] | ||
861 | { | ||
862 | // since keeper state GC is stopped, let's run a step once in a while if required | ||
863 | int const gc_threshold{ U->keepers->gc_threshold }; | ||
864 | if (gc_threshold == 0) [[unlikely]] | ||
865 | { | ||
866 | lua_gc(K, LUA_GCSTEP, 0); | ||
867 | } | ||
868 | else if (gc_threshold > 0) [[likely]] | ||
869 | { | ||
870 | int const gc_usage{ lua_gc(K, LUA_GCCOUNT, 0) }; | ||
871 | if (gc_usage >= gc_threshold) | ||
872 | { | ||
873 | lua_gc(K, LUA_GCCOLLECT, 0); | ||
874 | int const gc_usage_after{ lua_gc(K, LUA_GCCOUNT, 0) }; | ||
875 | if (gc_usage_after > gc_threshold) [[unlikely]] | ||
876 | { | ||
877 | luaL_error(L, "Keeper GC threshold is too low, need at least %d", gc_usage_after); | ||
878 | } | ||
879 | } | ||
880 | } | ||
881 | } | ||
882 | |||
883 | return retvals; | ||
884 | } | ||