@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.
Files changed (38) hide show
  1. package/README.md +35 -27
  2. package/flamework.build +3 -3
  3. package/out/dependency.d.ts +4 -0
  4. package/out/dependency.luau +4 -0
  5. package/out/flamework.luau +8 -0
  6. package/out/index.d.ts +4 -2
  7. package/out/init.luau +10 -2
  8. package/out/lifecycle/lifecyclePlugin.d.ts +175 -2
  9. package/out/lifecycle/lifecyclePlugin.luau +636 -102
  10. package/out/module/module.luau +337 -41
  11. package/out/module/moduleBuilder.d.ts +12 -3
  12. package/out/module/moduleBuilder.luau +22 -0
  13. package/out/module/moduleDefinition.d.ts +6 -0
  14. package/out/module/providerRegistration.d.ts +8 -0
  15. package/out/module/providerRegistration.luau +29 -0
  16. package/out/plugin/pluginDefinition.d.ts +14 -4
  17. package/out/provider.d.ts +19 -0
  18. package/out/provider.luau +8 -0
  19. package/out/reflect.luau +17 -0
  20. package/out/utility/explainUnresolved.d.ts +9 -0
  21. package/out/utility/explainUnresolved.luau +43 -0
  22. package/out/utility/getClassImplements.d.ts +9 -2
  23. package/out/utility/getClassImplements.luau +89 -6
  24. package/out/utility/getClassesInPath.d.ts +10 -2
  25. package/out/utility/getClassesInPath.luau +70 -31
  26. package/out/utility/globs.d.ts +2 -2
  27. package/out/utility/globs.luau +3 -3
  28. package/out/utility/implementsCache.d.ts +16 -0
  29. package/out/utility/implementsCache.luau +35 -0
  30. package/out/utility/leftOut.d.ts +35 -0
  31. package/out/utility/leftOut.luau +171 -0
  32. package/out/utility/moduleClasses.d.ts +9 -0
  33. package/out/utility/moduleClasses.luau +64 -0
  34. package/out/utility/recycleThread.d.ts +12 -0
  35. package/out/utility/recycleThread.luau +29 -10
  36. package/out/utility/threadWaits.d.ts +36 -0
  37. package/out/utility/threadWaits.luau +81 -0
  38. 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 designed around portable, isolated and testable modules.
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)** -- start there. A ten-part guide that builds up from a working entry
8
- point to plugins and project layout, plus reference material:
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
 
@@ -27,6 +29,7 @@ bun install
27
29
  bun run build # builds every package in dependency order
28
30
  bun run test # build + transformer tests + runtime specs
29
31
  bun run lint
