pixifact 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +3 -3
  2. package/dist/builtin-scenes/CenterContainer.d.ts +13 -0
  3. package/dist/builtin-scenes/CenterContainer.js +71 -0
  4. package/dist/builtin-scenes/CenterContainer.scene +3 -0
  5. package/dist/builtin-scenes/CenterContainer.ts +77 -0
  6. package/dist/builtin-scenes/Control.d.ts +44 -0
  7. package/dist/builtin-scenes/Control.js +206 -0
  8. package/dist/builtin-scenes/Control.scene +3 -0
  9. package/dist/builtin-scenes/Control.ts +233 -0
  10. package/dist/builtin-scenes/FlexItem.d.ts +45 -0
  11. package/dist/builtin-scenes/FlexItem.js +164 -0
  12. package/dist/builtin-scenes/FlexItem.scene +3 -0
  13. package/dist/builtin-scenes/FlexItem.ts +180 -0
  14. package/dist/builtin-scenes/FlexLayout.d.ts +29 -0
  15. package/dist/builtin-scenes/FlexLayout.js +358 -0
  16. package/dist/builtin-scenes/FlexLayout.scene +3 -0
  17. package/dist/builtin-scenes/FlexLayout.ts +409 -0
  18. package/dist/builtin-scenes/HBoxContainer.d.ts +15 -0
  19. package/dist/builtin-scenes/HBoxContainer.js +70 -0
  20. package/dist/builtin-scenes/HBoxContainer.scene +3 -0
  21. package/dist/builtin-scenes/HBoxContainer.ts +75 -0
  22. package/dist/builtin-scenes/MarginContainer.d.ts +23 -0
  23. package/dist/builtin-scenes/MarginContainer.js +131 -0
  24. package/dist/builtin-scenes/MarginContainer.scene +3 -0
  25. package/dist/builtin-scenes/MarginContainer.ts +139 -0
  26. package/dist/builtin-scenes/VBoxContainer.d.ts +15 -0
  27. package/dist/builtin-scenes/VBoxContainer.js +70 -0
  28. package/dist/builtin-scenes/VBoxContainer.scene +3 -0
  29. package/dist/builtin-scenes/VBoxContainer.ts +75 -0
  30. package/dist/builtin-scenes/controlLayout.d.ts +57 -0
  31. package/dist/builtin-scenes/controlLayout.js +202 -0
  32. package/dist/builtin-scenes/controlLayout.ts +270 -0
  33. package/dist/compiler/builtinScenes.d.ts +14 -0
  34. package/dist/compiler/builtinScenes.js +62 -0
  35. package/dist/compiler/compileScenes.js +126 -19
  36. package/dist/compiler/decorators.js +3 -0
  37. package/dist/compiler/index.d.ts +1 -0
  38. package/dist/compiler/index.js +1 -0
  39. package/dist/compiler/sceneAssetPair.js +19 -0
  40. package/dist/compiler/sceneRuntime.d.ts +5 -4
  41. package/dist/compiler/scriptInterfaceExtractor.d.ts +5 -0
  42. package/dist/compiler/scriptInterfaceExtractor.js +214 -8
  43. package/dist/compiler/spec.d.ts +2 -0
  44. package/dist/compiler/templateParser.js +4 -3
  45. package/dist/compiler/templateSerializer.js +2 -1
  46. package/dist/compiler/typescriptCompiler.js +87 -13
  47. package/dist/compiler-node/builtinSceneAssets.d.ts +8 -0
  48. package/dist/compiler-node/builtinSceneAssets.js +57 -0
  49. package/dist/compiler-node/index.d.ts +1 -0
  50. package/dist/compiler-node/index.js +1 -0
  51. package/dist/errors.d.ts +1 -0
  52. package/dist/errors.js +24 -0
  53. package/dist/index.d.ts +2 -5
  54. package/dist/index.js +2 -5
  55. package/dist/{authoring/ProjectConfig.d.ts → project/index.d.ts} +7 -0
  56. package/dist/{authoring/ProjectConfig.js → project/index.js} +22 -0
  57. package/dist/runtime/Group.d.ts +18 -0
  58. package/dist/runtime/Group.js +45 -0
  59. package/dist/runtime/index.d.ts +1 -9
  60. package/dist/runtime/index.js +1 -9
  61. package/package.json +4 -26
  62. package/dist/authoring/ActionRegistry.d.ts +0 -10
  63. package/dist/authoring/ActionRegistry.js +0 -26
  64. package/dist/authoring/ComponentPalette.d.ts +0 -16
  65. package/dist/authoring/ComponentPalette.js +0 -50
  66. package/dist/authoring/DesignToken.d.ts +0 -16
  67. package/dist/authoring/DesignToken.js +0 -33
  68. package/dist/authoring/DiffModel.d.ts +0 -10
  69. package/dist/authoring/DiffModel.js +0 -104
  70. package/dist/authoring/InspectorModel.d.ts +0 -27
  71. package/dist/authoring/InspectorModel.js +0 -139
  72. package/dist/authoring/LogicGraph.d.ts +0 -66
  73. package/dist/authoring/LogicGraph.js +0 -184
  74. package/dist/authoring/OverrideJournal.d.ts +0 -18
  75. package/dist/authoring/OverrideJournal.js +0 -23
  76. package/dist/authoring/PreferenceMemory.d.ts +0 -21
  77. package/dist/authoring/PreferenceMemory.js +0 -39
  78. package/dist/authoring/RuntimePreview.d.ts +0 -11
  79. package/dist/authoring/RuntimePreview.js +0 -25
  80. package/dist/authoring/SceneDocument.d.ts +0 -74
  81. package/dist/authoring/SceneDocument.js +0 -332
  82. package/dist/authoring/SceneProjectState.d.ts +0 -20
  83. package/dist/authoring/SceneProjectState.js +0 -8
  84. package/dist/authoring/SceneSelection.d.ts +0 -11
  85. package/dist/authoring/SceneSelection.js +0 -1
  86. package/dist/authoring/index.d.ts +0 -13
  87. package/dist/authoring/index.js +0 -13
  88. package/dist/commands/Command.d.ts +0 -63
  89. package/dist/commands/Command.js +0 -1
  90. package/dist/commands/CommandStack.d.ts +0 -14
  91. package/dist/commands/CommandStack.js +0 -45
  92. package/dist/commands/applyCommand.d.ts +0 -23
  93. package/dist/commands/applyCommand.js +0 -316
  94. package/dist/commands/index.d.ts +0 -4
  95. package/dist/commands/index.js +0 -4
  96. package/dist/commands/utils.d.ts +0 -17
  97. package/dist/commands/utils.js +0 -77
  98. package/dist/commands/validateCommand.d.ts +0 -12
  99. package/dist/commands/validateCommand.js +0 -285
  100. package/dist/nodes/Input.d.ts +0 -91
  101. package/dist/nodes/Input.js +0 -352
  102. package/dist/nodes/Textarea.d.ts +0 -10
  103. package/dist/nodes/Textarea.js +0 -38
  104. package/dist/nodes/components/Button.d.ts +0 -8
  105. package/dist/nodes/components/Button.js +0 -45
  106. package/dist/nodes/components/InputField.d.ts +0 -12
  107. package/dist/nodes/components/InputField.js +0 -53
  108. package/dist/nodes/components/ProgressBar.d.ts +0 -14
  109. package/dist/nodes/components/ProgressBar.js +0 -66
  110. package/dist/nodes/components/ScrollRect.d.ts +0 -23
  111. package/dist/nodes/components/ScrollRect.js +0 -108
  112. package/dist/nodes/components/Selectable.d.ts +0 -32
  113. package/dist/nodes/components/Selectable.js +0 -153
  114. package/dist/nodes/components/index.d.ts +0 -5
  115. package/dist/nodes/components/index.js +0 -5
  116. package/dist/nodes/graphics/Graphic.d.ts +0 -14
  117. package/dist/nodes/graphics/Graphic.js +0 -47
  118. package/dist/nodes/graphics/ImageGraphic.d.ts +0 -9
  119. package/dist/nodes/graphics/ImageGraphic.js +0 -42
  120. package/dist/nodes/graphics/RoundedRectGraphic.d.ts +0 -13
  121. package/dist/nodes/graphics/RoundedRectGraphic.js +0 -65
  122. package/dist/nodes/graphics/TextGraphic.d.ts +0 -13
  123. package/dist/nodes/graphics/TextGraphic.js +0 -70
  124. package/dist/nodes/graphics/index.d.ts +0 -4
  125. package/dist/nodes/graphics/index.js +0 -4
  126. package/dist/nodes/index.d.ts +0 -3
  127. package/dist/nodes/index.js +0 -3
  128. package/dist/runtime/Application.d.ts +0 -15
  129. package/dist/runtime/Application.js +0 -67
  130. package/dist/runtime/GameObject.d.ts +0 -100
  131. package/dist/runtime/GameObject.js +0 -297
  132. package/dist/runtime/GameObjectEvent.d.ts +0 -52
  133. package/dist/runtime/GameObjectEvent.js +0 -38
  134. package/dist/runtime/Transform.d.ts +0 -32
  135. package/dist/runtime/Transform.js +0 -157
  136. package/dist/runtime/component/Component.d.ts +0 -13
  137. package/dist/runtime/component/Component.js +0 -13
  138. package/dist/runtime/component/ComponentRegistry.d.ts +0 -49
  139. package/dist/runtime/component/ComponentRegistry.js +0 -25
  140. package/dist/runtime/component/Flex.d.ts +0 -6
  141. package/dist/runtime/component/Flex.js +0 -15
  142. package/dist/runtime/component/FlexGroup.d.ts +0 -26
  143. package/dist/runtime/component/FlexGroup.js +0 -136
  144. package/dist/runtime/component/GridLayout.d.ts +0 -47
  145. package/dist/runtime/component/GridLayout.js +0 -129
  146. package/dist/runtime/component/Layout.d.ts +0 -104
  147. package/dist/runtime/component/Layout.js +0 -248
  148. package/dist/runtime/component/decorators.d.ts +0 -4
  149. package/dist/runtime/component/decorators.js +0 -52
  150. package/dist/runtime/component/index.d.ts +0 -7
  151. package/dist/runtime/component/index.js +0 -7
  152. package/dist/runtime/graphics/index.d.ts +0 -11
  153. package/dist/runtime/graphics/index.js +0 -29
  154. package/dist/runtime/group/index.d.ts +0 -39
  155. package/dist/runtime/group/index.js +0 -102
  156. package/dist/runtime/image/Image.d.ts +0 -19
  157. package/dist/runtime/image/Image.js +0 -60
  158. package/dist/runtime/image/NineSliceImage.d.ts +0 -23
  159. package/dist/runtime/image/NineSliceImage.js +0 -64
  160. package/dist/runtime/image/index.d.ts +0 -2
  161. package/dist/runtime/image/index.js +0 -2
  162. package/dist/runtime/label/Label.d.ts +0 -11
  163. package/dist/runtime/label/Label.js +0 -26
  164. package/dist/runtime/label/LabelStyle.d.ts +0 -3
  165. package/dist/runtime/label/LabelStyle.js +0 -3
  166. package/dist/runtime/label/index.d.ts +0 -2
  167. package/dist/runtime/label/index.js +0 -2
  168. package/dist/runtime/utils/index.d.ts +0 -1
  169. package/dist/runtime/utils/index.js +0 -1
  170. package/dist/runtime/utils/onlyOnceQueueMicrotask.d.ts +0 -1
  171. package/dist/runtime/utils/onlyOnceQueueMicrotask.js +0 -12
  172. package/dist/runtime/utils/setProps.d.ts +0 -10
  173. package/dist/runtime/utils/setProps.js +0 -17
  174. package/dist/scene/dsl.d.ts +0 -25
  175. package/dist/scene/dsl.js +0 -112
  176. package/dist/scene/index.d.ts +0 -5
  177. package/dist/scene/index.js +0 -5
  178. package/dist/scene/instantiate.d.ts +0 -8
  179. package/dist/scene/instantiate.js +0 -208
  180. package/dist/scene/nodesDsl.d.ts +0 -42
  181. package/dist/scene/nodesDsl.js +0 -169
  182. package/dist/scene/spec.d.ts +0 -100
  183. package/dist/scene/spec.js +0 -1
  184. package/dist/scene/templates.d.ts +0 -21
  185. package/dist/scene/templates.js +0 -239
