@moku-labs/game 0.15.36 → 0.15.38
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/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +3 -3
- package/dist/app.mjs +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-BGrtxBga.d.mts → commands-CMFdiDcg.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-0af8h-Ro.d.mts → define-D27mfOhZ.d.mts} +2 -2
- package/dist/{headless-BFI7EXHo.d.mts → headless-CRqMxzqv.d.mts} +1 -1
- package/dist/{index-Cs4qaPMq.d.mts → index-DFAjLwxi.d.mts} +10 -8
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +6 -6
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{runtime-DIOe0nCH.d.mts → runtime-CbLZyDXj.d.mts} +1 -1
- package/dist/{src-BPEIEsWH.mjs → src-B2vblgmb.mjs} +626 -99
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-BAv5qOZX.d.mts → types-0Br0SjTB.d.mts} +28 -2
- package/dist/{types-B1W2CPUu.d.mts → types-54KzmAA6.d.mts} +2 -2
- package/dist/{types-D52biu5v.d.mts → types-CGPWYbZO.d.mts} +2 -2
- package/dist/{types-K3qfyTu0.d.mts → types-C_dk-lXz.d.mts} +79 -9
- package/dist/{types-Dh2cyp0H.d.mts → types-H1FiWmKa.d.mts} +4 -4
- package/dist/{visual-DMx0vv_3.d.mts → visual-DjTJ02c3.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
|
@@ -838,7 +838,7 @@ const BUSY = 4;
|
|
|
838
838
|
/** The thermal rank from which the rule never steps up: `"moderate"` and every hotter status. */
|
|
839
839
|
const HEAT_LIMIT_RANK = 2;
|
|
840
840
|
/** Milliseconds in a second: turns a rate into the period of one frame. */
|
|
841
|
-
const SECOND_MS$
|
|
841
|
+
const SECOND_MS$2 = 1e3;
|
|
842
842
|
/** Every choice `setRate` takes: `"auto"` and the three fixed rates. */
|
|
843
843
|
const RATE_CHOICES = Object.freeze([
|
|
844
844
|
"auto",
|
|
@@ -1432,8 +1432,8 @@ function judgeFrame$1(ctx, g, intervalMs, at) {
|
|
|
1432
1432
|
const { fps } = ctx.state.rate;
|
|
1433
1433
|
const higher = stepAbove(g.ladder, fps);
|
|
1434
1434
|
const missed = intervalMs > missLineMs(fps, ctx.state.rate.display.hz ?? fps);
|
|
1435
|
-
const heavy = higher !== void 0 && g.lastWorkMs > .5 * SECOND_MS$
|
|
1436
|
-
const busy = missed && g.lastWorkMs > SECOND_MS$
|
|
1435
|
+
const heavy = higher !== void 0 && g.lastWorkMs > .5 * SECOND_MS$2 / higher;
|
|
1436
|
+
const busy = missed && g.lastWorkMs > SECOND_MS$2 / fps;
|
|
1437
1437
|
const slot = g.written % 512;
|
|
1438
1438
|
evict$2(g, at);
|
|
1439
1439
|
g.sampleAt[slot] = at;
|
|
@@ -9647,8 +9647,9 @@ function inertHandle() {
|
|
|
9647
9647
|
return INERT;
|
|
9648
9648
|
}
|
|
9649
9649
|
/**
|
|
9650
|
-
* The fields another writer owns on one component of one entity: the fields a
|
|
9651
|
-
*
|
|
9650
|
+
* The fields another writer owns on one component of one entity: the fields a `mute()` holds and
|
|
9651
|
+
* the `initialOnly` fields of a view's projection (one table, `state.mutes`), and the fields the
|
|
9652
|
+
* component type gives to a plugin for good.
|
|
9652
9653
|
*
|
|
9653
9654
|
* @param pctx - Domain context of the projection module.
|
|
9654
9655
|
* @param entity - The entity to ask about.
|
|
@@ -10803,6 +10804,221 @@ function routeHint(hints, projection, key) {
|
|
|
10803
10804
|
}
|
|
10804
10805
|
}
|
|
10805
10806
|
//#endregion
|
|
10807
|
+
//#region src/plugins/world/projection/mutes.ts
|
|
10808
|
+
/**
|
|
10809
|
+
* Tells a table `mute()` made apart from a shared `initialOnly` seed, which is frozen.
|
|
10810
|
+
*
|
|
10811
|
+
* @param table - A table of `state.mutes`.
|
|
10812
|
+
* @returns True when the table may be written.
|
|
10813
|
+
*/
|
|
10814
|
+
function isOwnTable(table) {
|
|
10815
|
+
return !Object.isFrozen(table);
|
|
10816
|
+
}
|
|
10817
|
+
/**
|
|
10818
|
+
* Tells a field set `mute()` made apart from a set of a shared `initialOnly` seed, which is frozen.
|
|
10819
|
+
*
|
|
10820
|
+
* @param fields - A field set of one table.
|
|
10821
|
+
* @returns True when the set may be written.
|
|
10822
|
+
*/
|
|
10823
|
+
function isOwnFields(fields) {
|
|
10824
|
+
return !Object.isFrozen(fields);
|
|
10825
|
+
}
|
|
10826
|
+
/**
|
|
10827
|
+
* The table a mute may write: the entity's own one, a copy of the seed it was born with, or a new one.
|
|
10828
|
+
*
|
|
10829
|
+
* @param stored - The table the entity has now, if any.
|
|
10830
|
+
* @returns A writable table.
|
|
10831
|
+
*/
|
|
10832
|
+
function ownTable(stored) {
|
|
10833
|
+
if (stored === void 0) return /* @__PURE__ */ new Map();
|
|
10834
|
+
return isOwnTable(stored) ? stored : new Map(stored);
|
|
10835
|
+
}
|
|
10836
|
+
/**
|
|
10837
|
+
* The field set a mute may write: the component's own one, a copy of its seed set, or a new one.
|
|
10838
|
+
*
|
|
10839
|
+
* @param stored - The field set the component has now, if any.
|
|
10840
|
+
* @returns A writable set.
|
|
10841
|
+
*/
|
|
10842
|
+
function ownFields(stored) {
|
|
10843
|
+
if (stored === void 0) return /* @__PURE__ */ new Set();
|
|
10844
|
+
return isOwnFields(stored) ? stored : new Set(stored);
|
|
10845
|
+
}
|
|
10846
|
+
/**
|
|
10847
|
+
* The `initialOnly` fields of one component of a view, from the spec of its projection.
|
|
10848
|
+
*
|
|
10849
|
+
* @param state - The projection branch of the plugin state.
|
|
10850
|
+
* @param entity - The entity of the view.
|
|
10851
|
+
* @param name - The component name.
|
|
10852
|
+
* @returns The seed set, or `undefined` for an entity that is no view of such a projection.
|
|
10853
|
+
*/
|
|
10854
|
+
function initialFieldsOf(state, entity, name) {
|
|
10855
|
+
const projection = state.byEntity.get(entity)?.projection;
|
|
10856
|
+
const spec = projection === void 0 ? void 0 : state.specs.get(projection);
|
|
10857
|
+
return spec === void 0 ? void 0 : state.initialOnly.get(spec)?.get(name);
|
|
10858
|
+
}
|
|
10859
|
+
/**
|
|
10860
|
+
* Deletes a released field set once it is empty, and the entity's table once no component is
|
|
10861
|
+
* left. Only the table and the set this mute filled are touched, so a remover that runs after a
|
|
10862
|
+
* clear or a new birth on the same id never drops a newer mute.
|
|
10863
|
+
*
|
|
10864
|
+
* @param mutes - The mute tables of every entity.
|
|
10865
|
+
* @param entity - The muted entity.
|
|
10866
|
+
* @param table - The table the mute wrote.
|
|
10867
|
+
* @param name - The muted component's name.
|
|
10868
|
+
* @param owned - The field set the remover released from.
|
|
10869
|
+
*/
|
|
10870
|
+
function dropEmptyMute(mutes, entity, table, name, owned) {
|
|
10871
|
+
if (owned.size > 0 || mutes.get(entity) !== table || table.get(name) !== owned) return;
|
|
10872
|
+
table.delete(name);
|
|
10873
|
+
if (table.size === 0) mutes.delete(entity);
|
|
10874
|
+
}
|
|
10875
|
+
/**
|
|
10876
|
+
* Hands fields of one component of one entity to another writer, until the remover runs. A view
|
|
10877
|
+
* born with an `initialOnly` seed gets its own copy first. The remover keeps the seed fields, asked
|
|
10878
|
+
* when it runs: a mute made inside the spawn came before the view was known.
|
|
10879
|
+
*
|
|
10880
|
+
* @param state - The projection branch of the plugin state.
|
|
10881
|
+
* @param entity - The entity.
|
|
10882
|
+
* @param name - The component name.
|
|
10883
|
+
* @param fields - The field names the caller owns.
|
|
10884
|
+
* @returns The remover; a no-op when called twice.
|
|
10885
|
+
*/
|
|
10886
|
+
function muteFields(state, entity, name, fields) {
|
|
10887
|
+
const table = ownTable(state.mutes.get(entity));
|
|
10888
|
+
const owned = ownFields(table.get(name));
|
|
10889
|
+
for (const field of fields) owned.add(field);
|
|
10890
|
+
forgetJoined(state, owned);
|
|
10891
|
+
table.set(name, owned);
|
|
10892
|
+
state.mutes.set(entity, table);
|
|
10893
|
+
let released = false;
|
|
10894
|
+
return () => {
|
|
10895
|
+
if (released) return;
|
|
10896
|
+
released = true;
|
|
10897
|
+
const kept = initialFieldsOf(state, entity, name);
|
|
10898
|
+
for (const field of fields) if (kept?.has(field) !== true) owned.delete(field);
|
|
10899
|
+
forgetJoined(state, owned);
|
|
10900
|
+
dropEmptyMute(state.mutes, entity, table, name, owned);
|
|
10901
|
+
};
|
|
10902
|
+
}
|
|
10903
|
+
/**
|
|
10904
|
+
* Builds the frozen seed of a projection's `initialOnly` list: component name to its fields, a
|
|
10905
|
+
* component named twice with both lists joined.
|
|
10906
|
+
*
|
|
10907
|
+
* @param spec - The projection spec.
|
|
10908
|
+
* @param pairs - Its `initialOnly` list, not empty.
|
|
10909
|
+
* @returns The seed.
|
|
10910
|
+
* @throws {Error} For a pair with no field.
|
|
10911
|
+
*/
|
|
10912
|
+
function buildSeed(spec, pairs) {
|
|
10913
|
+
const seed = /* @__PURE__ */ new Map();
|
|
10914
|
+
for (const [component, fields] of pairs) {
|
|
10915
|
+
const name = component.componentName;
|
|
10916
|
+
if (fields.length === 0) throw new Error(`[game] Projection "${spec.name}" lists ${name} in initialOnly with no field.\n Name the fields only the birth of a view writes, or drop the pair.`);
|
|
10917
|
+
seed.set(name, Object.freeze(/* @__PURE__ */ new Set([...seed.get(name) ?? [], ...fields])));
|
|
10918
|
+
}
|
|
10919
|
+
return Object.freeze(seed);
|
|
10920
|
+
}
|
|
10921
|
+
/**
|
|
10922
|
+
* Checks the `initialOnly` list of a spec and keeps its seed: once per spec, since a name is
|
|
10923
|
+
* registered once and `replace` brings a new spec. A spec without the option, or with an empty
|
|
10924
|
+
* list, has none.
|
|
10925
|
+
*
|
|
10926
|
+
* @param state - The projection branch of the plugin state.
|
|
10927
|
+
* @param spec - The spec `register` or `replace` stores.
|
|
10928
|
+
* @returns The seed, or `undefined`.
|
|
10929
|
+
* @throws {Error} For a pair with no field; nothing is kept then.
|
|
10930
|
+
*/
|
|
10931
|
+
function learnSeed(state, spec) {
|
|
10932
|
+
const pairs = spec.initialOnly;
|
|
10933
|
+
if (pairs === void 0 || pairs.length === 0) return void 0;
|
|
10934
|
+
const seed = buildSeed(spec, pairs);
|
|
10935
|
+
state.initialOnly.set(spec, seed);
|
|
10936
|
+
return seed;
|
|
10937
|
+
}
|
|
10938
|
+
/**
|
|
10939
|
+
* Joins a seed into a table of the entity's own, in place, so the remover of every mute that wrote
|
|
10940
|
+
* the table still holds its table and its sets: a component the table lacks gets the shared set
|
|
10941
|
+
* of the seed, the set a mute wrote gets the fields of the seed.
|
|
10942
|
+
*
|
|
10943
|
+
* @param state - The projection branch of the plugin state.
|
|
10944
|
+
* @param table - The entity's own table.
|
|
10945
|
+
* @param seed - The seed to join.
|
|
10946
|
+
*/
|
|
10947
|
+
function joinSeed(state, table, seed) {
|
|
10948
|
+
for (const [name, fields] of seed) {
|
|
10949
|
+
const stored = table.get(name);
|
|
10950
|
+
if (stored === void 0) {
|
|
10951
|
+
table.set(name, fields);
|
|
10952
|
+
continue;
|
|
10953
|
+
}
|
|
10954
|
+
const owned = ownFields(stored);
|
|
10955
|
+
for (const field of fields) owned.add(field);
|
|
10956
|
+
forgetJoined(state, owned);
|
|
10957
|
+
table.set(name, owned);
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
/**
|
|
10961
|
+
* Gives a view that was just born the seed of its projection, so from now on nothing of the
|
|
10962
|
+
* projection writes or compares those fields. The birth itself wrote them from `view`. A view a
|
|
10963
|
+
* listener of its spawn muted already keeps its table, and the seed joins it.
|
|
10964
|
+
*
|
|
10965
|
+
* @param state - The projection branch of the plugin state.
|
|
10966
|
+
* @param spec - The projection spec.
|
|
10967
|
+
* @param entity - The entity of the new view.
|
|
10968
|
+
*/
|
|
10969
|
+
function seedMutes(state, spec, entity) {
|
|
10970
|
+
if (spec.initialOnly === void 0) return;
|
|
10971
|
+
const seed = state.initialOnly.get(spec);
|
|
10972
|
+
if (seed === void 0) return;
|
|
10973
|
+
const stored = state.mutes.get(entity);
|
|
10974
|
+
if (stored === void 0) {
|
|
10975
|
+
state.mutes.set(entity, seed);
|
|
10976
|
+
return;
|
|
10977
|
+
}
|
|
10978
|
+
const table = ownTable(stored);
|
|
10979
|
+
joinSeed(state, table, seed);
|
|
10980
|
+
state.mutes.set(entity, table);
|
|
10981
|
+
}
|
|
10982
|
+
/**
|
|
10983
|
+
* Moves a view with a table of its own from one seed to the next: a set of the old seed goes, the
|
|
10984
|
+
* new seed joins. A set a mute wrote keeps every field it holds, an old seed's field it copied too,
|
|
10985
|
+
* since the table does not say which mute named it. A table left empty goes.
|
|
10986
|
+
*
|
|
10987
|
+
* @param state - The projection branch of the plugin state.
|
|
10988
|
+
* @param entity - The entity of the view.
|
|
10989
|
+
* @param stored - The table the view has now, if any.
|
|
10990
|
+
* @param from - The seed of the old spec, if any.
|
|
10991
|
+
* @param to - The seed of the new spec, if any.
|
|
10992
|
+
*/
|
|
10993
|
+
function reseedOwn(state, entity, stored, from, to) {
|
|
10994
|
+
const table = ownTable(stored);
|
|
10995
|
+
for (const [name, fields] of from ?? []) if (table.get(name) === fields) table.delete(name);
|
|
10996
|
+
if (to !== void 0) joinSeed(state, table, to);
|
|
10997
|
+
if (table.size === 0) state.mutes.delete(entity);
|
|
10998
|
+
else state.mutes.set(entity, table);
|
|
10999
|
+
}
|
|
11000
|
+
/**
|
|
11001
|
+
* Moves the views of a replaced projection from the seed of the old spec to the seed of the new
|
|
11002
|
+
* one. A view that holds the old seed, or none when the old spec had none, takes the new seed. A
|
|
11003
|
+
* view a `mute()` gave its own table keeps it: the sets of the old seed in it go, the new seed
|
|
11004
|
+
* joins it.
|
|
11005
|
+
*
|
|
11006
|
+
* @param state - The projection branch of the plugin state.
|
|
11007
|
+
* @param name - The projection name.
|
|
11008
|
+
* @param from - The seed of the old spec, if any.
|
|
11009
|
+
* @param to - The seed of the new spec, if any.
|
|
11010
|
+
*/
|
|
11011
|
+
function reseedViews(state, name, from, to) {
|
|
11012
|
+
if (from === to) return;
|
|
11013
|
+
for (const view of state.byEntity.values()) {
|
|
11014
|
+
if (view.projection !== name) continue;
|
|
11015
|
+
const stored = state.mutes.get(view.entity);
|
|
11016
|
+
if (stored !== from) reseedOwn(state, view.entity, stored, from, to);
|
|
11017
|
+
else if (to === void 0) state.mutes.delete(view.entity);
|
|
11018
|
+
else state.mutes.set(view.entity, to);
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
//#endregion
|
|
10806
11022
|
//#region src/plugins/world/projection/reconcile.ts
|
|
10807
11023
|
/**
|
|
10808
11024
|
* Creates the empty set of used hints. It lives in its own non-exported function because lint
|
|
@@ -10894,6 +11110,7 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
10894
11110
|
const view = createViewRecord(entity, spec.name, entry.key, entry.item, values, entry.direct ? "mount" : "change");
|
|
10895
11111
|
const state = pctx.ctx.state.projection;
|
|
10896
11112
|
const building = state.building;
|
|
11113
|
+
seedMutes(state, spec, entity);
|
|
10897
11114
|
mounted.live.set(entry.key, view);
|
|
10898
11115
|
state.byEntity.set(entity, view);
|
|
10899
11116
|
state.membershipVersion += 1;
|
|
@@ -11739,39 +11956,6 @@ function isConvergeCause(cause) {
|
|
|
11739
11956
|
return cause === "load" || cause === "restore";
|
|
11740
11957
|
}
|
|
11741
11958
|
/**
|
|
11742
|
-
* Creates the empty mute table of one entity. It lives in its own non-exported function because
|
|
11743
|
-
* lint rule L5 refuses a collection built inside an exported declaration.
|
|
11744
|
-
*
|
|
11745
|
-
* @returns An empty table of component name to owned fields.
|
|
11746
|
-
*/
|
|
11747
|
-
function emptyMuteTable() {
|
|
11748
|
-
return /* @__PURE__ */ new Map();
|
|
11749
|
-
}
|
|
11750
|
-
/**
|
|
11751
|
-
* Creates the empty field set of one muted component.
|
|
11752
|
-
*
|
|
11753
|
-
* @returns An empty set of field names.
|
|
11754
|
-
*/
|
|
11755
|
-
function emptyFieldSet() {
|
|
11756
|
-
return /* @__PURE__ */ new Set();
|
|
11757
|
-
}
|
|
11758
|
-
/**
|
|
11759
|
-
* Deletes a released field set once it is empty, and the entity's table once no component is
|
|
11760
|
-
* left. Only the set and table still stored are touched, so a remover that runs after a clear
|
|
11761
|
-
* never drops a newer mute.
|
|
11762
|
-
*
|
|
11763
|
-
* @param mutes - The mute tables of every entity.
|
|
11764
|
-
* @param entity - The muted entity.
|
|
11765
|
-
* @param name - The muted component's name.
|
|
11766
|
-
* @param owned - The field set the remover released from.
|
|
11767
|
-
*/
|
|
11768
|
-
function dropEmptyMute(mutes, entity, name, owned) {
|
|
11769
|
-
const byComponent = mutes.get(entity);
|
|
11770
|
-
if (owned.size > 0 || byComponent?.get(name) !== owned) return;
|
|
11771
|
-
byComponent.delete(name);
|
|
11772
|
-
if (byComponent.size === 0) mutes.delete(entity);
|
|
11773
|
-
}
|
|
11774
|
-
/**
|
|
11775
11959
|
* Creates the empty dirty record the next reconcile reads.
|
|
11776
11960
|
*
|
|
11777
11961
|
* @returns Nothing to reconcile yet.
|
|
@@ -11876,13 +12060,18 @@ function createProjectionApi(ctx, deps) {
|
|
|
11876
12060
|
return {
|
|
11877
12061
|
register: (spec) => {
|
|
11878
12062
|
if (state.specs.has(spec.name)) throw new Error(`[game] Projection "${spec.name}" is registered twice.\n Give the second projection another name.`);
|
|
12063
|
+
learnSeed(state, spec);
|
|
11879
12064
|
state.specs.set(spec.name, spec);
|
|
11880
12065
|
},
|
|
11881
12066
|
replace: (spec) => {
|
|
11882
|
-
|
|
12067
|
+
const previous = state.specs.get(spec.name);
|
|
12068
|
+
if (previous === void 0) throw new Error(`[game] Projection "${spec.name}" is not registered.\n Register it before replacing it.`);
|
|
11883
12069
|
const mounted = state.mounted.has(spec.name);
|
|
11884
12070
|
if (mounted) checkLayers(spec, state.layers);
|
|
12071
|
+
const from = state.initialOnly.get(previous);
|
|
12072
|
+
const seed = learnSeed(state, spec);
|
|
11885
12073
|
state.specs.set(spec.name, spec);
|
|
12074
|
+
reseedViews(state, spec.name, from, seed);
|
|
11886
12075
|
if (mounted) forceRerun();
|
|
11887
12076
|
},
|
|
11888
12077
|
setLayers: (list) => {
|
|
@@ -11915,22 +12104,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11915
12104
|
playSettle(pctx, view, spec, restingPeers(mounted), looseComponents(pctx, view));
|
|
11916
12105
|
dropIfStill(pctx, view);
|
|
11917
12106
|
},
|
|
11918
|
-
mute: (entity, component, fields) =>
|
|
11919
|
-
const byComponent = state.mutes.get(entity) ?? emptyMuteTable();
|
|
11920
|
-
const owned = byComponent.get(component.componentName) ?? emptyFieldSet();
|
|
11921
|
-
for (const field of fields) owned.add(field);
|
|
11922
|
-
forgetJoined(state, owned);
|
|
11923
|
-
byComponent.set(component.componentName, owned);
|
|
11924
|
-
state.mutes.set(entity, byComponent);
|
|
11925
|
-
let released = false;
|
|
11926
|
-
return () => {
|
|
11927
|
-
if (released) return;
|
|
11928
|
-
released = true;
|
|
11929
|
-
for (const field of fields) owned.delete(field);
|
|
11930
|
-
forgetJoined(state, owned);
|
|
11931
|
-
dropEmptyMute(state.mutes, entity, component.componentName, owned);
|
|
11932
|
-
};
|
|
11933
|
-
},
|
|
12107
|
+
mute: (entity, component, fields) => muteFields(state, entity, component.componentName, fields),
|
|
11934
12108
|
lift: (entity, on) => {
|
|
11935
12109
|
const view = state.byEntity.get(entity);
|
|
11936
12110
|
if (view === void 0) return;
|
|
@@ -12380,6 +12554,7 @@ function createProjectionState() {
|
|
|
12380
12554
|
keys: emptyMap$9(),
|
|
12381
12555
|
keysByEntity: emptyMap$9(),
|
|
12382
12556
|
mutes: emptyMap$9(),
|
|
12557
|
+
initialOnly: emptyWeakMap$2(),
|
|
12383
12558
|
ownedSets: emptyWeakMap$2(),
|
|
12384
12559
|
joinedMutes: emptyWeakMap$2(),
|
|
12385
12560
|
converged: [],
|
|
@@ -29685,7 +29860,7 @@ function layerOf(sctx, entity) {
|
|
|
29685
29860
|
//#endregion
|
|
29686
29861
|
//#region src/plugins/renderer/sync/build.ts
|
|
29687
29862
|
/** Views made between two reads of the clock: a read costs more than a small view. */
|
|
29688
|
-
const CLOCK_EVERY = 8;
|
|
29863
|
+
const CLOCK_EVERY$1 = 8;
|
|
29689
29864
|
/**
|
|
29690
29865
|
* Tells whether a scene is being built: `left()` answers a finite number only while a build runs.
|
|
29691
29866
|
*
|
|
@@ -29783,7 +29958,7 @@ function makeAside(sctx, report) {
|
|
|
29783
29958
|
made += makeParentFirst(sctx, entity, report);
|
|
29784
29959
|
if (made >= check) {
|
|
29785
29960
|
if (projection.left() <= 0) break;
|
|
29786
|
-
check = made + CLOCK_EVERY;
|
|
29961
|
+
check = made + CLOCK_EVERY$1;
|
|
29787
29962
|
}
|
|
29788
29963
|
}
|
|
29789
29964
|
if (aside.head >= aside.count) {
|
|
@@ -38719,10 +38894,12 @@ function swapIn(ctx, scene, named, build) {
|
|
|
38719
38894
|
* `world.projection.built()` first answers true commits it with the swap of a one-block switch. An
|
|
38720
38895
|
* abort of the node discards the build and leaves the world as it stood.
|
|
38721
38896
|
*/
|
|
38722
|
-
/**
|
|
38723
|
-
const
|
|
38724
|
-
/** The
|
|
38725
|
-
const
|
|
38897
|
+
/** A second in ms: the frame interval is this over the frame rate. */
|
|
38898
|
+
const SECOND_MS$1 = 1e3;
|
|
38899
|
+
/** The share of a frame interval `"auto"` gives the building work: two thirds. */
|
|
38900
|
+
const BUILD_SHARE = 2 / 3;
|
|
38901
|
+
/** The budget of a frame when `time` answers no usable frame rate: two thirds of a 60 Hz frame, ≈ 11.1 ms. */
|
|
38902
|
+
const FALLBACK_BUDGET_MS = SECOND_MS$1 / 60 * BUILD_SHARE;
|
|
38726
38903
|
/**
|
|
38727
38904
|
* Tells whether a value is a number a frame can count with: finite and above 0. Pure.
|
|
38728
38905
|
*
|
|
@@ -38752,8 +38929,8 @@ function agentNow(ctx) {
|
|
|
38752
38929
|
}
|
|
38753
38930
|
/**
|
|
38754
38931
|
* The building budget of a frame for a build that starts now: the number a game configured, or
|
|
38755
|
-
* for `"auto"`
|
|
38756
|
-
* here. A rate that is not a finite number above 0 gives
|
|
38932
|
+
* for `"auto"` two thirds of the frame interval the loop runs at, `(1000 / time.rate().fps) * 2 / 3`,
|
|
38933
|
+
* read once here. A rate that is not a finite number above 0 gives two thirds of a 60 Hz frame.
|
|
38757
38934
|
*
|
|
38758
38935
|
* @param ctx - Domain context of the plugin.
|
|
38759
38936
|
* @returns Milliseconds per frame the building work may take.
|
|
@@ -38762,7 +38939,7 @@ function budgetOf(ctx) {
|
|
|
38762
38939
|
const { buildMs } = ctx.config;
|
|
38763
38940
|
if (buildMs !== "auto") return buildMs;
|
|
38764
38941
|
const { fps } = ctx.deps.time.rate();
|
|
38765
|
-
return isPositiveFinite(fps) ?
|
|
38942
|
+
return isPositiveFinite(fps) ? SECOND_MS$1 / fps * BUILD_SHARE : FALLBACK_BUDGET_MS;
|
|
38766
38943
|
}
|
|
38767
38944
|
/**
|
|
38768
38945
|
* Ends the build with the swap: its listeners go and the record is dropped, then the swap of a
|
|
@@ -39371,7 +39548,7 @@ function createScenesState() {
|
|
|
39371
39548
|
* projections: [wardRoom]
|
|
39372
39549
|
* });
|
|
39373
39550
|
*
|
|
39374
|
-
* // A game gives the next scene a fixed 3 ms of each frame (
|
|
39551
|
+
* // A game gives the next scene a fixed 3 ms of each frame (two thirds of it by default) while the old one stays on the screen.
|
|
39375
39552
|
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { buildMs: 3 } } });
|
|
39376
39553
|
* ```
|
|
39377
39554
|
*/
|
|
@@ -53712,7 +53889,9 @@ function dropHost(state, entity) {
|
|
|
53712
53889
|
if (state.hosts.delete(entity)) hostsMoved(state);
|
|
53713
53890
|
}
|
|
53714
53891
|
/**
|
|
53715
|
-
* The element of a host entity while it hosts: mounted, live and not leaving.
|
|
53892
|
+
* The element of a host entity while it hosts: mounted, live and not leaving. A host of a root
|
|
53893
|
+
* built aside that is applied in parts hosts nothing until the root is whole (spec 33 A7): the
|
|
53894
|
+
* last part gives it its views.
|
|
53716
53895
|
*
|
|
53717
53896
|
* @param state - The jsx state.
|
|
53718
53897
|
* @param host - An entity of the host set.
|
|
@@ -53720,7 +53899,8 @@ function dropHost(state, entity) {
|
|
|
53720
53899
|
*/
|
|
53721
53900
|
function liveHost(state, host) {
|
|
53722
53901
|
const element = state.elements.get(host);
|
|
53723
|
-
|
|
53902
|
+
if (element === void 0 || !element.live || state.exiting.has(host)) return void 0;
|
|
53903
|
+
return state.roots.get(element.root)?.applying === void 0 ? element : void 0;
|
|
53724
53904
|
}
|
|
53725
53905
|
/**
|
|
53726
53906
|
* The host that owns the views of a projection: the first live host that names it. A view
|
|
@@ -55810,7 +55990,7 @@ function coveredByLive(state, root) {
|
|
|
55810
55990
|
* @param root - A mounted root.
|
|
55811
55991
|
* @returns True when the settle step has to look at it.
|
|
55812
55992
|
*/
|
|
55813
|
-
function waits(state, root) {
|
|
55993
|
+
function waits$1(state, root) {
|
|
55814
55994
|
return (root.popup?.released === true || root.covered) && !state.removing.has(root.entity);
|
|
55815
55995
|
}
|
|
55816
55996
|
/**
|
|
@@ -55821,7 +56001,7 @@ function waits(state, root) {
|
|
|
55821
56001
|
* @returns True when at least one root waits.
|
|
55822
56002
|
*/
|
|
55823
56003
|
function anyWaits(state) {
|
|
55824
|
-
for (const root of state.roots.values()) if (waits(state, root)) return true;
|
|
56004
|
+
for (const root of state.roots.values()) if (waits$1(state, root)) return true;
|
|
55825
56005
|
return false;
|
|
55826
56006
|
}
|
|
55827
56007
|
/**
|
|
@@ -55844,7 +56024,7 @@ function settlePopups(ctx, state, unmount) {
|
|
|
55844
56024
|
* @param unmount - Starts the exit of a root.
|
|
55845
56025
|
*/
|
|
55846
56026
|
function settleWaiting(ctx, state, unmount) {
|
|
55847
|
-
const waiting = [...state.roots.values()].filter((root) => waits(state, root));
|
|
56027
|
+
const waiting = [...state.roots.values()].filter((root) => waits$1(state, root));
|
|
55848
56028
|
const rests = flowRests(ctx.deps.flow.state());
|
|
55849
56029
|
waiting.sort((first, second) => second.order - first.order);
|
|
55850
56030
|
for (const root of waiting) {
|
|
@@ -57184,6 +57364,320 @@ function settleOverlays(ctx, state, layout) {
|
|
|
57184
57364
|
}
|
|
57185
57365
|
}
|
|
57186
57366
|
//#endregion
|
|
57367
|
+
//#region src/plugins/ui/jsx/parts.ts
|
|
57368
|
+
/**
|
|
57369
|
+
* How many elements a root built aside makes or applies in a frame between two reads of the
|
|
57370
|
+
* frame's budget, from the first read on (spec 33 A2, A6): the clock is read every 8 elements.
|
|
57371
|
+
*/
|
|
57372
|
+
const CLOCK_EVERY = 8;
|
|
57373
|
+
/**
|
|
57374
|
+
* The most elements of roots built aside one frame applies. The components `applyFirst` adds are
|
|
57375
|
+
* flushed after the system, where the budget does not see them: the cap keeps that flush short
|
|
57376
|
+
* (spec 33 A6; 275 elements in one frame took 3.6 ms in Node, about 15 ms on the A26).
|
|
57377
|
+
*/
|
|
57378
|
+
const APPLY_CAP = 100;
|
|
57379
|
+
/** What a parked part holds once it is back in the pool: no nodes and no element, so the pool keeps no tree alive. */
|
|
57380
|
+
const NO_NODES = [];
|
|
57381
|
+
/**
|
|
57382
|
+
* Tells whether a root has parked parts.
|
|
57383
|
+
*
|
|
57384
|
+
* @param root - The root.
|
|
57385
|
+
* @returns True while children of its elements wait.
|
|
57386
|
+
*/
|
|
57387
|
+
function hasParked(root) {
|
|
57388
|
+
return (root.parked?.length ?? 0) > 0;
|
|
57389
|
+
}
|
|
57390
|
+
/**
|
|
57391
|
+
* Tells whether parts of a root built aside wait: children parked, or its application open.
|
|
57392
|
+
* Two field reads; false for every other root.
|
|
57393
|
+
*
|
|
57394
|
+
* @param root - The root, if it is still known.
|
|
57395
|
+
* @returns True while its build waits for a later frame.
|
|
57396
|
+
*/
|
|
57397
|
+
function waits(root) {
|
|
57398
|
+
return root !== void 0 && (root.applying !== void 0 || hasParked(root));
|
|
57399
|
+
}
|
|
57400
|
+
/**
|
|
57401
|
+
* Builds the parts half of the reconcile: the park queue of each root built aside and the pools of
|
|
57402
|
+
* its parts, the drain of the queue, the hold of a dirty root, and the application in parts.
|
|
57403
|
+
*
|
|
57404
|
+
* @param ctx - Domain context of the ui plugin.
|
|
57405
|
+
* @param reconcile - The diff and the application the reconcile lends.
|
|
57406
|
+
* @returns The steps the reconcile calls.
|
|
57407
|
+
*/
|
|
57408
|
+
function createParts(ctx, reconcile) {
|
|
57409
|
+
const state = ctx.state.jsx;
|
|
57410
|
+
const lookup = (entity) => state.elements.get(entity);
|
|
57411
|
+
let entered = 0;
|
|
57412
|
+
let spent = false;
|
|
57413
|
+
let unitRan = false;
|
|
57414
|
+
let applied = 0;
|
|
57415
|
+
let landed = false;
|
|
57416
|
+
const parkedPool = [];
|
|
57417
|
+
const cursorPool = [];
|
|
57418
|
+
/**
|
|
57419
|
+
* Starts the frame's reconcile: no element entered, the budget not read, no building unit run.
|
|
57420
|
+
*/
|
|
57421
|
+
function beginReconcile() {
|
|
57422
|
+
entered = 0;
|
|
57423
|
+
spent = false;
|
|
57424
|
+
unitRan = false;
|
|
57425
|
+
}
|
|
57426
|
+
/**
|
|
57427
|
+
* Starts the frame's solve: no element applied in parts, no application ended.
|
|
57428
|
+
*/
|
|
57429
|
+
function beginSolve() {
|
|
57430
|
+
applied = 0;
|
|
57431
|
+
landed = false;
|
|
57432
|
+
}
|
|
57433
|
+
/**
|
|
57434
|
+
* Tells whether an application of a root built aside ended in this solve: its scrolls settle now.
|
|
57435
|
+
*
|
|
57436
|
+
* @returns True after the last part of an application.
|
|
57437
|
+
*/
|
|
57438
|
+
function hasLanded() {
|
|
57439
|
+
return landed;
|
|
57440
|
+
}
|
|
57441
|
+
/**
|
|
57442
|
+
* Tells whether the frame's building budget is spent, read once every `CLOCK_EVERY` units of
|
|
57443
|
+
* work: a read of the clock costs more than a small unit.
|
|
57444
|
+
*
|
|
57445
|
+
* @param count - The units of work made in this frame so far.
|
|
57446
|
+
* @returns True when the count is a reading point and no budget is left.
|
|
57447
|
+
*/
|
|
57448
|
+
function spentAt(count) {
|
|
57449
|
+
return count > 0 && count % CLOCK_EVERY === 0 && ctx.deps.world.projection.left() <= 0;
|
|
57450
|
+
}
|
|
57451
|
+
/**
|
|
57452
|
+
* Counts an element entered for a root built aside and tells whether its children wait for a
|
|
57453
|
+
* later frame. From the 8th element of the frame the budget is read every 8; once it is spent,
|
|
57454
|
+
* the children of every next element wait. A row a range change scrolls in never parks: the
|
|
57455
|
+
* window moves in one block, as on a live root.
|
|
57456
|
+
*
|
|
57457
|
+
* @returns True when the children of the element wait.
|
|
57458
|
+
*/
|
|
57459
|
+
function parks() {
|
|
57460
|
+
if (reconcile.isMoving()) return false;
|
|
57461
|
+
entered += 1;
|
|
57462
|
+
if (!spent) spent = spentAt(entered);
|
|
57463
|
+
return spent;
|
|
57464
|
+
}
|
|
57465
|
+
/**
|
|
57466
|
+
* Leaves the children of a new element of a root built aside for a later frame: the element
|
|
57467
|
+
* itself exists, so the child list of its parent is complete. The part is queued behind the
|
|
57468
|
+
* root's other parts, with the cause of the diff running now.
|
|
57469
|
+
*
|
|
57470
|
+
* @param root - The root built aside.
|
|
57471
|
+
* @param element - The element that entered.
|
|
57472
|
+
* @param nodes - The children it would have diffed.
|
|
57473
|
+
* @param instance - The identity of the nearest component instance.
|
|
57474
|
+
*/
|
|
57475
|
+
function park(root, element, nodes, instance) {
|
|
57476
|
+
const cause = state.building;
|
|
57477
|
+
const part = parkedPool.pop() ?? {
|
|
57478
|
+
element,
|
|
57479
|
+
nodes,
|
|
57480
|
+
instance,
|
|
57481
|
+
cause
|
|
57482
|
+
};
|
|
57483
|
+
part.element = element;
|
|
57484
|
+
part.nodes = nodes;
|
|
57485
|
+
part.instance = instance;
|
|
57486
|
+
part.cause = cause;
|
|
57487
|
+
root.parked ??= [];
|
|
57488
|
+
root.parked.push(part);
|
|
57489
|
+
}
|
|
57490
|
+
/**
|
|
57491
|
+
* Puts a parked part back in the pool, its element and nodes dropped.
|
|
57492
|
+
*
|
|
57493
|
+
* @param part - A part that was diffed or forgotten.
|
|
57494
|
+
*/
|
|
57495
|
+
function recycle(part) {
|
|
57496
|
+
part.element = void 0;
|
|
57497
|
+
part.nodes = NO_NODES;
|
|
57498
|
+
parkedPool.push(part);
|
|
57499
|
+
}
|
|
57500
|
+
/**
|
|
57501
|
+
* Tells whether the next building unit may run in this frame: the first one always does, so a
|
|
57502
|
+
* build always moves; a later one while the budget lasts.
|
|
57503
|
+
*
|
|
57504
|
+
* @returns True when the unit runs.
|
|
57505
|
+
*/
|
|
57506
|
+
function mayRun() {
|
|
57507
|
+
return !unitRan || ctx.deps.world.projection.left() > 0;
|
|
57508
|
+
}
|
|
57509
|
+
/**
|
|
57510
|
+
* Diffs one parked part under the cause it was parked with. A part whose element left meanwhile
|
|
57511
|
+
* is dropped. A part that throws fails its root alone, as the root's own diff does.
|
|
57512
|
+
*
|
|
57513
|
+
* @param root - The root built aside.
|
|
57514
|
+
* @param part - The part.
|
|
57515
|
+
*/
|
|
57516
|
+
function diffPart(root, part) {
|
|
57517
|
+
const element = part.element;
|
|
57518
|
+
if (element === void 0 || lookup(element.entity) !== element) return;
|
|
57519
|
+
const cause = state.building;
|
|
57520
|
+
unitRan = true;
|
|
57521
|
+
state.building = part.cause;
|
|
57522
|
+
try {
|
|
57523
|
+
reconcile.diffChildren(root, element, part.nodes, part.instance);
|
|
57524
|
+
} catch (error) {
|
|
57525
|
+
ctx.log.error("ui:root-failed", { root: root.name }, asError(error));
|
|
57526
|
+
} finally {
|
|
57527
|
+
state.building = cause;
|
|
57528
|
+
}
|
|
57529
|
+
root.needsSolve = true;
|
|
57530
|
+
}
|
|
57531
|
+
/**
|
|
57532
|
+
* Diffs the parked parts of the roots built aside, each root's in the order they were parked,
|
|
57533
|
+
* while the frame's budget lasts (spec 33 A3). Children that park again go to the end of their
|
|
57534
|
+
* root's queue.
|
|
57535
|
+
*/
|
|
57536
|
+
function drainParked() {
|
|
57537
|
+
if (state.asideRoots === 0) return;
|
|
57538
|
+
for (const root of state.roots.values()) {
|
|
57539
|
+
const parked = root.parked;
|
|
57540
|
+
while (parked !== void 0 && parked.length > 0) {
|
|
57541
|
+
if (!mayRun()) return;
|
|
57542
|
+
const part = parked.shift();
|
|
57543
|
+
if (part === void 0) break;
|
|
57544
|
+
diffPart(root, part);
|
|
57545
|
+
recycle(part);
|
|
57546
|
+
}
|
|
57547
|
+
}
|
|
57548
|
+
}
|
|
57549
|
+
/**
|
|
57550
|
+
* Holds a dirty root built aside for a later frame instead of diffing it now: while parked parts
|
|
57551
|
+
* of it wait, so no element is made twice, and once the frame's budget is spent after its first
|
|
57552
|
+
* building unit. It stays dirty: the next frame lists it again and diffs its newest tree once
|
|
57553
|
+
* (spec 33 A1, A4). The drain runs before, until its queue is empty or the budget is spent, so a
|
|
57554
|
+
* root with parked parts meets a spent budget here too. A live root is never held.
|
|
57555
|
+
*
|
|
57556
|
+
* @param root - A root with work this frame.
|
|
57557
|
+
* @returns True when the root waits.
|
|
57558
|
+
*/
|
|
57559
|
+
function holdRoot(root) {
|
|
57560
|
+
if (!ctx.deps.world.projection.isBuilding(root.entity)) return false;
|
|
57561
|
+
if (hasParked(root) || !mayRun()) {
|
|
57562
|
+
root.dirty = true;
|
|
57563
|
+
return true;
|
|
57564
|
+
}
|
|
57565
|
+
unitRan = true;
|
|
57566
|
+
return false;
|
|
57567
|
+
}
|
|
57568
|
+
/**
|
|
57569
|
+
* Forgets the parked parts and the open application of a root whose build ends here: a drop, a
|
|
57570
|
+
* discard, its exit or a restore.
|
|
57571
|
+
*
|
|
57572
|
+
* @param root - The root.
|
|
57573
|
+
*/
|
|
57574
|
+
function forgetParts(root) {
|
|
57575
|
+
const parked = root.parked;
|
|
57576
|
+
while (parked !== void 0 && parked.length > 0) {
|
|
57577
|
+
const part = parked.pop();
|
|
57578
|
+
if (part !== void 0) recycle(part);
|
|
57579
|
+
}
|
|
57580
|
+
const cursor = root.applying;
|
|
57581
|
+
if (cursor === void 0) return;
|
|
57582
|
+
while (cursor.stack.length > 0) cursor.stack.pop();
|
|
57583
|
+
delete root.applying;
|
|
57584
|
+
cursorPool.push(cursor);
|
|
57585
|
+
}
|
|
57586
|
+
/**
|
|
57587
|
+
* Opens the application of a solved root built aside and applies its first part (spec 33 A6).
|
|
57588
|
+
*
|
|
57589
|
+
* @param root - The root built aside.
|
|
57590
|
+
* @param element - Its root element.
|
|
57591
|
+
* @param quiet - Whether its live elements take their rects with no motion.
|
|
57592
|
+
*/
|
|
57593
|
+
function applyInParts(root, element, quiet) {
|
|
57594
|
+
const cursor = cursorPool.pop() ?? {
|
|
57595
|
+
stack: [],
|
|
57596
|
+
quiet
|
|
57597
|
+
};
|
|
57598
|
+
cursor.quiet = quiet;
|
|
57599
|
+
cursor.stack.push(element);
|
|
57600
|
+
root.applying = cursor;
|
|
57601
|
+
applyPart(root, cursor);
|
|
57602
|
+
}
|
|
57603
|
+
/**
|
|
57604
|
+
* Applies the next elements of an open application, top-down, parent before children in the
|
|
57605
|
+
* order of `applyRects`, until the frame's budget is spent (read every 8 elements of the frame)
|
|
57606
|
+
* or the frame applied `APPLY_CAP` elements of roots built aside. An element that left on the
|
|
57607
|
+
* way is skipped. The last element closes the application.
|
|
57608
|
+
*
|
|
57609
|
+
* @param root - The root built aside.
|
|
57610
|
+
* @param cursor - Its open application.
|
|
57611
|
+
*/
|
|
57612
|
+
function applyPart(root, cursor) {
|
|
57613
|
+
const stack = cursor.stack;
|
|
57614
|
+
while (stack.length > 0 && applied < APPLY_CAP) {
|
|
57615
|
+
if (spentAt(applied)) return;
|
|
57616
|
+
const element = stack.pop();
|
|
57617
|
+
if (element === void 0 || lookup(element.entity) !== element) continue;
|
|
57618
|
+
reconcile.applyElement(element, cursor.quiet);
|
|
57619
|
+
applied += 1;
|
|
57620
|
+
stackChildren(stack, element);
|
|
57621
|
+
}
|
|
57622
|
+
if (stack.length === 0) closeApplication(root, cursor);
|
|
57623
|
+
}
|
|
57624
|
+
/**
|
|
57625
|
+
* Puts the children of an applied element on the stack of its application, the first one on top,
|
|
57626
|
+
* so they come next, in the order of `applyRects`.
|
|
57627
|
+
*
|
|
57628
|
+
* @param stack - The elements still to apply.
|
|
57629
|
+
* @param element - The element just applied.
|
|
57630
|
+
*/
|
|
57631
|
+
function stackChildren(stack, element) {
|
|
57632
|
+
for (let index = element.children.length - 1; index >= 0; index -= 1) {
|
|
57633
|
+
const child = element.children[index];
|
|
57634
|
+
const childElement = child === void 0 ? void 0 : lookup(child);
|
|
57635
|
+
if (childElement !== void 0) stack.push(childElement);
|
|
57636
|
+
}
|
|
57637
|
+
}
|
|
57638
|
+
/**
|
|
57639
|
+
* Ends the application of a root built aside: its scrolls settle in this solve, and its live
|
|
57640
|
+
* hosts take their views in it, now that the root is whole (spec 33 A7).
|
|
57641
|
+
*
|
|
57642
|
+
* @param root - The root built aside.
|
|
57643
|
+
* @param cursor - Its application, empty.
|
|
57644
|
+
*/
|
|
57645
|
+
function closeApplication(root, cursor) {
|
|
57646
|
+
delete root.applying;
|
|
57647
|
+
cursorPool.push(cursor);
|
|
57648
|
+
landed = true;
|
|
57649
|
+
for (const host of state.hosts) if (lookup(host)?.root === root.entity) state.risen.add(host);
|
|
57650
|
+
}
|
|
57651
|
+
/**
|
|
57652
|
+
* Applies the next part of every open application of a root built aside, in mount order, while
|
|
57653
|
+
* the frame's budget lasts. It runs before any root is solved: a root whose application ends
|
|
57654
|
+
* here is solved again in the same call when it asks for it.
|
|
57655
|
+
*/
|
|
57656
|
+
function applyParts() {
|
|
57657
|
+
if (state.asideRoots === 0) return;
|
|
57658
|
+
for (const root of state.roots.values()) {
|
|
57659
|
+
const cursor = root.applying;
|
|
57660
|
+
if (cursor === void 0) continue;
|
|
57661
|
+
if (!mayRun()) return;
|
|
57662
|
+
unitRan = true;
|
|
57663
|
+
applyPart(root, cursor);
|
|
57664
|
+
}
|
|
57665
|
+
}
|
|
57666
|
+
return {
|
|
57667
|
+
beginReconcile,
|
|
57668
|
+
beginSolve,
|
|
57669
|
+
hasLanded,
|
|
57670
|
+
parks,
|
|
57671
|
+
park,
|
|
57672
|
+
waits,
|
|
57673
|
+
drainParked,
|
|
57674
|
+
holdRoot,
|
|
57675
|
+
forgetParts,
|
|
57676
|
+
applyInParts,
|
|
57677
|
+
applyParts
|
|
57678
|
+
};
|
|
57679
|
+
}
|
|
57680
|
+
//#endregion
|
|
57187
57681
|
//#region src/plugins/ui/jsx/range.ts
|
|
57188
57682
|
/**
|
|
57189
57683
|
* Builds the windowed-scroll half of the reconcile: the row style, the slots a mount or a patch
|
|
@@ -57559,8 +58053,8 @@ function sameValue(left, right) {
|
|
|
57559
58053
|
* that stays is compared prop by prop, by value: one that says what it said is neither styled nor
|
|
57560
58054
|
* written. Beside it:
|
|
57561
58055
|
* the range of a windowed scroll (`range.ts`), the rest and the motion of the extras
|
|
57562
|
-
* (`extras.ts`), the hint routed to a keyed element (`hints.ts`)
|
|
57563
|
-
* `text` resolved again (`remeasure.ts`).
|
|
58056
|
+
* (`extras.ts`), the hint routed to a keyed element (`hints.ts`), the remeasure of the texts
|
|
58057
|
+
* `text` resolved again (`remeasure.ts`) and a scene build in parts (`parts.ts`).
|
|
57564
58058
|
*/
|
|
57565
58059
|
/** The four components an element may be drawn with; a state change can trade one for another. */
|
|
57566
58060
|
const VISUALS = [
|
|
@@ -58067,6 +58561,11 @@ function createReconciler(ctx, modules, fields) {
|
|
|
58067
58561
|
forgetSubtree,
|
|
58068
58562
|
despawnTree
|
|
58069
58563
|
});
|
|
58564
|
+
const parts = createParts(ctx, {
|
|
58565
|
+
diffChildren,
|
|
58566
|
+
applyElement,
|
|
58567
|
+
isMoving: range.isMoving
|
|
58568
|
+
});
|
|
58070
58569
|
const dirtyList = [];
|
|
58071
58570
|
const scrollList = [];
|
|
58072
58571
|
const movedRoots = [];
|
|
@@ -58137,7 +58636,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
58137
58636
|
function enter(root, parent, node, identity, instance, index) {
|
|
58138
58637
|
checkTag(node);
|
|
58139
58638
|
const entity = ecs.spawn(UI_OWNER, []);
|
|
58140
|
-
|
|
58639
|
+
const aside = isAside(root.entity);
|
|
58640
|
+
if (aside) ctx.deps.world.projection.adopt(entity);
|
|
58141
58641
|
const is = isFlagsOf(node, {
|
|
58142
58642
|
pressed: false,
|
|
58143
58643
|
hover: false,
|
|
@@ -58203,7 +58703,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
58203
58703
|
epoch: state.epoch,
|
|
58204
58704
|
rowHeight: range.rowHeightOf(parent, node)
|
|
58205
58705
|
};
|
|
58206
|
-
|
|
58706
|
+
const nodes = childrenOf(node, range.slotsFor(element), element.scroll?.axis);
|
|
58707
|
+
if (aside && parts.parks() && nodes.length > 0) parts.park(root, element, nodes, instance);
|
|
58708
|
+
else diffChildren(root, element, nodes, instance);
|
|
58207
58709
|
root.needsSolve = true;
|
|
58208
58710
|
return entity;
|
|
58209
58711
|
}
|
|
@@ -58875,16 +59377,25 @@ function createReconciler(ctx, modules, fields) {
|
|
|
58875
59377
|
* @param quiet - Whether a live element takes its rect with no motion.
|
|
58876
59378
|
*/
|
|
58877
59379
|
function applyRects(element, quiet = false) {
|
|
58878
|
-
|
|
58879
|
-
if (element.live) applyLive(element, parent, quiet);
|
|
58880
|
-
else applyFirst(element, parent);
|
|
58881
|
-
element.moved = false;
|
|
59380
|
+
applyElement(element, quiet);
|
|
58882
59381
|
for (const child of element.children) {
|
|
58883
59382
|
const childElement = lookup(child);
|
|
58884
59383
|
if (childElement !== void 0) applyRects(childElement, quiet);
|
|
58885
59384
|
}
|
|
58886
59385
|
}
|
|
58887
59386
|
/**
|
|
59387
|
+
* Applies the solved rect of one element: a live one moves, a new one spawns its components.
|
|
59388
|
+
*
|
|
59389
|
+
* @param element - The element to apply.
|
|
59390
|
+
* @param quiet - Whether a live element takes its rect with no motion.
|
|
59391
|
+
*/
|
|
59392
|
+
function applyElement(element, quiet) {
|
|
59393
|
+
const parent = parentRect(element);
|
|
59394
|
+
if (element.live) applyLive(element, parent, quiet);
|
|
59395
|
+
else applyFirst(element, parent);
|
|
59396
|
+
element.moved = false;
|
|
59397
|
+
}
|
|
59398
|
+
/**
|
|
58888
59399
|
* Where the element of a root draws: its layer and order, and its slot with the place the slot
|
|
58889
59400
|
* lists the root's projection at.
|
|
58890
59401
|
*
|
|
@@ -59031,7 +59542,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59031
59542
|
}
|
|
59032
59543
|
/**
|
|
59033
59544
|
* Solves one root in a box and applies its rects: a root on the viewport when it is marked, a
|
|
59034
|
-
* root in a slot also when its slot was resized.
|
|
59545
|
+
* root in a slot also when its slot was resized. A root built aside waits while parts of it wait,
|
|
59546
|
+
* and is applied in parts.
|
|
59035
59547
|
*
|
|
59036
59548
|
* @param root - The root.
|
|
59037
59549
|
* @param box - The viewport, or the slot's content box.
|
|
@@ -59040,7 +59552,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59040
59552
|
*/
|
|
59041
59553
|
function solveOne(root, box, quiet) {
|
|
59042
59554
|
const element = root.element === void 0 ? void 0 : lookup(root.element);
|
|
59043
|
-
if (element === void 0) return false;
|
|
59555
|
+
if (element === void 0 || parts.waits(root)) return false;
|
|
59044
59556
|
const resized = root.slot !== void 0 && !sameBox(root.slotRect, box);
|
|
59045
59557
|
if (!root.needsSolve && !resized) return false;
|
|
59046
59558
|
if (root.slot !== void 0) root.slotRect = {
|
|
@@ -59051,7 +59563,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59051
59563
|
};
|
|
59052
59564
|
modules.layout.solve(element, box, lookup);
|
|
59053
59565
|
root.needsSolve = false;
|
|
59054
|
-
|
|
59566
|
+
if (root.aside !== void 0 && isAside(root.entity) && !element.live) parts.applyInParts(root, element, quiet);
|
|
59567
|
+
else applyRects(element, quiet);
|
|
59055
59568
|
return true;
|
|
59056
59569
|
}
|
|
59057
59570
|
/**
|
|
@@ -59213,6 +59726,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59213
59726
|
for (const root of state.roots.values()) {
|
|
59214
59727
|
if (state.removing.has(root.entity)) continue;
|
|
59215
59728
|
const element = root.element === void 0 ? void 0 : lookup(root.element);
|
|
59729
|
+
parts.forgetParts(root);
|
|
59216
59730
|
if (element !== void 0) {
|
|
59217
59731
|
forgetSubtree(element);
|
|
59218
59732
|
forgetInstances(state, element.identity);
|
|
@@ -59247,7 +59761,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59247
59761
|
}
|
|
59248
59762
|
/**
|
|
59249
59763
|
* The live scroll containers, which the frame step moves with the finger. Read from the set the
|
|
59250
|
-
* enter fills and the exit empties, so an idle frame walks no other element.
|
|
59764
|
+
* enter fills and the exit empties, so an idle frame walks no other element. A scroll of a root
|
|
59765
|
+
* built aside waits while parts of the root wait: it is ranged, moved and settled once the root
|
|
59766
|
+
* is whole (spec 33 A7).
|
|
59251
59767
|
*
|
|
59252
59768
|
* @returns Every live element whose tag is `scroll`: the reconciler's own list, valid until the
|
|
59253
59769
|
* next call.
|
|
@@ -59256,7 +59772,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59256
59772
|
while (scrollList.length > 0) scrollList.pop();
|
|
59257
59773
|
for (const entity of state.scrolls) {
|
|
59258
59774
|
const element = state.elements.get(entity);
|
|
59259
|
-
if (element?.live
|
|
59775
|
+
if (element?.live !== true) continue;
|
|
59776
|
+
if (state.asideRoots > 0 && parts.waits(state.roots.get(element.root))) continue;
|
|
59777
|
+
scrollList.push(element);
|
|
59260
59778
|
}
|
|
59261
59779
|
return scrollList;
|
|
59262
59780
|
}
|
|
@@ -59361,16 +59879,16 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59361
59879
|
}
|
|
59362
59880
|
/**
|
|
59363
59881
|
* The part of a scene build `ui` does, which `world` asks through `onBuild` (spec 31): done when no
|
|
59364
|
-
* root of the build is dirty (its tree not diffed yet, or the layout engine not loaded)
|
|
59365
|
-
* waits for its solve
|
|
59366
|
-
* the swap.
|
|
59882
|
+
* root of the build is dirty (its tree not diffed yet, or the layout engine not loaded), has
|
|
59883
|
+
* parked parts, waits for its solve or is applied in parts (spec 33 A8). A root whose first diff
|
|
59884
|
+
* threw has no element to solve and does not hold the swap.
|
|
59367
59885
|
*
|
|
59368
59886
|
* @returns True when every root built aside is reconciled and solved.
|
|
59369
59887
|
*/
|
|
59370
59888
|
function asideBuilt() {
|
|
59371
59889
|
for (const root of state.roots.values()) {
|
|
59372
59890
|
const solves = root.needsSolve && root.element !== void 0 && lookup(root.element) !== void 0;
|
|
59373
|
-
if ((root.dirty || solves) && isAside(root.entity)) return false;
|
|
59891
|
+
if ((root.dirty || solves || parts.waits(root)) && isAside(root.entity)) return false;
|
|
59374
59892
|
}
|
|
59375
59893
|
return true;
|
|
59376
59894
|
}
|
|
@@ -59378,11 +59896,13 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59378
59896
|
* The first of the two systems of phase `layout`: the sweep, the roots a scene build committed, the
|
|
59379
59897
|
* roots a restore asked to build again, the roots a scene placed again, the popups that may leave,
|
|
59380
59898
|
* the pull of the text being typed, the scroll offsets and the windowed ranges, the bound texts
|
|
59381
|
-
* that show a string of another size, the viewport, the
|
|
59899
|
+
* that show a string of another size, the viewport, the parked parts of a scene build, the dirty
|
|
59900
|
+
* roots and the hosted views.
|
|
59382
59901
|
*
|
|
59383
59902
|
* @param time - The frame's `Time`: the scrolls move by its `delta`.
|
|
59384
59903
|
*/
|
|
59385
59904
|
function reconcile(time = NO_TIME) {
|
|
59905
|
+
parts.beginReconcile();
|
|
59386
59906
|
sweep();
|
|
59387
59907
|
state.reconciles += 1;
|
|
59388
59908
|
lookWriter.pass += 1;
|
|
@@ -59400,9 +59920,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59400
59920
|
if (viewportChanged) state.epoch += 1;
|
|
59401
59921
|
const count = dirtyRoots(viewportChanged);
|
|
59402
59922
|
rendered = count;
|
|
59923
|
+
parts.drainParked();
|
|
59403
59924
|
for (let index = 0; index < count; index += 1) {
|
|
59404
59925
|
const root = dirtyList[index];
|
|
59405
|
-
if (root !== void 0) reconcileDirtyRoot(root);
|
|
59926
|
+
if (root !== void 0 && !parts.holdRoot(root)) reconcileDirtyRoot(root);
|
|
59406
59927
|
}
|
|
59407
59928
|
hostViews(ctx, state);
|
|
59408
59929
|
writeCounters();
|
|
@@ -59423,12 +59944,13 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59423
59944
|
counters.written = state.written;
|
|
59424
59945
|
}
|
|
59425
59946
|
/**
|
|
59426
|
-
* The second system of phase `layout`: every root takes its slot,
|
|
59427
|
-
*
|
|
59428
|
-
*
|
|
59429
|
-
*
|
|
59430
|
-
*
|
|
59431
|
-
*
|
|
59947
|
+
* The second system of phase `layout`: every root takes its slot, the open applications of roots
|
|
59948
|
+
* built aside take their next part, then one solve per marked root, a root in a slot after the
|
|
59949
|
+
* root of its slot and in its content box, then the rects, the rest poses and the hooks, the
|
|
59950
|
+
* scrolls settled on what the solve and the render gave them, the hosting of the hosts that came,
|
|
59951
|
+
* a sweep of what left with nothing to wait for, and, in the swap frame of a scene build, the
|
|
59952
|
+
* start pose of the enters it held. The hints released since the last frame step are dropped at
|
|
59953
|
+
* its end, so the `change.Box` and `change.Transform` hooks the solve plays get the hint the
|
|
59432
59954
|
* reconcile routed, and the next frame starts with none.
|
|
59433
59955
|
*
|
|
59434
59956
|
* @param time - The frame's `Time`: a scroll left moving asks for a frame only while it runs.
|
|
@@ -59437,10 +59959,12 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59437
59959
|
const viewport = modules.styles.viewport();
|
|
59438
59960
|
viewportBox.w = viewport?.width ?? 0;
|
|
59439
59961
|
viewportBox.h = viewport?.height ?? 0;
|
|
59962
|
+
parts.beginSolve();
|
|
59440
59963
|
assignSlots();
|
|
59964
|
+
parts.applyParts();
|
|
59441
59965
|
const changed = layOutRoots(viewportBox);
|
|
59442
59966
|
if (changed) ctx.deps.time.wake();
|
|
59443
|
-
if (changed || rendered > 0) modules.layout.settleScroll(scrollContainers(), lookup, time);
|
|
59967
|
+
if (changed || rendered > 0 || parts.hasLanded()) modules.layout.settleScroll(scrollContainers(), lookup, time);
|
|
59444
59968
|
rendered = 0;
|
|
59445
59969
|
settleOverlays(ctx, state, modules.layout);
|
|
59446
59970
|
if (state.risen.size > 0) {
|
|
@@ -59516,6 +60040,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
59516
60040
|
if (root === void 0 || state.removing.has(entity)) return;
|
|
59517
60041
|
const element = root.element === void 0 ? void 0 : lookup(root.element);
|
|
59518
60042
|
const cause = root.popup === void 0 ? ctx.deps.world.projection.causeOf(entity) ?? "change" : "change";
|
|
60043
|
+
parts.forgetParts(root);
|
|
59519
60044
|
if (cause === "discard" || isAside(entity)) {
|
|
59520
60045
|
dropRoot(root, element);
|
|
59521
60046
|
return;
|
|
@@ -64225,9 +64750,11 @@ function scenesFor() {
|
|
|
64225
64750
|
/**
|
|
64226
64751
|
* Types a projection: keyed items of the model turned into entities. `Item` is inferred from the
|
|
64227
64752
|
* return of `from` and reaches `key`, `view` and every motion hook; `layer` and `lift` stay
|
|
64228
|
-
* literal, so `defineScene` can check them against the layers of the scene.
|
|
64753
|
+
* literal, so `defineScene` can check them against the layers of the scene. Each pair of
|
|
64754
|
+
* `initialOnly` has its field names checked against its own component.
|
|
64229
64755
|
*
|
|
64230
|
-
* @param spec - Name, layer, optional lift layer, `from`, `key`, `view
|
|
64756
|
+
* @param spec - Name, layer, optional lift layer, `from`, `key`, `view`, the motion hooks and
|
|
64757
|
+
* the `initialOnly` fields.
|
|
64231
64758
|
* @returns The same spec, typed.
|
|
64232
64759
|
* @example
|
|
64233
64760
|
* ```ts
|
|
@@ -64628,7 +65155,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
64628
65155
|
* | assets | preloadDepth | 2 |
|
|
64629
65156
|
* | assets | baseUrl | undefined |
|
|
64630
65157
|
* | assets | io | undefined, the browser fetch and the renderer textures |
|
|
64631
|
-
* | scenes | buildMs | "auto",
|
|
65158
|
+
* | scenes | buildMs | "auto", two thirds of a frame ((1000 / time.rate().fps) * 2 / 3, 11.1 ms at 60 Hz), read at each build start; a number fixes it |
|
|
64632
65159
|
* | anim | maxTracks | 2000 |
|
|
64633
65160
|
* | anim | reducedMotion | false |
|
|
64634
65161
|
* | i18n | locale | "en" |
|