@flamework-experimental/core 2.0.0-alpha.1 → 2.0.0-alpha.3
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/README.md +35 -27
- package/flamework.build +3 -3
- package/out/dependency.d.ts +4 -0
- package/out/dependency.luau +4 -0
- package/out/flamework.luau +8 -0
- package/out/index.d.ts +4 -2
- package/out/init.luau +10 -2
- package/out/lifecycle/lifecyclePlugin.d.ts +175 -2
- package/out/lifecycle/lifecyclePlugin.luau +636 -102
- package/out/module/module.luau +337 -41
- package/out/module/moduleBuilder.d.ts +12 -3
- package/out/module/moduleBuilder.luau +22 -0
- package/out/module/moduleDefinition.d.ts +6 -0
- package/out/module/providerRegistration.d.ts +8 -0
- package/out/module/providerRegistration.luau +29 -0
- package/out/plugin/pluginDefinition.d.ts +14 -4
- package/out/provider.d.ts +19 -0
- package/out/provider.luau +8 -0
- package/out/reflect.luau +17 -0
- package/out/utility/explainUnresolved.d.ts +9 -0
- package/out/utility/explainUnresolved.luau +43 -0
- package/out/utility/getClassImplements.d.ts +9 -2
- package/out/utility/getClassImplements.luau +89 -6
- package/out/utility/getClassesInPath.d.ts +10 -2
- package/out/utility/getClassesInPath.luau +70 -31
- package/out/utility/globs.d.ts +2 -2
- package/out/utility/globs.luau +3 -3
- package/out/utility/implementsCache.d.ts +16 -0
- package/out/utility/implementsCache.luau +35 -0
- package/out/utility/leftOut.d.ts +35 -0
- package/out/utility/leftOut.luau +171 -0
- package/out/utility/moduleClasses.d.ts +9 -0
- package/out/utility/moduleClasses.luau +64 -0
- package/out/utility/recycleThread.d.ts +12 -0
- package/out/utility/recycleThread.luau +29 -10
- package/out/utility/threadWaits.d.ts +36 -0
- package/out/utility/threadWaits.luau +81 -0
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local describeConditions = TS.import(script, script.Parent.Parent, "module", "scopes").describeConditions
|
|
4
|
+
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
5
|
+
local getGlobPaths = TS.import(script, script.Parent, "globs").getGlobPaths
|
|
6
|
+
local findModuleClass = TS.import(script, script.Parent, "moduleClasses").findModuleClass
|
|
7
|
+
local getPathRoot = TS.import(script, script.Parent, "pathRoot").getPathRoot
|
|
8
|
+
--[[
|
|
9
|
+
*
|
|
10
|
+
* A path or glob registration that its own scope condition left out. Its folder was never looked up,
|
|
11
|
+
* so nothing under it was loaded, and nothing from it was registered or recorded as inactive: this
|
|
12
|
+
* is what a miss can still say about the classes there.
|
|
13
|
+
|
|
14
|
+
]]
|
|
15
|
+
--[[
|
|
16
|
+
*
|
|
17
|
+
* Records a registration left out by its own condition. Reads nothing but the path root, which is
|
|
18
|
+
* found from Flamework's own metadata, never from the folder.
|
|
19
|
+
|
|
20
|
+
]]
|
|
21
|
+
local function leftOutRegistration(call, condition, folder)
|
|
22
|
+
return {
|
|
23
|
+
call = call,
|
|
24
|
+
condition = {
|
|
25
|
+
activeIn = condition.activeIn,
|
|
26
|
+
inactiveIn = condition.inactiveIn,
|
|
27
|
+
},
|
|
28
|
+
path = folder.path,
|
|
29
|
+
glob = folder.glob,
|
|
30
|
+
root = getPathRoot(),
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
--[[
|
|
34
|
+
*
|
|
35
|
+
* The tree path of a loaded module below `root`, in the form a compile-time path has: a client's
|
|
36
|
+
* `PlayerScripts` is written as `StarterPlayer/StarterPlayerScripts`, where its content comes from.
|
|
37
|
+
* Nothing when the module is not below `root`.
|
|
38
|
+
|
|
39
|
+
]]
|
|
40
|
+
local function treePathOf(moduleScript, root)
|
|
41
|
+
local names = {}
|
|
42
|
+
local current = moduleScript
|
|
43
|
+
while current ~= nil and current ~= root do
|
|
44
|
+
local _name = current.Name
|
|
45
|
+
table.insert(names, 1, _name)
|
|
46
|
+
current = current.Parent
|
|
47
|
+
end
|
|
48
|
+
if current == nil then
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
if root == game and names[1] == "Players" and names[3] == "PlayerScripts" then
|
|
52
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
53
|
+
local _newValue = {}
|
|
54
|
+
local _callback = function(_, index)
|
|
55
|
+
return index >= 3
|
|
56
|
+
end
|
|
57
|
+
local _length = 0
|
|
58
|
+
for _k, _v in names do
|
|
59
|
+
if _callback(_v, _k - 1, names) == true then
|
|
60
|
+
_length += 1
|
|
61
|
+
_newValue[_length] = _v
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
65
|
+
local rest = _newValue
|
|
66
|
+
local _array = { "StarterPlayer", "StarterPlayerScripts" }
|
|
67
|
+
local _length_1 = #_array
|
|
68
|
+
table.move(rest, 1, #rest, _length_1 + 1, _array)
|
|
69
|
+
return _array
|
|
70
|
+
end
|
|
71
|
+
return names
|
|
72
|
+
end
|
|
73
|
+
local function startsWith(path, prefix)
|
|
74
|
+
if #path < #prefix then
|
|
75
|
+
return false
|
|
76
|
+
end
|
|
77
|
+
for i = 0, #prefix - 1 do
|
|
78
|
+
if path[i + 1] ~= prefix[i + 1] then
|
|
79
|
+
return false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
return true
|
|
83
|
+
end
|
|
84
|
+
--* Whether a loaded module is under the folder, or one of the folders, a left-out registration named.
|
|
85
|
+
local function isUnder(moduleScript, registration)
|
|
86
|
+
local modulePath = treePathOf(moduleScript, registration.root)
|
|
87
|
+
if modulePath == nil then
|
|
88
|
+
return false
|
|
89
|
+
end
|
|
90
|
+
if registration.path ~= nil then
|
|
91
|
+
return startsWith(modulePath, registration.path)
|
|
92
|
+
end
|
|
93
|
+
if registration.glob ~= nil then
|
|
94
|
+
-- `globs.json`, not the folders: what the glob matched when the build was compiled.
|
|
95
|
+
local ok, paths = pcall(getGlobPaths, registration.glob)
|
|
96
|
+
if not ok then
|
|
97
|
+
return false
|
|
98
|
+
end
|
|
99
|
+
-- ▼ ReadonlyArray.some ▼
|
|
100
|
+
local _result = false
|
|
101
|
+
local _callback = function(path)
|
|
102
|
+
return startsWith(modulePath, path)
|
|
103
|
+
end
|
|
104
|
+
for _k, _v in paths do
|
|
105
|
+
if _callback(_v, _k - 1, paths) then
|
|
106
|
+
_result = true
|
|
107
|
+
break
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
-- ▲ ReadonlyArray.some ▲
|
|
111
|
+
return _result
|
|
112
|
+
end
|
|
113
|
+
return false
|
|
114
|
+
end
|
|
115
|
+
--[[
|
|
116
|
+
*
|
|
117
|
+
* What a miss on `id` can say about the registrations its module or plugin left out by their own
|
|
118
|
+
* condition, without looking at their folders:
|
|
119
|
+
*
|
|
120
|
+
* - the class has loaded some other way, and its module is under one of them: that one, and why;
|
|
121
|
+
* - the class has not loaded: every one of them, since the class may be under any;
|
|
122
|
+
* - nothing when there are none, or when the loaded class is under none of them.
|
|
123
|
+
|
|
124
|
+
]]
|
|
125
|
+
local function explainLeftOut(id, leftOut)
|
|
126
|
+
if #leftOut == 0 then
|
|
127
|
+
return nil
|
|
128
|
+
end
|
|
129
|
+
local advice = "the build's scopes so that the condition holds, or do not depend on it in this build"
|
|
130
|
+
local found = findModuleClass(function(value)
|
|
131
|
+
return Reflect.getOwnMetadata(value, "identifier") == id
|
|
132
|
+
end)
|
|
133
|
+
if found ~= nil then
|
|
134
|
+
local _binding = found
|
|
135
|
+
local value = _binding[1]
|
|
136
|
+
local moduleScript = _binding[2]
|
|
137
|
+
-- ▼ ReadonlyArray.find ▼
|
|
138
|
+
local _callback = function(registration)
|
|
139
|
+
return isUnder(moduleScript, registration)
|
|
140
|
+
end
|
|
141
|
+
local _result
|
|
142
|
+
for _i, _v in leftOut do
|
|
143
|
+
if _callback(_v, _i - 1, leftOut) == true then
|
|
144
|
+
_result = _v
|
|
145
|
+
break
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
-- ▲ ReadonlyArray.find ▲
|
|
149
|
+
local under = _result
|
|
150
|
+
if under == nil then
|
|
151
|
+
return nil
|
|
152
|
+
end
|
|
153
|
+
local where = if typeof(moduleScript) == "Instance" then moduleScript:GetFullName() else tostring(moduleScript)
|
|
154
|
+
return `'{value}' ({where}) is under {under.call}, which is left out by its scope ` .. `({describeConditions({ under.condition })}): nothing under it is registered. Change {advice}`
|
|
155
|
+
end
|
|
156
|
+
-- ▼ ReadonlyArray.map ▼
|
|
157
|
+
local _newValue = table.create(#leftOut)
|
|
158
|
+
local _callback = function(v)
|
|
159
|
+
return `{v.call} ({describeConditions({ v.condition })})`
|
|
160
|
+
end
|
|
161
|
+
for _k, _v in leftOut do
|
|
162
|
+
_newValue[_k] = _callback(_v, _k - 1, leftOut)
|
|
163
|
+
end
|
|
164
|
+
-- ▲ ReadonlyArray.map ▲
|
|
165
|
+
local listed = table.concat(_newValue, ", ")
|
|
166
|
+
return `nothing registers it. Left out by their scope, without loading their folders: {listed}. ` .. `If it is defined under one of them, change {advice}`
|
|
167
|
+
end
|
|
168
|
+
return {
|
|
169
|
+
leftOutRegistration = leftOutRegistration,
|
|
170
|
+
explainLeftOut = explainLeftOut,
|
|
171
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Records a class against the module that defined it. Called by `Reflect.defineMetadata`. */
|
|
2
|
+
export declare function recordModuleClass(moduleScript: unknown, value: object): void;
|
|
3
|
+
/** The classes a module defined as it loaded, in definition order, or none. */
|
|
4
|
+
export declare function getModuleClasses(moduleScript: unknown): ReadonlyArray<object> | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Every recorded class, with the module that defined it, until the callback answers `true`. For
|
|
7
|
+
* explaining a failed resolution, which is rare: nothing here is indexed for it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findModuleClass(predicate: (value: object) => boolean): [object, unknown] | undefined;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
--[[
|
|
3
|
+
*
|
|
4
|
+
* The Flamework classes each module defined as it loaded, by the module's `script`, in the order
|
|
5
|
+
* they were defined: what path registration finds in a module besides what it exports.
|
|
6
|
+
*
|
|
7
|
+
* The transformer records a class with `Reflect.defineMetadata(class, "flamework:module", script)`,
|
|
8
|
+
* and only a class the module creates once per load -- declared at the top level of the file, or
|
|
9
|
+
* of a namespace in it. A class declared inside a function is created by every call and is never
|
|
10
|
+
* recorded, so nothing here grows with calls, and a later path registration never finds a class
|
|
11
|
+
* that belongs to a test case or a factory. Keyed by the ModuleScript rather than read off the
|
|
12
|
+
* class's identifier, which says nothing about where the class came from once ids are short, tiny
|
|
13
|
+
* or obfuscated.
|
|
14
|
+
*
|
|
15
|
+
* Held for good, as the module's own exports are held by the require cache: a module loads once.
|
|
16
|
+
|
|
17
|
+
]]
|
|
18
|
+
local classesByModule = {}
|
|
19
|
+
--* Records a class against the module that defined it. Called by `Reflect.defineMetadata`.
|
|
20
|
+
local function recordModuleClass(moduleScript, value)
|
|
21
|
+
if moduleScript == nil then
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
local _moduleScript = moduleScript
|
|
25
|
+
local classes = classesByModule[_moduleScript]
|
|
26
|
+
if classes == nil then
|
|
27
|
+
classes = {}
|
|
28
|
+
local _moduleScript_1 = moduleScript
|
|
29
|
+
local _classes = classes
|
|
30
|
+
classesByModule[_moduleScript_1] = _classes
|
|
31
|
+
end
|
|
32
|
+
local _classes = classes
|
|
33
|
+
local _value = value
|
|
34
|
+
if not (table.find(_classes, _value) ~= nil) then
|
|
35
|
+
local _classes_1 = classes
|
|
36
|
+
local _value_1 = value
|
|
37
|
+
table.insert(_classes_1, _value_1)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
--* The classes a module defined as it loaded, in definition order, or none.
|
|
41
|
+
local function getModuleClasses(moduleScript)
|
|
42
|
+
local _moduleScript = moduleScript
|
|
43
|
+
return classesByModule[_moduleScript]
|
|
44
|
+
end
|
|
45
|
+
--[[
|
|
46
|
+
*
|
|
47
|
+
* Every recorded class, with the module that defined it, until the callback answers `true`. For
|
|
48
|
+
* explaining a failed resolution, which is rare: nothing here is indexed for it.
|
|
49
|
+
|
|
50
|
+
]]
|
|
51
|
+
local function findModuleClass(predicate)
|
|
52
|
+
for moduleScript, classes in classesByModule do
|
|
53
|
+
for _, value in classes do
|
|
54
|
+
if predicate(value) then
|
|
55
|
+
return { value, moduleScript }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
return {
|
|
61
|
+
recordModuleClass = recordModuleClass,
|
|
62
|
+
getModuleClasses = getModuleClasses,
|
|
63
|
+
findModuleClass = findModuleClass,
|
|
64
|
+
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `func` with the arguments given, on a thread that is kept for the next call once it returns:
|
|
3
|
+
* one that yields keeps its thread, and the next call gets a new one.
|
|
4
|
+
*
|
|
5
|
+
* The arguments are passed through rather than closed over, so that a caller running many callbacks
|
|
6
|
+
* -- the per-frame events, once per listener every frame -- creates nothing per call.
|
|
7
|
+
*/
|
|
1
8
|
export declare function recycleThread(func: () => void): void;
|
|
9
|
+
export declare function recycleThread<A>(func: (a: A) => void, a: A): void;
|
|
10
|
+
export declare function recycleThread<A, B>(func: (a: A, b: B) => void, a: A, b: B): void;
|
|
11
|
+
export declare function recycleThread<A, B, C>(func: (a: A, b: B, c: C) => void, a: A, b: B, c: C): void;
|
|
12
|
+
export declare function recycleThread<A, B, C, D>(func: (a: A, b: B, c: C, d: D) => void, a: A, b: B, c: C, d: D): void;
|
|
13
|
+
export declare function recycleThread<A, B, C, D, E>(func: (a: A, b: B, c: C, d: D, e: E) => void, a: A, b: B, c: C, d: D, e: E): void;
|
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
-- Compiled with roblox-ts v3.0.0
|
|
2
2
|
local inactiveThread
|
|
3
|
-
local function reusableThread(func)
|
|
3
|
+
local function reusableThread(func, a, b, c, d, e)
|
|
4
4
|
local thread = coroutine.running()
|
|
5
5
|
while true do
|
|
6
6
|
if inactiveThread == thread then
|
|
7
7
|
inactiveThread = nil
|
|
8
8
|
end
|
|
9
|
-
func()
|
|
9
|
+
func(a, b, c, d, e)
|
|
10
10
|
-- If there's a different idle thread, we should end the current thread.
|
|
11
11
|
if inactiveThread ~= nil then
|
|
12
12
|
break
|
|
13
13
|
end
|
|
14
14
|
inactiveThread = thread
|
|
15
|
-
-- Let go of the callback while idle:
|
|
16
|
-
-- listener, a component -- which would otherwise stay reachable from this thread
|
|
17
|
-
-- next callback comes through, and for good when none does.
|
|
15
|
+
-- Let go of the callback and its arguments while idle: they are whatever it was run for -- a
|
|
16
|
+
-- lifecycle listener, a component -- which would otherwise stay reachable from this thread
|
|
17
|
+
-- until the next callback comes through, and for good when none does.
|
|
18
18
|
func = nil
|
|
19
|
-
|
|
19
|
+
a = nil
|
|
20
|
+
b = nil
|
|
21
|
+
c = nil
|
|
22
|
+
d = nil
|
|
23
|
+
e = nil
|
|
24
|
+
func, a, b, c, d, e = coroutine.yield()
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
--[[
|
|
28
|
+
*
|
|
29
|
+
* Runs `func` with the arguments given, on a thread that is kept for the next call once it returns:
|
|
30
|
+
* one that yields keeps its thread, and the next call gets a new one.
|
|
31
|
+
*
|
|
32
|
+
* The arguments are passed through rather than closed over, so that a caller running many callbacks
|
|
33
|
+
* -- the per-frame events, once per listener every frame -- creates nothing per call.
|
|
34
|
+
|
|
35
|
+
]]
|
|
36
|
+
local function recycleThread(func, a, b, c, d, e)
|
|
37
|
+
-- Only while it is parked: a callback that kept `coroutine.running()` and cancelled it later left
|
|
38
|
+
-- a dead thread here, and every later call, of every module, raised trying to resume it. One that
|
|
39
|
+
-- is not is dropped, and a new thread takes its place.
|
|
40
|
+
local idle = inactiveThread
|
|
41
|
+
if idle ~= nil and coroutine.status(idle) == "suspended" then
|
|
42
|
+
task.spawn(idle, func, a, b, c, d, e)
|
|
25
43
|
else
|
|
26
|
-
|
|
44
|
+
inactiveThread = nil
|
|
45
|
+
task.spawn(reusableThread, func, a, b, c, d, e)
|
|
27
46
|
end
|
|
28
47
|
end
|
|
29
48
|
return {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every thread waiting for another to finish something, to the thread it waits for: an extinguish
|
|
3
|
+
* waiting in `awaitExtinguished` for one running on another thread, and an ignition waiting for an
|
|
4
|
+
* `onInit` the lifecycle plugin runs on a thread of its own. Shared by every module, since a chain
|
|
5
|
+
* of waits runs through several: `awaitExtinguished` follows it to refuse the wait that would close
|
|
6
|
+
* a circle.
|
|
7
|
+
*
|
|
8
|
+
* Or to the Promise it waits for: an ignition waiting for the Promise an `onInit` returned, whose
|
|
9
|
+
* work runs on a thread nothing can name -- an `async` method's body runs on one of its own, not the
|
|
10
|
+
* `onInit` thread. A chain that ends at one may end at any thread, the waiting one included.
|
|
11
|
+
*
|
|
12
|
+
* An entry is removed by whatever ends the wait -- what is waited for, as it finishes, or the poll
|
|
13
|
+
* that sees the waiting thread die -- and not by the waiting thread once it resumes: one that is
|
|
14
|
+
* cancelled never resumes.
|
|
15
|
+
*/
|
|
16
|
+
export declare const threadWaits: Map<thread, thread | Promise<unknown>>;
|
|
17
|
+
/**
|
|
18
|
+
* How many extinguishes have begun, in any module, counted from the first line of `extinguish()`.
|
|
19
|
+
* The lifecycle plugin's per-frame walks read it after every callback and ask their module whether
|
|
20
|
+
* it has begun to extinguish only when it has changed, so that a callback that extinguishes the
|
|
21
|
+
* module -- itself, on a thread it starts, through a signal it fires -- stops the walk without a
|
|
22
|
+
* call per listener.
|
|
23
|
+
*/
|
|
24
|
+
export declare const extinguishesBegun: {
|
|
25
|
+
count: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Whether a thread does Promise work: whether its outermost function -- what it was started with --
|
|
29
|
+
* is the Promise library's or the one an `async` function's body runs in.
|
|
30
|
+
*
|
|
31
|
+
* The lifecycle plugin takes such a thread to be doing the work of the Promise an `onInit` returned
|
|
32
|
+
* while it waits on one, since nothing records which threads that is: an `async` body that has
|
|
33
|
+
* yielded, an executor, a chained callback. An unrelated Promise's thread passes too, and a thread
|
|
34
|
+
* an executor starts itself with `task.spawn` or `task.delay` does not.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runsPromiseWork(thread: thread): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
--[[
|
|
4
|
+
*
|
|
5
|
+
* Every thread waiting for another to finish something, to the thread it waits for: an extinguish
|
|
6
|
+
* waiting in `awaitExtinguished` for one running on another thread, and an ignition waiting for an
|
|
7
|
+
* `onInit` the lifecycle plugin runs on a thread of its own. Shared by every module, since a chain
|
|
8
|
+
* of waits runs through several: `awaitExtinguished` follows it to refuse the wait that would close
|
|
9
|
+
* a circle.
|
|
10
|
+
*
|
|
11
|
+
* Or to the Promise it waits for: an ignition waiting for the Promise an `onInit` returned, whose
|
|
12
|
+
* work runs on a thread nothing can name -- an `async` method's body runs on one of its own, not the
|
|
13
|
+
* `onInit` thread. A chain that ends at one may end at any thread, the waiting one included.
|
|
14
|
+
*
|
|
15
|
+
* An entry is removed by whatever ends the wait -- what is waited for, as it finishes, or the poll
|
|
16
|
+
* that sees the waiting thread die -- and not by the waiting thread once it resumes: one that is
|
|
17
|
+
* cancelled never resumes.
|
|
18
|
+
|
|
19
|
+
]]
|
|
20
|
+
local threadWaits = {}
|
|
21
|
+
--[[
|
|
22
|
+
*
|
|
23
|
+
* How many extinguishes have begun, in any module, counted from the first line of `extinguish()`.
|
|
24
|
+
* The lifecycle plugin's per-frame walks read it after every callback and ask their module whether
|
|
25
|
+
* it has begun to extinguish only when it has changed, so that a callback that extinguishes the
|
|
26
|
+
* module -- itself, on a thread it starts, through a signal it fires -- stops the walk without a
|
|
27
|
+
* call per listener.
|
|
28
|
+
|
|
29
|
+
]]
|
|
30
|
+
local extinguishesBegun = {
|
|
31
|
+
count = 0,
|
|
32
|
+
}
|
|
33
|
+
local asyncFunction = TS.async(function() end)
|
|
34
|
+
--[[
|
|
35
|
+
*
|
|
36
|
+
* The scripts a thread doing Promise work starts in: the Promise library, which runs executors and
|
|
37
|
+
* chained callbacks on threads of their own, and the runtime library, which runs an `async`
|
|
38
|
+
* function's body on one of its own.
|
|
39
|
+
|
|
40
|
+
]]
|
|
41
|
+
local promiseSources = {
|
|
42
|
+
[(debug.info(TS.Promise.is, "s"))] = true,
|
|
43
|
+
[(debug.info(asyncFunction, "s"))] = true,
|
|
44
|
+
}
|
|
45
|
+
--[[
|
|
46
|
+
*
|
|
47
|
+
* Whether a thread does Promise work: whether its outermost function -- what it was started with --
|
|
48
|
+
* is the Promise library's or the one an `async` function's body runs in.
|
|
49
|
+
*
|
|
50
|
+
* The lifecycle plugin takes such a thread to be doing the work of the Promise an `onInit` returned
|
|
51
|
+
* while it waits on one, since nothing records which threads that is: an `async` body that has
|
|
52
|
+
* yielded, an executor, a chained callback. An unrelated Promise's thread passes too, and a thread
|
|
53
|
+
* an executor starts itself with `task.spawn` or `task.delay` does not.
|
|
54
|
+
|
|
55
|
+
]]
|
|
56
|
+
local function runsPromiseWork(thread)
|
|
57
|
+
local outermost
|
|
58
|
+
local level = 1
|
|
59
|
+
while true do
|
|
60
|
+
local source = (debug.info(thread, level, "s"))
|
|
61
|
+
if source == nil then
|
|
62
|
+
break
|
|
63
|
+
end
|
|
64
|
+
-- A C function (`pcall`, the scheduler's resume) is not where the thread's work comes from.
|
|
65
|
+
if source ~= "[C]" then
|
|
66
|
+
outermost = source
|
|
67
|
+
end
|
|
68
|
+
level += 1
|
|
69
|
+
end
|
|
70
|
+
local _condition = outermost ~= nil
|
|
71
|
+
if _condition then
|
|
72
|
+
local _outermost = outermost
|
|
73
|
+
_condition = promiseSources[_outermost] ~= nil
|
|
74
|
+
end
|
|
75
|
+
return _condition
|
|
76
|
+
end
|
|
77
|
+
return {
|
|
78
|
+
runsPromiseWork = runsPromiseWork,
|
|
79
|
+
threadWaits = threadWaits,
|
|
80
|
+
extinguishesBegun = extinguishesBegun,
|
|
81
|
+
}
|