@thigasdevelopment/luam 0.19.12 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,44 +1,41 @@
1
- local luamDevelopmentEvent = "luam:development-log"
2
- local luamDevelopmentMarker = "__LUAM_DEV_LOG__"
3
- local luamMaximumMessageLength = __LUAM_MAX_MESSAGE_LENGTH__
4
- local luamRateLimit = __LUAM_RATE_LIMIT__
5
- local luamRateWindow = __LUAM_RATE_WINDOW_MS__
6
- local luamRateBuckets = {}
7
-
8
- addEvent(luamDevelopmentEvent, true)
9
- addEventHandler(luamDevelopmentEvent, resourceRoot, function(message, level)
10
- if client == nil or source ~= resourceRoot or type(message) ~= "string" or #message > luamMaximumMessageLength then
11
- return
12
- end
13
-
14
- if type(level) ~= "number" or level < 0 or level > 4 or level ~= math.floor(level) then
15
- return
16
- end
17
-
18
- local now = getTickCount()
19
- local bucket = luamRateBuckets[client]
20
-
21
- if bucket == nil or now < bucket.started or now - bucket.started >= luamRateWindow then
22
- bucket = { started = now, count = 0 }
23
- luamRateBuckets[client] = bucket
24
- end
25
-
26
- if bucket.count >= luamRateLimit then
27
- return
28
- end
29
-
30
- bucket.count = bucket.count + 1
31
-
32
- local record = {
33
- environment = "client",
34
- level = level,
35
- message = message,
36
- resource = getResourceName(getThisResource())
37
- }
38
-
39
- outputDebugString(luamDevelopmentMarker .. toJSON(record, true), 4)
40
- end)
41
-
42
- addEventHandler("onPlayerQuit", root, function()
43
- luamRateBuckets[source] = nil
44
- end)
1
+ local luamDevelopmentEvent = 'luam:development-log';
2
+ local luamDevelopmentMarker = '__LUAM_DEV_LOG__';
3
+ local luamMaximumMessageLength = __LUAM_MAX_MESSAGE_LENGTH__; -- Replaced at build time with the limits the manifest sets.
4
+ local luamRateLimit = __LUAM_RATE_LIMIT__;
5
+ local luamRateWindow = __LUAM_RATE_WINDOW_MS__;
6
+ local luamRateBuckets = { };
7
+
8
+ addEvent (luamDevelopmentEvent, true);
9
+
10
+ ---@param message string
11
+ ---@param level number
12
+ addEventHandler (luamDevelopmentEvent, resourceRoot, function (message, level)
13
+ if (client == nil) or (source ~= resourceRoot) or (type (message) ~= 'string') or (#message > luamMaximumMessageLength) then return end
14
+
15
+ if (type (level) ~= 'number') or (level < 0) or (level > 4) or (level ~= math.floor (level)) then return end
16
+
17
+ local now = getTickCount ();
18
+ local bucket = luamRateBuckets[client];
19
+
20
+ if (bucket == nil) or (now < bucket.started) or ((now - bucket.started) >= luamRateWindow) then
21
+ bucket = { started = now, count = 0 };
22
+ luamRateBuckets[client] = bucket;
23
+ end
24
+
25
+ if (bucket.count >= luamRateLimit) then return end
26
+
27
+ bucket.count = bucket.count + 1;
28
+
29
+ local record = {
30
+ environment = 'client',
31
+ level = level,
32
+ message = message,
33
+ resource = getResourceName (getThisResource ()),
34
+ };
35
+
36
+ outputDebugString (luamDevelopmentMarker .. toJSON (record, true), 4);
37
+ end);
38
+
39
+ addEventHandler ('onPlayerQuit', root, function ()
40
+ luamRateBuckets[source] = nil;
41
+ end);
package/lua/math.lua CHANGED
@@ -1,24 +1,27 @@
1
- function math.clamp(value, minimum, maximum)
2
- value = tonumber(value)
1
+ ---@param value number
2
+ ---@param minimum? number
3
+ ---@param maximum? number
4
+ ---@return number
5
+ function math.clamp (value, minimum, maximum)
6
+ value = tonumber (value);
7
+ if (not value) then
8
+ return 0;
9
+ end
3
10
 
4
- if not value then
5
- return 0
6
- end
11
+ minimum = tonumber (minimum) or value;
12
+ maximum = tonumber (maximum) or value;
7
13
 
8
- minimum = tonumber(minimum) or value
9
- maximum = tonumber(maximum) or value
14
+ if (minimum > maximum) then
15
+ minimum, maximum = maximum, minimum;
16
+ end
10
17
 
11
- if minimum > maximum then
12
- minimum, maximum = maximum, minimum
13
- end
18
+ if (value < minimum) then
19
+ return minimum;
20
+ end
14
21
 
15
- if value < minimum then
16
- return minimum
17
- end
22
+ if (value > maximum) then
23
+ return maximum;
24
+ end
18
25
 
19
- if value > maximum then
20
- return maximum
21
- end
22
-
23
- return value
26
+ return value;
24
27
  end
@@ -0,0 +1,446 @@
1
+ local PENDING, FULFILLED, REJECTED = 'pending', 'fulfilled', 'rejected';
2
+ local MINIMUM_DELAY = 50; -- MTA never fires a timer sooner than this, so a shorter wait resumes on the next tick.
3
+ local AWAITING = { }; -- Yielded by a task waiting on a promise, so a thread pool leaves it to the scheduler.
4
+
5
+ local tasks = setmetatable ({ }, { __mode = 'k' });
6
+ local jobs = setmetatable ({ }, { __mode = 'k' });
7
+ local awaiting = setmetatable ({ }, { __mode = 'k' });
8
+
9
+ local queue, draining = { }, false;
10
+ local pulses, pulseTimer = { }, nil;
11
+
12
+ ---@param ... any
13
+ ---@return table
14
+ local function pack (...)
15
+ return { n = select ('#', ...), ... };
16
+ end
17
+
18
+ ---@param values table
19
+ ---@return ...
20
+ local function spread (values)
21
+ return unpack (values, 1, values.n);
22
+ end
23
+
24
+ ---@param message any
25
+ local function report (message)
26
+ if (type (outputDebugString) == 'function') then
27
+ outputDebugString ('[Promise] ' .. tostring (message), 1);
28
+ return;
29
+ end
30
+
31
+ print ('[Promise] ' .. tostring (message));
32
+ end
33
+
34
+ ---@param value any
35
+ ---@return boolean
36
+ local function isPromise (value)
37
+ return (type (value) == 'table') and (getmetatable (value) == Promise);
38
+ end
39
+
40
+ ---@return Promise
41
+ local function create ()
42
+ ---@type Promise
43
+ return setmetatable ({ state = PENDING, values = { n = 0 }, handlers = { } }, Promise);
44
+ end
45
+
46
+ ---@param promise Promise
47
+ ---@param state string
48
+ ---@param ... any
49
+ ---@return boolean
50
+ local function settle (promise, state, ...)
51
+ if (promise.state ~= PENDING) then return false end
52
+
53
+ promise.state = state;
54
+ promise.values = pack (...);
55
+
56
+ local handlers = promise.handlers;
57
+ promise.handlers = { };
58
+
59
+ for index = 1, #handlers do
60
+ handlers[index] (promise);
61
+ end
62
+
63
+ return true;
64
+ end
65
+
66
+ local function drain ()
67
+ if (draining) then return end
68
+
69
+ draining = true;
70
+ while (#queue > 0) do
71
+ table.remove (queue, 1) ();
72
+ end
73
+ draining = false;
74
+ end
75
+
76
+ ---@param job fun()
77
+ local function schedule (job)
78
+ queue[#queue + 1] = job;
79
+ drain ();
80
+ end
81
+
82
+ ---@param routine thread
83
+ ---@param ... any
84
+ ---@return boolean
85
+ local function resumeTask (routine, ...)
86
+ if (coroutine.status (routine) ~= 'suspended') then return false end
87
+
88
+ local promise = tasks[routine];
89
+ local outcome = pack (coroutine.resume (routine, ...));
90
+
91
+ if (not outcome[1]) then
92
+ if (not promise) or (#promise.handlers == 0) then -- Nobody is waiting on it, so the reason would vanish.
93
+ report (outcome[2]);
94
+ end
95
+
96
+ if (promise) then
97
+ settle (promise, REJECTED, outcome[2]);
98
+ end
99
+ return false;
100
+ end
101
+
102
+ if (promise) and (coroutine.status (routine) == 'dead') then
103
+ settle (promise, FULFILLED, unpack (outcome, 2, outcome.n));
104
+ end
105
+
106
+ return true;
107
+ end
108
+
109
+ ---@param routine thread
110
+ ---@param ... any
111
+ ---@return ...
112
+ local function clearAwait (routine, ...)
113
+ awaiting[routine] = nil;
114
+ return ...;
115
+ end
116
+
117
+ ---@param promise Promise
118
+ ---@return boolean, ...
119
+ local function wait (promise)
120
+ local routine = coroutine.running ();
121
+ if (not routine) then
122
+ error ('"await" is only valid inside an async function, and this call is not running inside one.', 0);
123
+ end
124
+
125
+ if (not tasks[routine]) then
126
+ error ('"await" is only valid inside a coroutine the promise scheduler drives.', 0);
127
+ end
128
+
129
+ if (promise.state ~= PENDING) then
130
+ return (promise.state == FULFILLED), spread (promise.values);
131
+ end
132
+
133
+ local handlers = promise.handlers;
134
+ handlers[#handlers + 1] = function (source)
135
+ schedule (function ()
136
+ resumeTask (routine, (source.state == FULFILLED), spread (source.values));
137
+ end);
138
+ end
139
+
140
+ awaiting[routine] = true;
141
+ return clearAwait (routine, coroutine.yield (AWAITING));
142
+ end
143
+
144
+ ---@param ok boolean
145
+ ---@param ... any
146
+ ---@return ...
147
+ local function unwrap (ok, ...)
148
+ if (not ok) then
149
+ error ((...), 0);
150
+ end
151
+ return ...;
152
+ end
153
+
154
+ local function tick ()
155
+ for index = 1, #pulses do
156
+ local job = pulses[index];
157
+ if (job ~= nil) then
158
+ job ();
159
+ end
160
+ end
161
+ end
162
+
163
+ ---@class Promise
164
+ ---@field state string
165
+ ---@field values table
166
+ ---@field handlers table
167
+ ---@field new fun(executor: fun(resolve: fun(...): any, reject: fun(...): any): any): Promise
168
+ ---@field spawn fun(func: fun(...): any, ...): Promise
169
+ ---@field resolve fun(...): Promise
170
+ ---@field reject fun(...): Promise
171
+ ---@field all fun(list: table): Promise
172
+ ---@field race fun(list: table): Promise
173
+ ---@field await fun(promise: Promise): any
174
+ ---@field settle fun(promise: Promise): boolean, any
175
+ ---@field delay fun(milliseconds: number): Promise
176
+ ---@field next fun(self: Promise, onFulfilled: fun(...): any, onRejected?: fun(reason: any): any): Promise
177
+ ---@field catch fun(self: Promise, onRejected: fun(reason: any): any): Promise
178
+ Promise = {
179
+ ---@param executor fun(resolve: fun(...): any, reject: fun(...): any): any
180
+ ---@return Promise
181
+ new = function (executor)
182
+ local promise = create ();
183
+ local executorType = type (executor);
184
+ if (executorType ~= 'function') then
185
+ error ('bad argument #1 to \'new\' (\'function\' expected got \'' .. executorType .. '\').', 2);
186
+ end
187
+
188
+ local ok, reason = pcall (executor, function (...)
189
+ settle (promise, FULFILLED, ...);
190
+ end, function (...)
191
+ settle (promise, REJECTED, ...);
192
+ end);
193
+
194
+ if (not ok) then
195
+ settle (promise, REJECTED, reason);
196
+ end
197
+
198
+ return promise;
199
+ end,
200
+
201
+ ---@param func fun(...): any
202
+ ---@param ... any
203
+ ---@return Promise
204
+ spawn = function (func, ...)
205
+ local promise = create ();
206
+
207
+ if (type (func) ~= 'function') then
208
+ settle (promise, REJECTED, 'Promise.spawn expects a function.');
209
+ return promise;
210
+ end
211
+
212
+ local routine = coroutine.create (func);
213
+ tasks[routine] = promise;
214
+
215
+ resumeTask (routine, ...);
216
+ return promise;
217
+ end,
218
+
219
+ ---@param ... any
220
+ ---@return Promise
221
+ resolve = function (...)
222
+ local value = ...;
223
+ if (select ('#', ...) == 1) and (isPromise (value)) then
224
+ return value;
225
+ end
226
+
227
+ local promise = create ();
228
+ settle (promise, FULFILLED, ...);
229
+ return promise;
230
+ end,
231
+
232
+ ---@param ... any
233
+ ---@return Promise
234
+ reject = function (...)
235
+ local promise = create ();
236
+ settle (promise, REJECTED, ...);
237
+ return promise;
238
+ end,
239
+
240
+ ---@param list table
241
+ ---@return Promise
242
+ all = function (list)
243
+ local result = create ();
244
+ local values, remaining = { }, #list;
245
+
246
+ if (remaining == 0) then
247
+ settle (result, FULFILLED, values);
248
+ return result;
249
+ end
250
+
251
+ for index = 1, #list do
252
+ Promise.resolve (list[index]):next (function (value)
253
+ values[index] = value;
254
+ remaining = remaining - 1;
255
+
256
+ if (remaining == 0) then
257
+ settle (result, FULFILLED, values);
258
+ end
259
+ end, function (...)
260
+ settle (result, REJECTED, ...);
261
+ end);
262
+ end
263
+
264
+ return result;
265
+ end,
266
+
267
+ ---@param list table
268
+ ---@return Promise
269
+ race = function (list)
270
+ local result = create ();
271
+ for index = 1, #list do
272
+ Promise.resolve (list[index]):next (function (...)
273
+ settle (result, FULFILLED, ...);
274
+ end, function (...)
275
+ settle (result, REJECTED, ...);
276
+ end);
277
+ end
278
+
279
+ return result;
280
+ end,
281
+
282
+ ---@param promise Promise
283
+ ---@return ...
284
+ await = function (promise)
285
+ if (not isPromise (promise)) then
286
+ return promise;
287
+ end
288
+ return unwrap (wait (promise));
289
+ end,
290
+
291
+ ---@param promise Promise
292
+ ---@return boolean, ...
293
+ settle = function (promise)
294
+ if (not isPromise (promise)) then
295
+ return true, promise;
296
+ end
297
+ return wait (promise);
298
+ end,
299
+
300
+ ---@param milliseconds number
301
+ ---@return Promise
302
+ delay = function (milliseconds)
303
+ milliseconds = tonumber (milliseconds) or 0;
304
+ if (milliseconds < MINIMUM_DELAY) then
305
+ milliseconds = MINIMUM_DELAY;
306
+ end
307
+
308
+ return Promise.new (function (resolve)
309
+ setTimer (resolve, milliseconds, 1);
310
+ end);
311
+ end,
312
+
313
+ ---@param routine thread
314
+ ---@return Promise
315
+ adopt = function (routine)
316
+ local promise = create ();
317
+ tasks[routine] = promise;
318
+ jobs[routine] = true;
319
+
320
+ return promise;
321
+ end,
322
+
323
+ ---@param routine thread
324
+ ---@param ... any
325
+ ---@return boolean
326
+ resume = function (routine, ...)
327
+ return resumeTask (routine, ...);
328
+ end,
329
+
330
+ ---@param routine thread
331
+ ---@return boolean
332
+ isAwaiting = function (routine)
333
+ return (routine ~= nil) and (awaiting[routine] == true);
334
+ end,
335
+
336
+ ---@param callback fun()
337
+ ---@return fun()
338
+ pulse = function (callback)
339
+ pulses[#pulses + 1] = callback;
340
+ if (pulseTimer == nil) then
341
+ pulseTimer = setTimer (tick, MINIMUM_DELAY, 0);
342
+ end
343
+
344
+ return callback;
345
+ end,
346
+
347
+ ---@param callback fun()
348
+ unpulse = function (callback)
349
+ for index = #pulses, 1, -1 do
350
+ if (pulses[index] == callback) then
351
+ table.remove (pulses, index);
352
+ end
353
+ end
354
+
355
+ if (#pulses == 0) and (pulseTimer ~= nil) then
356
+ killTimer (pulseTimer);
357
+ pulseTimer = nil;
358
+ end
359
+ end,
360
+
361
+ ---@param self Promise
362
+ ---@param onFulfilled fun(...): any
363
+ ---@param onRejected? fun(reason: any): any
364
+ ---@return Promise
365
+ next = function (self, onFulfilled, onRejected)
366
+ local chained = create ();
367
+
368
+ local function forward (source)
369
+ local handler = onRejected;
370
+ if (source.state == FULFILLED) then
371
+ handler = onFulfilled;
372
+ end
373
+
374
+ if (type (handler) ~= 'function') then
375
+ settle (chained, source.state, spread (source.values));
376
+ return;
377
+ end
378
+
379
+ local outcome = pack (pcall (handler, spread (source.values)));
380
+ if (not outcome[1]) then
381
+ settle (chained, REJECTED, outcome[2]);
382
+ return;
383
+ end
384
+
385
+ local value = outcome[2];
386
+ if (isPromise (value)) then
387
+ value:next (function (...)
388
+ settle (chained, FULFILLED, ...);
389
+ end, function (...)
390
+ settle (chained, REJECTED, ...);
391
+ end);
392
+ return;
393
+ end
394
+
395
+ settle (chained, FULFILLED, unpack (outcome, 2, outcome.n));
396
+ end
397
+
398
+ if (self.state == PENDING) then
399
+ self.handlers[#self.handlers + 1] = forward;
400
+ else
401
+ forward (self);
402
+ end
403
+
404
+ return chained;
405
+ end,
406
+
407
+ ---@param self Promise
408
+ ---@param onRejected fun(reason: any): any
409
+ ---@return Promise
410
+ catch = function (self, onRejected)
411
+ return self:next (nil, onRejected);
412
+ end,
413
+ };
414
+ Promise.__index = Promise;
415
+
416
+ ---@param milliseconds number
417
+ ---@return Promise
418
+ function delay (milliseconds)
419
+ return Promise.delay (milliseconds);
420
+ end
421
+
422
+ ---@param milliseconds number
423
+ function sleep (milliseconds)
424
+ milliseconds = tonumber (milliseconds) or 0;
425
+
426
+ local routine = coroutine.running ();
427
+ if (routine) and (jobs[routine]) then
428
+ if (milliseconds < 1) then
429
+ coroutine.yield ();
430
+ return;
431
+ end
432
+
433
+ local start = getTickCount ();
434
+ repeat
435
+ coroutine.yield ();
436
+ until (getTickCount () - start) >= milliseconds
437
+
438
+ return;
439
+ end
440
+
441
+ if (not routine) or (not tasks[routine]) then
442
+ error ('"sleep" is only valid inside an async function or a thread pool job.', 0);
443
+ end
444
+
445
+ return Promise.await (Promise.delay (milliseconds));
446
+ end