@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
package/out/module/module.luau
CHANGED
|
@@ -5,11 +5,20 @@ local convertConciseDependencyInfo = TS.import(script, script.Parent.Parent, "ut
|
|
|
5
5
|
local getClassImplements = TS.import(script, script.Parent.Parent, "utility", "getClassImplements").getClassImplements
|
|
6
6
|
local getClassesInPath = TS.import(script, script.Parent.Parent, "utility", "getClassesInPath").getClassesInPath
|
|
7
7
|
local getClassesInGlob = TS.import(script, script.Parent.Parent, "utility", "globs").getClassesInGlob
|
|
8
|
+
local explainUnresolvedClass = TS.import(script, script.Parent.Parent, "utility", "explainUnresolved").explainUnresolvedClass
|
|
9
|
+
local _leftOut = TS.import(script, script.Parent.Parent, "utility", "leftOut")
|
|
10
|
+
local explainLeftOut = _leftOut.explainLeftOut
|
|
11
|
+
local leftOutRegistration = _leftOut.leftOutRegistration
|
|
12
|
+
local _threadWaits = TS.import(script, script.Parent.Parent, "utility", "threadWaits")
|
|
13
|
+
local extinguishesBegun = _threadWaits.extinguishesBegun
|
|
14
|
+
local threadWaits = _threadWaits.threadWaits
|
|
8
15
|
local clearDefaultModule = TS.import(script, script.Parent, "defaultModule").clearDefaultModule
|
|
9
16
|
local HookPriority = TS.import(script, script.Parent, "moduleHooks").HookPriority
|
|
10
17
|
local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
|
|
18
|
+
local DEFAULT_LOAD_ORDER = _providerRegistration.DEFAULT_LOAD_ORDER
|
|
11
19
|
local getProviderClassId = _providerRegistration.getProviderClassId
|
|
12
20
|
local getProviderClassScope = _providerRegistration.getProviderClassScope
|
|
21
|
+
local getProviderLoadOrder = _providerRegistration.getProviderLoadOrder
|
|
13
22
|
local normalizeProviderConfig = _providerRegistration.normalizeProviderConfig
|
|
14
23
|
local _scopes = TS.import(script, script.Parent, "scopes")
|
|
15
24
|
local NO_CONDITION = _scopes.NO_CONDITION
|
|
@@ -73,6 +82,12 @@ local function createModuleInstantiation(state, options)
|
|
|
73
82
|
local providers = {}
|
|
74
83
|
--* The registrations left out, by id, with the conditions that were judged: for the error a miss gets.
|
|
75
84
|
local skipped = {}
|
|
85
|
+
--* The path and glob registrations left out by their own condition, the builder's and the plugins'.
|
|
86
|
+
local _array_2 = {}
|
|
87
|
+
local _length_1 = #_array_2
|
|
88
|
+
local _array_3 = (state.leftOut or {})
|
|
89
|
+
table.move(_array_3, 1, #_array_3, _length_1 + 1, _array_2)
|
|
90
|
+
local leftOut = _array_2
|
|
76
91
|
--* Modules searched after this one's own providers, in order. Ignited before this one, and extinguished after.
|
|
77
92
|
local _result = options
|
|
78
93
|
if _result ~= nil then
|
|
@@ -88,12 +103,24 @@ local function createModuleInstantiation(state, options)
|
|
|
88
103
|
local instantiatedProviders = {}
|
|
89
104
|
--* Objects handed over by `provideInstance`, attached to their interfaces once every plugin is set up.
|
|
90
105
|
local providedInstances = {}
|
|
106
|
+
--[[
|
|
107
|
+
*
|
|
108
|
+
* Provided objects that have not joined their interfaces: until the ignition reaches them, and
|
|
109
|
+
* for good when an observer refuses one. `release` has nothing to take them out of, and a failed
|
|
110
|
+
* ignition told every observer `onRemoved` for objects it had never been told of, and told the
|
|
111
|
+
* observers that had just undone a refused one of it again.
|
|
112
|
+
|
|
113
|
+
]]
|
|
114
|
+
local unjoinedInstances = {}
|
|
91
115
|
local observers = {}
|
|
92
116
|
local hooks = {}
|
|
93
117
|
local includedPlugins = {}
|
|
94
118
|
local filledSlots = {}
|
|
95
119
|
local temporaryInstances = {}
|
|
96
120
|
local moduleInitState = ModuleInitState.Created
|
|
121
|
+
--* The thread `extinguish` runs on, and what wakes each thread waiting in `awaitExtinguished` for it to finish.
|
|
122
|
+
local extinguishingThread
|
|
123
|
+
local extinguishWaiters = {}
|
|
97
124
|
local switchInitState = function(from, to)
|
|
98
125
|
if moduleInitState ~= from then
|
|
99
126
|
error(`module '{state.debugName}' is in invalid state when transitioning to '{ModuleInitState[to]}', got '{ModuleInitState[moduleInitState]}' when '{ModuleInitState[from]}' was expected.`)
|
|
@@ -214,6 +241,67 @@ local function createModuleInstantiation(state, options)
|
|
|
214
241
|
end
|
|
215
242
|
providers = active
|
|
216
243
|
end
|
|
244
|
+
--[[
|
|
245
|
+
*
|
|
246
|
+
* The registrations in ascending `loadOrder`, registration order among equals. The same list
|
|
247
|
+
* when none sets one, which is every module that does not use it.
|
|
248
|
+
|
|
249
|
+
]]
|
|
250
|
+
local inLoadOrder = function(entries)
|
|
251
|
+
-- ▼ ReadonlyArray.map ▼
|
|
252
|
+
local _newValue = table.create(#entries)
|
|
253
|
+
local _callback = function(entry)
|
|
254
|
+
local _condition_1 = getProviderLoadOrder(entry.config)
|
|
255
|
+
if _condition_1 == nil then
|
|
256
|
+
_condition_1 = DEFAULT_LOAD_ORDER
|
|
257
|
+
end
|
|
258
|
+
return _condition_1
|
|
259
|
+
end
|
|
260
|
+
for _k, _v in entries do
|
|
261
|
+
_newValue[_k] = _callback(_v, _k - 1, entries)
|
|
262
|
+
end
|
|
263
|
+
-- ▲ ReadonlyArray.map ▲
|
|
264
|
+
local orders = _newValue
|
|
265
|
+
-- ▼ ReadonlyArray.every ▼
|
|
266
|
+
local _result_1 = true
|
|
267
|
+
local _callback_1 = function(order)
|
|
268
|
+
return order == DEFAULT_LOAD_ORDER
|
|
269
|
+
end
|
|
270
|
+
for _k, _v in orders do
|
|
271
|
+
if not _callback_1(_v, _k - 1, orders) then
|
|
272
|
+
_result_1 = false
|
|
273
|
+
break
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
-- ▲ ReadonlyArray.every ▲
|
|
277
|
+
if _result_1 then
|
|
278
|
+
return entries
|
|
279
|
+
end
|
|
280
|
+
-- `table.sort` is not stable, so equals are ordered by their position.
|
|
281
|
+
-- ▼ ReadonlyArray.map ▼
|
|
282
|
+
local _newValue_1 = table.create(#entries)
|
|
283
|
+
local _callback_2 = function(_, index)
|
|
284
|
+
return index
|
|
285
|
+
end
|
|
286
|
+
for _k, _v in entries do
|
|
287
|
+
_newValue_1[_k] = _callback_2(_v, _k - 1, entries)
|
|
288
|
+
end
|
|
289
|
+
-- ▲ ReadonlyArray.map ▲
|
|
290
|
+
local indices = _newValue_1
|
|
291
|
+
table.sort(indices, function(a, b)
|
|
292
|
+
return if orders[a + 1] ~= orders[b + 1] then orders[a + 1] < orders[b + 1] else a < b
|
|
293
|
+
end)
|
|
294
|
+
-- ▼ ReadonlyArray.map ▼
|
|
295
|
+
local _newValue_2 = table.create(#indices)
|
|
296
|
+
local _callback_3 = function(index)
|
|
297
|
+
return entries[index + 1]
|
|
298
|
+
end
|
|
299
|
+
for _k, _v in indices do
|
|
300
|
+
_newValue_2[_k] = _callback_3(_v, _k - 1, indices)
|
|
301
|
+
end
|
|
302
|
+
-- ▲ ReadonlyArray.map ▲
|
|
303
|
+
return _newValue_2
|
|
304
|
+
end
|
|
217
305
|
local registerHook = function(phase, callback, priority)
|
|
218
306
|
local _object = {
|
|
219
307
|
phase = phase,
|
|
@@ -233,11 +321,11 @@ local function createModuleInstantiation(state, options)
|
|
|
233
321
|
local _callback = function(hook)
|
|
234
322
|
return hook.phase == phase
|
|
235
323
|
end
|
|
236
|
-
local
|
|
324
|
+
local _length_2 = 0
|
|
237
325
|
for _k, _v in hooks do
|
|
238
326
|
if _callback(_v, _k - 1, hooks) == true then
|
|
239
|
-
|
|
240
|
-
_newValue[
|
|
327
|
+
_length_2 += 1
|
|
328
|
+
_newValue[_length_2] = _v
|
|
241
329
|
end
|
|
242
330
|
end
|
|
243
331
|
-- ▲ ReadonlyArray.filter ▲
|
|
@@ -292,12 +380,18 @@ local function createModuleInstantiation(state, options)
|
|
|
292
380
|
* observer that raises from its `onAdded` has the ones before it told `onRemoved`, and the
|
|
293
381
|
* error comes out, so a refused object is attached nowhere -- rather than left ticking in the
|
|
294
382
|
* lifecycle's sets, with no handle to remove it by.
|
|
383
|
+
*
|
|
384
|
+
* `dependencies`, what a provider's constructor was given, is handed to the observers with it.
|
|
295
385
|
|
|
296
386
|
]]
|
|
297
|
-
local registerClassInterfaces = function(instance, kind)
|
|
298
|
-
local
|
|
387
|
+
local registerClassInterfaces = function(instance, kind, dependencies, loadOrder)
|
|
388
|
+
local interfaces = getClassImplements(instance)
|
|
389
|
+
-- How many observers have been told `onAdded`, in the order they were told: what a refusal
|
|
390
|
+
-- undoes. Counted rather than recorded, since an attachment that goes through -- every one,
|
|
391
|
+
-- in a game -- then builds nothing to throw away.
|
|
392
|
+
local attached = 0
|
|
299
393
|
local success, err = pcall(function()
|
|
300
|
-
for _, interfaceId in
|
|
394
|
+
for _, interfaceId in interfaces do
|
|
301
395
|
local interested = observers[interfaceId]
|
|
302
396
|
if not interested then
|
|
303
397
|
continue
|
|
@@ -308,18 +402,36 @@ local function createModuleInstantiation(state, options)
|
|
|
308
402
|
_result_1(instance, {
|
|
309
403
|
interfaceId = interfaceId,
|
|
310
404
|
kind = kind,
|
|
405
|
+
dependencies = dependencies,
|
|
406
|
+
loadOrder = loadOrder,
|
|
311
407
|
})
|
|
312
408
|
end
|
|
313
|
-
|
|
314
|
-
table.insert(added, _arg0)
|
|
409
|
+
attached += 1
|
|
315
410
|
end
|
|
316
411
|
end
|
|
317
412
|
end)
|
|
318
413
|
if not success then
|
|
414
|
+
-- The observers told, found again by walking the same way as far as the count goes.
|
|
415
|
+
local told = {}
|
|
416
|
+
for _, interfaceId in interfaces do
|
|
417
|
+
local interested = observers[interfaceId]
|
|
418
|
+
if not interested then
|
|
419
|
+
continue
|
|
420
|
+
end
|
|
421
|
+
for _1, observer in interested do
|
|
422
|
+
if #told == attached then
|
|
423
|
+
break
|
|
424
|
+
end
|
|
425
|
+
local _arg0 = { interfaceId, observer }
|
|
426
|
+
table.insert(told, _arg0)
|
|
427
|
+
end
|
|
428
|
+
end
|
|
319
429
|
-- 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
|
-
|
|
322
|
-
|
|
430
|
+
-- the rest attached; the observer's error is what comes out. Marked as refused, so that
|
|
431
|
+
-- an observer owing a departing object a last event -- the lifecycle plugin's
|
|
432
|
+
-- `onExtinguished` while the module extinguishes -- does not deliver it.
|
|
433
|
+
for i = #told - 1, 0, -1 do
|
|
434
|
+
local _binding = told[i + 1]
|
|
323
435
|
local interfaceId = _binding[1]
|
|
324
436
|
local observer = _binding[2]
|
|
325
437
|
guarded("undoing an attachment", function()
|
|
@@ -328,6 +440,7 @@ local function createModuleInstantiation(state, options)
|
|
|
328
440
|
_result_1 = _result_1(instance, {
|
|
329
441
|
interfaceId = interfaceId,
|
|
330
442
|
kind = kind,
|
|
443
|
+
refused = true,
|
|
331
444
|
})
|
|
332
445
|
end
|
|
333
446
|
return _result_1
|
|
@@ -353,11 +466,15 @@ local function createModuleInstantiation(state, options)
|
|
|
353
466
|
end
|
|
354
467
|
end
|
|
355
468
|
end
|
|
469
|
+
--* Constructs a class, resolving what its constructor takes into `resolvedParameters`.
|
|
356
470
|
local resolveDependencyWithOrigin
|
|
357
|
-
local instantiateClassWithDependencies = function(constructor, resolve)
|
|
471
|
+
local instantiateClassWithDependencies = function(constructor, resolve, resolvedParameters)
|
|
472
|
+
if resolvedParameters == nil then
|
|
473
|
+
resolvedParameters = {}
|
|
474
|
+
end
|
|
358
475
|
local dependencies = Reflect.getMetadata(constructor, "flamework:dependencies") or {}
|
|
359
|
-
local resolvedParameters = {}
|
|
360
476
|
for _, dependency in dependencies do
|
|
477
|
+
local _resolvedParameters = resolvedParameters
|
|
361
478
|
local _result_1 = resolve
|
|
362
479
|
if _result_1 ~= nil then
|
|
363
480
|
_result_1 = _result_1(dependency)
|
|
@@ -366,7 +483,7 @@ local function createModuleInstantiation(state, options)
|
|
|
366
483
|
if _condition_1 == nil then
|
|
367
484
|
_condition_1 = resolveDependencyWithOrigin(dependency, constructor)
|
|
368
485
|
end
|
|
369
|
-
table.insert(
|
|
486
|
+
table.insert(_resolvedParameters, _condition_1)
|
|
370
487
|
end
|
|
371
488
|
return constructor.new(unpack(resolvedParameters))
|
|
372
489
|
end
|
|
@@ -405,10 +522,21 @@ local function createModuleInstantiation(state, options)
|
|
|
405
522
|
if moduleProvider then
|
|
406
523
|
local config = moduleProvider.config
|
|
407
524
|
if config.type == "class" then
|
|
408
|
-
local
|
|
525
|
+
local dependencies = {}
|
|
526
|
+
local instantiatedProvider = instantiateClassWithDependencies(config.value, nil, dependencies)
|
|
409
527
|
local _id_1 = info.id
|
|
410
528
|
instantiatedProviders[_id_1] = instantiatedProvider
|
|
411
|
-
|
|
529
|
+
-- Held only if it is attached: one an observer refuses is attached nowhere, so it must
|
|
530
|
+
-- not be cached either -- the next resolve handed it out with no lifecycle at all, and
|
|
531
|
+
-- `release` told every observer, the refusing one included, it was removed again.
|
|
532
|
+
local attached, err = pcall(function()
|
|
533
|
+
return registerClassInterfaces(instantiatedProvider, "provider", dependencies, getProviderLoadOrder(config))
|
|
534
|
+
end)
|
|
535
|
+
if not attached then
|
|
536
|
+
local _id_2 = info.id
|
|
537
|
+
instantiatedProviders[_id_2] = nil
|
|
538
|
+
error(err, 0)
|
|
539
|
+
end
|
|
412
540
|
return instantiatedProvider
|
|
413
541
|
elseif config.type == "function" then
|
|
414
542
|
-- Function providers are not cached.
|
|
@@ -456,6 +584,17 @@ local function createModuleInstantiation(state, options)
|
|
|
456
584
|
if inactive ~= nil then
|
|
457
585
|
error(`module '{state.debugName}' could not resolve dependency '{info.id}': it is registered but inactive ({describeConditions(inactive)}){searched}`)
|
|
458
586
|
end
|
|
587
|
+
-- A folder registration left out by its own condition registered nothing to be inactive.
|
|
588
|
+
local leftOutReason = explainLeftOut(info.id, leftOut)
|
|
589
|
+
if leftOutReason ~= nil then
|
|
590
|
+
error(`module '{state.debugName}' could not resolve dependency '{info.id}': {leftOutReason}{searched}`)
|
|
591
|
+
end
|
|
592
|
+
-- A class that has been loaded says why it is not here: a component, a provider nothing
|
|
593
|
+
-- registered, a class that is not a provider at all.
|
|
594
|
+
local explanation = explainUnresolvedClass(info.id, requestingOrigin)
|
|
595
|
+
if explanation ~= nil then
|
|
596
|
+
error(`module '{state.debugName}' could not resolve dependency '{info.id}': {explanation}{searched}`)
|
|
597
|
+
end
|
|
459
598
|
error(`module '{state.debugName}' could not resolve dependency '{info.id}'{searched}`)
|
|
460
599
|
end
|
|
461
600
|
return dependency
|
|
@@ -602,14 +741,27 @@ local function createModuleInstantiation(state, options)
|
|
|
602
741
|
-- every plugin's observers are in place.
|
|
603
742
|
for _, instance in providedInstances do
|
|
604
743
|
registerClassInterfaces(instance, "provider")
|
|
744
|
+
unjoinedInstances[instance] = nil
|
|
605
745
|
end
|
|
606
|
-
|
|
746
|
+
-- In ascending `loadOrder`, each after what its constructor takes, which is the order the
|
|
747
|
+
-- lifecycle plugin runs `onInit` in: a low `loadOrder` goes first and pulls its
|
|
748
|
+
-- dependencies forward with it, and dependency order still wins over `loadOrder`.
|
|
749
|
+
for _, provider in inLoadOrder(providers) do
|
|
607
750
|
-- Lazy providers are constructed the first time they are resolved instead.
|
|
608
751
|
if provider.config.type == "class" and provider.config.lazy ~= true then
|
|
609
752
|
resolveDependency(provider.injectionId)
|
|
610
753
|
end
|
|
611
754
|
end
|
|
612
755
|
runHooks("postIgnite")
|
|
756
|
+
-- Checked again once everything has run: an `onInit` that yields lets other threads run in
|
|
757
|
+
-- the middle of ignition, and one that extinguished an import then did not see this module
|
|
758
|
+
-- among its importers, since it joins them only below. Carrying on left it ignited onto a
|
|
759
|
+
-- released import that would never take it down.
|
|
760
|
+
for _, imported in imports do
|
|
761
|
+
if not imported.isIgnited() then
|
|
762
|
+
error(`module '{state.debugName}': imported module '{imported.debugName}' was extinguished while this module was igniting`)
|
|
763
|
+
end
|
|
764
|
+
end
|
|
613
765
|
end)
|
|
614
766
|
if not success then
|
|
615
767
|
moduleInitState = ModuleInitState.Extinguishing
|
|
@@ -621,21 +773,40 @@ local function createModuleInstantiation(state, options)
|
|
|
621
773
|
for _, imported in imports do
|
|
622
774
|
imported.addImporter(module)
|
|
623
775
|
end
|
|
776
|
+
-- Ignition has completed, so this is where the lifecycle plugin starts the providers: an
|
|
777
|
+
-- `onStart` sees the module ignited, may extinguish it, and may ignite one that imports it.
|
|
778
|
+
-- Nothing here can fail the ignition, so a hook that raises is warned about. One that
|
|
779
|
+
-- extinguishes the module ends the phase: the hooks after it would set up a module gone.
|
|
780
|
+
for _, hook in sortedHooks("ignited") do
|
|
781
|
+
if moduleInitState ~= ModuleInitState.Ignited then
|
|
782
|
+
break
|
|
783
|
+
end
|
|
784
|
+
guarded("an onIgnited hook", function()
|
|
785
|
+
return hook.callback(module)
|
|
786
|
+
end)
|
|
787
|
+
end
|
|
624
788
|
return module
|
|
625
789
|
end
|
|
626
790
|
local extinguish = function()
|
|
627
791
|
switchInitState(ModuleInitState.Ignited, ModuleInitState.Extinguishing)
|
|
792
|
+
extinguishingThread = coroutine.running()
|
|
793
|
+
extinguishesBegun.count += 1
|
|
628
794
|
-- 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
|
-
|
|
631
|
-
|
|
795
|
+
-- down before it returns: the deepest goes first. Copied, since each removes itself. One
|
|
796
|
+
-- extinguishing already, on another thread suspended in a handler that yields, is still
|
|
797
|
+
-- using this module's providers, so it is waited for: skipped, it had this module released
|
|
798
|
+
-- under it.
|
|
799
|
+
local _array_4 = {}
|
|
800
|
+
local _length_2 = #_array_4
|
|
632
801
|
for _v in importers do
|
|
633
|
-
|
|
634
|
-
|
|
802
|
+
_length_2 += 1
|
|
803
|
+
_array_4[_length_2] = _v
|
|
635
804
|
end
|
|
636
|
-
for _, importer in
|
|
805
|
+
for _, importer in _array_4 do
|
|
637
806
|
if not importer.isExtinguished() then
|
|
638
807
|
importer.extinguish()
|
|
808
|
+
else
|
|
809
|
+
importer.awaitExtinguished()
|
|
639
810
|
end
|
|
640
811
|
end
|
|
641
812
|
table.clear(importers)
|
|
@@ -660,13 +831,13 @@ local function createModuleInstantiation(state, options)
|
|
|
660
831
|
end)
|
|
661
832
|
end
|
|
662
833
|
-- Copied first: removal callbacks may themselves remove instances.
|
|
663
|
-
local
|
|
664
|
-
local
|
|
834
|
+
local _array_4 = {}
|
|
835
|
+
local _length_2 = #_array_4
|
|
665
836
|
for _v in temporaryInstances do
|
|
666
|
-
|
|
667
|
-
|
|
837
|
+
_length_2 += 1
|
|
838
|
+
_array_4[_length_2] = _v
|
|
668
839
|
end
|
|
669
|
-
for _, temporaryInstance in
|
|
840
|
+
for _, temporaryInstance in _array_4 do
|
|
670
841
|
guarded("removing an instance", function()
|
|
671
842
|
return removeClassInstance(temporaryInstance)
|
|
672
843
|
end)
|
|
@@ -674,10 +845,31 @@ local function createModuleInstantiation(state, options)
|
|
|
674
845
|
-- Providers join their interfaces when they are instantiated, so they have to leave them
|
|
675
846
|
-- too. Without this, a plugin such as the lifecycle plugin keeps holding (and ticking)
|
|
676
847
|
-- providers that belong to an extinguished module.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
848
|
+
--
|
|
849
|
+
-- Over a copy, taken again until nothing new turns up: a removal runs user code -- the
|
|
850
|
+
-- lifecycle plugin tells a provider resolved since its walk `onExtinguished` as it leaves --
|
|
851
|
+
-- which may resolve a lazy provider for the first time, and a table gaining keys while it
|
|
852
|
+
-- is walked skipped providers, or had others unregistered twice. By object, not by id: an
|
|
853
|
+
-- object provided under several ids joined its interfaces once, so it leaves them once.
|
|
854
|
+
local released = {}
|
|
855
|
+
while true do
|
|
856
|
+
local remaining = {}
|
|
857
|
+
for _, provider in instantiatedProviders do
|
|
858
|
+
if not (released[provider] ~= nil) then
|
|
859
|
+
released[provider] = true
|
|
860
|
+
if not (unjoinedInstances[provider] ~= nil) then
|
|
861
|
+
table.insert(remaining, provider)
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
if #remaining == 0 then
|
|
866
|
+
break
|
|
867
|
+
end
|
|
868
|
+
for _, provider in remaining do
|
|
869
|
+
guarded("removing a provider", function()
|
|
870
|
+
return unregisterClassInterfaces(provider, "provider")
|
|
871
|
+
end)
|
|
872
|
+
end
|
|
681
873
|
end
|
|
682
874
|
-- A removal that raised leaves its instance behind, and nothing can add one any more.
|
|
683
875
|
table.clear(temporaryInstances)
|
|
@@ -689,6 +881,80 @@ local function createModuleInstantiation(state, options)
|
|
|
689
881
|
-- Released last, once nothing in here can resolve any more, so that the next root ignited
|
|
690
882
|
-- becomes the default rather than `Dependency<T>()` answering from a dead module.
|
|
691
883
|
clearDefaultModule(module)
|
|
884
|
+
for _, wake in extinguishWaiters do
|
|
885
|
+
wake()
|
|
886
|
+
end
|
|
887
|
+
table.clear(extinguishWaiters)
|
|
888
|
+
end
|
|
889
|
+
local awaitExtinguished = function()
|
|
890
|
+
if moduleInitState ~= ModuleInitState.Extinguishing then
|
|
891
|
+
return nil
|
|
892
|
+
end
|
|
893
|
+
-- On the extinguish's own thread -- one of its handlers extinguishing an import -- it cannot
|
|
894
|
+
-- finish until this returns, so waiting would never end. Nor on a thread the extinguish's
|
|
895
|
+
-- thread is itself waiting on here, however many waits away: two extinguishes, each waiting
|
|
896
|
+
-- for a module the other is taking down, held each other -- and every module between them,
|
|
897
|
+
-- still ticking -- for good. The wait that would close the circle is skipped, as the wait on
|
|
898
|
+
-- the extinguish's own thread is. The chain runs through an ignition waiting for an `onInit`
|
|
899
|
+
-- on its own thread too, which the lifecycle plugin records -- and through one waiting for the
|
|
900
|
+
-- Promise an `onInit` returned, whose work runs on a thread nothing can name: an `async`
|
|
901
|
+
-- method's body runs on one of its own. A chain that ends at one is taken to close the circle,
|
|
902
|
+
-- since the thread that settles it may be this one.
|
|
903
|
+
local running = coroutine.running()
|
|
904
|
+
local thread = extinguishingThread
|
|
905
|
+
local closesCircle = function()
|
|
906
|
+
local waitedOn = thread
|
|
907
|
+
while waitedOn ~= nil do
|
|
908
|
+
local _condition_1 = waitedOn == running
|
|
909
|
+
if not _condition_1 then
|
|
910
|
+
local _waitedOn = waitedOn
|
|
911
|
+
_condition_1 = not (type(_waitedOn) == "thread")
|
|
912
|
+
end
|
|
913
|
+
if _condition_1 then
|
|
914
|
+
return true
|
|
915
|
+
end
|
|
916
|
+
local _waitedOn = waitedOn
|
|
917
|
+
waitedOn = threadWaits[_waitedOn]
|
|
918
|
+
end
|
|
919
|
+
return false
|
|
920
|
+
end
|
|
921
|
+
if closesCircle() then
|
|
922
|
+
return nil
|
|
923
|
+
end
|
|
924
|
+
-- Nor on a thread that has died -- cancelled, as the testing runner cancels a body that
|
|
925
|
+
-- overran, or closed: that extinguish never finishes, and waiting for it held this module,
|
|
926
|
+
-- and everything in it, for good. One that dies while this waits is noticed within a frame.
|
|
927
|
+
if coroutine.status(thread) == "dead" then
|
|
928
|
+
return nil
|
|
929
|
+
end
|
|
930
|
+
local woken = false
|
|
931
|
+
local wake = function()
|
|
932
|
+
if woken then
|
|
933
|
+
return nil
|
|
934
|
+
end
|
|
935
|
+
woken = true
|
|
936
|
+
-- Removed here rather than by this thread once it resumes: one cancelled while it waits
|
|
937
|
+
-- never does, and left its entry behind for good.
|
|
938
|
+
threadWaits[running] = nil
|
|
939
|
+
if coroutine.status(running) ~= "dead" then
|
|
940
|
+
task.spawn(running)
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
threadWaits[running] = thread
|
|
944
|
+
table.insert(extinguishWaiters, wake)
|
|
945
|
+
-- Checked again every frame, as well as for a dead extinguish: this thread may be cancelled
|
|
946
|
+
-- while it waits, and the chain may come to close the circle after the wait began -- an
|
|
947
|
+
-- `async` `onInit`'s body waits here before the ignition that called it takes the Promise it
|
|
948
|
+
-- returned and waits on that.
|
|
949
|
+
task.spawn(function()
|
|
950
|
+
while not woken do
|
|
951
|
+
task.wait()
|
|
952
|
+
if coroutine.status(thread) == "dead" or coroutine.status(running) == "dead" or closesCircle() then
|
|
953
|
+
wake()
|
|
954
|
+
end
|
|
955
|
+
end
|
|
956
|
+
end)
|
|
957
|
+
coroutine.yield()
|
|
692
958
|
end
|
|
693
959
|
local isExtinguished = function()
|
|
694
960
|
return moduleInitState >= ModuleInitState.Extinguishing
|
|
@@ -721,6 +987,7 @@ local function createModuleInstantiation(state, options)
|
|
|
721
987
|
-- ▲ Set.delete ▲
|
|
722
988
|
return _valueExisted
|
|
723
989
|
end,
|
|
990
|
+
awaitExtinguished = awaitExtinguished,
|
|
724
991
|
}
|
|
725
992
|
--* What a plugin's setup is handed. Everything registers into this instantiation.
|
|
726
993
|
pluginTarget = {
|
|
@@ -728,21 +995,35 @@ local function createModuleInstantiation(state, options)
|
|
|
728
995
|
scope = if hasCondition(moduleScope) then moduleScope else nil,
|
|
729
996
|
isActive = function(...)
|
|
730
997
|
local conditions = { ... }
|
|
731
|
-
local
|
|
732
|
-
local
|
|
733
|
-
table.move(conditions, 1, #conditions,
|
|
734
|
-
return holdsEveryCondition(
|
|
998
|
+
local _array_4 = { moduleScope }
|
|
999
|
+
local _length_2 = #_array_4
|
|
1000
|
+
table.move(conditions, 1, #conditions, _length_2 + 1, _array_4)
|
|
1001
|
+
return holdsEveryCondition(_array_4)
|
|
735
1002
|
end,
|
|
736
1003
|
registerClassProvider = registerClassProvider,
|
|
737
|
-
registerProviders = function(
|
|
1004
|
+
registerProviders = function(path, registrationOptions, resolved)
|
|
738
1005
|
local _arg0 = resolved ~= nil
|
|
739
1006
|
assert(_arg0)
|
|
740
|
-
|
|
1007
|
+
if holdsCondition(registrationOptions) then
|
|
1008
|
+
registerProviderClasses(getClassesInPath(resolved), registrationOptions)
|
|
1009
|
+
else
|
|
1010
|
+
local _arg0_1 = leftOutRegistration(`registerProviders("{path}")`, registrationOptions, {
|
|
1011
|
+
path = resolved,
|
|
1012
|
+
})
|
|
1013
|
+
table.insert(leftOut, _arg0_1)
|
|
1014
|
+
end
|
|
741
1015
|
end,
|
|
742
|
-
registerProvidersGlob = function(
|
|
1016
|
+
registerProvidersGlob = function(glob, registrationOptions, resolved)
|
|
743
1017
|
local _arg0 = resolved ~= nil
|
|
744
1018
|
assert(_arg0)
|
|
745
|
-
|
|
1019
|
+
if holdsCondition(registrationOptions) then
|
|
1020
|
+
registerProviderClasses(getClassesInGlob(resolved), registrationOptions)
|
|
1021
|
+
else
|
|
1022
|
+
local _arg0_1 = leftOutRegistration(`registerProvidersGlob("{glob}")`, registrationOptions, {
|
|
1023
|
+
glob = resolved,
|
|
1024
|
+
})
|
|
1025
|
+
table.insert(leftOut, _arg0_1)
|
|
1026
|
+
end
|
|
746
1027
|
end,
|
|
747
1028
|
registerProvider = function(config, injectionId)
|
|
748
1029
|
local _arg0 = injectionId ~= nil
|
|
@@ -760,8 +1041,15 @@ local function createModuleInstantiation(state, options)
|
|
|
760
1041
|
local _injectionId = injectionId
|
|
761
1042
|
local _instance = instance
|
|
762
1043
|
instantiatedProviders[_injectionId] = _instance
|
|
1044
|
+
-- Once, however many ids it is provided under: it joins its interfaces once per entry,
|
|
1045
|
+
-- and the same object twice was initialised, started and told `onAdded` twice.
|
|
763
1046
|
local _instance_1 = instance
|
|
764
|
-
table.
|
|
1047
|
+
if not (table.find(providedInstances, _instance_1) ~= nil) then
|
|
1048
|
+
local _instance_2 = instance
|
|
1049
|
+
table.insert(providedInstances, _instance_2)
|
|
1050
|
+
local _instance_3 = instance
|
|
1051
|
+
unjoinedInstances[_instance_3] = true
|
|
1052
|
+
end
|
|
765
1053
|
end,
|
|
766
1054
|
includePlugin = includePlugin,
|
|
767
1055
|
onPreIgnite = function(callback, hookOptions)
|
|
@@ -780,6 +1068,14 @@ local function createModuleInstantiation(state, options)
|
|
|
780
1068
|
end
|
|
781
1069
|
return registerHook("postIgnite", _exp, _result_1)
|
|
782
1070
|
end,
|
|
1071
|
+
onIgnited = function(callback, hookOptions)
|
|
1072
|
+
local _exp = callback
|
|
1073
|
+
local _result_1 = hookOptions
|
|
1074
|
+
if _result_1 ~= nil then
|
|
1075
|
+
_result_1 = _result_1.priority
|
|
1076
|
+
end
|
|
1077
|
+
return registerHook("ignited", _exp, _result_1)
|
|
1078
|
+
end,
|
|
783
1079
|
onExtinguished = function(callback, hookOptions)
|
|
784
1080
|
local _exp = callback
|
|
785
1081
|
local _result_1 = hookOptions
|
|
@@ -2,7 +2,7 @@ import { Modding } from "../modding";
|
|
|
2
2
|
import { ModuleDefinition, ProviderConfig, type IgniteOptions, type ProviderRegistrationOptions } from "./moduleDefinition";
|
|
3
3
|
import type { Constructor } from "../utility/constructors";
|
|
4
4
|
import { type PluginDefinition } from "../plugin/pluginDefinition";
|
|
5
|
-
import type
|
|
5
|
+
import { type ScopeCondition } from "./scopes";
|
|
6
6
|
type GenericId<T> = string | Modding.Target.Id<T>;
|
|
7
7
|
export declare class ModuleBuilder {
|
|
8
8
|
/** A global count of the number of module builders. Used to disambiguate identical module debug names. */
|
|
@@ -36,10 +36,16 @@ export declare class ModuleBuilder {
|
|
|
36
36
|
/**
|
|
37
37
|
* Register all providers under the specified path and its descendants.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* Every `@Provider()` class the modules there define at their top level is registered, exported
|
|
40
|
+
* or not, as v1 registered every decorated class it required, and so is one they export from
|
|
41
|
+
* elsewhere; each once. A class declared inside a function is registered only if its module
|
|
42
|
+
* exports it. A provider must carry the `@Provider()` decorator itself: an undecorated subclass
|
|
43
|
+
* of a provider is not registered.
|
|
41
44
|
*
|
|
42
45
|
* The options apply to every provider found: a scope condition here scopes the whole folder.
|
|
46
|
+
* When it does not hold, the folder is not touched at all -- not looked up, nothing under it
|
|
47
|
+
* required -- so a build can leave the folder out of the place (a release build without its
|
|
48
|
+
* tests, say).
|
|
43
49
|
*
|
|
44
50
|
* @metadata macro
|
|
45
51
|
*/
|
|
@@ -51,6 +57,9 @@ export declare class ModuleBuilder {
|
|
|
51
57
|
* This is the v2 equivalent of v1's `Flamework.addPathsGlob`. Globs can match a large number of
|
|
52
58
|
* paths, so keep them as specific as possible.
|
|
53
59
|
*
|
|
60
|
+
* As with `registerProviders`, a scope condition that does not hold leaves every matched folder
|
|
61
|
+
* untouched.
|
|
62
|
+
*
|
|
54
63
|
* @metadata macro
|
|
55
64
|
*/
|
|
56
65
|
registerProvidersGlob<T extends string>(_glob: T, options?: ProviderRegistrationOptions, glob?: Modding.Intrinsic<"pathglob", [T], string>): this;
|
|
@@ -8,6 +8,8 @@ local LIFECYCLE_SLOT = TS.import(script, script.Parent.Parent, "plugin", "plugin
|
|
|
8
8
|
local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
|
|
9
9
|
local getProviderClassId = _providerRegistration.getProviderClassId
|
|
10
10
|
local normalizeProviderConfig = _providerRegistration.normalizeProviderConfig
|
|
11
|
+
local holdsCondition = TS.import(script, script.Parent, "scopes").holdsCondition
|
|
12
|
+
local leftOutRegistration = TS.import(script, script.Parent.Parent, "utility", "leftOut").leftOutRegistration
|
|
11
13
|
local ModuleBuilder
|
|
12
14
|
do
|
|
13
15
|
ModuleBuilder = setmetatable({}, {
|
|
@@ -28,6 +30,7 @@ do
|
|
|
28
30
|
debugName = "Anonymous",
|
|
29
31
|
providers = {},
|
|
30
32
|
plugins = {},
|
|
33
|
+
leftOut = {},
|
|
31
34
|
}
|
|
32
35
|
end
|
|
33
36
|
function ModuleBuilder:includePlugin(plugin, options)
|
|
@@ -107,11 +110,30 @@ do
|
|
|
107
110
|
function ModuleBuilder:registerProviders(_stringPath, options, path)
|
|
108
111
|
local _path = path
|
|
109
112
|
assert(_path)
|
|
113
|
+
-- The active scopes are the ones the build was compiled with, so this is the answer
|
|
114
|
+
-- ignition would give; what it would register is only ever skipped there. Recorded, so that
|
|
115
|
+
-- a miss on a class under the folder can say why it is missing.
|
|
116
|
+
if not holdsCondition(options) then
|
|
117
|
+
local _leftOut = self.module.leftOut
|
|
118
|
+
local _arg0 = leftOutRegistration(`registerProviders("{_stringPath}")`, options, {
|
|
119
|
+
path = path,
|
|
120
|
+
})
|
|
121
|
+
table.insert(_leftOut, _arg0)
|
|
122
|
+
return self
|
|
123
|
+
end
|
|
110
124
|
return self:registerProviderClasses(getClassesInPath(path), options)
|
|
111
125
|
end
|
|
112
126
|
function ModuleBuilder:registerProvidersGlob(_glob, options, glob)
|
|
113
127
|
local _arg0 = glob ~= nil
|
|
114
128
|
assert(_arg0)
|
|
129
|
+
if not holdsCondition(options) then
|
|
130
|
+
local _leftOut = self.module.leftOut
|
|
131
|
+
local _arg0_1 = leftOutRegistration(`registerProvidersGlob("{_glob}")`, options, {
|
|
132
|
+
glob = glob,
|
|
133
|
+
})
|
|
134
|
+
table.insert(_leftOut, _arg0_1)
|
|
135
|
+
return self
|
|
136
|
+
end
|
|
115
137
|
return self:registerProviderClasses(getClassesInGlob(glob), options)
|
|
116
138
|
end
|
|
117
139
|
function ModuleBuilder:registerProviderClasses(classes, options)
|
|
@@ -2,6 +2,7 @@ import type { Modding } from "../modding";
|
|
|
2
2
|
import type { PluginDefinition } from "../plugin/pluginDefinition";
|
|
3
3
|
import { type Module } from "./module";
|
|
4
4
|
import type { ScopeCondition } from "./scopes";
|
|
5
|
+
import type { LeftOutRegistration } from "../utility/leftOut";
|
|
5
6
|
/**
|
|
6
7
|
* Options for one ignition of a module.
|
|
7
8
|
*
|
|
@@ -44,6 +45,11 @@ export interface ModuleState {
|
|
|
44
45
|
readonly providers: readonly ModuleProvider[];
|
|
45
46
|
/** The plugins to set up on ignition, in inclusion order. */
|
|
46
47
|
readonly plugins: readonly PluginInclusion[];
|
|
48
|
+
/**
|
|
49
|
+
* The path and glob registrations left out by their own scope condition, whose folders were
|
|
50
|
+
* never looked up: what a miss on a class under one of them names.
|
|
51
|
+
*/
|
|
52
|
+
readonly leftOut?: readonly LeftOutRegistration[];
|
|
47
53
|
}
|
|
48
54
|
export declare class ModuleDefinition {
|
|
49
55
|
private moduleState;
|