@thigasdevelopment/luam 1.0.0 → 1.0.2

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.
package/lua/threads.lua CHANGED
@@ -1,274 +1,331 @@
1
1
  local PRIORITIES = {
2
- low = { pulsing = 250, frame = 8 },
3
- normal = { pulsing = 100, frame = 15 },
4
- high = { pulsing = 50, frame = 25 },
5
- extreme = { pulsing = 0, frame = 50 },
6
- }
2
+ low = { pulsing = 250, frame = 8 },
3
+ normal = { pulsing = 100, frame = 15 },
4
+ high = { pulsing = 50, frame = 25 },
5
+ extreme = { pulsing = 0, frame = 50 }, -- Every pulse: the scheduler already runs on MTA's 50ms floor.
6
+ };
7
7
 
8
8
  local STYLES = {
9
- concurrent = true,
10
- priority = true,
11
- sequential = true,
12
- }
13
-
14
- function sleep(milliseconds)
15
- milliseconds = tonumber(milliseconds) or 0
16
-
17
- if milliseconds < 1 then
18
- coroutine.yield()
19
- return
20
- end
21
-
22
- local start = getTickCount()
23
-
24
- repeat
25
- coroutine.yield()
26
- until (getTickCount() - start) >= milliseconds
27
- end
28
-
29
- local Thread = {}
30
- Thread.__index = Thread
31
-
32
- function Thread:get()
33
- return self.priority
34
- end
35
-
36
- function Thread:set(priority)
37
- priority = tonumber(priority)
38
- if not priority or self.priority == priority then
39
- return false
40
- end
41
-
42
- self.priority = priority
43
- return true
44
- end
45
-
46
- function Thread:pause()
47
- if self.paused then
48
- return false
49
- end
50
-
51
- self.paused = true
52
- return true
53
- end
54
-
55
- function Thread:resume()
56
- if not self.paused then
57
- return false
58
- end
59
-
60
- self.paused = false
61
- return true
62
- end
63
-
64
- function Thread:isPaused()
65
- return self.paused
66
- end
67
-
68
- function Thread:isStarted()
69
- return self.started
70
- end
71
-
72
- Threads = {}
73
- Threads.__index = Threads
74
-
75
- local function resumeThread(self, id, thread)
76
- if coroutine.status(thread.routine) == 'dead' then
77
- self:remove(id)
78
- return false
79
- end
80
-
81
- if thread:isPaused() then
82
- return false
83
- end
84
-
85
- local success, message
86
- if thread:isStarted() then
87
- success, message = coroutine.resume(thread.routine, thread)
88
- else
89
- success, message = coroutine.resume(thread.routine, thread, unpack(thread.arguments))
90
- thread.started = true
91
- end
92
-
93
- if not success then
94
- self:remove(id)
95
- error('[Threads] Thread ' .. tostring(id) .. ' failed: ' .. tostring(message))
96
- end
97
-
98
- return true
99
- end
100
-
101
- function Threads.new(style, priority)
102
- local self = setmetatable({}, Threads)
103
-
104
- self.threads = {}
105
- self.nextId, self.currentId = 0, -1
106
- self.type, self.priority = 'concurrent', 'normal'
107
- self.timer = nil
108
- self:setType(style)
109
- self:setPriority(priority)
110
-
111
- return self
112
- end
113
-
114
- function Threads:add(func, options, ...)
115
- options = options or {}
116
-
117
- local thread = setmetatable({
118
- routine = coroutine.create(func),
119
- arguments = { ... },
120
- paused = false,
121
- started = false,
122
- priority = options.priority or -1,
123
- }, Thread)
124
-
125
- local id = self.nextId + 1
126
- self.nextId = id
127
- self.threads[id] = thread
128
-
129
- self:start()
130
- return id
9
+ concurrent = true,
10
+ priority = true,
11
+ sequential = true,
12
+ };
13
+
14
+ ---@class Thread
15
+ ---@field routine thread
16
+ ---@field arguments table
17
+ ---@field paused boolean
18
+ ---@field started boolean
19
+ ---@field priority number
20
+ ---@field get fun(self: Thread): number
21
+ ---@field set fun(self: Thread, priority: number): boolean
22
+ ---@field pause fun(self: Thread): boolean
23
+ ---@field resume fun(self: Thread): boolean
24
+ ---@field isPaused fun(self: Thread): boolean
25
+ ---@field isStarted fun(self: Thread): boolean
26
+ local Thread = {
27
+ ---@param self Thread
28
+ ---@return number
29
+ get = function (self)
30
+ return self.priority;
31
+ end,
32
+
33
+ ---@param self Thread
34
+ ---@param priority number
35
+ ---@return boolean
36
+ set = function (self, priority)
37
+ priority = tonumber (priority);
38
+ if (not priority) or (self.priority == priority) then
39
+ return false;
40
+ end
41
+
42
+ self.priority = priority;
43
+ return true;
44
+ end,
45
+
46
+ ---@param self Thread
47
+ ---@return boolean
48
+ pause = function (self)
49
+ if (self.paused) then
50
+ return false;
51
+ end
52
+
53
+ self.paused = true;
54
+ return true;
55
+ end,
56
+
57
+ ---@param self Thread
58
+ ---@return boolean
59
+ resume = function (self)
60
+ if (not self.paused) then
61
+ return false;
62
+ end
63
+
64
+ self.paused = false;
65
+ return true;
66
+ end,
67
+
68
+ ---@param self Thread
69
+ ---@return boolean
70
+ isPaused = function (self)
71
+ return self.paused;
72
+ end,
73
+
74
+ ---@param self Thread
75
+ ---@return boolean
76
+ isStarted = function (self)
77
+ return self.started;
78
+ end,
79
+ };
80
+ Thread.__index = Thread;
81
+
82
+ ---@param self Threads
83
+ ---@param id number
84
+ ---@param thread Thread
85
+ ---@return boolean
86
+ local function resumeThread (self, id, thread)
87
+ if (coroutine.status (thread.routine) == 'dead') then
88
+ self:remove (id);
89
+ return false;
90
+ end
91
+
92
+ if (thread:isPaused ()) or (Promise.isAwaiting (thread.routine)) then
93
+ return false;
94
+ end
95
+
96
+ if (thread:isStarted ()) then
97
+ return Promise.resume (thread.routine);
98
+ end
99
+
100
+ thread.started = true;
101
+ return Promise.resume (thread.routine, thread, unpack (thread.arguments));
131
102
  end
