@flamework-experimental/core 2.0.0-alpha.1 → 2.0.0-alpha.2

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 CHANGED
@@ -27,6 +27,7 @@ bun install
27
27
  bun run build # builds every package in dependency order
28
28
  bun run test # build + transformer tests + runtime specs
29
29
  bun run lint
30
+ bun run test:place # the in-place suite in Roblox Studio (tests/place); needs Studio, Rojo and Lune
30
31
  ```
31
32
 
32
33
  ### Packages
@@ -64,3 +65,8 @@ Two suites, both run by `bun run test`:
64
65
 
65
66
  Specs live in [`packages/specs`](packages/specs) and are compiled by `rbxtsc` like any other
66
67
  Flamework consumer, so they exercise the transformer and the runtime together.
68
+
69
+ A third suite runs against the real engine and is left out of `bun run test`: the
70
+ [test place](tests/place/README.md), a small game linked to the packages' builds, whose
71
+ `@flamework-experimental/testing` sections `bun run test:place` runs in Roblox Studio, both realms,
72
+ 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.3",
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",
@@ -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
@@ -29,10 +29,35 @@ export declare class LifecycleProvider {
29
29
  onPhysics: Set<OnPhysics>;
30
30
  onRender: Set<OnRender>;
31
31
  onExtinguished: Set<OnExtinguished>;
32
+ private tickListeners;
33
+ private physicsListeners;
34
+ private renderListeners;
32
35
  private identifiers;
33
36
  private moduleConnections;
34
37
  private lateProviders;
38
+ /** Late providers resolved since the last turn began, in the order they were resolved: the next turn's. */
39
+ private lateQueue;
40
+ /** Whether a turn is deferred that takes whatever joins `lateQueue`. */
41
+ private hasLateTurn;
42
+ /** The turns still running `onInit`, by their thread, to the providers each walks. */
43
+ private lateTurns;
44
+ /** The provider whose `onInit` each turn is running, by the turn's thread. */
45
+ private lateTurnInits;
46
+ /** Late providers whose turn came while the module was still igniting, for `start` to schedule again. */
47
+ private heldLateProviders;
48
+ /** What each provider's constructor was given, until its `onInit` has waited for theirs. */
49
+ private initDependencies;
50
+ /**
51
+ * What joined `onExtinguished` once `extinguished` had begun -- a lazy provider resolved for the
52
+ * first time by a handler, or by an extinguished hook after this one -- and has not been told
53
+ * yet. Nothing else can join then: `listen` and `createClassInstance` refuse a module that is
54
+ * extinguishing.
55
+ */
56
+ private untold;
57
+ /** Set as `extinguished` begins its walk, for `untold`; not whether the module has begun to extinguish. */
58
+ private isExtinguishing;
35
59
  private hasStarted;
60
+ private module?;
36
61
  private isProfiling;
37
62
  constructor(options: LifecyclePluginOptions);
38
63
  private getIdentifier;
@@ -50,25 +75,161 @@ export declare class LifecycleProvider {
50
75
  * next frame look it up again.
51
76
  */
52
77
  private forget;
53
- private profile;
78
+ private observeFrameEvent;
79
+ /**
80
+ * A provider on a per-frame event does not tick before the pending `onInit`s of what its
81
+ * constructor took have finished, as its `onStart` does not start before them: during ignition
82
+ * the ignition waits for them (see `postIgnite`), and afterwards one without an `onInit` or
83
+ * `onStart` of its own gets a turn for it, which keeps it out of the per-frame events until then.
84
+ */
85
+ private addFrameProvider;
86
+ /**
87
+ * Calls a per-frame event's method on every listener attached to it, each on a recycled thread.
88
+ *
89
+ * Walked in place (see `FrameListeners`): one attached during the walk gets its first call on the
90
+ * next frame, and one detached before its turn is passed over. A late provider still waiting on
91
+ * its `onInit` is passed over too, as an eager provider does not tick before every `onInit` has
92
+ * run; with none, nothing is looked up.
93
+ *
94
+ * Nothing once the module has begun to extinguish, and the walk stops when a callback begins it:
95
+ * that callback returns here as soon as a later step of the extinguish yields, and by then
96
+ * everything may have been told `onExtinguished` while still in the sets, which only `release`
97
+ * empties. Checked once before the walk, and then only when an extinguish has begun somewhere
98
+ * since (`extinguishesBegun`), so that a frame costs no call per listener.
99
+ *
100
+ * The callback is read off the listener and called with it as `self`, and its arguments are
101
+ * handed to the thread rather than closed over, so that a frame creates nothing per listener.
102
+ */
103
+ private walkFrame;
54
104
  /**
55
105
  * Runs `onInit` synchronously, waiting on a returned Promise, so that initialisation happens in
56
106
  * dependency order and is complete before anything starts.
57
107
  */
58
108
  private runInit;
109
+ /**
110
+ * Calls `onInit` on a thread of its own, waiting for it if it yields, and hands back what it
111
+ * returned or raises what it raised.
112
+ *
113
+ * Its own so that the memory category profiling files it under stays on it: a category belongs
114
+ * to the thread it is set on and cannot be read back to be restored, so one set on the caller's
115
+ * -- the thread that called `ignite()` -- outlived the call. An `onInit` that raised left the
116
+ * provider's category there, and one that returned reset the caller's own to the default. The
117
+ * same thread whether profiling or not, so that Studio and a live server run `onInit` alike.
118
+ */
119
+ private callInit;
120
+ /**
121
+ * Whether the module has begun to extinguish: from the first line of `extinguish()`. Not from
122
+ * this plugin's `extinguished` hook, which runs after the importers have gone down and the hooks
123
+ * ahead of it have run -- any of which may yield, and let a late provider start, the providers
124
+ * start or the frame loops tick against a module that is on its way out.
125
+ */
126
+ private hasBegunExtinguishing;
59
127
  private runStart;
60
128
  /**
61
129
  * A provider constructed after ignition (a lazy one) still gets `onInit` and `onStart`, in that
62
130
  * order, once every one of its interfaces has been attached. Instances attached late through
63
131
  * `listen` or `createClassInstance` do not; they are owned by whoever created them.
132
+ *
133
+ * Until its `onInit` has finished it stays in `lateProviders`, which keeps it out of the
134
+ * per-frame events, as an eager provider is kept out of them until every `onInit` has run: it
135
+ * is attached to them at once, and used to tick before it was initialised.
64
136
  */
65
137
  private scheduleLateProvider;
138
+ /**
139
+ * Queues a late provider for the next turn, which one deferred thread takes for everything queued
140
+ * by then, the way `postIgnite` and `start` take the eager providers: every `onInit` in the order
141
+ * the providers were resolved -- a dependency, constructed as a constructor parameter, before
142
+ * what needs it -- each finished before the next begins, then every `onStart`. A thread per
143
+ * provider ran the next one's `onInit` as soon as the one before yielded, and started it before
144
+ * its dependency had finished initialising.
145
+ *
146
+ * One that an `onInit` of a running turn resolves joins that turn, as one an eager `onInit`
147
+ * resolves joins ignition's. One resolved anywhere else once a turn has begun gets the next turn
148
+ * and does not wait for that one's `onInit`s: a single turn for everything held it back behind
149
+ * an `onInit` it had nothing to do with that yielded -- for good, when that `onInit` waited for it.
150
+ * It waits only for the `onInit`s of what its constructor took, when those are still running in
151
+ * a turn of their own (see `awaitDependencies`).
152
+ */
153
+ private deferLateProvider;
154
+ /**
155
+ * The thread of the turn whose `onInit` the running thread is part of, if any: the `onInit`'s
156
+ * own thread, which `callInit` records the turn waiting on; one it resumed and has not got back
157
+ * from -- a thread it spawned, an `async` body before its first yield, a Promise's executor --
158
+ * which leaves the turn's thread `normal`; or, while the turn waits on the Promise an `onInit`
159
+ * returned, a thread doing Promise work (see `runsPromiseWork`): the `async` body once it has
160
+ * yielded, a deferred executor, an `andThen` callback, whose threads nothing records.
161
+ */
162
+ private findRunningTurn;
163
+ /**
164
+ * Whether a late provider's `onInit` can only run once the running thread is done with the turn
165
+ * it is part of: the turn that initialises the provider is that turn, and is running the provider's
166
+ * own `onInit` or one ahead of it. An `onInit` of that turn that ignites a module importing this
167
+ * one, whose eager provider takes the provider -- resolving it for the first time there, which
168
+ * has it join the turn -- holds up the very `onInit` the eager one would wait for.
169
+ *
170
+ * Only where the running thread is known to be part of the turn: the `onInit`'s own thread, or
171
+ * one it resumed and has not got back from. Not merely because the turn waits on a Promise and
172
+ * the running thread does Promise work, the guess `findRunningTurn` makes to join a turn: any
173
+ * Promise's thread passes it, and an ignition started from an unrelated Promise's work -- a
174
+ * profile load's `andThen`, an `async` handler -- then went ahead of the very `onInit` it takes.
175
+ * Nor for what that guess joined to the turn from the running thread: a lazy provider the
176
+ * ignition resolved for the first time while another's `async` `onInit` was loading joined that
177
+ * turn, and its dependent went ahead of its `onInit`. Where the guess is all there is, the wait
178
+ * goes on, and warns if it lasts (see `mayWaitForRunningThread`).
179
+ */
180
+ private initWaitsForRunningThread;
181
+ /**
182
+ * Whether a late provider's `onInit` may be waiting for the running thread after all, where
183
+ * `initWaitsForRunningThread` cannot tell: the turn that initialises it waits on the Promise an
184
+ * `onInit` returned, and the running thread does Promise work, which may be that Promise's -- an
185
+ * `async` `onInit` that ignites, after an `await`, a module taking what its turn initialises.
186
+ */
187
+ private mayWaitForRunningThread;
188
+ /**
189
+ * Waits until no provider a provider's constructor took has an `onInit` still to finish, as an
190
+ * eager provider's `onInit` comes after the eager providers' it takes. Run before the `onInit` of
191
+ * a late provider in its turn, and of an eager one during ignition -- or, for one without an
192
+ * `onInit`, before its `onStart` and per-frame events, which saw the same. A late provider resolved in a
193
+ * turn of its own, or a lazy provider of an import that an eager provider's constructor resolved
194
+ * for the first time -- which the import's plugin initialises on a turn of its own -- had the
195
+ * provider taking it initialised, and started, against a dependency not yet initialised.
196
+ * Resolved together, a dependency comes first in the same turn, so this finds nothing to wait for.
197
+ *
198
+ * Polled, since a dependency's `onInit` ends in several ways -- it finishes, it raises, its turn
199
+ * drops it as the module extinguishes -- and a wait nothing ended would hold this turn, or the
200
+ * ignition, for good. Does not wait for one whose `onInit` waits for the running thread (see
201
+ * `initWaitsForRunningThread`), which would never end; one that may, as far as can be told, is
202
+ * waited for, and warned about once the wait has lasted `SELF_WAIT_WARNING` seconds.
203
+ *
204
+ * Answers whether the provider's `onInit` may run: not once its own module has begun to
205
+ * extinguish, nor once the module of a dependency it waited for has, which dropped that
206
+ * dependency uninitialised or is releasing it. A late provider's module goes down then too, as
207
+ * an importer of that module; an igniting module is no importer yet, so its ignition fails.
208
+ */
209
+ private awaitDependencies;
210
+ private runLateProviders;
211
+ /**
212
+ * Records what a provider's constructor took, whose pending `onInit`s its own `onInit` waits
213
+ * for -- or its `onStart` and per-frame events, when it has no `onInit` -- in its turn, or
214
+ * during ignition (see `awaitDependencies`). Answers whether it took anything.
215
+ */
216
+ private recordDependencies;
66
217
  addInit(object: OnInit, context: InterfaceContext): void;
67
218
  removeInit(object: OnInit): void;
68
219
  addStart(object: OnStart, context: InterfaceContext): void;
69
220
  removeStart(object: OnStart): void;
70
221
  postIgnite(module: Module): void;
222
+ /**
223
+ * Starts the providers, then connects the per-frame events, once ignition has completed.
224
+ *
225
+ * Not at `postIgnite`, where `onStart` ran while the module was still igniting: `isIgnited()`
226
+ * answered false, `extinguish()` raised, a module importing this one could not ignite, the
227
+ * hooks of plugins after this one had not run, and ignition could still fail -- an import
228
+ * extinguished while an `onInit` yielded had the providers started against it first.
229
+ */
230
+ start(module: Module): void;
71
231
  extinguished(module: Module): void;
232
+ private tellExtinguished;
72
233
  }
73
234
  /**
74
235
  * Creates a lifecycle plugin with the specified options.