@@ -1,8 +1,10 @@
1
1
  import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { builtinSceneAssetIds, builtinSceneNameFromAssetId, isBuiltinSceneAssetId, } from './builtinScenes.js';
4
5
  import { defaultSceneSourceRoots, generatedSceneModuleImport, generatedSceneModulePath, isIgnoredSceneSourceDirectory, normalizeSceneAssetId, pairedSceneScriptPath, resolveSceneReference, sceneClassAlias, sceneLocalName, toPosixPath, } from './sceneAssetPair.js';
5
- import { extractSceneScriptInterface } from './scriptInterfaceExtractor.js';
6
+ import { builtinSceneScriptPath, readBuiltinSceneScriptSources, readBuiltinSceneSource, } from '../compiler-node/builtinSceneAssets.js';
7
+ import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
6
8
  import { findMissingScenePartReferences } from './scenePartValidation.js';
7
9
  import { parseSceneTemplate } from './templateParser.js';
8
10
  import { compileSceneTemplateToTs } from './typescriptCompiler.js';
@@ -21,24 +23,48 @@ export async function compileScenes(options) {
21
23
  ? options.projectRoot
22
24
  : fileURLToPath(options.projectRoot);
23
25
  const generatedDir = path.resolve(projectRoot, options.generatedDir ?? '.pixifact/generated');
24
- const scenePaths = await collectSceneAssetIds(projectRoot, options.sourceRoots);
26
+ const projectScenePaths = await collectSceneAssetIds(projectRoot, options.sourceRoots);
27
+ const sceneRecords = [
28
+ ...projectScenePaths.map((scenePath) => ({ scenePath, kind: 'project' })),
29
+ ...builtinSceneAssetIds().map((scenePath) => ({ scenePath, kind: 'builtin' })),
30
+ ];
31
+ const builtinScriptSources = await readBuiltinSceneScriptSources();
25
32
  await mkdir(generatedDir, { recursive: true });
33
+ const sceneSources = new Map();
26
34
  const templates = new Map();
27
- for (const scenePath of scenePaths) {
28
- const source = await readFile(path.resolve(projectRoot, scenePath), 'utf8');
35
+ for (const record of sceneRecords) {
36
+ const { scenePath } = record;
37
+ const source = record.kind === 'builtin'
38
+ ? await readBuiltinSceneSource(scenePath)
39
+ : await readFile(path.resolve(projectRoot, scenePath), 'utf8');
40
+ sceneSources.set(scenePath, source);
29
41
  try {
30
42
  const template = parseSceneTemplate(source);
31
43
  assertSceneLocalName(scenePath, template);
32
- const descriptor = await readPairedSceneScript(projectRoot, scenePath, template);
33
- template.interface = descriptor.interface;
34
44
  templates.set(scenePath, normalizeSceneReferences(scenePath, template));
35
45
  }
36
46
  catch (error) {
37
47
  throw new CompileSceneError(error instanceof Error ? error.message : String(error), scenePath, source);
38
48
  }
39
49
  }
40
- const registryImports = [];
41
- for (const scenePath of scenePaths) {
50
+ const sceneScriptDescriptors = await readSceneScriptDescriptors(projectRoot, projectScenePaths, builtinScriptSources, sceneSources);
51
+ for (const record of sceneRecords) {
52
+ const { scenePath } = record;
53
+ const template = templates.get(scenePath);
54
+ if (!template) {
55
+ throw new Error(`Missing parsed template for ${scenePath}.`);
56
+ }
57
+ try {
58
+ template.interface = bindSceneScriptInterface(scenePath, template, sceneScriptDescriptors[scenePath]).interface;
59
+ }
60
+ catch (error) {
61
+ throw new CompileSceneError(error instanceof Error ? error.message : String(error), scenePath, sceneSources.get(scenePath));
62
+ }
63
+ }
64
+ const projectRegistryImports = [];
65
+ const builtinRegistryImports = [];
66
+ for (const record of sceneRecords) {
67
+ const { scenePath } = record;
42
68
  const template = templates.get(scenePath);
43
69
  if (!template) {
44
70
  throw new Error(`Missing parsed template for ${scenePath}.`);
@@ -51,7 +77,7 @@ export async function compileScenes(options) {
51
77
  scriptImport: {
52
78
  exportName: template.name,
53
79
  localName: sceneClassAlias(scenePath),
54
- source: importSourceFor(path.resolve(projectRoot, pairedSceneScriptPath(scenePath)), generatedFileDir),
80
+ source: importSourceFor(sceneScriptAbsolutePath(projectRoot, scenePath), generatedFileDir),
55
81
  },
56
82
  sceneImports: sceneImportsFor(template, templates, projectRoot, generatedFileDir),
57
83
  sceneClassAliases: sceneClassAliasesFor(template),
@@ -60,9 +86,43 @@ export async function compileScenes(options) {
60
86
  });
61
87
  await mkdir(path.dirname(generatedFile), { recursive: true });
62
88
  await writeFile(generatedFile, code);
63
- registryImports.push(`import ${JSON.stringify(generatedSceneModuleImport(scenePath))};`);
89
+ if (record.kind === 'project') {
90
+ projectRegistryImports.push(`import ${JSON.stringify(generatedSceneModuleImport(scenePath))};`);
91
+ }
92
+ else if (sceneIsReferencedByProjectScenes(scenePath, projectScenePaths, templates)) {
93
+ builtinRegistryImports.push(`import ${JSON.stringify(generatedSceneModuleImport(scenePath))};`);
94
+ }
64
95
  }
65
- await writeFile(path.join(generatedDir, 'scenes.generated.ts'), `${registryImports.join('\n')}\n`);
96
+ await writeFile(path.join(generatedDir, 'scenes.generated.ts'), `${[
97
+ ...builtinRegistryImports,
98
+ ...projectRegistryImports,
99
+ ].join('\n')}\n`);
100
+ }
101
+ function sceneIsReferencedByProjectScenes(scenePath, projectScenePaths, templates) {
102
+ if (!isBuiltinSceneAssetId(scenePath)) {
103
+ return false;
104
+ }
105
+ const visited = new Set();
106
+ const stack = [...projectScenePaths];
107
+ while (stack.length > 0) {
108
+ const current = stack.pop();
109
+ if (visited.has(current)) {
110
+ continue;
111
+ }
112
+ visited.add(current);
113
+ for (const referencedScenePath of collectSceneInstancePaths(templates.get(current)?.children ?? [])) {
114
+ if (referencedScenePath === scenePath) {
115
+ return true;
116
+ }
117
+ stack.push(referencedScenePath);
118
+ }
119
+ }
120
+ return false;
121
+ }
122
+ function sceneScriptAbsolutePath(projectRoot, scenePath) {
123
+ return isBuiltinSceneAssetId(scenePath)
124
+ ? builtinSceneScriptPath(scenePath)
125
+ : path.resolve(projectRoot, pairedSceneScriptPath(scenePath));
66
126
  }
67
127
  async function collectSceneAssetIds(projectRoot, sourceRoots = defaultSceneSourceRoots) {
68
128
  const scenes = [];
@@ -97,19 +157,39 @@ async function exists(filePath) {
97
157
  return false;
98
158
  }
99
159
  }
160
+ async function readSceneScriptDescriptors(projectRoot, projectScenePaths, builtinScriptSources, sceneSources) {
161
+ const sources = builtinSceneAssetIds().map((scene) => {
162
+ const name = builtinSceneNameFromAssetId(scene);
163
+ return {
164
+ scene,
165
+ fileName: builtinSceneScriptPath(scene),
166
+ source: builtinScriptSources[name],
167
+ };
168
+ });
169
+ for (const scenePath of projectScenePaths) {
170
+ const scriptPath = pairedSceneScriptPath(scenePath);
171
+ const absoluteScriptPath = path.resolve(projectRoot, scriptPath);
172
+ if (!await exists(absoluteScriptPath)) {
173
+ throw new CompileSceneError(`Scene "${scenePath}" requires paired script "${scriptPath}".`, scenePath, sceneSources.get(scenePath));
174
+ }
175
+ sources.push({
176
+ scene: scenePath,
177
+ fileName: absoluteScriptPath,
178
+ source: await readFile(absoluteScriptPath, 'utf8'),
179
+ });
180
+ }
181
+ return extractSceneScriptInterfaces(sources);
182
+ }
100
183
  function assertSceneLocalName(scenePath, template) {
101
184
  const expected = sceneLocalName(scenePath);
102
185
  if (template.name !== expected) {
103
186
  throw new Error(`Scene "${scenePath}" name "${template.name}" must match file basename "${expected}".`);
104
187
  }
105
188
  }
106
- async function readPairedSceneScript(projectRoot, scenePath, template) {
107
- const scriptPath = pairedSceneScriptPath(scenePath);
108
- const absoluteScriptPath = path.resolve(projectRoot, scriptPath);
109
- if (!await exists(absoluteScriptPath)) {
110
- throw new Error(`Scene "${scenePath}" requires paired script "${scriptPath}".`);
189
+ function bindSceneScriptInterface(scenePath, template, descriptor) {
190
+ if (!descriptor) {
191
+ throw new Error(`Scene "${scenePath}" requires a @scene script class.`);
111
192
  }
112
- const descriptor = extractSceneScriptInterface(await readFile(absoluteScriptPath, 'utf8'), absoluteScriptPath, { scene: scenePath });
113
193
  if (descriptor.className !== template.name) {
114
194
  throw new Error(`Scene "${scenePath}" name "${template.name}" must match @scene class "${descriptor.className}".`);
115
195
  }
@@ -141,7 +221,7 @@ function normalizeSceneReferences(scenePath, template) {
141
221
  return next;
142
222
  }
143
223
  function sceneImportsFor(template, templates, projectRoot, generatedFileDir) {
144
- return [...collectSceneInstancePaths(template.children)]
224
+ return [...collectSceneImportPaths(template, templates)]
145
225
  .sort()
146
226
  .map((scenePath) => {
147
227
  const sceneTemplate = templates.get(scenePath);
@@ -149,12 +229,39 @@ function sceneImportsFor(template, templates, projectRoot, generatedFileDir) {
149
229
  throw new Error(`Scene "${template.name}" references unknown Scene "${scenePath}".`);
150
230
  }
151
231
  return {
232
+ scene: scenePath,
152
233
  exportName: sceneTemplate.name,
153
234
  localName: sceneClassAlias(scenePath),
154
- source: importSourceFor(path.resolve(projectRoot, pairedSceneScriptPath(scenePath)), generatedFileDir),
235
+ source: importSourceFor(sceneScriptAbsolutePath(projectRoot, scenePath), generatedFileDir),
155
236
  };
156
237
  });
157
238
  }
239
+ function collectSceneImportPaths(template, templates) {
240
+ const scenePaths = collectSceneInstancePaths(template.children);
241
+ collectSceneStructSourcePaths(template.children, templates, scenePaths);
242
+ return scenePaths;
243
+ }
244
+ function collectSceneStructSourcePaths(nodes, templates, scenePaths) {
245
+ for (const node of nodes) {
246
+ if (node.kind === 'slotOutlet') {
247
+ continue;
248
+ }
249
+ if (node.kind === 'pixi') {
250
+ collectSceneStructSourcePaths(node.children, templates, scenePaths);
251
+ continue;
252
+ }
253
+ const sceneInterface = templates.get(node.scene)?.interface;
254
+ for (const [key, value] of Object.entries(node.props)) {
255
+ const contract = value && typeof value === 'object' ? sceneInterface?.props[key] : undefined;
256
+ if (contract?.type === 'struct' && contract.sourceScene) {
257
+ scenePaths.add(contract.sourceScene);
258
+ }
259
+ }
260
+ for (const children of Object.values(node.slots)) {
261
+ collectSceneStructSourcePaths(children, templates, scenePaths);
262
+ }
263
+ }
264
+ }
158
265
  function sceneClassAliasesFor(template) {
159
266
  return Object.fromEntries([...collectSceneInstancePaths(template.children)].map((scenePath) => [scenePath, sceneClassAlias(scenePath)]));
160
267
  }
@@ -5,6 +5,9 @@ export function scene() {
5
5
  const SceneClass = class extends constructor {
6
6
  constructor(...args) {
7
7
  super(...args);
8
+ if (new.target !== SceneClass) {
9
+ return;
10
+ }
8
11
  const result = mountSceneClass(this, SceneClass);
9
12
  const metadata = sceneMetadata(constructor);
10
13
  for (const [property, id] of metadata.parts) {
@@ -1,4 +1,5 @@
1
1
  export * from './commands/index.js';
2
+ export * from './builtinScenes.js';
2
3
  export * from './decorators.js';
3
4
  export * from './eventRuntime.js';
4
5
  export * from './pixiNodeSchema.js';
@@ -1,4 +1,5 @@
1
1
  export * from './commands/index.js';
2
+ export * from './builtinScenes.js';
2
3
  export * from './decorators.js';
3
4
  export * from './eventRuntime.js';
4
5
  export * from './pixiNodeSchema.js';
@@ -1,3 +1,4 @@
1
+ import { builtinSceneAssetId, builtinSceneNameFromAssetId, isBuiltinSceneAssetId } from './builtinScenes.js';
1
2
  export const defaultSceneSourceRoots = ['src'];
2
3
  export const ignoredSceneSourceDirectories = new Set([
3
4
  'node_modules',
@@ -45,6 +46,9 @@ function posixJoin(...parts) {
45
46
  return normalizePosixPath(parts.filter(Boolean).join('/'));
46
47
  }
47
48
  export function normalizeSceneAssetId(value) {
49
+ if (isBuiltinSceneAssetId(value)) {
50
+ return builtinSceneAssetId(builtinSceneNameFromAssetId(value));
51
+ }
48
52
  const normalized = toPosixPath(value).replace(/^\.\/+/, '').replace(/^\/+/, '');
49
53
  const parts = normalized.split('/').filter(Boolean);
50
54
  if (parts.includes('..')) {
@@ -59,19 +63,31 @@ export function isIgnoredSceneSourceDirectory(name) {
59
63
  return ignoredSceneSourceDirectories.has(name);
60
64
  }
61
65
  export function sceneLocalName(scenePath) {
66
+ if (isBuiltinSceneAssetId(scenePath)) {
67
+ return builtinSceneNameFromAssetId(scenePath);
68
+ }
62
69
  return posixBasename(normalizeSceneAssetId(scenePath), '.scene');
63
70
  }
64
71
  export function pairedSceneScriptPath(scenePath) {
72
+ if (isBuiltinSceneAssetId(scenePath)) {
73
+ return `${builtinSceneNameFromAssetId(scenePath)}.ts`;
74
+ }
65
75
  const assetId = normalizeSceneAssetId(scenePath);
66
76
  return `${assetId.slice(0, -'.scene'.length)}.ts`;
67
77
  }
68
78
  export function generatedSceneModulePath(scenePath) {
79
+ if (isBuiltinSceneAssetId(scenePath)) {
80
+ return `pixifact-builtin/${builtinSceneNameFromAssetId(scenePath)}.scene.generated.ts`;
81
+ }
69
82
  return `${normalizeSceneAssetId(scenePath).slice(0, -'.scene'.length)}.scene.generated.ts`;
70
83
  }
71
84
  export function generatedSceneModuleImport(scenePath) {
72
85
  return `./${generatedSceneModulePath(scenePath).replace(/\.ts$/, '')}`;
73
86
  }
74
87
  export function sceneClassAlias(scenePath) {
88
+ if (isBuiltinSceneAssetId(scenePath)) {
89
+ return `BuiltinSceneClass_${builtinSceneNameFromAssetId(scenePath)}`;
90
+ }
75
91
  const base = normalizeSceneAssetId(scenePath)
76
92
  .slice(0, -'.scene'.length)
77
93
  .split('/')
@@ -81,6 +97,9 @@ export function sceneClassAlias(scenePath) {
81
97
  }
82
98
  export function resolveSceneReference(fromScenePath, reference) {
83
99
  const value = toPosixPath(reference.trim());
100
+ if (isBuiltinSceneAssetId(value)) {
101
+ return normalizeSceneAssetId(value);
102
+ }
84
103
  if (!value.endsWith('.scene')) {
85
104
  throw new Error('Scene references must use .scene paths.');
86
105
  }
@@ -1,13 +1,14 @@
1
1
  import type { Container } from 'pixi.js';
2
+ import type { Group } from '../runtime/index.js';
2
3
  export interface SceneMountResult {
3
- root: Container;
4
+ root: Group;
4
5
  parts: Record<string, Container>;
5
6
  slots: Record<string, Container>;
6
7
  }
7
8
  export interface SceneDefinition {
8
- mount(root: Container): SceneMountResult;
9
+ mount(root: Group): SceneMountResult;
9
10
  }
10
11
  export declare function registerScene(path: string, definition: SceneDefinition): void;
11
12
  export declare function registerSceneClass(constructor: object, path: string): void;
12
- export declare function mountScene(root: Container, path: string): SceneMountResult;
13
- export declare function mountSceneClass(root: Container, constructor: object): SceneMountResult;
13
+ export declare function mountScene(root: Group, path: string): SceneMountResult;
14
+ export declare function mountSceneClass(root: Group, constructor: object): SceneMountResult;
@@ -2,5 +2,10 @@ import type { SceneScriptInterface } from './spec.js';
2
2
  export interface ExtractSceneScriptInterfaceOptions {
3
3
  scene: string;
4
4
  }
5
+ export interface ExtractSceneScriptInterfaceSource extends ExtractSceneScriptInterfaceOptions {
6
+ source: string;
7
+ fileName?: string;
8
+ }
5
9
  export declare function extractSceneScriptInterface(source: string, fileName: string | undefined, options: ExtractSceneScriptInterfaceOptions): SceneScriptInterface;
10
+ export declare function extractSceneScriptInterfaces(sources: readonly ExtractSceneScriptInterfaceSource[]): Record<string, SceneScriptInterface>;
6
11
  export declare function emitSceneScriptInterfaceDescriptor(source: string, fileName: string | undefined, options: ExtractSceneScriptInterfaceOptions): string;
@@ -1,17 +1,32 @@
1
1
  import ts from 'typescript';
2
2
  export function extractSceneScriptInterface(source, fileName = 'scene-script.ts', options) {
3
+ const descriptors = composeSceneScriptClasses(extractSceneScriptClasses(source, fileName, options));
4
+ const descriptor = descriptors[options.scene];
5
+ if (!descriptor) {
6
+ throw new Error('No @scene decorator found.');
7
+ }
8
+ return descriptor;
9
+ }
10
+ export function extractSceneScriptInterfaces(sources) {
11
+ return composeSceneScriptClasses(sources.flatMap((source) => extractSceneScriptClasses(source.source, source.fileName ?? 'scene-script.ts', { scene: source.scene })));
12
+ }
13
+ function extractSceneScriptClasses(source, fileName, options) {
3
14
  const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
15
+ const sourceId = normalizeScriptSourceId(fileName);
16
+ const imports = collectNamedImports(sourceFile, sourceId);
4
17
  const structClasses = collectStructClasses(sourceFile);
18
+ const classes = [];
5
19
  for (const statement of sourceFile.statements) {
6
20
  if (!ts.isClassDeclaration(statement)) {
7
21
  continue;
8
22
  }
9
- if (!hasSceneDecorator(statement)) {
10
- continue;
11
- }
23
+ const isSceneClass = hasSceneDecorator(statement);
12
24
  const className = statement.name?.text;
13
25
  if (!className) {
14
- throw new Error('Scene script class is missing a name.');
26
+ if (isSceneClass) {
27
+ throw new Error('Scene script class is missing a name.');
28
+ }
29
+ continue;
15
30
  }
16
31
  const props = {};
17
32
  const events = {};
@@ -39,22 +54,199 @@ export function extractSceneScriptInterface(source, fileName = 'scene-script.ts'
39
54
  parts[name] = typeof part.id === 'string' ? part.id : name;
40
55
  }
41
56
  }
42
- return {
43
- scene: options.scene,
57
+ const parentClassName = parentClassNameOf(statement);
58
+ if (!isSceneClass && !parentClassName && !hasPublicContract(props, events, slots, parts)) {
59
+ continue;
60
+ }
61
+ classes.push({
62
+ sourceId,
63
+ scriptScene: options.scene,
64
+ ...(isSceneClass ? { scene: options.scene } : {}),
44
65
  className,
66
+ ...(parentClassName ? { parentClassName } : {}),
67
+ imports,
45
68
  interface: {
46
69
  props,
47
70
  events,
48
71
  slots,
49
72
  },
50
73
  parts,
51
- };
74
+ });
52
75
  }
53
- throw new Error('No @scene decorator found.');
76
+ return classes;
54
77
  }
55
78
  export function emitSceneScriptInterfaceDescriptor(source, fileName = 'scene-script.ts', options) {
56
79
  return `${JSON.stringify(extractSceneScriptInterface(source, fileName, options), null, 2)}\n`;
57
80
  }
81
+ function composeSceneScriptClasses(classes) {
82
+ const classesByName = new Map();
83
+ const classesBySourceAndName = new Map();
84
+ for (const item of classes) {
85
+ const bucket = classesByName.get(item.className) ?? [];
86
+ bucket.push(item);
87
+ classesByName.set(item.className, bucket);
88
+ classesBySourceAndName.set(sourceClassKey(item.sourceId, item.className), item);
89
+ }
90
+ const composed = new Map();
91
+ const visiting = new Set();
92
+ function compose(item) {
93
+ const cached = composed.get(item);
94
+ if (cached) {
95
+ return cached;
96
+ }
97
+ if (visiting.has(item)) {
98
+ throw new Error(`Scene script class "${item.className}" has a circular inheritance chain.`);
99
+ }
100
+ visiting.add(item);
101
+ const parent = item.parentClassName ? resolveParentClass(item, classesByName, classesBySourceAndName) : undefined;
102
+ const parentInterface = parent ? inheritedSceneInterface(compose(parent), parent.scriptScene, item.scriptScene) : emptySceneInterface();
103
+ const sceneInterface = mergeSceneInterfaces(parentInterface, item.interface);
104
+ visiting.delete(item);
105
+ composed.set(item, sceneInterface);
106
+ return sceneInterface;
107
+ }
108
+ const result = {};
109
+ for (const item of classes) {
110
+ if (!item.scene) {
111
+ continue;
112
+ }
113
+ if (result[item.scene]) {
114
+ throw new Error(`Scene script "${item.scene}" has multiple @scene classes.`);
115
+ }
116
+ result[item.scene] = {
117
+ scene: item.scene,
118
+ className: item.className,
119
+ interface: compose(item),
120
+ parts: item.parts,
121
+ };
122
+ }
123
+ return result;
124
+ }
125
+ function resolveParentClass(item, classesByName, classesBySourceAndName) {
126
+ const parentClassName = item.parentClassName ?? '';
127
+ const sameSourceParent = classesBySourceAndName.get(sourceClassKey(item.sourceId, parentClassName));
128
+ if (sameSourceParent) {
129
+ return sameSourceParent;
130
+ }
131
+ const importedSource = item.imports.get(parentClassName);
132
+ if (importedSource) {
133
+ return classesBySourceAndName.get(sourceClassKey(importedSource, parentClassName));
134
+ }
135
+ const candidates = classesByName.get(parentClassName) ?? [];
136
+ if (candidates.length === 0) {
137
+ return undefined;
138
+ }
139
+ if (candidates.length > 1) {
140
+ throw new Error(`Scene script parent class "${item.parentClassName}" is ambiguous for "${item.className}".`);
141
+ }
142
+ return candidates[0];
143
+ }
144
+ function sourceClassKey(sourceId, className) {
145
+ return `${sourceId}:${className}`;
146
+ }
147
+ function emptySceneInterface() {
148
+ return {
149
+ props: {},
150
+ events: {},
151
+ slots: {},
152
+ };
153
+ }
154
+ function mergeSceneInterfaces(parent, own) {
155
+ return {
156
+ props: {
157
+ ...parent.props,
158
+ ...own.props,
159
+ },
160
+ events: {
161
+ ...parent.events,
162
+ ...own.events,
163
+ },
164
+ slots: {
165
+ ...parent.slots,
166
+ ...own.slots,
167
+ },
168
+ };
169
+ }
170
+ function inheritedSceneInterface(parent, parentSourceScene, childSourceScene) {
171
+ return {
172
+ props: Object.fromEntries(Object.entries(parent.props).map(([name, contract]) => {
173
+ if (contract.type !== 'struct') {
174
+ return [name, contract];
175
+ }
176
+ const sourceScene = contract.sourceScene ?? parentSourceScene;
177
+ return [
178
+ name,
179
+ sourceScene === childSourceScene
180
+ ? contract
181
+ : { ...contract, sourceScene },
182
+ ];
183
+ })),
184
+ events: parent.events,
185
+ slots: parent.slots,
186
+ };
187
+ }
188
+ function hasPublicContract(props, events, slots, parts) {
189
+ return Object.keys(props).length > 0
190
+ || Object.keys(events).length > 0
191
+ || Object.keys(slots).length > 0
192
+ || Object.keys(parts).length > 0;
193
+ }
194
+ function collectNamedImports(sourceFile, sourceId) {
195
+ const imports = new Map();
196
+ for (const statement of sourceFile.statements) {
197
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
198
+ continue;
199
+ }
200
+ const moduleSpecifier = statement.moduleSpecifier.text;
201
+ if (!moduleSpecifier.startsWith('.')) {
202
+ continue;
203
+ }
204
+ const namedBindings = statement.importClause?.namedBindings;
205
+ if (!namedBindings || !ts.isNamedImports(namedBindings)) {
206
+ continue;
207
+ }
208
+ const targetSource = resolveScriptSourceId(sourceId, moduleSpecifier);
209
+ for (const element of namedBindings.elements) {
210
+ imports.set(element.name.text, targetSource);
211
+ }
212
+ }
213
+ return imports;
214
+ }
215
+ function normalizeScriptSourceId(value) {
216
+ const normalized = normalizePosixPath(value);
217
+ return hasScriptExtension(normalized) ? normalized : `${normalized}.ts`;
218
+ }
219
+ function resolveScriptSourceId(fromSourceId, moduleSpecifier) {
220
+ const dirname = posixDirname(fromSourceId);
221
+ return normalizeScriptSourceId(normalizePosixPath(`${dirname}/${moduleSpecifier}`));
222
+ }
223
+ function hasScriptExtension(value) {
224
+ return /\.[cm]?[tj]sx?$/.test(value);
225
+ }
226
+ function posixDirname(value) {
227
+ const normalized = normalizePosixPath(value);
228
+ const index = normalized.lastIndexOf('/');
229
+ return index >= 0 ? normalized.slice(0, index) : '';
230
+ }
231
+ function normalizePosixPath(value) {
232
+ const segments = [];
233
+ for (const segment of value.replaceAll('\\', '/').split('/')) {
234
+ if (!segment || segment === '.') {
235
+ continue;
236
+ }
237
+ if (segment === '..') {
238
+ if (segments.length && segments[segments.length - 1] !== '..') {
239
+ segments.pop();
240
+ }
241
+ else {
242
+ segments.push(segment);
243
+ }
244
+ continue;
245
+ }
246
+ segments.push(segment);
247
+ }
248
+ return segments.join('/');
249
+ }
58
250
  function hasSceneDecorator(node) {
59
251
  const decorator = decorators(node).find((item) => decoratorName(item) === 'scene');
60
252
  if (!decorator) {
@@ -66,6 +258,20 @@ function hasSceneDecorator(node) {
66
258
  }
67
259
  return true;
68
260
  }
261
+ function parentClassNameOf(node) {
262
+ const heritage = node.heritageClauses?.find((item) => item.token === ts.SyntaxKind.ExtendsKeyword);
263
+ const parent = heritage?.types[0]?.expression;
264
+ if (!parent) {
265
+ return undefined;
266
+ }
267
+ if (ts.isIdentifier(parent)) {
268
+ return parent.text;
269
+ }
270
+ if (ts.isPropertyAccessExpression(parent)) {
271
+ return parent.name.text;
272
+ }
273
+ return undefined;
274
+ }
69
275
  function memberDecoratorOptions(member, name) {
70
276
  const decorator = decorators(member).find((item) => decoratorName(item) === name);
71
277
  if (!decorator) {
@@ -14,6 +14,7 @@ export interface SceneTemplateStructFieldContract {
14
14
  export interface SceneTemplateStructPropContract {
15
15
  type: 'struct';
16
16
  struct: string;
17
+ sourceScene?: string;
17
18
  fields: Record<string, SceneTemplateStructFieldContract>;
18
19
  }
19
20
  export type SceneTemplatePropContract = SceneTemplatePrimitivePropContract | SceneTemplateStructPropContract;
@@ -56,6 +57,7 @@ export interface SlotOutletTemplateNode {
56
57
  name: string;
57
58
  }
58
59
  export interface SceneTemplateScriptImport {
60
+ scene?: string;
59
61
  exportName: string;
60
62
  localName: string;
61
63
  source: string;
@@ -1,4 +1,5 @@
1
1
  import { pixiSceneFieldSchema } from './pixiNodeSchema.js';
2
+ import { builtinSceneAssetId, isBuiltinSceneName } from './builtinScenes.js';
2
3
  const pixiTypes = new Set([
3
4
  'Container',
4
5
  'Sprite',
@@ -61,7 +62,7 @@ function parseTemplateNode(element) {
61
62
  };
62
63
  return node;
63
64
  }
64
- if (element.attributes.scene) {
65
+ if (element.attributes.scene !== undefined || isBuiltinSceneName(element.name)) {
65
66
  const slots = {};
66
67
  for (const child of element.children) {
67
68
  const slot = child.attributes.slot || 'default';
@@ -71,7 +72,7 @@ function parseTemplateNode(element) {
71
72
  const node = {
72
73
  kind: 'sceneInstance',
73
74
  type: element.name,
74
- scene: element.attributes.scene,
75
+ scene: element.attributes.scene !== undefined ? element.attributes.scene : builtinSceneAssetId(element.name),
75
76
  props: parseProps(element, ['id', 'scene', 'slot'], isEventAttribute),
76
77
  events: parseEvents(element),
77
78
  slots,
@@ -79,7 +80,7 @@ function parseTemplateNode(element) {
79
80
  };
80
81
  return node;
81
82
  }
82
- throw new Error(`Unsupported template tag <${element.name}>.`);
83
+ throw new Error(`Unsupported template tag <${element.name}>. Add scene="..." or use a built-in Scene name.`);
83
84
  }
84
85
  function parseProps(element, omitted, shouldOmit = () => false) {
85
86
  const props = {};
@@ -1,3 +1,4 @@
1
+ import { isBuiltinSceneAssetId } from './builtinScenes.js';
1
2
  const colorPropNames = new Set([
2
3
  'backgroundColor',
3
4
  'borderColor',
@@ -40,7 +41,7 @@ function serializeTemplateNode(node, depth, extraAttributes = []) {
40
41
  }
41
42
  const attributes = [
42
43
  ...(node.id ? [['id', node.id]] : []),
43
- ['scene', node.scene],
44
+ ...(isBuiltinSceneAssetId(node.scene) ? [] : [['scene', node.scene]]),
44
45
  ...extraAttributes,
45
46
  ...Object.entries(node.props),
46
47
  ...Object.entries(node.events).map(([name, value]) => [`@${name}`, value]),