@flamework-experimental/core 2.0.0-alpha.2 → 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 +34 -32
- package/flamework.build +1 -1
- package/out/dependency.d.ts +4 -0
- package/out/dependency.luau +4 -0
- package/out/index.d.ts +4 -2
- package/out/init.luau +10 -2
- package/out/lifecycle/lifecyclePlugin.d.ts +13 -1
- package/out/lifecycle/lifecyclePlugin.luau +67 -7
- package/out/module/module.luau +126 -24
- 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 +7 -4
- package/out/provider.d.ts +19 -0
- package/out/provider.luau +8 -0
- package/out/reflect.luau +6 -0
- package/out/utility/explainUnresolved.d.ts +9 -0
- package/out/utility/explainUnresolved.luau +43 -0
- 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/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/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# Flamework
|
|
2
2
|
|
|
3
|
-
Flamework is an extensible framework for roblox-ts
|
|
3
|
+
Flamework is an extensible framework for roblox-ts. It is built around modules that are portable,
|
|
4
|
+
isolated and easy to test.
|
|
4
5
|
|
|
5
6
|
## Documentation
|
|
6
7
|
|
|
7
|
-
**[docs/](docs/README.md)
|
|
8
|
-
point to plugins
|
|
8
|
+
Start with **[docs/](docs/README.md)**. It holds a twelve-part guide, which starts from a working
|
|
9
|
+
entry point and builds up to plugins, project layout, scopes and testing. It also holds reference
|
|
10
|
+
material:
|
|
9
11
|
|
|
10
12
|
| | |
|
|
11
13
|
|---|---|
|
|
12
|
-
| [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1. |
|
|
14
|
+
| [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1, scopes, testing in the place. |
|
|
13
15
|
| [Internals](docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
|
|
14
16
|
| [Transformer plugins](docs/reference/transformer-plugins.md) | Adding macro types of your own. |
|
|
15
17
|
|
|
@@ -37,36 +39,36 @@ bun run test:place # the in-place suite in Roblox Studio (tests/place); need
|
|
|
37
39
|
| `packages/core` | Modules, dependency injection, plugins and lifecycle events |
|
|
38
40
|
| `packages/components` | CollectionService components, built on the core plugin system |
|
|
39
41
|
| `packages/networking` | Remote events and functions |
|
|
40
|
-
| `packages/testing` |
|
|
42
|
+
| `packages/testing` | Tests that run inside a place: sections, cleanup, a bindable and a remote that run them, and a cloud entry point. Also `flamework-test` (`cli/`), the CLI that runs them in Roblox Studio on this machine or through Open Cloud |
|
|
41
43
|
| `packages/transformer` | The roblox-ts transformer |
|
|
42
44
|
| `packages/transformer-plugin` | Public API for writing transformer plugins |
|
|
43
|
-
| `packages/specs` | Runtime specs,
|
|
45
|
+
| `packages/specs` | Runtime specs, built by `rbxtsc` and run under Lune |
|
|
44
46
|
|
|
45
47
|
### Tests
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **Transformer tests** (`bun run test:unit`)
|
|
50
|
-
|
|
51
|
-
- **Runtime specs** (`bun run test:runtime`)
|
|
52
|
-
`networking` under Lune
|
|
53
|
-
roblox-ts's `TS.import` tree over the filesystem
|
|
54
|
-
|
|
55
|
-
`Enum`, and a `Heartbeat` pump so `Promise.delay`
|
|
56
|
-
|
|
57
|
-
construction, dependencies and streaming
|
|
58
|
-
middleware and the generated guards.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`@Provider`'s metadata, component streaming, and the client
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Specs live in [`packages/specs`](packages/specs)
|
|
67
|
-
Flamework
|
|
68
|
-
|
|
69
|
-
A third suite runs against the real engine and
|
|
70
|
-
[test place](tests/place/README.md), a small game linked to the packages' builds
|
|
71
|
-
|
|
72
|
-
under four Rojo projects (see [Testing in Roblox Studio](docs/testing/studio.md)).
|
|
49
|
+
`bun run test` runs two suites:
|
|
50
|
+
|
|
51
|
+
- **Transformer tests** (`bun run test:unit`) build a fixture project with the real `rbxtsc` and
|
|
52
|
+
check the Luau it emits: guard generation, identifiers, nested macros and the plugin system.
|
|
53
|
+
- **Runtime specs** (`bun run test:runtime`) run the built `@flamework-experimental/core`,
|
|
54
|
+
`components` and `networking` packages under Lune. The harness in [`tests/runtime`](tests/runtime)
|
|
55
|
+
models roblox-ts's `TS.import` tree over the filesystem. It also stubs the parts of the Roblox API
|
|
56
|
+
that Flamework uses: Instances, attributes, CollectionService, RemoteEvents, Players, signals,
|
|
57
|
+
`task`, `Enum`, and a `Heartbeat` pump so that `Promise.delay` runs (and with it, request
|
|
58
|
+
timeouts). The specs cover dependency injection, modules, hooks and the per-frame lifecycle
|
|
59
|
+
events; component construction, dependencies and streaming; and both halves of networking:
|
|
60
|
+
events, functions, middleware and the generated guards.
|
|
61
|
+
|
|
62
|
+
The specs run twice, once as `Server` and once as `Client`, because some code paths depend on the
|
|
63
|
+
realm: `@Provider`'s metadata, component streaming, and the client and server halves of
|
|
64
|
+
networking. Running a realm-specific spec from both sides proves that the two sides agree. For
|
|
65
|
+
example, from the server a function receives on `$name` and sends on `@name`, and from the client
|
|
66
|
+
it does the reverse, so the two runs pin down the wire format from both ends.
|
|
67
|
+
|
|
68
|
+
Specs live in [`packages/specs`](packages/specs). `rbxtsc` builds them like any other project that
|
|
69
|
+
uses Flamework, so they test the transformer and the runtime together.
|
|
70
|
+
|
|
71
|
+
A third suite runs against the real engine, and `bun run test` leaves it out. It lives in the
|
|
72
|
+
[test place](tests/place/README.md), a small game linked to the packages' builds.
|
|
73
|
+
`bun run test:place` runs its `@flamework-experimental/testing` sections in Roblox Studio, on both
|
|
74
|
+
realms, under four Rojo projects (see [Testing in Roblox Studio](docs/testing/studio.md)).
|
package/flamework.build
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"flameworkVersion": "2.0.0-alpha.
|
|
3
|
+
"flameworkVersion": "2.0.0-alpha.4",
|
|
4
4
|
"identifiers": {
|
|
5
5
|
"@flamework-experimental/core:out/module/module@Module": "$:module/module@Module",
|
|
6
6
|
"@flamework-experimental/core:out/lifecycle/lifecyclePlugin@LifecycleProvider": "$:lifecycle/lifecyclePlugin@LifecycleProvider",
|
package/out/dependency.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ import type { Module } from "./module/module";
|
|
|
10
10
|
* handed to something outside Flamework. Inside a provider, take a constructor parameter instead:
|
|
11
11
|
* it declares the dependency where it can be read, and it orders construction.
|
|
12
12
|
*
|
|
13
|
+
* It resolves what the module registers -- providers, and what plugins provide -- and nothing else:
|
|
14
|
+
* unlike v1, it does not build an unregistered class on demand. A component is never one; the
|
|
15
|
+
* transformer refuses `Dependency<T>()` on a `@Component()` class, which `Components` hands out.
|
|
16
|
+
*
|
|
13
17
|
* Raises if no module was given and none has been ignited yet, or if the default has since been
|
|
14
18
|
* extinguished.
|
|
15
19
|
*
|
package/out/dependency.luau
CHANGED
|
@@ -12,6 +12,10 @@ local getDefaultModule = TS.import(script, script.Parent, "module", "defaultModu
|
|
|
12
12
|
* handed to something outside Flamework. Inside a provider, take a constructor parameter instead:
|
|
13
13
|
* it declares the dependency where it can be read, and it orders construction.
|
|
14
14
|
*
|
|
15
|
+
* It resolves what the module registers -- providers, and what plugins provide -- and nothing else:
|
|
16
|
+
* unlike v1, it does not build an unregistered class on demand. A component is never one; the
|
|
17
|
+
* transformer refuses `Dependency<T>()` on a `@Component()` class, which `Components` hands out.
|
|
18
|
+
*
|
|
15
19
|
* Raises if no module was given and none has been ignited yet, or if the default has since been
|
|
16
20
|
* extinguished.
|
|
17
21
|
*
|
package/out/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { HookPriority } from "./module/moduleHooks";
|
|
|
13
13
|
export type { Module, ProviderLookup } from "./module/module";
|
|
14
14
|
export type { IgniteOptions, InjectionContext, ModuleProvider, ModuleState, PluginInclusion, ProviderConfig, ProviderRegistrationOptions, } from "./module/moduleDefinition";
|
|
15
15
|
export type { HookOptions } from "./module/moduleHooks";
|
|
16
|
-
export { describeConditions, holdsCondition, holdsEveryCondition
|
|
16
|
+
export { describeConditions, holdsCondition, holdsEveryCondition } from "./module/scopes";
|
|
17
17
|
export type { ScopeCondition } from "./module/scopes";
|
|
18
18
|
export { PluginDefinition } from "./plugin/pluginDefinition";
|
|
19
19
|
export { LifecyclePlugin, LifecycleProvider, createLifecyclePlugin } from "./lifecycle/lifecyclePlugin";
|
|
@@ -22,7 +22,9 @@ export type { InterfaceConfiguration, InterfaceContext, InterfaceTargetKind, Plu
|
|
|
22
22
|
export type { OnExtinguished, OnInit, OnPhysics, OnRender, OnStart, OnTick } from "./lifecycle/lifecycleInterfaces";
|
|
23
23
|
export { getClassesInPath, importModule, requireModulesInPath } from "./utility/getClassesInPath";
|
|
24
24
|
export { getClassesInGlob, getGlobPaths } from "./utility/globs";
|
|
25
|
-
export { getPathRoot, resolveRbxPath
|
|
25
|
+
export { getPathRoot, resolveRbxPath } from "./utility/pathRoot";
|
|
26
|
+
export { explainLeftOut, leftOutRegistration } from "./utility/leftOut";
|
|
27
|
+
export type { LeftOutRegistration } from "./utility/leftOut";
|
|
26
28
|
export { getRuntimeConfig } from "./utility/runtimeConfig";
|
|
27
29
|
export type { ComponentsRuntimeConfig, CoreRuntimeConfig, NetworkingRuntimeConfig, RuntimeConfig, ScopesRuntimeConfig, TestingRuntimeConfig, } from "./utility/runtimeConfig";
|
|
28
30
|
export type { AbstractConstructor, Constructor } from "./utility/constructors";
|
package/out/init.luau
CHANGED
|
@@ -16,7 +16,6 @@ local _scopes = TS.import(script, script, "module", "scopes")
|
|
|
16
16
|
exports.describeConditions = _scopes.describeConditions
|
|
17
17
|
exports.holdsCondition = _scopes.holdsCondition
|
|
18
18
|
exports.holdsEveryCondition = _scopes.holdsEveryCondition
|
|
19
|
-
exports.__setActiveScopes = _scopes.__setActiveScopes
|
|
20
19
|
-- Plugins
|
|
21
20
|
exports.PluginDefinition = TS.import(script, script, "plugin", "pluginDefinition").PluginDefinition
|
|
22
21
|
local _lifecyclePlugin = TS.import(script, script, "lifecycle", "lifecyclePlugin")
|
|
@@ -35,6 +34,15 @@ exports.getGlobPaths = _globs.getGlobPaths
|
|
|
35
34
|
local _pathRoot = TS.import(script, script, "utility", "pathRoot")
|
|
36
35
|
exports.getPathRoot = _pathRoot.getPathRoot
|
|
37
36
|
exports.resolveRbxPath = _pathRoot.resolveRbxPath
|
|
38
|
-
|
|
37
|
+
local _leftOut = TS.import(script, script, "utility", "leftOut")
|
|
38
|
+
exports.explainLeftOut = _leftOut.explainLeftOut
|
|
39
|
+
exports.leftOutRegistration = _leftOut.leftOutRegistration
|
|
39
40
|
exports.getRuntimeConfig = TS.import(script, script, "utility", "runtimeConfig").getRuntimeConfig
|
|
41
|
+
-- The test harness's hooks: exported for the Luau it reaches through the package entry, and marked
|
|
42
|
+
-- @internal like their declarations so that stripInternal leaves them out of the typings as well
|
|
43
|
+
-- (a re-export of a stripped declaration would not type-check).
|
|
44
|
+
--* @internal
|
|
45
|
+
exports.__setActiveScopes = TS.import(script, script, "module", "scopes").__setActiveScopes
|
|
46
|
+
--* @internal
|
|
47
|
+
exports.__setPathRoot = TS.import(script, script, "utility", "pathRoot").__setPathRoot
|
|
40
48
|
return exports
|
|
@@ -22,8 +22,13 @@ export declare class LifecycleProvider {
|
|
|
22
22
|
/** In attachment order, which for providers is dependency order. */
|
|
23
23
|
private onInit;
|
|
24
24
|
private initMembers;
|
|
25
|
-
/** The providers `
|
|
25
|
+
/** The providers `start` starts, in attachment order; `onStart` is every member. */
|
|
26
26
|
private startOrder;
|
|
27
|
+
/**
|
|
28
|
+
* The `loadOrder` of each provider in `startOrder` that has one other than the default, which
|
|
29
|
+
* `start` orders them by. Empty unless a provider sets one, and emptied once they have started.
|
|
30
|
+
*/
|
|
31
|
+
private startLoadOrders;
|
|
27
32
|
onStart: Set<OnStart>;
|
|
28
33
|
onTick: Set<OnTick>;
|
|
29
34
|
onPhysics: Set<OnPhysics>;
|
|
@@ -228,6 +233,13 @@ export declare class LifecycleProvider {
|
|
|
228
233
|
* extinguished while an `onInit` yielded had the providers started against it first.
|
|
229
234
|
*/
|
|
230
235
|
start(module: Module): void;
|
|
236
|
+
/**
|
|
237
|
+
* A copy of the providers to start, in ascending `loadOrder`: each on its own thread, so a lower
|
|
238
|
+
* one runs up to its first yield before the next is started. Attachment order -- dependency order
|
|
239
|
+
* -- among equals, and unchanged when no provider sets one. Sorted once, at ignition: nothing per
|
|
240
|
+
* frame is ordered.
|
|
241
|
+
*/
|
|
242
|
+
private inLoadOrder;
|
|
231
243
|
extinguished(module: Module): void;
|
|
232
244
|
private tellExtinguished;
|
|
233
245
|
}
|
|
@@ -10,6 +10,7 @@ local extinguishesBegun = _threadWaits.extinguishesBegun
|
|
|
10
10
|
local runsPromiseWork = _threadWaits.runsPromiseWork
|
|
11
11
|
local threadWaits = _threadWaits.threadWaits
|
|
12
12
|
local Reflect = TS.import(script, script.Parent.Parent, "reflect").Reflect
|
|
13
|
+
local DEFAULT_LOAD_ORDER = TS.import(script, script.Parent.Parent, "module", "providerRegistration").DEFAULT_LOAD_ORDER
|
|
13
14
|
local _pluginDefinition = TS.import(script, script.Parent.Parent, "plugin", "pluginDefinition")
|
|
14
15
|
local LIFECYCLE_SLOT = _pluginDefinition.LIFECYCLE_SLOT
|
|
15
16
|
local PluginDefinition = _pluginDefinition.PluginDefinition
|
|
@@ -144,6 +145,7 @@ do
|
|
|
144
145
|
self.onInit = {}
|
|
145
146
|
self.initMembers = {}
|
|
146
147
|
self.startOrder = {}
|
|
148
|
+
self.startLoadOrders = {}
|
|
147
149
|
self.onStart = {}
|
|
148
150
|
self.onTick = {}
|
|
149
151
|
self.onPhysics = {}
|
|
@@ -636,6 +638,12 @@ do
|
|
|
636
638
|
local _startOrder = self.startOrder
|
|
637
639
|
local _object_1 = object
|
|
638
640
|
table.insert(_startOrder, _object_1)
|
|
641
|
+
if context.loadOrder ~= nil and context.loadOrder ~= DEFAULT_LOAD_ORDER then
|
|
642
|
+
local _startLoadOrders = self.startLoadOrders
|
|
643
|
+
local _object_2 = object
|
|
644
|
+
local _loadOrder = context.loadOrder
|
|
645
|
+
_startLoadOrders[_object_2] = _loadOrder
|
|
646
|
+
end
|
|
639
647
|
else
|
|
640
648
|
self:scheduleLateProvider(object)
|
|
641
649
|
end
|
|
@@ -647,9 +655,12 @@ do
|
|
|
647
655
|
local _lateProviders = self.lateProviders
|
|
648
656
|
local _object_1 = object
|
|
649
657
|
_lateProviders[_object_1] = nil
|
|
650
|
-
local
|
|
658
|
+
local _startLoadOrders = self.startLoadOrders
|
|
651
659
|
local _object_2 = object
|
|
652
|
-
|
|
660
|
+
_startLoadOrders[_object_2] = nil
|
|
661
|
+
local _startOrder = self.startOrder
|
|
662
|
+
local _object_3 = object
|
|
663
|
+
local index = (table.find(_startOrder, _object_3) or 0) - 1
|
|
653
664
|
if index ~= -1 then
|
|
654
665
|
table.remove(self.startOrder, index + 1)
|
|
655
666
|
end
|
|
@@ -698,11 +709,7 @@ do
|
|
|
698
709
|
end
|
|
699
710
|
function LifecycleProvider:start(module)
|
|
700
711
|
-- An `onStart` may extinguish the module; nothing starts or ticks after that.
|
|
701
|
-
|
|
702
|
-
local _length = #_array
|
|
703
|
-
local _array_1 = self.startOrder
|
|
704
|
-
table.move(_array_1, 1, #_array_1, _length + 1, _array)
|
|
705
|
-
for _, object in _array do
|
|
712
|
+
for _, object in self:inLoadOrder(self.startOrder) do
|
|
706
713
|
if self:hasBegunExtinguishing() then
|
|
707
714
|
return nil
|
|
708
715
|
end
|
|
@@ -741,6 +748,58 @@ do
|
|
|
741
748
|
local _module = module
|
|
742
749
|
_moduleConnections[_module] = connections
|
|
743
750
|
end
|
|
751
|
+
function LifecycleProvider:inLoadOrder(objects)
|
|
752
|
+
local orders = self.startLoadOrders
|
|
753
|
+
if next(orders) == nil then
|
|
754
|
+
local _array = {}
|
|
755
|
+
local _length = #_array
|
|
756
|
+
table.move(objects, 1, #objects, _length + 1, _array)
|
|
757
|
+
return _array
|
|
758
|
+
end
|
|
759
|
+
self.startLoadOrders = {}
|
|
760
|
+
-- `table.sort` is not stable, so equals are ordered by their position.
|
|
761
|
+
local position = {}
|
|
762
|
+
-- ▼ ReadonlyArray.forEach ▼
|
|
763
|
+
local _callback = function(object, index)
|
|
764
|
+
local _object = object
|
|
765
|
+
local _index = index
|
|
766
|
+
position[_object] = _index
|
|
767
|
+
return position
|
|
768
|
+
end
|
|
769
|
+
for _k, _v in objects do
|
|
770
|
+
_callback(_v, _k - 1, objects)
|
|
771
|
+
end
|
|
772
|
+
-- ▲ ReadonlyArray.forEach ▲
|
|
773
|
+
local _array = {}
|
|
774
|
+
local _length = #_array
|
|
775
|
+
table.move(objects, 1, #objects, _length + 1, _array)
|
|
776
|
+
local sorted = _array
|
|
777
|
+
table.sort(sorted, function(a, b)
|
|
778
|
+
local _a = a
|
|
779
|
+
local _condition = orders[_a]
|
|
780
|
+
if _condition == nil then
|
|
781
|
+
_condition = DEFAULT_LOAD_ORDER
|
|
782
|
+
end
|
|
783
|
+
local orderA = _condition
|
|
784
|
+
local _b = b
|
|
785
|
+
local _condition_1 = orders[_b]
|
|
786
|
+
if _condition_1 == nil then
|
|
787
|
+
_condition_1 = DEFAULT_LOAD_ORDER
|
|
788
|
+
end
|
|
789
|
+
local orderB = _condition_1
|
|
790
|
+
local _result
|
|
791
|
+
if orderA ~= orderB then
|
|
792
|
+
_result = orderA < orderB
|
|
793
|
+
else
|
|
794
|
+
local _a_1 = a
|
|
795
|
+
local _exp = position[_a_1]
|
|
796
|
+
local _b_1 = b
|
|
797
|
+
_result = _exp < position[_b_1]
|
|
798
|
+
end
|
|
799
|
+
return _result
|
|
800
|
+
end)
|
|
801
|
+
return sorted
|
|
802
|
+
end
|
|
744
803
|
function LifecycleProvider:extinguished(module)
|
|
745
804
|
local _moduleConnections = self.moduleConnections
|
|
746
805
|
local _module = module
|
|
@@ -821,6 +880,7 @@ do
|
|
|
821
880
|
do
|
|
822
881
|
-- (Flamework) LifecycleProvider metadata
|
|
823
882
|
Reflect_1.defineMetadata(LifecycleProvider, "identifier", "$:lifecycle/lifecyclePlugin@LifecycleProvider")
|
|
883
|
+
Reflect_1.defineMetadata(LifecycleProvider, "flamework:module", script)
|
|
824
884
|
Reflect_1.defineMetadata(LifecycleProvider, "flamework:implements", {})
|
|
825
885
|
Reflect_1.defineMetadata(LifecycleProvider, "flamework:parameters", { "$:lifecycle/lifecyclePlugin@LifecyclePluginOptions" })
|
|
826
886
|
Reflect_1.defineMetadata(LifecycleProvider, "flamework:dependencies", { {
|
package/out/module/module.luau
CHANGED
|
@@ -5,14 +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
|
|
8
12
|
local _threadWaits = TS.import(script, script.Parent.Parent, "utility", "threadWaits")
|
|
9
13
|
local extinguishesBegun = _threadWaits.extinguishesBegun
|
|
10
14
|
local threadWaits = _threadWaits.threadWaits
|
|
11
15
|
local clearDefaultModule = TS.import(script, script.Parent, "defaultModule").clearDefaultModule
|
|
12
16
|
local HookPriority = TS.import(script, script.Parent, "moduleHooks").HookPriority
|
|
13
17
|
local _providerRegistration = TS.import(script, script.Parent, "providerRegistration")
|
|
18
|
+
local DEFAULT_LOAD_ORDER = _providerRegistration.DEFAULT_LOAD_ORDER
|
|
14
19
|
local getProviderClassId = _providerRegistration.getProviderClassId
|
|
15
20
|
local getProviderClassScope = _providerRegistration.getProviderClassScope
|
|
21
|
+
local getProviderLoadOrder = _providerRegistration.getProviderLoadOrder
|
|
16
22
|
local normalizeProviderConfig = _providerRegistration.normalizeProviderConfig
|
|
17
23
|
local _scopes = TS.import(script, script.Parent, "scopes")
|
|
18
24
|
local NO_CONDITION = _scopes.NO_CONDITION
|
|
@@ -76,6 +82,12 @@ local function createModuleInstantiation(state, options)
|
|
|
76
82
|
local providers = {}
|
|
77
83
|
--* The registrations left out, by id, with the conditions that were judged: for the error a miss gets.
|
|
78
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
|
|
79
91
|
--* Modules searched after this one's own providers, in order. Ignited before this one, and extinguished after.
|
|
80
92
|
local _result = options
|
|
81
93
|
if _result ~= nil then
|
|
@@ -229,6 +241,67 @@ local function createModuleInstantiation(state, options)
|
|
|
229
241
|
end
|
|
230
242
|
providers = active
|
|
231
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
|
|
232
305
|
local registerHook = function(phase, callback, priority)
|
|
233
306
|
local _object = {
|
|
234
307
|
phase = phase,
|
|
@@ -248,11 +321,11 @@ local function createModuleInstantiation(state, options)
|
|
|
248
321
|
local _callback = function(hook)
|
|
249
322
|
return hook.phase == phase
|
|
250
323
|
end
|
|
251
|
-
local
|
|
324
|
+
local _length_2 = 0
|
|
252
325
|
for _k, _v in hooks do
|
|
253
326
|
if _callback(_v, _k - 1, hooks) == true then
|
|
254
|
-
|
|
255
|
-
_newValue[
|
|
327
|
+
_length_2 += 1
|
|
328
|
+
_newValue[_length_2] = _v
|
|
256
329
|
end
|
|
257
330
|
end
|
|
258
331
|
-- ▲ ReadonlyArray.filter ▲
|
|
@@ -311,7 +384,7 @@ local function createModuleInstantiation(state, options)
|
|
|
311
384
|
* `dependencies`, what a provider's constructor was given, is handed to the observers with it.
|
|
312
385
|
|
|
313
386
|
]]
|
|
314
|
-
local registerClassInterfaces = function(instance, kind, dependencies)
|
|
387
|
+
local registerClassInterfaces = function(instance, kind, dependencies, loadOrder)
|
|
315
388
|
local interfaces = getClassImplements(instance)
|
|
316
389
|
-- How many observers have been told `onAdded`, in the order they were told: what a refusal
|
|
317
390
|
-- undoes. Counted rather than recorded, since an attachment that goes through -- every one,
|
|
@@ -330,6 +403,7 @@ local function createModuleInstantiation(state, options)
|
|
|
330
403
|
interfaceId = interfaceId,
|
|
331
404
|
kind = kind,
|
|
332
405
|
dependencies = dependencies,
|
|
406
|
+
loadOrder = loadOrder,
|
|
333
407
|
})
|
|
334
408
|
end
|
|
335
409
|
attached += 1
|
|
@@ -456,7 +530,7 @@ local function createModuleInstantiation(state, options)
|
|
|
456
530
|
-- not be cached either -- the next resolve handed it out with no lifecycle at all, and
|
|
457
531
|
-- `release` told every observer, the refusing one included, it was removed again.
|
|
458
532
|
local attached, err = pcall(function()
|
|
459
|
-
return registerClassInterfaces(instantiatedProvider, "provider", dependencies)
|
|
533
|
+
return registerClassInterfaces(instantiatedProvider, "provider", dependencies, getProviderLoadOrder(config))
|
|
460
534
|
end)
|
|
461
535
|
if not attached then
|
|
462
536
|
local _id_2 = info.id
|
|
@@ -510,6 +584,17 @@ local function createModuleInstantiation(state, options)
|
|
|
510
584
|
if inactive ~= nil then
|
|
511
585
|
error(`module '{state.debugName}' could not resolve dependency '{info.id}': it is registered but inactive ({describeConditions(inactive)}){searched}`)
|
|
512
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
|
|
513
598
|
error(`module '{state.debugName}' could not resolve dependency '{info.id}'{searched}`)
|
|
514
599
|
end
|
|
515
600
|
return dependency
|
|
@@ -658,7 +743,10 @@ local function createModuleInstantiation(state, options)
|
|
|
658
743
|
registerClassInterfaces(instance, "provider")
|
|
659
744
|
unjoinedInstances[instance] = nil
|
|
660
745
|
end
|
|
661
|
-
|
|
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
|
|
662
750
|
-- Lazy providers are constructed the first time they are resolved instead.
|
|
663
751
|
if provider.config.type == "class" and provider.config.lazy ~= true then
|
|
664
752
|
resolveDependency(provider.injectionId)
|
|
@@ -708,13 +796,13 @@ local function createModuleInstantiation(state, options)
|
|
|
708
796
|
-- extinguishing already, on another thread suspended in a handler that yields, is still
|
|
709
797
|
-- using this module's providers, so it is waited for: skipped, it had this module released
|
|
710
798
|
-- under it.
|
|
711
|
-
local
|
|
712
|
-
local
|
|
799
|
+
local _array_4 = {}
|
|
800
|
+
local _length_2 = #_array_4
|
|
713
801
|
for _v in importers do
|
|
714
|
-
|
|
715
|
-
|
|
802
|
+
_length_2 += 1
|
|
803
|
+
_array_4[_length_2] = _v
|
|
716
804
|
end
|
|
717
|
-
for _, importer in
|
|
805
|
+
for _, importer in _array_4 do
|
|
718
806
|
if not importer.isExtinguished() then
|
|
719
807
|
importer.extinguish()
|
|
720
808
|
else
|
|
@@ -743,13 +831,13 @@ local function createModuleInstantiation(state, options)
|
|
|
743
831
|
end)
|
|
744
832
|
end
|
|
745
833
|
-- Copied first: removal callbacks may themselves remove instances.
|
|
746
|
-
local
|
|
747
|
-
local
|
|
834
|
+
local _array_4 = {}
|
|
835
|
+
local _length_2 = #_array_4
|
|
748
836
|
for _v in temporaryInstances do
|
|
749
|
-
|
|
750
|
-
|
|
837
|
+
_length_2 += 1
|
|
838
|
+
_array_4[_length_2] = _v
|
|
751
839
|
end
|
|
752
|
-
for _, temporaryInstance in
|
|
840
|
+
for _, temporaryInstance in _array_4 do
|
|
753
841
|
guarded("removing an instance", function()
|
|
754
842
|
return removeClassInstance(temporaryInstance)
|
|
755
843
|
end)
|
|
@@ -907,21 +995,35 @@ local function createModuleInstantiation(state, options)
|
|
|
907
995
|
scope = if hasCondition(moduleScope) then moduleScope else nil,
|
|
908
996
|
isActive = function(...)
|
|
909
997
|
local conditions = { ... }
|
|
910
|
-
local
|
|
911
|
-
local
|
|
912
|
-
table.move(conditions, 1, #conditions,
|
|
913
|
-
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)
|
|
914
1002
|
end,
|
|
915
1003
|
registerClassProvider = registerClassProvider,
|
|
916
|
-
registerProviders = function(
|
|
1004
|
+
registerProviders = function(path, registrationOptions, resolved)
|
|
917
1005
|
local _arg0 = resolved ~= nil
|
|
918
1006
|
assert(_arg0)
|
|
919
|
-
|
|
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
|
|
920
1015
|
end,
|
|
921
|
-
registerProvidersGlob = function(
|
|
1016
|
+
registerProvidersGlob = function(glob, registrationOptions, resolved)
|
|
922
1017
|
local _arg0 = resolved ~= nil
|
|
923
1018
|
assert(_arg0)
|
|
924
|
-
|
|
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
|
|
925
1027
|
end,
|
|
926
1028
|
registerProvider = function(config, injectionId)
|
|
927
1029
|
local _arg0 = injectionId ~= nil
|
|
@@ -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)
|