132
103
 
133
- function Threads:remove(id)
134
- if not self.threads[id] then
135
- return false
136
- end
137
-
138
- if self.currentId == id then
139
- self.currentId = -1
140
- end
141
-
142
- self.threads[id] = nil
143
- return true
144
- end
145
-
146
- function Threads:clear()
147
- if next(self.threads) == nil then
148
- return false
149
- end
150
-
151
- self.threads, self.currentId = {}, -1
152
-
153
- self:stop()
154
- return true
155
- end
156
-
157
- function Threads:start()
158
- if isTimer(self.timer) then
159
- return false
160
- end
161
-
162
- self.timer = setTimer(function()
163
- self:process()
164
- end, PRIORITIES[self.priority].pulsing, 0)
165
- return true
166
- end
167
-
168
- function Threads:stop()
169
- if isTimer(self.timer) then
170
- killTimer(self.timer)
171
- end
172
- self.timer = nil
173
- end
174
-
175
- function Threads:processQueue(sorted)
176
- local queue = {}
177
-
178
- for id, thread in pairs(self.threads) do
179
- queue[#queue + 1] = { id = id, thread = thread }
180
- end
181
-
182
- if sorted then
183
- table.sort(queue, function(left, right)
184
- return left.thread:get() > right.thread:get()
185
- end)
186
- end
187
-
188
- local frames = 0
189
- for _, item in ipairs(queue) do
190
- if frames >= PRIORITIES[self.priority].frame then
191
- return
192
- end
193
-
194
- if resumeThread(self, item.id, item.thread) then
195
- frames = frames + 1
196
- end
197
- end
198
- end
199
-
200
- function Threads:processSequential()
201
- if not self.threads[self.currentId] then
202
- self.currentId = -1
203
-
204
- for id in pairs(self.threads) do
205
- self.currentId = id
206
- break
207
- end
208
- end
209
-
210
- local thread = self.threads[self.currentId]
211
- if not thread then
212
- return
213
- end
214
-
215
- local frames = 0
216
- while frames < PRIORITIES[self.priority].frame do
217
- if not resumeThread(self, self.currentId, thread) then
218
- return
219
- end
220
-
221
- frames = frames + 1
222
- end
223
- end
224
-
225
- function Threads:process()
226
- if self.type == 'sequential' then
227
- self:processSequential()
228
- else
229
- self:processQueue(self.type == 'priority')
230
- end
231
-
232
- if next(self.threads) == nil then
233
- self:stop()
234
- end
235
- end
236
-
237
- function Threads:getType()
238
- return self.type
239
- end
240
-
241
- function Threads:setType(style)
242
- if type(style) ~= 'string' then
243
- return false
244
- end
245
-
246
- style = style:lower()
247
- if not STYLES[style] or self.type == style then
248
- return false
249
- end
250
-
251
- self.type = style
252
- return true
253
- end
254
-
255
- function Threads:getPriority()
256
- return self.priority
257
- end
258
-
259
- function Threads:setPriority(priority)
260
- if type(priority) ~= 'string' or not PRIORITIES[priority] or self.priority == priority then
261
- return false
262
- end
263
-
264
- self.priority = priority
265
- if isTimer(self.timer) then
266
- self:stop()
267
- self:start()
268
- end
269
- return true
270
- end
271
-
272
- function Threads:getThread(id)
273
- return self.threads[id]
274
- end
104
+ ---@class Threads
105
+ ---@field threads table<number, Thread>
106
+ ---@field nextId number
107
+ ---@field currentId number
108
+ ---@field type string
109
+ ---@field priority string
110
+ ---@field pulse fun()?
111
+ ---@field pulsedAt number
112
+ ---@field add fun(self: Threads, func: fun(...): any, options?: table, ...): number, Promise
113
+ ---@field remove fun(self: Threads, id: number): boolean
114
+ ---@field clear fun(self: Threads): boolean
115
+ ---@field start fun(self: Threads): boolean
116
+ ---@field stop fun(self: Threads)
117
+ ---@field getType fun(self: Threads): string
118
+ ---@field setType fun(self: Threads, style: string): boolean
119
+ ---@field getPriority fun(self: Threads): string
120
+ ---@field setPriority fun(self: Threads, priority: string): boolean
121
+ ---@field getThread fun(self: Threads, id: number): Thread?
122
+ Threads = {
123
+ ---@param style string
124
+ ---@param priority string
125
+ ---@return Threads
126
+ new = function (style, priority)
127
+ ---@type Threads
128
+ local self = setmetatable ({ }, Threads);
129
+
130
+ self.threads = { };
131
+ self.nextId, self.currentId = 0, -1;
132
+ self.type, self.priority = 'concurrent', 'normal';
133
+ self.pulse, self.pulsedAt = nil, 0;
134
+
135
+ self:setType (style);
136
+ self:setPriority (priority);
137
+
138
+ return self;
139
+ end,
140
+
141
+ ---@param self Threads
142
+ ---@param func fun(...): any
143
+ ---@param options? table
144
+ ---@param ... any
145
+ ---@return number, Promise
146
+ add = function (self, func, options, ...)
147
+ options = options or { };
148
+
149
+ ---@type Thread
150
+ local thread = setmetatable ({
151
+ routine = coroutine.create (func),
152
+ arguments = { ... },
153
+ paused = false,
154
+ started = false,
155
+ priority = options.priority or -1,
156
+ }, Thread);
157
+
158
+ local settled = Promise.adopt (thread.routine);
159
+
160
+ local id = self.nextId + 1;
161
+ self.nextId = id;
162
+ self.threads[id] = thread;
163
+
164
+ self:start ();
165
+ return id, settled;
166
+ end,
167
+
168
+ ---@param self Threads
169
+ ---@param id number
170
+ ---@return boolean
171
+ remove = function (self, id)
172
+ if (not self.threads[id]) then
173
+ return false;
174
+ end
175
+
176
+ if (self.currentId == id) then
177
+ self.currentId = -1;
178
+ end
179
+
180
+ self.threads[id] = nil;
181
+ return true;
182
+ end,
183
+
184
+ ---@param self Threads
185
+ ---@return boolean
186
+ clear = function (self)
187
+ if (next (self.threads) == nil) then
188
+ return false;
189
+ end
190
+
191
+ self.threads, self.currentId = { }, -1;
192
+
193
+ self:stop ();
194
+ return true;
195
+ end,
196
+
197
+ ---@param self Threads
198
+ ---@return boolean
199
+ start = function (self)
200
+ if (self.pulse) then
201
+ return false;
202
+ end
203
+
204
+ self.pulse = Promise.pulse (function ()
205
+ self:process ();
206
+ end);
207
+ return true;
208
+ end,
209
+
210
+ ---@param self Threads
211
+ stop = function (self)
212
+ if (self.pulse) then
213
+ Promise.unpulse (self.pulse);
214
+ end
215
+ self.pulse = nil;
216
+ end,
217
+
218
+ ---@param self Threads
219
+ ---@param sorted boolean
220
+ processQueue = function (self, sorted)
221
+ local queue = { };
222
+
223
+ for id, thread in pairs (self.threads) do
224
+ queue[#queue + 1] = { id = id, thread = thread };
225
+ end
226
+
227
+ if (sorted) then
228
+ table.sort (queue, function (left, right)
229
+ return left.thread:get () > right.thread:get ();
230
+ end);
231
+ end
232
+
233
+ local frames = 0;
234
+ for _, item in ipairs (queue) do
235
+ if (frames >= PRIORITIES[self.priority].frame) then return end
236
+
237
+ if (resumeThread (self, item.id, item.thread)) then
238
+ frames = frames + 1;
239
+ end
240
+ end
241
+ end,
242
+
243
+ ---@param self Threads
244
+ processSequential = function (self)
245
+ if (not self.threads[self.currentId]) then
246
+ self.currentId = -1;
247
+
248
+ for id in pairs (self.threads) do
249
+ self.currentId = id;
250
+ break;
251
+ end
252
+ end
253
+
254
+ local thread = self.threads[self.currentId];
255
+ if (not thread) then return end
256
+
257
+ local frames = 0;
258
+ while (frames < PRIORITIES[self.priority].frame) do
259
+ if (not resumeThread (self, self.currentId, thread)) then return end
260
+
261
+ frames = frames + 1;
262
+ end
263
+ end,
264
+
265
+ ---@param self Threads
266
+ process = function (self)
267
+ local now = getTickCount ();
268
+ if ((now - self.pulsedAt) < PRIORITIES[self.priority].pulsing) then return end
269
+
270
+ self.pulsedAt = now;
271
+
272
+ if (self.type == 'sequential') then
273
+ self:processSequential ();
274
+ else
275
+ self:processQueue (self.type == 'priority');
276
+ end
277
+
278
+ if (next (self.threads) == nil) then
279
+ self:stop ();
280
+ end
281
+ end,
282
+
283
+ ---@param self Threads
284
+ ---@return string
285
+ getType = function (self)
286
+ return self.type;
287
+ end,
288
+
289
+ ---@param self Threads
290
+ ---@param style string
291
+ ---@return boolean
292
+ setType = function (self, style)
293
+ if (type (style) ~= 'string') then
294
+ return false;
295
+ end
296
+
297
+ style = style:lower ();
298
+ if (not STYLES[style]) or (self.type == style) then
299
+ return false;
300
+ end
301
+
302
+ self.type = style;
303
+ return true;
304
+ end,
305
+
306
+ ---@param self Threads
307
+ ---@return string
308
+ getPriority = function (self)
309
+ return self.priority;
310
+ end,
311
+
312
+ ---@param self Threads
313
+ ---@param priority string
314
+ ---@return boolean
315
+ setPriority = function (self, priority)
316
+ if (type (priority) ~= 'string') or (not PRIORITIES[priority]) or (self.priority == priority) then
317
+ return false;
318
+ end
319
+
320
+ self.priority = priority;
321
+ return true;
322
+ end,
323
+
324
+ ---@param self Threads
325
+ ---@param id number
326
+ ---@return Thread?
327
+ getThread = function (self, id)
328
+ return self.threads[id];
329
+ end,
330
+ };
331
+ Threads.__index = Threads;