@flamework-experimental/core 2.0.0-alpha.0
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 +66 -0
- package/flamework.build +17 -0
- package/out/dependency.d.ts +18 -0
- package/out/dependency.luau +32 -0
- package/out/flamework.d.ts +78 -0
- package/out/flamework.luau +138 -0
- package/out/index.d.ts +28 -0
- package/out/init.luau +40 -0
- package/out/injectable.d.ts +13 -0
- package/out/injectable.luau +23 -0
- package/out/lifecycle/lifecycleInterfaces.d.ts +94 -0
- package/out/lifecycle/lifecycleInterfaces.luau +55 -0
- package/out/lifecycle/lifecyclePlugin.d.ts +85 -0
- package/out/lifecycle/lifecyclePlugin.luau +424 -0
- package/out/modding.d.ts +171 -0
- package/out/modding.luau +66 -0
- package/out/module/defaultModule.d.ts +5 -0
- package/out/module/defaultModule.luau +28 -0
- package/out/module/module.d.ts +78 -0
- package/out/module/module.luau +811 -0
- package/out/module/moduleBuilder.d.ts +88 -0
- package/out/module/moduleBuilder.luau +169 -0
- package/out/module/moduleDefinition.d.ts +105 -0
- package/out/module/moduleDefinition.luau +79 -0
- package/out/module/moduleHooks.d.ts +23 -0
- package/out/module/moduleHooks.luau +19 -0
- package/out/module/providerRegistration.d.ts +22 -0
- package/out/module/providerRegistration.luau +80 -0
- package/out/module/scopes.d.ts +40 -0
- package/out/module/scopes.luau +159 -0
- package/out/plugin/pluginDefinition.d.ts +128 -0
- package/out/plugin/pluginDefinition.luau +61 -0
- package/out/prelude.d.ts +8 -0
- package/out/prelude.luau +14 -0
- package/out/provider.d.ts +27 -0
- package/out/provider.luau +26 -0
- package/out/reflect.d.ts +60 -0
- package/out/reflect.luau +310 -0
- package/out/serialization/types.d.ts +86 -0
- package/out/serialization/types.luau +9 -0
- package/out/utility/constructors.d.ts +4 -0
- package/out/utility/constructors.luau +12 -0
- package/out/utility/convertConciseDependencyInfo.d.ts +5 -0
- package/out/utility/convertConciseDependencyInfo.luau +29 -0
- package/out/utility/getClassImplements.d.ts +7 -0
- package/out/utility/getClassImplements.luau +27 -0
- package/out/utility/getClassesInPath.d.ts +23 -0
- package/out/utility/getClassesInPath.luau +91 -0
- package/out/utility/globs.d.ts +9 -0
- package/out/utility/globs.luau +64 -0
- package/out/utility/metadata.d.ts +12 -0
- package/out/utility/metadata.luau +43 -0
- package/out/utility/pathRoot.d.ts +17 -0
- package/out/utility/pathRoot.luau +105 -0
- package/out/utility/recycleThread.d.ts +1 -0
- package/out/utility/recycleThread.luau +31 -0
- package/out/utility/runtimeConfig.d.ts +59 -0
- package/out/utility/runtimeConfig.luau +25 -0
- package/out/utility/tsImport.d.ts +6 -0
- package/out/utility/tsImport.luau +15 -0
- package/out/utility/types.d.ts +16 -0
- package/out/utility/types.luau +22 -0
- package/out/utility/writable.d.ts +4 -0
- package/out/utility/writable.luau +2 -0
- package/package.json +33 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
4
|
+
local convertConciseDependencyInfo = TS.import(script, script.Parent.Parent, "utility", "convertConciseDependencyInfo").convertConciseDependencyInfo
|
|
5
|
+
local getClassImplements = TS.import(script, script.Parent.Parent, "utility", "getClassImplements").getClassImplements
|
|
6
|
+
local getClassesInPath = TS.import(script, script.Parent.Parent, "utility", "getClassesInPath").getClassesInPath
|
|
7
|
+
local getClassesInGlob = TS.import(script, script.Parent.Parent, "utility", "globs").getClassesInGlob
|
|
8
|
+
local clearDefaultModule = TS.import(script, script.Parent, "defaultModule").clearDefaultModule
|
|
9
|
+
local HookPriority = TS.import(script, script.Parent, "moduleHooks").HookPriority
|
|
10
|
+
local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
|
|
11
|
+
local getProviderClassId = _providerRegistration.getProviderClassId
|
|
12
|
+
local getProviderClassScope = _providerRegistration.getProviderClassScope
|
|
13
|
+
local normalizeProviderConfig = _providerRegistration.normalizeProviderConfig
|
|
14
|
+
local _scopes = TS.import(script, script.Parent, "scopes")
|
|
15
|
+
local NO_CONDITION = _scopes.NO_CONDITION
|
|
16
|
+
local describeConditions = _scopes.describeConditions
|
|
17
|
+
local hasCondition = _scopes.hasCondition
|
|
18
|
+
local holdsCondition = _scopes.holdsCondition
|
|
19
|
+
local holdsEveryCondition = _scopes.holdsEveryCondition
|
|
20
|
+
--* What a lookup through a module and its imports found: where, and which class when it is a class provider.
|
|
21
|
+
local ModuleInitState
|
|
22
|
+
do
|
|
23
|
+
local _inverse = {}
|
|
24
|
+
ModuleInitState = setmetatable({}, {
|
|
25
|
+
__index = _inverse,
|
|
26
|
+
})
|
|
27
|
+
ModuleInitState.Created = 0
|
|
28
|
+
_inverse[0] = "Created"
|
|
29
|
+
ModuleInitState.PreIgniting = 1
|
|
30
|
+
_inverse[1] = "PreIgniting"
|
|
31
|
+
ModuleInitState.Igniting = 2
|
|
32
|
+
_inverse[2] = "Igniting"
|
|
33
|
+
ModuleInitState.Ignited = 3
|
|
34
|
+
_inverse[3] = "Ignited"
|
|
35
|
+
ModuleInitState.Extinguishing = 4
|
|
36
|
+
_inverse[4] = "Extinguishing"
|
|
37
|
+
ModuleInitState.Extinguished = 5
|
|
38
|
+
_inverse[5] = "Extinguished"
|
|
39
|
+
end
|
|
40
|
+
local MODULE_ID = "$:module/module@Module"
|
|
41
|
+
--* The class-and-path registration forms take their options as a separate argument; here they join the config.
|
|
42
|
+
local function withOptions(config, options)
|
|
43
|
+
local _result
|
|
44
|
+
if options ~= nil then
|
|
45
|
+
local _object = {}
|
|
46
|
+
if config then
|
|
47
|
+
for _k, _v in config do
|
|
48
|
+
_object[_k] = _v
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
for _k, _v in options do
|
|
52
|
+
_object[_k] = _v
|
|
53
|
+
end
|
|
54
|
+
_result = _object
|
|
55
|
+
else
|
|
56
|
+
_result = config
|
|
57
|
+
end
|
|
58
|
+
return _result
|
|
59
|
+
end
|
|
60
|
+
local function createModuleInstantiation(state, options)
|
|
61
|
+
--* The module's own scope condition, from `ignite`. Applies to everything it registers.
|
|
62
|
+
local moduleScope = if options ~= nil and (options.activeIn ~= nil or options.inactiveIn ~= nil) then {
|
|
63
|
+
activeIn = options.activeIn,
|
|
64
|
+
inactiveIn = options.inactiveIn,
|
|
65
|
+
} else NO_CONDITION
|
|
66
|
+
--* Every registration: the definition's providers, followed by whatever the plugins register.
|
|
67
|
+
local _array = {}
|
|
68
|
+
local _length = #_array
|
|
69
|
+
local _array_1 = state.providers
|
|
70
|
+
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
71
|
+
local registered = _array
|
|
72
|
+
--* The registrations whose conditions hold, decided once every plugin has been set up.
|
|
73
|
+
local providers = {}
|
|
74
|
+
--* The registrations left out, by id, with the conditions that were judged: for the error a miss gets.
|
|
75
|
+
local skipped = {}
|
|
76
|
+
--* Modules searched after this one's own providers, in order. Ignited before this one, and extinguished after.
|
|
77
|
+
local _result = options
|
|
78
|
+
if _result ~= nil then
|
|
79
|
+
_result = _result.imports
|
|
80
|
+
end
|
|
81
|
+
local _condition = _result
|
|
82
|
+
if _condition == nil then
|
|
83
|
+
_condition = {}
|
|
84
|
+
end
|
|
85
|
+
local imports = _condition
|
|
86
|
+
--* Modules that import this one. They go down before this one does.
|
|
87
|
+
local importers = {}
|
|
88
|
+
local instantiatedProviders = {}
|
|
89
|
+
--* Objects handed over by `provideInstance`, attached to their interfaces once every plugin is set up.
|
|
90
|
+
local providedInstances = {}
|
|
91
|
+
local observers = {}
|
|
92
|
+
local hooks = {}
|
|
93
|
+
local includedPlugins = {}
|
|
94
|
+
local filledSlots = {}
|
|
95
|
+
local temporaryInstances = {}
|
|
96
|
+
local moduleInitState = ModuleInitState.Created
|
|
97
|
+
local switchInitState = function(from, to)
|
|
98
|
+
if moduleInitState ~= from then
|
|
99
|
+
error(`module '{state.debugName}' is in invalid state when transitioning to '{ModuleInitState[to]}', got '{ModuleInitState[moduleInitState]}' when '{ModuleInitState[from]}' was expected.`)
|
|
100
|
+
end
|
|
101
|
+
moduleInitState = to
|
|
102
|
+
end
|
|
103
|
+
local assertAlive = function(action)
|
|
104
|
+
if moduleInitState >= ModuleInitState.Extinguishing then
|
|
105
|
+
error(`module '{state.debugName}' has been extinguished, cannot {action}`)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
local assertNotProvided = function(injectionId)
|
|
109
|
+
local _injectionId = injectionId
|
|
110
|
+
if instantiatedProviders[_injectionId] ~= nil then
|
|
111
|
+
error(`module '{state.debugName}': provider ID was registered more than once: {injectionId}`)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
local registerClassProvider = function(provider, registrationOptions)
|
|
115
|
+
local injectionId = getProviderClassId(provider)
|
|
116
|
+
local _arg0 = {
|
|
117
|
+
config = normalizeProviderConfig(withOptions({
|
|
118
|
+
type = "class",
|
|
119
|
+
value = provider,
|
|
120
|
+
}, registrationOptions)),
|
|
121
|
+
injectionId = injectionId,
|
|
122
|
+
}
|
|
123
|
+
table.insert(registered, _arg0)
|
|
124
|
+
end
|
|
125
|
+
--* Own metadata only, as the builder does: an undecorated subclass of a provider is skipped.
|
|
126
|
+
local registerProviderClasses = function(classes, registrationOptions)
|
|
127
|
+
for _, provider in classes do
|
|
128
|
+
if Reflect.hasOwnMetadata(provider, "flamework:provider") then
|
|
129
|
+
registerClassProvider(provider, registrationOptions)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
--[[
|
|
134
|
+
*
|
|
135
|
+
* Sets a plugin up against this module, once. A plugin reached again -- included by the module
|
|
136
|
+
* and by a plugin, or by two plugins -- is skipped, and so is one that includes itself through
|
|
137
|
+
* another: it is marked before its setup runs, so the ring stops on the second arrival.
|
|
138
|
+
*
|
|
139
|
+
* An inclusion given a scope condition that does not hold is skipped entirely: the setup does
|
|
140
|
+
* not run, so nothing it would have registered exists. The plugin itself is not marked, since
|
|
141
|
+
* it is the inclusion that was scoped, not the plugin.
|
|
142
|
+
|
|
143
|
+
]]
|
|
144
|
+
local pluginTarget
|
|
145
|
+
local includePlugin = function(plugin, scope)
|
|
146
|
+
local _plugin = plugin
|
|
147
|
+
if includedPlugins[_plugin] ~= nil then
|
|
148
|
+
return nil
|
|
149
|
+
end
|
|
150
|
+
if not holdsCondition(scope) then
|
|
151
|
+
return nil
|
|
152
|
+
end
|
|
153
|
+
-- The builder swaps a slotted plugin for the one in its slot, so by the time a module
|
|
154
|
+
-- ignites the only way to reach a second one is through a plugin. That is refused rather
|
|
155
|
+
-- than run: two lifecycle plugins would tick everything twice.
|
|
156
|
+
if plugin.slot ~= nil then
|
|
157
|
+
local _slot = plugin.slot
|
|
158
|
+
local occupant = filledSlots[_slot]
|
|
159
|
+
if occupant ~= nil then
|
|
160
|
+
error(`module '{state.debugName}': plugin '{plugin.name}' fills the '{plugin.slot}' slot that plugin '{occupant.name}' already fills; include it on the module instead`)
|
|
161
|
+
end
|
|
162
|
+
local _slot_1 = plugin.slot
|
|
163
|
+
local _plugin_1 = plugin
|
|
164
|
+
filledSlots[_slot_1] = _plugin_1
|
|
165
|
+
end
|
|
166
|
+
local _plugin_1 = plugin
|
|
167
|
+
includedPlugins[_plugin_1] = true
|
|
168
|
+
plugin.setup(pluginTarget)
|
|
169
|
+
end
|
|
170
|
+
local lookupInImports = function(injectionId)
|
|
171
|
+
for _, imported in imports do
|
|
172
|
+
local found = imported.lookupProvider(injectionId)
|
|
173
|
+
if found ~= nil then
|
|
174
|
+
return found
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
--[[
|
|
179
|
+
*
|
|
180
|
+
* Decides which registrations this ignition keeps: those whose conditions -- the module's, the
|
|
181
|
+
* registration's own, and the class's decorator -- all hold. Two registrations may share an id
|
|
182
|
+
* as long as at most one of them is kept, which is how a fake takes a real provider's place
|
|
183
|
+
* under one scope.
|
|
184
|
+
*
|
|
185
|
+
* A class an import already resolves to is not constructed again here: the import's instance
|
|
186
|
+
* answers, so the registration is dropped unless it asked to be isolated. A different class
|
|
187
|
+
* under the same id is kept, and answers ahead of the import's.
|
|
188
|
+
|
|
189
|
+
]]
|
|
190
|
+
local activateProviders = function()
|
|
191
|
+
local active = {}
|
|
192
|
+
local seen = {}
|
|
193
|
+
for _, entry in registered do
|
|
194
|
+
local conditions = { moduleScope, entry.config, getProviderClassScope(entry.config) }
|
|
195
|
+
if not holdsEveryCondition(conditions) then
|
|
196
|
+
local _injectionId = entry.injectionId
|
|
197
|
+
skipped[_injectionId] = conditions
|
|
198
|
+
continue
|
|
199
|
+
end
|
|
200
|
+
if entry.config.type == "class" and entry.config.isolated ~= true then
|
|
201
|
+
local found = lookupInImports(entry.injectionId)
|
|
202
|
+
if found ~= nil and found.classValue == entry.config.value then
|
|
203
|
+
continue
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
assertNotProvided(entry.injectionId)
|
|
207
|
+
local _injectionId = entry.injectionId
|
|
208
|
+
if seen[_injectionId] ~= nil then
|
|
209
|
+
error(`module '{state.debugName}': provider ID was registered more than once: {entry.injectionId}`)
|
|
210
|
+
end
|
|
211
|
+
local _injectionId_1 = entry.injectionId
|
|
212
|
+
seen[_injectionId_1] = true
|
|
213
|
+
table.insert(active, entry)
|
|
214
|
+
end
|
|
215
|
+
providers = active
|
|
216
|
+
end
|
|
217
|
+
local registerHook = function(phase, callback, priority)
|
|
218
|
+
local _object = {
|
|
219
|
+
phase = phase,
|
|
220
|
+
callback = callback,
|
|
221
|
+
}
|
|
222
|
+
local _left = "priority"
|
|
223
|
+
local _condition_1 = priority
|
|
224
|
+
if _condition_1 == nil then
|
|
225
|
+
_condition_1 = HookPriority.Normal
|
|
226
|
+
end
|
|
227
|
+
_object[_left] = _condition_1
|
|
228
|
+
table.insert(hooks, _object)
|
|
229
|
+
end
|
|
230
|
+
local sortedHooks = function(phase)
|
|
231
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
232
|
+
local _newValue = {}
|
|
233
|
+
local _callback = function(hook)
|
|
234
|
+
return hook.phase == phase
|
|
235
|
+
end
|
|
236
|
+
local _length_1 = 0
|
|
237
|
+
for _k, _v in hooks do
|
|
238
|
+
if _callback(_v, _k - 1, hooks) == true then
|
|
239
|
+
_length_1 += 1
|
|
240
|
+
_newValue[_length_1] = _v
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
244
|
+
local matching = _newValue
|
|
245
|
+
-- `table.sort` is not stable, so hooks of equal priority are ordered by the position they
|
|
246
|
+
-- were registered at to keep registration order meaningful.
|
|
247
|
+
local order = {}
|
|
248
|
+
-- ▼ ReadonlyArray.forEach ▼
|
|
249
|
+
local _callback_1 = function(hook, index)
|
|
250
|
+
local _hook = hook
|
|
251
|
+
local _index = index
|
|
252
|
+
order[_hook] = _index
|
|
253
|
+
return order
|
|
254
|
+
end
|
|
255
|
+
for _k, _v in matching do
|
|
256
|
+
_callback_1(_v, _k - 1, matching)
|
|
257
|
+
end
|
|
258
|
+
-- ▲ ReadonlyArray.forEach ▲
|
|
259
|
+
table.sort(matching, function(a, b)
|
|
260
|
+
if a.priority ~= b.priority then
|
|
261
|
+
return a.priority < b.priority
|
|
262
|
+
end
|
|
263
|
+
local _a = a
|
|
264
|
+
local _exp = order[_a]
|
|
265
|
+
local _b = b
|
|
266
|
+
return _exp < order[_b]
|
|
267
|
+
end)
|
|
268
|
+
return matching
|
|
269
|
+
end
|
|
270
|
+
local module
|
|
271
|
+
local runHooks = function(phase)
|
|
272
|
+
for _, hook in sortedHooks(phase) do
|
|
273
|
+
hook.callback(module)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
--[[
|
|
277
|
+
*
|
|
278
|
+
* Runs one step of a teardown, reporting a raise rather than letting it out, so that the steps
|
|
279
|
+
* after it still run: one hook or removal callback that fails must not leave the module holding
|
|
280
|
+
* the rest -- the guarantee the lifecycle plugin gives for its own handlers, made the module's.
|
|
281
|
+
|
|
282
|
+
]]
|
|
283
|
+
local guarded = function(what, callback)
|
|
284
|
+
local success, err = pcall(callback)
|
|
285
|
+
if not success then
|
|
286
|
+
warn(`[Flamework] module '{state.debugName}': {what} failed: {tostring(err)}`)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
--[[
|
|
290
|
+
*
|
|
291
|
+
* Attaches an object to every observer of every interface it implements, all or nothing: an
|
|
292
|
+
* observer that raises from its `onAdded` has the ones before it told `onRemoved`, and the
|
|
293
|
+
* error comes out, so a refused object is attached nowhere -- rather than left ticking in the
|
|
294
|
+
* lifecycle's sets, with no handle to remove it by.
|
|
295
|
+
|
|
296
|
+
]]
|
|
297
|
+
local registerClassInterfaces = function(instance, kind)
|
|
298
|
+
local added = {}
|
|
299
|
+
local success, err = pcall(function()
|
|
300
|
+
for _, interfaceId in getClassImplements(instance) do
|
|
301
|
+
local interested = observers[interfaceId]
|
|
302
|
+
if not interested then
|
|
303
|
+
continue
|
|
304
|
+
end
|
|
305
|
+
for _1, observer in interested do
|
|
306
|
+
local _result_1 = observer.onAdded
|
|
307
|
+
if _result_1 ~= nil then
|
|
308
|
+
_result_1(instance, {
|
|
309
|
+
interfaceId = interfaceId,
|
|
310
|
+
kind = kind,
|
|
311
|
+
})
|
|
312
|
+
end
|
|
313
|
+
local _arg0 = { interfaceId, observer }
|
|
314
|
+
table.insert(added, _arg0)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end)
|
|
318
|
+
if not success then
|
|
319
|
+
-- Undone in reverse, each step guarded, so that one `onRemoved` raising does not leave
|
|
320
|
+
-- the rest attached; the observer's error is what comes out.
|
|
321
|
+
for i = #added - 1, 0, -1 do
|
|
322
|
+
local _binding = added[i + 1]
|
|
323
|
+
local interfaceId = _binding[1]
|
|
324
|
+
local observer = _binding[2]
|
|
325
|
+
guarded("undoing an attachment", function()
|
|
326
|
+
local _result_1 = observer.onRemoved
|
|
327
|
+
if _result_1 ~= nil then
|
|
328
|
+
_result_1 = _result_1(instance, {
|
|
329
|
+
interfaceId = interfaceId,
|
|
330
|
+
kind = kind,
|
|
331
|
+
})
|
|
332
|
+
end
|
|
333
|
+
return _result_1
|
|
334
|
+
end)
|
|
335
|
+
end
|
|
336
|
+
error(err, 0)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
local unregisterClassInterfaces = function(instance, kind)
|
|
340
|
+
for _, interfaceId in getClassImplements(instance) do
|
|
341
|
+
local interested = observers[interfaceId]
|
|
342
|
+
if not interested then
|
|
343
|
+
continue
|
|
344
|
+
end
|
|
345
|
+
for _1, observer in interested do
|
|
346
|
+
local _result_1 = observer.onRemoved
|
|
347
|
+
if _result_1 ~= nil then
|
|
348
|
+
_result_1(instance, {
|
|
349
|
+
interfaceId = interfaceId,
|
|
350
|
+
kind = kind,
|
|
351
|
+
})
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
local resolveDependencyWithOrigin
|
|
357
|
+
local instantiateClassWithDependencies = function(constructor, resolve)
|
|
358
|
+
local dependencies = Reflect.getMetadata(constructor, "flamework:dependencies") or {}
|
|
359
|
+
local resolvedParameters = {}
|
|
360
|
+
for _, dependency in dependencies do
|
|
361
|
+
local _result_1 = resolve
|
|
362
|
+
if _result_1 ~= nil then
|
|
363
|
+
_result_1 = _result_1(dependency)
|
|
364
|
+
end
|
|
365
|
+
local _condition_1 = _result_1
|
|
366
|
+
if _condition_1 == nil then
|
|
367
|
+
_condition_1 = resolveDependencyWithOrigin(dependency, constructor)
|
|
368
|
+
end
|
|
369
|
+
table.insert(resolvedParameters, _condition_1)
|
|
370
|
+
end
|
|
371
|
+
return constructor.new(unpack(resolvedParameters))
|
|
372
|
+
end
|
|
373
|
+
local tryResolveDependency
|
|
374
|
+
tryResolveDependency = function(info, requestingOrigin)
|
|
375
|
+
if moduleInitState <= ModuleInitState.PreIgniting then
|
|
376
|
+
error(`module '{state.debugName}' is in pre-ignite phase, dependency cannot be resolved: {info.id}`)
|
|
377
|
+
end
|
|
378
|
+
-- Extinguished hooks and removal callbacks may still resolve siblings while extinguishing,
|
|
379
|
+
-- so only a fully extinguished module refuses.
|
|
380
|
+
if moduleInitState == ModuleInitState.Extinguished then
|
|
381
|
+
error(`module '{state.debugName}' has been extinguished, dependency cannot be resolved: {info.id}`)
|
|
382
|
+
end
|
|
383
|
+
local _id = info.id
|
|
384
|
+
local instantiatedProvider = instantiatedProviders[_id]
|
|
385
|
+
if instantiatedProvider ~= nil then
|
|
386
|
+
return instantiatedProvider
|
|
387
|
+
end
|
|
388
|
+
-- The Module type always refers to the current module instantiation.
|
|
389
|
+
if info.id == MODULE_ID then
|
|
390
|
+
return module
|
|
391
|
+
end
|
|
392
|
+
-- ▼ ReadonlyArray.find ▼
|
|
393
|
+
local _callback = function(v)
|
|
394
|
+
return v.injectionId == info.id
|
|
395
|
+
end
|
|
396
|
+
local _result_1
|
|
397
|
+
for _i, _v in providers do
|
|
398
|
+
if _callback(_v, _i - 1, providers) == true then
|
|
399
|
+
_result_1 = _v
|
|
400
|
+
break
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
-- ▲ ReadonlyArray.find ▲
|
|
404
|
+
local moduleProvider = _result_1
|
|
405
|
+
if moduleProvider then
|
|
406
|
+
local config = moduleProvider.config
|
|
407
|
+
if config.type == "class" then
|
|
408
|
+
local instantiatedProvider = instantiateClassWithDependencies(config.value)
|
|
409
|
+
local _id_1 = info.id
|
|
410
|
+
instantiatedProviders[_id_1] = instantiatedProvider
|
|
411
|
+
registerClassInterfaces(instantiatedProvider, "provider")
|
|
412
|
+
return instantiatedProvider
|
|
413
|
+
elseif config.type == "function" then
|
|
414
|
+
-- Function providers are not cached.
|
|
415
|
+
-- It is up to the provider to decide whether to cache dependency resolution, based on the injection context.
|
|
416
|
+
return config.callback({
|
|
417
|
+
injectionId = info.id,
|
|
418
|
+
dependencyInfo = info,
|
|
419
|
+
module = module,
|
|
420
|
+
origin = requestingOrigin,
|
|
421
|
+
})
|
|
422
|
+
elseif config.type == "alias" then
|
|
423
|
+
return tryResolveDependency(convertConciseDependencyInfo(config.injectionId), requestingOrigin)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
-- Through the owner's resolver, so that a lazy provider is constructed by the module that
|
|
427
|
+
-- registered it and joins that module's interfaces, not this one's.
|
|
428
|
+
for _, imported in imports do
|
|
429
|
+
local found = imported.tryResolveDependency(info, requestingOrigin)
|
|
430
|
+
if found ~= nil then
|
|
431
|
+
return found
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
resolveDependencyWithOrigin = function(info, requestingOrigin)
|
|
436
|
+
local dependency = tryResolveDependency(info, requestingOrigin)
|
|
437
|
+
if dependency == nil then
|
|
438
|
+
local _result_1
|
|
439
|
+
if #imports > 0 then
|
|
440
|
+
-- ▼ ReadonlyArray.map ▼
|
|
441
|
+
local _newValue = table.create(#imports)
|
|
442
|
+
local _callback = function(v)
|
|
443
|
+
return v.debugName
|
|
444
|
+
end
|
|
445
|
+
for _k, _v in imports do
|
|
446
|
+
_newValue[_k] = _callback(_v, _k - 1, imports)
|
|
447
|
+
end
|
|
448
|
+
-- ▲ ReadonlyArray.map ▲
|
|
449
|
+
_result_1 = `; not found in imports [{table.concat(_newValue, ", ")}]`
|
|
450
|
+
else
|
|
451
|
+
_result_1 = ""
|
|
452
|
+
end
|
|
453
|
+
local searched = _result_1
|
|
454
|
+
local _id = info.id
|
|
455
|
+
local inactive = skipped[_id]
|
|
456
|
+
if inactive ~= nil then
|
|
457
|
+
error(`module '{state.debugName}' could not resolve dependency '{info.id}': it is registered but inactive ({describeConditions(inactive)}){searched}`)
|
|
458
|
+
end
|
|
459
|
+
error(`module '{state.debugName}' could not resolve dependency '{info.id}'{searched}`)
|
|
460
|
+
end
|
|
461
|
+
return dependency
|
|
462
|
+
end
|
|
463
|
+
local lookupProvider = function(injectionId)
|
|
464
|
+
-- ▼ ReadonlyArray.find ▼
|
|
465
|
+
local _callback = function(v)
|
|
466
|
+
return v.injectionId == injectionId
|
|
467
|
+
end
|
|
468
|
+
local _result_1
|
|
469
|
+
for _i, _v in providers do
|
|
470
|
+
if _callback(_v, _i - 1, providers) == true then
|
|
471
|
+
_result_1 = _v
|
|
472
|
+
break
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
-- ▲ ReadonlyArray.find ▲
|
|
476
|
+
local own = _result_1
|
|
477
|
+
if own ~= nil then
|
|
478
|
+
return {
|
|
479
|
+
module = module,
|
|
480
|
+
classValue = if own.config.type == "class" then own.config.value else nil,
|
|
481
|
+
}
|
|
482
|
+
end
|
|
483
|
+
-- A provided instance is an object, not a class, so nothing registered here is the same class as it.
|
|
484
|
+
local _injectionId = injectionId
|
|
485
|
+
if instantiatedProviders[_injectionId] ~= nil then
|
|
486
|
+
return {
|
|
487
|
+
module = module,
|
|
488
|
+
}
|
|
489
|
+
end
|
|
490
|
+
return lookupInImports(injectionId)
|
|
491
|
+
end
|
|
492
|
+
local resolveDependency = function(info)
|
|
493
|
+
local _arg0 = info ~= nil
|
|
494
|
+
assert(_arg0)
|
|
495
|
+
return resolveDependencyWithOrigin(convertConciseDependencyInfo(info))
|
|
496
|
+
end
|
|
497
|
+
local createClassInstance = function(constructor, config)
|
|
498
|
+
assertAlive("create class instances")
|
|
499
|
+
local _exp = constructor
|
|
500
|
+
local _result_1 = config
|
|
501
|
+
if _result_1 ~= nil then
|
|
502
|
+
_result_1 = _result_1.overrideDependency
|
|
503
|
+
end
|
|
504
|
+
local instance = instantiateClassWithDependencies(_exp, _result_1)
|
|
505
|
+
-- Attached before it is held: an observer that refuses it leaves it attached nowhere, so
|
|
506
|
+
-- there is nothing for the module to hold, or to release on extinguish.
|
|
507
|
+
registerClassInterfaces(instance, "instance")
|
|
508
|
+
temporaryInstances[instance] = true
|
|
509
|
+
return instance
|
|
510
|
+
end
|
|
511
|
+
local removeClassInstance = function(instance)
|
|
512
|
+
local _instance = instance
|
|
513
|
+
if not (temporaryInstances[_instance] ~= nil) then
|
|
514
|
+
return nil
|
|
515
|
+
end
|
|
516
|
+
unregisterClassInterfaces(instance, "instance")
|
|
517
|
+
local _instance_1 = instance
|
|
518
|
+
temporaryInstances[_instance_1] = nil
|
|
519
|
+
end
|
|
520
|
+
local listen = function(param, metaId, metaKey)
|
|
521
|
+
local _arg0 = metaId ~= nil
|
|
522
|
+
assert(_arg0)
|
|
523
|
+
assertAlive("listen for lifecycle events")
|
|
524
|
+
local listener
|
|
525
|
+
if metaKey == nil then
|
|
526
|
+
-- Non-shorthand
|
|
527
|
+
-- We create a proxy object so that we have a unique reference for this specific listener.
|
|
528
|
+
-- It stands in for the object without being it: a method reached through it runs with
|
|
529
|
+
-- the object as `self`, so what the method writes to `this` lands on the object; and
|
|
530
|
+
-- its `__index` is a function rather than the object, which is where the metadata walk
|
|
531
|
+
-- stops -- through the object it went on to the object's class, and attached the proxy
|
|
532
|
+
-- to every interface the class implements instead of the one named here.
|
|
533
|
+
local target = param
|
|
534
|
+
listener = setmetatable({}, {
|
|
535
|
+
__index = function(_, key)
|
|
536
|
+
local value = target[key]
|
|
537
|
+
if type(value) == "function" then
|
|
538
|
+
return function(_self, ...)
|
|
539
|
+
local args = { ... }
|
|
540
|
+
return value(target, unpack(args))
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
return value
|
|
544
|
+
end,
|
|
545
|
+
})
|
|
546
|
+
-- The class's identifier is what the object was profiled under through the old walk.
|
|
547
|
+
local identifier = Reflect.getMetadata(target, "identifier")
|
|
548
|
+
if identifier ~= nil then
|
|
549
|
+
Reflect.defineMetadata(listener, "identifier", identifier)
|
|
550
|
+
end
|
|
551
|
+
else
|
|
552
|
+
local _arg0_1 = type(param) == "function"
|
|
553
|
+
assert(_arg0_1)
|
|
554
|
+
listener = {
|
|
555
|
+
[metaKey] = function(self, ...)
|
|
556
|
+
local args = { ... }
|
|
557
|
+
return param(unpack(args))
|
|
558
|
+
end,
|
|
559
|
+
}
|
|
560
|
+
end
|
|
561
|
+
-- Register the lifecycle event
|
|
562
|
+
Reflect.defineMetadata(listener, "flamework:implements", { metaId })
|
|
563
|
+
-- Attached before it is held, as in `createClassInstance`.
|
|
564
|
+
registerClassInterfaces(listener, "instance")
|
|
565
|
+
local _listener = listener
|
|
566
|
+
temporaryInstances[_listener] = true
|
|
567
|
+
return function()
|
|
568
|
+
local _arg0_1 = listener ~= nil
|
|
569
|
+
assert(_arg0_1, "listeners cannot be destructed more than once")
|
|
570
|
+
removeClassInstance(listener)
|
|
571
|
+
listener = nil
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
local release
|
|
575
|
+
local ignite = function()
|
|
576
|
+
-- Checked before anything changes: an import that is not ready leaves this module as it was,
|
|
577
|
+
-- and `ignite()` being synchronous is what makes "ignite it first" a line order.
|
|
578
|
+
for _, imported in imports do
|
|
579
|
+
if not imported.isIgnited() then
|
|
580
|
+
error(`module '{state.debugName}': imported module '{imported.debugName}' is not ignited; ignite it first`)
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
-- Raises on a module that has ignited already: a definition is what gets ignited twice.
|
|
584
|
+
switchInitState(ModuleInitState.Created, ModuleInitState.PreIgniting)
|
|
585
|
+
-- From here on the module holds things: what the plugins set up, then the providers, then
|
|
586
|
+
-- what the hooks connect. A raise anywhere in it takes the module down the way `extinguish`
|
|
587
|
+
-- does before it comes out, so that a failed ignition holds nothing. The lifecycle plugin's
|
|
588
|
+
-- postIgnite hook connects the RunService signals, and a hook after it that raised used to
|
|
589
|
+
-- leave those ticking a module stuck in `Igniting`, which nothing could extinguish.
|
|
590
|
+
local success, err = pcall(function()
|
|
591
|
+
-- Plugins are set up before any provider exists, so a setup that resolves is refused the
|
|
592
|
+
-- same way an `onPreIgnite` hook is.
|
|
593
|
+
for _, inclusion in state.plugins do
|
|
594
|
+
includePlugin(inclusion.plugin, inclusion.scope)
|
|
595
|
+
end
|
|
596
|
+
runHooks("preIgnite")
|
|
597
|
+
-- Every registration is in by now, the plugins' included, so this is where the conditions
|
|
598
|
+
-- are judged and the ids checked for collisions among what is kept.
|
|
599
|
+
activateProviders()
|
|
600
|
+
switchInitState(ModuleInitState.PreIgniting, ModuleInitState.Igniting)
|
|
601
|
+
-- Provided instances exist already; here they join the interfaces they implement, now that
|
|
602
|
+
-- every plugin's observers are in place.
|
|
603
|
+
for _, instance in providedInstances do
|
|
604
|
+
registerClassInterfaces(instance, "provider")
|
|
605
|
+
end
|
|
606
|
+
for _, provider in providers do
|
|
607
|
+
-- Lazy providers are constructed the first time they are resolved instead.
|
|
608
|
+
if provider.config.type == "class" and provider.config.lazy ~= true then
|
|
609
|
+
resolveDependency(provider.injectionId)
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
runHooks("postIgnite")
|
|
613
|
+
end)
|
|
614
|
+
if not success then
|
|
615
|
+
moduleInitState = ModuleInitState.Extinguishing
|
|
616
|
+
release()
|
|
617
|
+
error(err, 0)
|
|
618
|
+
end
|
|
619
|
+
switchInitState(ModuleInitState.Igniting, ModuleInitState.Ignited)
|
|
620
|
+
-- Told last, once this module is whole: an importer only counts once it can be extinguished.
|
|
621
|
+
for _, imported in imports do
|
|
622
|
+
imported.addImporter(module)
|
|
623
|
+
end
|
|
624
|
+
return module
|
|
625
|
+
end
|
|
626
|
+
local extinguish = function()
|
|
627
|
+
switchInitState(ModuleInitState.Ignited, ModuleInitState.Extinguishing)
|
|
628
|
+
-- Importers hold this module's instances, so they go first, and each takes its own importers
|
|
629
|
+
-- down before it returns: the deepest goes first. Copied, since each removes itself.
|
|
630
|
+
local _array_2 = {}
|
|
631
|
+
local _length_1 = #_array_2
|
|
632
|
+
for _v in importers do
|
|
633
|
+
_length_1 += 1
|
|
634
|
+
_array_2[_length_1] = _v
|
|
635
|
+
end
|
|
636
|
+
for _, importer in _array_2 do
|
|
637
|
+
if not importer.isExtinguished() then
|
|
638
|
+
importer.extinguish()
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
table.clear(importers)
|
|
642
|
+
release()
|
|
643
|
+
end
|
|
644
|
+
--[[
|
|
645
|
+
*
|
|
646
|
+
* Lets go of everything this instantiation holds, from `Extinguishing`: the extinguished hooks
|
|
647
|
+
* run, the instances it created and the providers it constructed leave their interfaces, and
|
|
648
|
+
* the default is released. Shared by `extinguish` and by an ignition that raised, so the hooks
|
|
649
|
+
* that ran before the raise are undone by the same hooks that undo them on extinguish.
|
|
650
|
+
*
|
|
651
|
+
* Every step is guarded: one that raises is warned about and the rest still run, so the module
|
|
652
|
+
* cannot get stuck half-extinguished -- which used to hold it, and everything in it, for good,
|
|
653
|
+
* as the default `Dependency<T>()` answered from.
|
|
654
|
+
|
|
655
|
+
]]
|
|
656
|
+
release = function()
|
|
657
|
+
for _, hook in sortedHooks("extinguished") do
|
|
658
|
+
guarded("an onExtinguished hook", function()
|
|
659
|
+
return hook.callback(module)
|
|
660
|
+
end)
|
|
661
|
+
end
|
|
662
|
+
-- Copied first: removal callbacks may themselves remove instances.
|
|
663
|
+
local _array_2 = {}
|
|
664
|
+
local _length_1 = #_array_2
|
|
665
|
+
for _v in temporaryInstances do
|
|
666
|
+
_length_1 += 1
|
|
667
|
+
_array_2[_length_1] = _v
|
|
668
|
+
end
|
|
669
|
+
for _, temporaryInstance in _array_2 do
|
|
670
|
+
guarded("removing an instance", function()
|
|
671
|
+
return removeClassInstance(temporaryInstance)
|
|
672
|
+
end)
|
|
673
|
+
end
|
|
674
|
+
-- Providers join their interfaces when they are instantiated, so they have to leave them
|
|
675
|
+
-- too. Without this, a plugin such as the lifecycle plugin keeps holding (and ticking)
|
|
676
|
+
-- providers that belong to an extinguished module.
|
|
677
|
+
for _, provider in instantiatedProviders do
|
|
678
|
+
guarded("removing a provider", function()
|
|
679
|
+
return unregisterClassInterfaces(provider, "provider")
|
|
680
|
+
end)
|
|
681
|
+
end
|
|
682
|
+
-- A removal that raised leaves its instance behind, and nothing can add one any more.
|
|
683
|
+
table.clear(temporaryInstances)
|
|
684
|
+
table.clear(instantiatedProviders)
|
|
685
|
+
switchInitState(ModuleInitState.Extinguishing, ModuleInitState.Extinguished)
|
|
686
|
+
for _, imported in imports do
|
|
687
|
+
imported.removeImporter(module)
|
|
688
|
+
end
|
|
689
|
+
-- Released last, once nothing in here can resolve any more, so that the next root ignited
|
|
690
|
+
-- becomes the default rather than `Dependency<T>()` answering from a dead module.
|
|
691
|
+
clearDefaultModule(module)
|
|
692
|
+
end
|
|
693
|
+
local isExtinguished = function()
|
|
694
|
+
return moduleInitState >= ModuleInitState.Extinguishing
|
|
695
|
+
end
|
|
696
|
+
local isIgnited = function()
|
|
697
|
+
return moduleInitState == ModuleInitState.Ignited
|
|
698
|
+
end
|
|
699
|
+
module = {
|
|
700
|
+
debugName = state.debugName,
|
|
701
|
+
tryResolveDependency = tryResolveDependency,
|
|
702
|
+
resolveDependency = resolveDependency,
|
|
703
|
+
listen = listen,
|
|
704
|
+
createClassInstance = createClassInstance,
|
|
705
|
+
removeClassInstance = removeClassInstance,
|
|
706
|
+
ignite = ignite,
|
|
707
|
+
extinguish = extinguish,
|
|
708
|
+
isExtinguished = isExtinguished,
|
|
709
|
+
isIgnited = isIgnited,
|
|
710
|
+
lookupProvider = lookupProvider,
|
|
711
|
+
addImporter = function(importer)
|
|
712
|
+
local _importer = importer
|
|
713
|
+
importers[_importer] = true
|
|
714
|
+
return importers
|
|
715
|
+
end,
|
|
716
|
+
removeImporter = function(importer)
|
|
717
|
+
local _importer = importer
|
|
718
|
+
-- ▼ Set.delete ▼
|
|
719
|
+
local _valueExisted = importers[_importer] ~= nil
|
|
720
|
+
importers[_importer] = nil
|
|
721
|
+
-- ▲ Set.delete ▲
|
|
722
|
+
return _valueExisted
|
|
723
|
+
end,
|
|
724
|
+
}
|
|
725
|
+
--* What a plugin's setup is handed. Everything registers into this instantiation.
|
|
726
|
+
pluginTarget = {
|
|
727
|
+
module = module,
|
|
728
|
+
scope = if hasCondition(moduleScope) then moduleScope else nil,
|
|
729
|
+
isActive = function(...)
|
|
730
|
+
local conditions = { ... }
|
|
731
|
+
local _array_2 = { moduleScope }
|
|
732
|
+
local _length_1 = #_array_2
|
|
733
|
+
table.move(conditions, 1, #conditions, _length_1 + 1, _array_2)
|
|
734
|
+
return holdsEveryCondition(_array_2)
|
|
735
|
+
end,
|
|
736
|
+
registerClassProvider = registerClassProvider,
|
|
737
|
+
registerProviders = function(_path, registrationOptions, resolved)
|
|
738
|
+
local _arg0 = resolved ~= nil
|
|
739
|
+
assert(_arg0)
|
|
740
|
+
registerProviderClasses(getClassesInPath(resolved), registrationOptions)
|
|
741
|
+
end,
|
|
742
|
+
registerProvidersGlob = function(_glob, registrationOptions, resolved)
|
|
743
|
+
local _arg0 = resolved ~= nil
|
|
744
|
+
assert(_arg0)
|
|
745
|
+
registerProviderClasses(getClassesInGlob(resolved), registrationOptions)
|
|
746
|
+
end,
|
|
747
|
+
registerProvider = function(config, injectionId)
|
|
748
|
+
local _arg0 = injectionId ~= nil
|
|
749
|
+
assert(_arg0)
|
|
750
|
+
local _arg0_1 = {
|
|
751
|
+
config = normalizeProviderConfig(config),
|
|
752
|
+
injectionId = injectionId,
|
|
753
|
+
}
|
|
754
|
+
table.insert(registered, _arg0_1)
|
|
755
|
+
end,
|
|
756
|
+
provideInstance = function(instance, injectionId)
|
|
757
|
+
local _arg0 = injectionId ~= nil
|
|
758
|
+
assert(_arg0)
|
|
759
|
+
assertNotProvided(injectionId)
|
|
760
|
+
local _injectionId = injectionId
|
|
761
|
+
local _instance = instance
|
|
762
|
+
instantiatedProviders[_injectionId] = _instance
|
|
763
|
+
local _instance_1 = instance
|
|
764
|
+
table.insert(providedInstances, _instance_1)
|
|
765
|
+
end,
|
|
766
|
+
includePlugin = includePlugin,
|
|
767
|
+
onPreIgnite = function(callback, hookOptions)
|
|
768
|
+
local _exp = callback
|
|
769
|
+
local _result_1 = hookOptions
|
|
770
|
+
if _result_1 ~= nil then
|
|
771
|
+
_result_1 = _result_1.priority
|
|
772
|
+
end
|
|
773
|
+
return registerHook("preIgnite", _exp, _result_1)
|
|
774
|
+
end,
|
|
775
|
+
onPostIgnite = function(callback, hookOptions)
|
|
776
|
+
local _exp = callback
|
|
777
|
+
local _result_1 = hookOptions
|
|
778
|
+
if _result_1 ~= nil then
|
|
779
|
+
_result_1 = _result_1.priority
|
|
780
|
+
end
|
|
781
|
+
return registerHook("postIgnite", _exp, _result_1)
|
|
782
|
+
end,
|
|
783
|
+
onExtinguished = function(callback, hookOptions)
|
|
784
|
+
local _exp = callback
|
|
785
|
+
local _result_1 = hookOptions
|
|
786
|
+
if _result_1 ~= nil then
|
|
787
|
+
_result_1 = _result_1.priority
|
|
788
|
+
end
|
|
789
|
+
return registerHook("extinguished", _exp, _result_1)
|
|
790
|
+
end,
|
|
791
|
+
observe = function(config, interfaceId)
|
|
792
|
+
local _arg0 = interfaceId ~= nil
|
|
793
|
+
assert(_arg0)
|
|
794
|
+
local _interfaceId = interfaceId
|
|
795
|
+
local interested = observers[_interfaceId]
|
|
796
|
+
if not interested then
|
|
797
|
+
local _exp = interfaceId
|
|
798
|
+
interested = {}
|
|
799
|
+
local _interested = interested
|
|
800
|
+
observers[_exp] = _interested
|
|
801
|
+
end
|
|
802
|
+
local _interested = interested
|
|
803
|
+
local _config = config
|
|
804
|
+
table.insert(_interested, _config)
|
|
805
|
+
end,
|
|
806
|
+
}
|
|
807
|
+
return module
|
|
808
|
+
end
|
|
809
|
+
return {
|
|
810
|
+
createModuleInstantiation = createModuleInstantiation,
|
|
811
|
+
}
|