32
+ bun run test:place # the in-place suite in Roblox Studio (tests/place); needs Studio, Rojo and Lune
30
33
  ```
31
34
 
32
35
  ### Packages
@@ -36,31 +39,36 @@ bun run lint
36
39
  | `packages/core` | Modules, dependency injection, plugins and lifecycle events |
37
40
  | `packages/components` | CollectionService components, built on the core plugin system |
38
41
  | `packages/networking` | Remote events and functions |
39
- | `packages/testing` | In-place tests: sections, cleanup, a bindable and a remote to run them, a cloud entry; and `flamework-test`, the CLI that runs them in Roblox Studio on this machine or through Open Cloud (`cli/`) |
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 |
40
43
  | `packages/transformer` | The roblox-ts transformer |
41
44
  | `packages/transformer-plugin` | Public API for writing transformer plugins |
42
- | `packages/specs` | Runtime specs, compiled by `rbxtsc` and executed under Lune |
45
+ | `packages/specs` | Runtime specs, built by `rbxtsc` and run under Lune |
43
46
 
44
47
  ### Tests
45
48
 
46
- Two suites, both run by `bun run test`:
47
-
48
- - **Transformer tests** (`bun run test:unit`) compile a fixture project with the real `rbxtsc` and
49
- assert on the emitted Luau — guard generation, identifiers, nested macros and the plugin system.
50
- - **Runtime specs** (`bun run test:runtime`) execute compiled `@flamework-experimental/core`, `components` and
51
- `networking` under Lune using the harness in [`tests/runtime`](tests/runtime), which models
52
- roblox-ts's `TS.import` tree over the filesystem and stubs the Roblox API surface Flamework
53
- touches (Instances, attributes, CollectionService, RemoteEvents, Players, signals, `task`,
54
- `Enum`, and a `Heartbeat` pump so `Promise.delay` -- and therefore request timeouts -- runs).
55
- They cover dependency injection, modules, hooks and the per-frame lifecycle events, component
56
- construction, dependencies and streaming, and both halves of networking: events, functions,
57
- middleware and the generated guards.
58
-
59
- They run twice, once as `Server` and once as `Client`, because realm-dependent code paths --
60
- `@Provider`'s metadata, component streaming, and the client/server halves of networking -- differ
61
- between them. Where a spec asserts something realm-specific, running it from both sides is what
62
- proves the two agree: a function receives on `$name` and sends on `@name` from the server and the
63
- mirror image from the client, so the pair of runs pins the wire format down from both ends.
64
-
65
- Specs live in [`packages/specs`](packages/specs) and are compiled by `rbxtsc` like any other
66
- Flamework consumer, so they exercise the transformer and the runtime together.
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,15 +1,15 @@
1
1
  {
2
2
  "version": 1,
3
- "flameworkVersion": "2.0.0-alpha.1",
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",
7
7
  "@flamework-experimental/core:out/lifecycle/lifecyclePlugin@LifecyclePluginOptions": "$:lifecycle/lifecyclePlugin@LifecyclePluginOptions",
8
- "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnInit": "$:lifecycle/lifecycleInterfaces@OnInit",
9
- "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnStart": "$:lifecycle/lifecycleInterfaces@OnStart",
10
8
  "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnTick": "$:lifecycle/lifecycleInterfaces@OnTick",
11
9
  "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnRender": "$:lifecycle/lifecycleInterfaces@OnRender",
12
10
  "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnPhysics": "$:lifecycle/lifecycleInterfaces@OnPhysics",
11
+ "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnInit": "$:lifecycle/lifecycleInterfaces@OnInit",
12
+ "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnStart": "$:lifecycle/lifecycleInterfaces@OnStart",
13
13
  "@flamework-experimental/core:out/lifecycle/lifecycleInterfaces@OnExtinguished": "$:lifecycle/lifecycleInterfaces@OnExtinguished"
14
14
  },
15
15
  "idGenerationMode": "full",
@@ -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
  *
@@ -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
  *
@@ -1,6 +1,7 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  local TS = _G[script]
3
3
  local Reflect = TS.import(script, script.Parent, "reflect").Reflect
4
+ local getClassImplements = TS.import(script, script.Parent, "utility", "getClassImplements").getClassImplements
4
5
  local ModuleBuilder = TS.import(script, script.Parent, "module", "moduleBuilder").ModuleBuilder
5
6
  local PluginDefinition = TS.import(script, script.Parent, "plugin", "pluginDefinition").PluginDefinition
6
7
  local LifecyclePlugin = TS.import(script, script.Parent, "lifecycle", "lifecyclePlugin").LifecyclePlugin
@@ -55,6 +56,13 @@ do
55
56
  _container.isScopeActive = isScopeActive
56
57
  --* @hidden
57
58
  local function _implements(object, id)
59
+ -- A table -- an instance, a class -- through the list kept per class.
60
+ local _object = object
61
+ if type(_object) == "table" then
62
+ local _exp = getClassImplements(object)
63
+ local _id = id
64
+ return table.find(_exp, _id) ~= nil
65
+ end
58
66
  local _exp = Reflect.getMetadatas(object, "flamework:implements")
59
67
  -- ▼ ReadonlyArray.some ▼
60
68
  local _result = false
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, __setActiveScopes } from "./module/scopes";
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, __setPathRoot } from "./utility/pathRoot";
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
- exports.__setPathRoot = _pathRoot.__setPathRoot
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,17 +22,47 @@ 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 `postIgnite` starts, in attachment order; `onStart` is every member. */
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>;
30
35
  onRender: Set<OnRender>;
31
36
  onExtinguished: Set<OnExtinguished>;
37
+ private tickListeners;
38
+ private physicsListeners;
39
+ private renderListeners;
32
40
  private identifiers;
33
41
  private moduleConnections;
34
42
  private lateProviders;
43
+ /** Late providers resolved since the last turn began, in the order they were resolved: the next turn's. */
44
+ private lateQueue;
45
+ /** Whether a turn is deferred that takes whatever joins `lateQueue`. */
46
+ private hasLateTurn;
47
+ /** The turns still running `onInit`, by their thread, to the providers each walks. */
48
+ private lateTurns;
49
+ /** The provider whose `onInit` each turn is running, by the turn's thread. */
50
+ private lateTurnInits;
51
+ /** Late providers whose turn came while the module was still igniting, for `start` to schedule again. */
52
+ private heldLateProviders;
53
+ /** What each provider's constructor was given, until its `onInit` has waited for theirs. */
54
+ private initDependencies;
55
+ /**
56
+ * What joined `onExtinguished` once `extinguished` had begun -- a lazy provider resolved for the
57
+ * first time by a handler, or by an extinguished hook after this one -- and has not been told
58
+ * yet. Nothing else can join then: `listen` and `createClassInstance` refuse a module that is
59
+ * extinguishing.
60
+ */
61
+ private untold;
62
+ /** Set as `extinguished` begins its walk, for `untold`; not whether the module has begun to extinguish. */
63
+ private isExtinguishing;
35
64
  private hasStarted;
65
+ private module?;
36
66
  private isProfiling;
37
67
  constructor(options: LifecyclePluginOptions);
38
68
  private getIdentifier;
@@ -50,25 +80,168 @@ export declare class LifecycleProvider {
50
80
  * next frame look it up again.
51
81
  */
52
82
  private forget;
53
- private profile;
83
+ private observeFrameEvent;
84
+ /**
85
+ * A provider on a per-frame event does not tick before the pending `onInit`s of what its
86
+ * constructor took have finished, as its `onStart` does not start before them: during ignition
87
+ * the ignition waits for them (see `postIgnite`), and afterwards one without an `onInit` or
88
+ * `onStart` of its own gets a turn for it, which keeps it out of the per-frame events until then.
89
+ */
90
+ private addFrameProvider;
91
+ /**
92
+ * Calls a per-frame event's method on every listener attached to it, each on a recycled thread.
93
+ *
94
+ * Walked in place (see `FrameListeners`): one attached during the walk gets its first call on the
95
+ * next frame, and one detached before its turn is passed over. A late provider still waiting on
96
+ * its `onInit` is passed over too, as an eager provider does not tick before every `onInit` has
97
+ * run; with none, nothing is looked up.
98
+ *
99
+ * Nothing once the module has begun to extinguish, and the walk stops when a callback begins it:
100
+ * that callback returns here as soon as a later step of the extinguish yields, and by then
101
+ * everything may have been told `onExtinguished` while still in the sets, which only `release`
102
+ * empties. Checked once before the walk, and then only when an extinguish has begun somewhere
103
+ * since (`extinguishesBegun`), so that a frame costs no call per listener.
104
+ *
105
+ * The callback is read off the listener and called with it as `self`, and its arguments are
106
+ * handed to the thread rather than closed over, so that a frame creates nothing per listener.
107
+ */
108
+ private walkFrame;
54
109
  /**
55
110
  * Runs `onInit` synchronously, waiting on a returned Promise, so that initialisation happens in
56
111
  * dependency order and is complete before anything starts.
57
112
  */
58
113
  private runInit;
114
+ /**
115
+ * Calls `onInit` on a thread of its own, waiting for it if it yields, and hands back what it
116
+ * returned or raises what it raised.
117
+ *
118
+ * Its own so that the memory category profiling files it under stays on it: a category belongs
119
+ * to the thread it is set on and cannot be read back to be restored, so one set on the caller's
120
+ * -- the thread that called `ignite()` -- outlived the call. An `onInit` that raised left the
121
+ * provider's category there, and one that returned reset the caller's own to the default. The
122
+ * same thread whether profiling or not, so that Studio and a live server run `onInit` alike.
123
+ */
124
+ private callInit;
125
+ /**
126
+ * Whether the module has begun to extinguish: from the first line of `extinguish()`. Not from
127
+ * this plugin's `extinguished` hook, which runs after the importers have gone down and the hooks
128
+ * ahead of it have run -- any of which may yield, and let a late provider start, the providers
129
+ * start or the frame loops tick against a module that is on its way out.
130
+ */
131
+ private hasBegunExtinguishing;
59
132
  private runStart;
60
133
  /**
61
134
  * A provider constructed after ignition (a lazy one) still gets `onInit` and `onStart`, in that
62
135
  * order, once every one of its interfaces has been attached. Instances attached late through
63
136
  * `listen` or `createClassInstance` do not; they are owned by whoever created them.
137
+ *
138
+ * Until its `onInit` has finished it stays in `lateProviders`, which keeps it out of the
139
+ * per-frame events, as an eager provider is kept out of them until every `onInit` has run: it
140
+ * is attached to them at once, and used to tick before it was initialised.
64
141
  */
65
142
  private scheduleLateProvider;
143
+ /**
144
+ * Queues a late provider for the next turn, which one deferred thread takes for everything queued
145
+ * by then, the way `postIgnite` and `start` take the eager providers: every `onInit` in the order
146
+ * the providers were resolved -- a dependency, constructed as a constructor parameter, before
147
+ * what needs it -- each finished before the next begins, then every `onStart`. A thread per
148
+ * provider ran the next one's `onInit` as soon as the one before yielded, and started it before
149
+ * its dependency had finished initialising.
150
+ *
151
+ * One that an `onInit` of a running turn resolves joins that turn, as one an eager `onInit`
152
+ * resolves joins ignition's. One resolved anywhere else once a turn has begun gets the next turn
153
+ * and does not wait for that one's `onInit`s: a single turn for everything held it back behind
154
+ * an `onInit` it had nothing to do with that yielded -- for good, when that `onInit` waited for it.
155
+ * It waits only for the `onInit`s of what its constructor took, when those are still running in
156
+ * a turn of their own (see `awaitDependencies`).
157
+ */
158
+ private deferLateProvider;
159
+ /**
160
+ * The thread of the turn whose `onInit` the running thread is part of, if any: the `onInit`'s
161
+ * own thread, which `callInit` records the turn waiting on; one it resumed and has not got back
162
+ * from -- a thread it spawned, an `async` body before its first yield, a Promise's executor --
163
+ * which leaves the turn's thread `normal`; or, while the turn waits on the Promise an `onInit`
164
+ * returned, a thread doing Promise work (see `runsPromiseWork`): the `async` body once it has
165
+ * yielded, a deferred executor, an `andThen` callback, whose threads nothing records.
166
+ */
167
+ private findRunningTurn;
168
+ /**
169
+ * Whether a late provider's `onInit` can only run once the running thread is done with the turn
170
+ * it is part of: the turn that initialises the provider is that turn, and is running the provider's
171
+ * own `onInit` or one ahead of it. An `onInit` of that turn that ignites a module importing this
172
+ * one, whose eager provider takes the provider -- resolving it for the first time there, which
173
+ * has it join the turn -- holds up the very `onInit` the eager one would wait for.
174
+ *
175
+ * Only where the running thread is known to be part of the turn: the `onInit`'s own thread, or
176
+ * one it resumed and has not got back from. Not merely because the turn waits on a Promise and
177
+ * the running thread does Promise work, the guess `findRunningTurn` makes to join a turn: any
178
+ * Promise's thread passes it, and an ignition started from an unrelated Promise's work -- a
179
+ * profile load's `andThen`, an `async` handler -- then went ahead of the very `onInit` it takes.
180
+ * Nor for what that guess joined to the turn from the running thread: a lazy provider the
181
+ * ignition resolved for the first time while another's `async` `onInit` was loading joined that
182
+ * turn, and its dependent went ahead of its `onInit`. Where the guess is all there is, the wait
183
+ * goes on, and warns if it lasts (see `mayWaitForRunningThread`).
184
+ */
185
+ private initWaitsForRunningThread;
186
+ /**
187
+ * Whether a late provider's `onInit` may be waiting for the running thread after all, where
188
+ * `initWaitsForRunningThread` cannot tell: the turn that initialises it waits on the Promise an
189
+ * `onInit` returned, and the running thread does Promise work, which may be that Promise's -- an
190
+ * `async` `onInit` that ignites, after an `await`, a module taking what its turn initialises.
191
+ */
192
+ private mayWaitForRunningThread;
193
+ /**
194
+ * Waits until no provider a provider's constructor took has an `onInit` still to finish, as an
195
+ * eager provider's `onInit` comes after the eager providers' it takes. Run before the `onInit` of
196
+ * a late provider in its turn, and of an eager one during ignition -- or, for one without an
197
+ * `onInit`, before its `onStart` and per-frame events, which saw the same. A late provider resolved in a
198
+ * turn of its own, or a lazy provider of an import that an eager provider's constructor resolved
199
+ * for the first time -- which the import's plugin initialises on a turn of its own -- had the
200
+ * provider taking it initialised, and started, against a dependency not yet initialised.
201
+ * Resolved together, a dependency comes first in the same turn, so this finds nothing to wait for.
202
+ *
203
+ * Polled, since a dependency's `onInit` ends in several ways -- it finishes, it raises, its turn
204
+ * drops it as the module extinguishes -- and a wait nothing ended would hold this turn, or the
205
+ * ignition, for good. Does not wait for one whose `onInit` waits for the running thread (see
206
+ * `initWaitsForRunningThread`), which would never end; one that may, as far as can be told, is
207
+ * waited for, and warned about once the wait has lasted `SELF_WAIT_WARNING` seconds.
208
+ *
209
+ * Answers whether the provider's `onInit` may run: not once its own module has begun to
210
+ * extinguish, nor once the module of a dependency it waited for has, which dropped that
211
+ * dependency uninitialised or is releasing it. A late provider's module goes down then too, as
212
+ * an importer of that module; an igniting module is no importer yet, so its ignition fails.
213
+ */
214
+ private awaitDependencies;
215
+ private runLateProviders;
216
+ /**
217
+ * Records what a provider's constructor took, whose pending `onInit`s its own `onInit` waits
218
+ * for -- or its `onStart` and per-frame events, when it has no `onInit` -- in its turn, or
219
+ * during ignition (see `awaitDependencies`). Answers whether it took anything.
220
+ */
221
+ private recordDependencies;
66
222
  addInit(object: OnInit, context: InterfaceContext): void;
67
223
  removeInit(object: OnInit): void;
68
224
  addStart(object: OnStart, context: InterfaceContext): void;
69
225
  removeStart(object: OnStart): void;
70
226
  postIgnite(module: Module): void;
227
+ /**
228
+ * Starts the providers, then connects the per-frame events, once ignition has completed.
229
+ *
230
+ * Not at `postIgnite`, where `onStart` ran while the module was still igniting: `isIgnited()`
231
+ * answered false, `extinguish()` raised, a module importing this one could not ignite, the
232
+ * hooks of plugins after this one had not run, and ignition could still fail -- an import
233
+ * extinguished while an `onInit` yielded had the providers started against it first.
234
+ */
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;
71
243
  extinguished(module: Module): void;
244
+ private tellExtinguished;
72
245
  }
73
246
  /**
74
247
  * Creates a lifecycle plugin with the specified options.