@opsydyn/effect-three 0.3.0 → 0.4.0
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/CHANGELOG.md +15 -0
- package/README.md +12 -0
- package/dist/{index-8cf8t6by.js → index-h3vf50fn.js} +2113 -83
- package/dist/index-h3vf50fn.js.map +53 -0
- package/dist/index-ttwy0znv.js +145 -0
- package/dist/index-ttwy0znv.js.map +12 -0
- package/dist/index.js +488 -147
- package/dist/index.js.map +8 -6
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +142 -6
- package/dist/kernel/build.d.ts.map +1 -1
- package/dist/kernel/cameraShake.d.ts.map +1 -1
- package/dist/kernel/describeScene.d.ts +50 -0
- package/dist/kernel/describeScene.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +155 -1
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +15 -0
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +9 -1
- package/dist/kernel/equality.d.ts.map +1 -1
- package/dist/kernel/gizmo.d.ts +69 -0
- package/dist/kernel/gizmo.d.ts.map +1 -0
- package/dist/kernel/history.d.ts +61 -0
- package/dist/kernel/history.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +13 -2
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +106 -3
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/inspect.d.ts +85 -0
- package/dist/kernel/inspect.d.ts.map +1 -0
- package/dist/kernel/lineGeometry.d.ts +25 -0
- package/dist/kernel/lineGeometry.d.ts.map +1 -0
- package/dist/kernel/lockstep.d.ts +114 -0
- package/dist/kernel/lockstep.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts.map +1 -1
- package/dist/kernel/materials.d.ts.map +1 -1
- package/dist/kernel/pivot.d.ts +54 -0
- package/dist/kernel/pivot.d.ts.map +1 -0
- package/dist/kernel/pointBuffers.d.ts +17 -0
- package/dist/kernel/pointBuffers.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +12 -0
- package/dist/kernel/pointer.d.ts.map +1 -1
- package/dist/kernel/renderKit.d.ts +21 -1
- package/dist/kernel/renderKit.d.ts.map +1 -1
- package/dist/kernel/renderer.d.ts +6 -0
- package/dist/kernel/renderer.d.ts.map +1 -1
- package/dist/kernel/sceneChanges.d.ts +87 -0
- package/dist/kernel/sceneChanges.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +6 -0
- package/dist/kernel/sceneGraph.d.ts.map +1 -1
- package/dist/kernel/seeded.d.ts +9 -0
- package/dist/kernel/seeded.d.ts.map +1 -0
- package/dist/kernel/shots.d.ts +26 -0
- package/dist/kernel/shots.d.ts.map +1 -0
- package/dist/kernel/tape.d.ts +38 -0
- package/dist/kernel/tape.d.ts.map +1 -0
- package/dist/kernel/trail.d.ts +31 -0
- package/dist/kernel/trail.d.ts.map +1 -0
- package/dist/kernel/viewportGizmo.d.ts +25 -0
- package/dist/kernel/viewportGizmo.d.ts.map +1 -0
- package/dist/runtime/Inspector.d.ts +20 -0
- package/dist/runtime/Inspector.d.ts.map +1 -0
- package/dist/runtime/SceneFile.d.ts +448 -0
- package/dist/runtime/SceneFile.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +318 -0
- package/dist/runtime/SceneSchema.d.ts.map +1 -1
- package/dist/runtime/errors.d.ts +9 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/webgl/index.d.ts.map +1 -1
- package/dist/webgl/index.js +228 -3
- package/dist/webgl/index.js.map +6 -4
- package/dist/webgl/line.d.ts +4 -0
- package/dist/webgl/line.d.ts.map +1 -0
- package/dist/webgl/points.d.ts +4 -0
- package/dist/webgl/points.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts.map +1 -1
- package/dist/webgpu/index.js +206 -26
- package/dist/webgpu/index.js.map +6 -4
- package/dist/webgpu/line.d.ts +4 -0
- package/dist/webgpu/line.d.ts.map +1 -0
- package/dist/webgpu/points.d.ts +4 -0
- package/dist/webgpu/points.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/kernel/boxSelect.ts +73 -0
- package/src/kernel/build.ts +332 -0
- package/src/kernel/cameraShake.ts +3 -13
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +175 -0
- package/src/kernel/diff.ts +39 -0
- package/src/kernel/equality.ts +101 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +97 -0
- package/src/kernel/inspect.ts +214 -0
- package/src/kernel/lineGeometry.ts +75 -0
- package/src/kernel/lockstep.ts +237 -0
- package/src/kernel/loop.ts +5 -0
- package/src/kernel/materials.ts +3 -0
- package/src/kernel/pivot.ts +418 -0
- package/src/kernel/pointBuffers.ts +115 -0
- package/src/kernel/pointer.ts +55 -12
- package/src/kernel/renderKit.ts +23 -1
- package/src/kernel/renderer.ts +6 -0
- package/src/kernel/sceneChanges.ts +265 -0
- package/src/kernel/sceneGraph.ts +319 -8
- package/src/kernel/seeded.ts +18 -0
- package/src/kernel/shots.ts +167 -0
- package/src/kernel/tape.ts +124 -0
- package/src/kernel/trail.ts +170 -0
- package/src/kernel/viewportGizmo.ts +194 -0
- package/src/runtime/Inspector.ts +43 -0
- package/src/runtime/SceneFile.ts +46 -0
- package/src/runtime/SceneSchema.ts +247 -0
- package/src/runtime/errors.ts +6 -0
- package/src/runtime/index.ts +3 -1
- package/src/webgl/index.ts +4 -0
- package/src/webgl/line.ts +66 -0
- package/src/webgl/points.ts +205 -0
- package/src/webgpu/index.ts +4 -0
- package/src/webgpu/line.ts +67 -0
- package/src/webgpu/points.ts +193 -0
- package/dist/index-8cf8t6by.js.map +0 -41
package/src/kernel/renderKit.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Object3D, Scene } from 'three';
|
|
11
11
|
|
|
12
|
-
import type { ContactShadows, ShaderGridNode, Sky } from './description.ts';
|
|
12
|
+
import type { ContactShadows, LineNode, PointsNode, ShaderGridNode, Sky } from './description.ts';
|
|
13
13
|
import type { SceneRenderer } from './renderer.ts';
|
|
14
14
|
|
|
15
15
|
/** A sky as a kit draws it: the object the graph adds to its scene, never picked. */
|
|
@@ -26,6 +26,24 @@ export type HostedShaderGrid = {
|
|
|
26
26
|
readonly dispose: () => void;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
/** A line as a kit draws it (three's fat lines), inside the node's wrapper. */
|
|
30
|
+
export type HostedLine = {
|
|
31
|
+
readonly object: Object3D;
|
|
32
|
+
/** Applies new points or a new look; the wrapper carries the transform. */
|
|
33
|
+
readonly update: (line: LineNode) => void;
|
|
34
|
+
readonly dispose: () => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Points as a kit draws them, inside the node's wrapper. */
|
|
38
|
+
export type HostedPoints = {
|
|
39
|
+
readonly object: Object3D;
|
|
40
|
+
/** Applies a new look; the wrapper carries the transform. */
|
|
41
|
+
readonly update: (points: PointsNode) => void;
|
|
42
|
+
/** Moves sparkles and twinkles stars to scene time `seconds`. */
|
|
43
|
+
readonly tick: (seconds: number) => void;
|
|
44
|
+
readonly dispose: () => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
29
47
|
/** The contact shadow passes for one scene, drawn by the frame loop before each frame. */
|
|
30
48
|
export type ContactShadowPasses = {
|
|
31
49
|
/** Draws the scene's contact shadows, if it has any, before the frame is drawn. */
|
|
@@ -50,5 +68,9 @@ export type RenderKit = {
|
|
|
50
68
|
readonly shading: 'webgl' | 'tsl';
|
|
51
69
|
readonly sky: () => HostedSky;
|
|
52
70
|
readonly shaderGrid: (node: ShaderGridNode) => HostedShaderGrid;
|
|
71
|
+
/** Draws a line; a kit without lines leaves them empty. */
|
|
72
|
+
readonly line?: (node: LineNode) => HostedLine;
|
|
73
|
+
/** Draws points; a kit without points leaves them empty. */
|
|
74
|
+
readonly points?: (node: PointsNode) => HostedPoints;
|
|
53
75
|
readonly contactShadows: (graph: ShadowedScene, renderer: SceneRenderer) => ContactShadowPasses;
|
|
54
76
|
};
|
package/src/kernel/renderer.ts
CHANGED
|
@@ -43,6 +43,12 @@ export type SceneRenderer = {
|
|
|
43
43
|
* otherwise across the whole surface.
|
|
44
44
|
*/
|
|
45
45
|
readonly render: (scene: Scene, camera: Camera, area?: RenderArea, target?: RenderTarget) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Draws `scene` over what the surface already shows, across the whole of it: keeping
|
|
48
|
+
* the colour, clearing only depth (a HUD, as Drei's `Hud` draws). Without it, a HUD is
|
|
49
|
+
* not drawn. Not while presenting immersively.
|
|
50
|
+
*/
|
|
51
|
+
readonly renderOver?: (scene: Scene, camera: Camera) => void;
|
|
46
52
|
/** Creates an offscreen target of `width` × `height` pixels to render into. */
|
|
47
53
|
readonly createTarget: (width: number, height: number) => RenderTarget;
|
|
48
54
|
/**
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene diffs for review (roadmap, "Beyond parity" 4): what changed between two
|
|
3
|
+
* descriptions, in the kernel's own terms (the operations `diff` would apply, plus the
|
|
4
|
+
* scene-wide settings the graph applies beside them), as typed changes and as lines a
|
|
5
|
+
* person reads in review. `formatChanges` writes one line per change:
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* + Mesh crate in table
|
|
9
|
+
* - Light sun
|
|
10
|
+
* ~ crate: position, rotation
|
|
11
|
+
* > crate to shelf
|
|
12
|
+
* = order of the root: table, crate
|
|
13
|
+
* ⋯ scene: camera, environment
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Changes inside the HUD and render textures are listed after them, each line prefixed
|
|
17
|
+
* with where it is (`hud`, `texture screen`).
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import type { SceneDescription, SceneNode } from './description.ts';
|
|
22
|
+
import { diff, type Field, type Op } from './diff.ts';
|
|
23
|
+
import { type DescriptionError, emptyIndex, indexScene, type ParentKey } from './sceneIndex.ts';
|
|
24
|
+
|
|
25
|
+
/** A scene-wide part of a description, which changes as a whole. */
|
|
26
|
+
export type SceneSetting =
|
|
27
|
+
| 'camera'
|
|
28
|
+
| 'views'
|
|
29
|
+
| 'render'
|
|
30
|
+
| 'resources'
|
|
31
|
+
| 'gestures'
|
|
32
|
+
| 'overlays'
|
|
33
|
+
| 'environment'
|
|
34
|
+
| 'sky'
|
|
35
|
+
| 'contactShadows'
|
|
36
|
+
| 'physics'
|
|
37
|
+
| 'xr'
|
|
38
|
+
| 'measure';
|
|
39
|
+
|
|
40
|
+
/** Where a change is: the scene, its HUD, or one of its render textures. */
|
|
41
|
+
export type ChangePlace = 'scene' | 'hud' | { readonly texture: string };
|
|
42
|
+
|
|
43
|
+
export type SceneChange = { readonly place: ChangePlace } & (
|
|
44
|
+
| {
|
|
45
|
+
readonly _tag: 'Added';
|
|
46
|
+
readonly key: string;
|
|
47
|
+
readonly kind: SceneNode['_tag'];
|
|
48
|
+
readonly parent: ParentKey;
|
|
49
|
+
}
|
|
50
|
+
| { readonly _tag: 'Removed'; readonly key: string }
|
|
51
|
+
| { readonly _tag: 'Replaced'; readonly key: string; readonly kind: SceneNode['_tag'] }
|
|
52
|
+
| { readonly _tag: 'Changed'; readonly key: string; readonly fields: ReadonlyArray<Field> }
|
|
53
|
+
| { readonly _tag: 'Moved'; readonly key: string; readonly parent: ParentKey }
|
|
54
|
+
| { readonly _tag: 'Reordered'; readonly parent: ParentKey; readonly keys: ReadonlyArray<string> }
|
|
55
|
+
| { readonly _tag: 'Settings'; readonly settings: ReadonlyArray<SceneSetting> }
|
|
56
|
+
| { readonly _tag: 'HudAdded' }
|
|
57
|
+
| { readonly _tag: 'HudRemoved' }
|
|
58
|
+
| { readonly _tag: 'TextureAdded'; readonly id: string }
|
|
59
|
+
| { readonly _tag: 'TextureRemoved'; readonly id: string }
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export type SceneChanges =
|
|
63
|
+
| { readonly _tag: 'Changes'; readonly changes: ReadonlyArray<SceneChange> }
|
|
64
|
+
/** The second description is not a valid scene: why, as `reconcile` would say. */
|
|
65
|
+
| { readonly _tag: 'Invalid'; readonly error: DescriptionError };
|
|
66
|
+
|
|
67
|
+
/** Settings `diff` reports as operations; the rest are compared here. */
|
|
68
|
+
const settingOf: Partial<Record<Op['_tag'], SceneSetting>> = {
|
|
69
|
+
SetCamera: 'camera',
|
|
70
|
+
SetViews: 'views',
|
|
71
|
+
SetRenderSettings: 'render',
|
|
72
|
+
DefineResources: 'resources',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const comparedSettings = [
|
|
76
|
+
'gestures',
|
|
77
|
+
'overlays',
|
|
78
|
+
'environment',
|
|
79
|
+
'sky',
|
|
80
|
+
'contactShadows',
|
|
81
|
+
'physics',
|
|
82
|
+
'xr',
|
|
83
|
+
'measure',
|
|
84
|
+
] as const satisfies ReadonlyArray<SceneSetting>;
|
|
85
|
+
|
|
86
|
+
const entriesOf = (value: object): ReadonlyArray<readonly [string, unknown]> =>
|
|
87
|
+
Object.entries(value);
|
|
88
|
+
|
|
89
|
+
/** Descriptions are plain data: equal when every array item and field is. */
|
|
90
|
+
const same = (a: unknown, b: unknown): boolean => {
|
|
91
|
+
if (a === b) return true;
|
|
92
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
93
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
94
|
+
const fieldsA = entriesOf(a);
|
|
95
|
+
const fieldsB = new Map(entriesOf(b));
|
|
96
|
+
return (
|
|
97
|
+
fieldsA.length === fieldsB.size &&
|
|
98
|
+
fieldsA.every(([key, value]) => fieldsB.has(key) && same(value, fieldsB.get(key)))
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const nodeChange = (op: Op, place: ChangePlace): ReadonlyArray<SceneChange> => {
|
|
103
|
+
switch (op._tag) {
|
|
104
|
+
case 'Create':
|
|
105
|
+
return [{ place, _tag: 'Added', key: op.node.key, kind: op.node._tag, parent: op.parent }];
|
|
106
|
+
case 'Remove':
|
|
107
|
+
return [{ place, _tag: 'Removed', key: op.key }];
|
|
108
|
+
case 'Replace':
|
|
109
|
+
return [{ place, _tag: 'Replaced', key: op.node.key, kind: op.node._tag }];
|
|
110
|
+
case 'Update':
|
|
111
|
+
return [{ place, _tag: 'Changed', key: op.node.key, fields: op.fields }];
|
|
112
|
+
case 'Move':
|
|
113
|
+
return [{ place, _tag: 'Moved', key: op.key, parent: op.parent }];
|
|
114
|
+
case 'Order':
|
|
115
|
+
return [{ place, _tag: 'Reordered', parent: op.parent, keys: op.keys }];
|
|
116
|
+
default:
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** Every behaviour a scene names: a review judges what changed, not what is registered. */
|
|
122
|
+
const behavioursIn = (description: SceneDescription): ReadonlySet<string> => {
|
|
123
|
+
const names = new Set<string>();
|
|
124
|
+
const visit = (node: SceneNode): void => {
|
|
125
|
+
if ('behaviour' in node && node.behaviour !== null) names.add(node.behaviour.name);
|
|
126
|
+
if (node._tag === 'Group') node.children.forEach(visit);
|
|
127
|
+
};
|
|
128
|
+
description.children.forEach(visit);
|
|
129
|
+
return names;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Where `before` starts from: its index, or nothing when it is itself invalid. */
|
|
133
|
+
const startOf = (before: SceneDescription) => {
|
|
134
|
+
const indexed = indexScene(before, behavioursIn(before));
|
|
135
|
+
return indexed._tag === 'Indexed' ? indexed.index : emptyIndex;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const isSetting = (setting: SceneSetting | undefined): setting is SceneSetting =>
|
|
139
|
+
setting !== undefined;
|
|
140
|
+
|
|
141
|
+
const settingsOf = (
|
|
142
|
+
before: SceneDescription,
|
|
143
|
+
after: SceneDescription,
|
|
144
|
+
ops: ReadonlyArray<Op>,
|
|
145
|
+
): ReadonlyArray<SceneSetting> => [
|
|
146
|
+
...ops.map((op) => settingOf[op._tag]).filter(isSetting),
|
|
147
|
+
...comparedSettings.filter((setting) => !same(before[setting], after[setting])),
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/** The changes to one scene's nodes and settings, or why `after` is invalid. */
|
|
151
|
+
const changesIn = (
|
|
152
|
+
before: SceneDescription,
|
|
153
|
+
after: SceneDescription,
|
|
154
|
+
place: ChangePlace,
|
|
155
|
+
): SceneChanges => {
|
|
156
|
+
const result = diff(startOf(before), after, behavioursIn(after));
|
|
157
|
+
if (result._tag === 'Invalid') return result;
|
|
158
|
+
const settings = settingsOf(before, after, result.ops);
|
|
159
|
+
const settingsChange: ReadonlyArray<SceneChange> =
|
|
160
|
+
settings.length === 0 ? [] : [{ place, _tag: 'Settings', settings }];
|
|
161
|
+
return {
|
|
162
|
+
_tag: 'Changes',
|
|
163
|
+
changes: [...settingsChange, ...result.ops.flatMap((op) => nodeChange(op, place))],
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** A nested scene (a HUD or a render texture) as a description of its own. */
|
|
168
|
+
const nestedScene = (parent: SceneDescription, children: ReadonlyArray<SceneNode>) =>
|
|
169
|
+
({ ...parent, hud: null, renderTextures: [], children }) satisfies SceneDescription;
|
|
170
|
+
|
|
171
|
+
const hudChanges = (before: SceneDescription, after: SceneDescription): SceneChanges => {
|
|
172
|
+
if (before.hud === null && after.hud === null) return { _tag: 'Changes', changes: [] };
|
|
173
|
+
if (after.hud === null)
|
|
174
|
+
return { _tag: 'Changes', changes: [{ place: 'hud', _tag: 'HudRemoved' }] };
|
|
175
|
+
const added: ReadonlyArray<SceneChange> =
|
|
176
|
+
before.hud === null ? [{ place: 'hud', _tag: 'HudAdded' }] : [];
|
|
177
|
+
const inside = changesIn(
|
|
178
|
+
nestedScene(before, before.hud?.children ?? []),
|
|
179
|
+
nestedScene(before, after.hud.children),
|
|
180
|
+
'hud',
|
|
181
|
+
);
|
|
182
|
+
return inside._tag === 'Invalid'
|
|
183
|
+
? inside
|
|
184
|
+
: { _tag: 'Changes', changes: [...added, ...inside.changes] };
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const textureChanges = (before: SceneDescription, after: SceneDescription): SceneChanges => {
|
|
188
|
+
const changes: Array<SceneChange> = [];
|
|
189
|
+
for (const texture of after.renderTextures) {
|
|
190
|
+
const was = before.renderTextures.find((candidate) => candidate.id === texture.id);
|
|
191
|
+
const place = { texture: texture.id };
|
|
192
|
+
if (was === undefined) changes.push({ place, _tag: 'TextureAdded', id: texture.id });
|
|
193
|
+
const inside = changesIn(
|
|
194
|
+
nestedScene(before, was?.children ?? []),
|
|
195
|
+
nestedScene(before, texture.children),
|
|
196
|
+
place,
|
|
197
|
+
);
|
|
198
|
+
if (inside._tag === 'Invalid') return inside;
|
|
199
|
+
changes.push(...inside.changes);
|
|
200
|
+
}
|
|
201
|
+
for (const texture of before.renderTextures) {
|
|
202
|
+
if (!after.renderTextures.some((candidate) => candidate.id === texture.id)) {
|
|
203
|
+
changes.push({ place: { texture: texture.id }, _tag: 'TextureRemoved', id: texture.id });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { _tag: 'Changes', changes };
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* What changed from `before` to `after`: the scene's settings and nodes, then its HUD's
|
|
211
|
+
* and its render textures'. Invalid when `after` is not a valid scene; a `before` that
|
|
212
|
+
* is not is compared as though it were empty.
|
|
213
|
+
*/
|
|
214
|
+
export const sceneChanges = (before: SceneDescription, after: SceneDescription): SceneChanges => {
|
|
215
|
+
const parts = [
|
|
216
|
+
changesIn(before, after, 'scene'),
|
|
217
|
+
hudChanges(before, after),
|
|
218
|
+
textureChanges(before, after),
|
|
219
|
+
];
|
|
220
|
+
const invalid = parts.find((part) => part._tag === 'Invalid');
|
|
221
|
+
if (invalid !== undefined) return invalid;
|
|
222
|
+
return {
|
|
223
|
+
_tag: 'Changes',
|
|
224
|
+
changes: parts.flatMap((part) => (part._tag === 'Changes' ? part.changes : [])),
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const placeLabel = (place: ChangePlace): string => {
|
|
229
|
+
if (place === 'scene') return '';
|
|
230
|
+
return place === 'hud' ? 'hud ' : `texture ${place.texture} `;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const parentLabel = (parent: ParentKey): string => (parent === null ? 'the root' : parent);
|
|
234
|
+
|
|
235
|
+
const lineOf = (change: SceneChange): string => {
|
|
236
|
+
const where = placeLabel(change.place);
|
|
237
|
+
switch (change._tag) {
|
|
238
|
+
case 'Added':
|
|
239
|
+
return `${where}+ ${change.kind} ${change.key}${change.parent === null ? '' : ` in ${change.parent}`}`;
|
|
240
|
+
case 'Removed':
|
|
241
|
+
return `${where}- ${change.key}`;
|
|
242
|
+
case 'Replaced':
|
|
243
|
+
return `${where}± ${change.key}, now a ${change.kind}`;
|
|
244
|
+
case 'Changed':
|
|
245
|
+
return `${where}~ ${change.key}: ${change.fields.join(', ')}`;
|
|
246
|
+
case 'Moved':
|
|
247
|
+
return `${where}> ${change.key} to ${parentLabel(change.parent)}`;
|
|
248
|
+
case 'Reordered':
|
|
249
|
+
return `${where}= order of ${parentLabel(change.parent)}: ${change.keys.join(', ')}`;
|
|
250
|
+
case 'Settings':
|
|
251
|
+
return `${where}⋯ scene: ${change.settings.join(', ')}`;
|
|
252
|
+
case 'HudAdded':
|
|
253
|
+
return '+ hud';
|
|
254
|
+
case 'HudRemoved':
|
|
255
|
+
return '- hud';
|
|
256
|
+
case 'TextureAdded':
|
|
257
|
+
return `+ texture ${change.id}`;
|
|
258
|
+
case 'TextureRemoved':
|
|
259
|
+
return `- texture ${change.id}`;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/** The changes as lines for review, one per change; `No changes.` when there are none. */
|
|
264
|
+
export const formatChanges = (changes: ReadonlyArray<SceneChange>): string =>
|
|
265
|
+
changes.length === 0 ? 'No changes.' : changes.map(lineOf).join('\n');
|