@scenoco-three/core 0.1.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/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/components/Follow.d.ts +15 -0
- package/dist/components/Follow.js +41 -0
- package/dist/components/LookAt.d.ts +11 -0
- package/dist/components/LookAt.js +30 -0
- package/dist/components/Oscillator.d.ts +13 -0
- package/dist/components/Oscillator.js +42 -0
- package/dist/components/Rotator.d.ts +5 -0
- package/dist/components/Rotator.js +21 -0
- package/dist/core/Component.d.ts +60 -0
- package/dist/core/Component.js +63 -0
- package/dist/core/Engine.d.ts +169 -0
- package/dist/core/Engine.js +468 -0
- package/dist/core/NodeRegistry.d.ts +99 -0
- package/dist/core/NodeRegistry.js +87 -0
- package/dist/core/Registry.d.ts +55 -0
- package/dist/core/Registry.js +28 -0
- package/dist/core/System.d.ts +33 -0
- package/dist/core/System.js +23 -0
- package/dist/core/decorators.d.ts +50 -0
- package/dist/core/decorators.js +142 -0
- package/dist/dsl/PrefabRef.d.ts +36 -0
- package/dist/dsl/PrefabRef.js +33 -0
- package/dist/dsl/bundle-format.d.ts +74 -0
- package/dist/dsl/bundle-format.js +41 -0
- package/dist/dsl/bundle-shared.d.ts +18 -0
- package/dist/dsl/bundle-shared.js +33 -0
- package/dist/dsl/compiled.d.ts +64 -0
- package/dist/dsl/compiled.js +1 -0
- package/dist/dsl/deserialize.d.ts +9 -0
- package/dist/dsl/deserialize.js +125 -0
- package/dist/dsl/instantiate.d.ts +40 -0
- package/dist/dsl/instantiate.js +111 -0
- package/dist/dsl/nodes.d.ts +1 -0
- package/dist/dsl/nodes.js +10 -0
- package/dist/dsl/types.d.ts +45 -0
- package/dist/dsl/types.js +120 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +17 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +7 -0
- package/dist/nodes/base.d.ts +31 -0
- package/dist/nodes/base.js +27 -0
- package/dist/nodes/geometry/BoxGeometry.d.ts +13 -0
- package/dist/nodes/geometry/BoxGeometry.js +44 -0
- package/dist/nodes/geometry/CylinderGeometry.d.ts +11 -0
- package/dist/nodes/geometry/CylinderGeometry.js +36 -0
- package/dist/nodes/geometry/PlaneGeometry.d.ts +11 -0
- package/dist/nodes/geometry/PlaneGeometry.js +36 -0
- package/dist/nodes/geometry/SphereGeometry.d.ts +10 -0
- package/dist/nodes/geometry/SphereGeometry.js +32 -0
- package/dist/nodes/geometry/TorusGeometry.d.ts +11 -0
- package/dist/nodes/geometry/TorusGeometry.js +36 -0
- package/dist/nodes/index.d.ts +36 -0
- package/dist/nodes/index.js +46 -0
- package/dist/nodes/manifest.d.ts +18 -0
- package/dist/nodes/manifest.js +64 -0
- package/dist/nodes/material/LineBasicMaterial.d.ts +11 -0
- package/dist/nodes/material/LineBasicMaterial.js +41 -0
- package/dist/nodes/material/MeshBasicMaterial.d.ts +11 -0
- package/dist/nodes/material/MeshBasicMaterial.js +41 -0
- package/dist/nodes/material/MeshStandardMaterial.d.ts +14 -0
- package/dist/nodes/material/MeshStandardMaterial.js +56 -0
- package/dist/nodes/material/PointsMaterial.d.ts +12 -0
- package/dist/nodes/material/PointsMaterial.js +46 -0
- package/dist/nodes/material/SpriteMaterial.d.ts +11 -0
- package/dist/nodes/material/SpriteMaterial.js +41 -0
- package/dist/nodes/object3d/AmbientLight.d.ts +7 -0
- package/dist/nodes/object3d/AmbientLight.js +26 -0
- package/dist/nodes/object3d/ArrayCamera.d.ts +5 -0
- package/dist/nodes/object3d/ArrayCamera.js +20 -0
- package/dist/nodes/object3d/BatchedMesh.d.ts +7 -0
- package/dist/nodes/object3d/BatchedMesh.js +31 -0
- package/dist/nodes/object3d/Bone.d.ts +5 -0
- package/dist/nodes/object3d/Bone.js +18 -0
- package/dist/nodes/object3d/DirectionalLight.d.ts +7 -0
- package/dist/nodes/object3d/DirectionalLight.js +29 -0
- package/dist/nodes/object3d/Group.d.ts +5 -0
- package/dist/nodes/object3d/Group.js +18 -0
- package/dist/nodes/object3d/HemisphereLight.d.ts +8 -0
- package/dist/nodes/object3d/HemisphereLight.js +30 -0
- package/dist/nodes/object3d/InstancedMesh.d.ts +6 -0
- package/dist/nodes/object3d/InstancedMesh.js +28 -0
- package/dist/nodes/object3d/LOD.d.ts +5 -0
- package/dist/nodes/object3d/LOD.js +20 -0
- package/dist/nodes/object3d/Line.d.ts +5 -0
- package/dist/nodes/object3d/Line.js +21 -0
- package/dist/nodes/object3d/LineLoop.d.ts +5 -0
- package/dist/nodes/object3d/LineLoop.js +21 -0
- package/dist/nodes/object3d/LineSegments.d.ts +5 -0
- package/dist/nodes/object3d/LineSegments.js +21 -0
- package/dist/nodes/object3d/Mesh.d.ts +5 -0
- package/dist/nodes/object3d/Mesh.js +21 -0
- package/dist/nodes/object3d/Object3D.d.ts +5 -0
- package/dist/nodes/object3d/Object3D.js +18 -0
- package/dist/nodes/object3d/OrthographicCamera.d.ts +11 -0
- package/dist/nodes/object3d/OrthographicCamera.js +42 -0
- package/dist/nodes/object3d/PerspectiveCamera.d.ts +8 -0
- package/dist/nodes/object3d/PerspectiveCamera.js +31 -0
- package/dist/nodes/object3d/PointLight.d.ts +9 -0
- package/dist/nodes/object3d/PointLight.js +34 -0
- package/dist/nodes/object3d/Points.d.ts +5 -0
- package/dist/nodes/object3d/Points.js +21 -0
- package/dist/nodes/object3d/RectAreaLight.d.ts +9 -0
- package/dist/nodes/object3d/RectAreaLight.js +34 -0
- package/dist/nodes/object3d/Scene.d.ts +5 -0
- package/dist/nodes/object3d/Scene.js +22 -0
- package/dist/nodes/object3d/SkinnedMesh.d.ts +5 -0
- package/dist/nodes/object3d/SkinnedMesh.js +23 -0
- package/dist/nodes/object3d/SpotLight.d.ts +11 -0
- package/dist/nodes/object3d/SpotLight.js +42 -0
- package/dist/nodes/object3d/Sprite.d.ts +5 -0
- package/dist/nodes/object3d/Sprite.js +21 -0
- package/dist/nodes/setting/Background.d.ts +6 -0
- package/dist/nodes/setting/Background.js +22 -0
- package/dist/nodes/setting/Fog.d.ts +8 -0
- package/dist/nodes/setting/Fog.js +30 -0
- package/dist/nodes/setting/FogExp2.d.ts +7 -0
- package/dist/nodes/setting/FogExp2.js +27 -0
- package/dist/nodes/targets.d.ts +5 -0
- package/dist/nodes/targets.js +5 -0
- package/package.json +42 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Component } from './Component.js';
|
|
2
|
+
export type ComponentCtor = new () => Component;
|
|
3
|
+
/**
|
|
4
|
+
* Primitive/math property wire types. Each maps to a canonical XML attribute
|
|
5
|
+
* format (defined in dsl/types.ts) and an XSD encoding:
|
|
6
|
+
* float/int/bool/string — native XSD types
|
|
7
|
+
* vec2/vec3 — space-separated numbers, A-Frame/X3D style: "0 2.5 -5"
|
|
8
|
+
* euler — degrees, space-separated: "0 90 0"
|
|
9
|
+
* color — "#rrggbb" or a CSS color name
|
|
10
|
+
* node — Object3D reference by scene path: "Turbine_01/Blade"
|
|
11
|
+
* prefab — a `.prefab.xml` file reference, resolved at build time to an
|
|
12
|
+
* embedded prefab bundle; the field receives a spawnable PrefabRef
|
|
13
|
+
*
|
|
14
|
+
* A property may instead be typed with a Component class (Unity/Cocos-style
|
|
15
|
+
* typed reference); its XML value is a node path resolved to that node's
|
|
16
|
+
* component of the declared type.
|
|
17
|
+
*/
|
|
18
|
+
export type PropType = 'float' | 'int' | 'bool' | 'string' | 'vec2' | 'vec3' | 'euler' | 'color' | 'node' | 'prefab';
|
|
19
|
+
export interface PropertyMeta {
|
|
20
|
+
type: PropType | ComponentCtor;
|
|
21
|
+
/**
|
|
22
|
+
* Default in compiled form (number/boolean/string, or number[] for vec/euler/
|
|
23
|
+
* color-as-array). Used for XSD/docs generation and bundle default-elision.
|
|
24
|
+
*/
|
|
25
|
+
default?: number | boolean | string | number[];
|
|
26
|
+
required?: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Scalar property types parse from text. Reference kinds — `'node'`, `'prefab'`,
|
|
31
|
+
* and Component classes — are resolved against the scene, not parsed as scalars.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isScalarType(t: PropertyMeta['type']): t is Exclude<PropType, 'node' | 'prefab'>;
|
|
34
|
+
export interface ComponentMeta {
|
|
35
|
+
/** XML tag name inside <Components>. */
|
|
36
|
+
name: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
/** Attribute name → metadata, in declaration order (subclass first, then inherited). */
|
|
39
|
+
properties: Map<string, PropertyMeta>;
|
|
40
|
+
}
|
|
41
|
+
export interface RegisteredComponent {
|
|
42
|
+
meta: ComponentMeta;
|
|
43
|
+
ctor: ComponentCtor;
|
|
44
|
+
}
|
|
45
|
+
declare class EngineRegistry {
|
|
46
|
+
private readonly components;
|
|
47
|
+
registerComponent(meta: ComponentMeta, ctor: ComponentCtor): void;
|
|
48
|
+
/** Mainly for tests and future hot-reload support. */
|
|
49
|
+
unregisterComponent(name: string): boolean;
|
|
50
|
+
getComponent(name: string): RegisteredComponent | undefined;
|
|
51
|
+
componentNames(): string[];
|
|
52
|
+
}
|
|
53
|
+
/** Global registry indexing all available components (and, later, node tags). */
|
|
54
|
+
export declare const Registry: EngineRegistry;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar property types parse from text. Reference kinds — `'node'`, `'prefab'`,
|
|
3
|
+
* and Component classes — are resolved against the scene, not parsed as scalars.
|
|
4
|
+
*/
|
|
5
|
+
export function isScalarType(t) {
|
|
6
|
+
return typeof t === 'string' && t !== 'node' && t !== 'prefab';
|
|
7
|
+
}
|
|
8
|
+
class EngineRegistry {
|
|
9
|
+
components = new Map();
|
|
10
|
+
registerComponent(meta, ctor) {
|
|
11
|
+
if (this.components.has(meta.name)) {
|
|
12
|
+
throw new Error(`Component "${meta.name}" is already registered`);
|
|
13
|
+
}
|
|
14
|
+
this.components.set(meta.name, { meta, ctor });
|
|
15
|
+
}
|
|
16
|
+
/** Mainly for tests and future hot-reload support. */
|
|
17
|
+
unregisterComponent(name) {
|
|
18
|
+
return this.components.delete(name);
|
|
19
|
+
}
|
|
20
|
+
getComponent(name) {
|
|
21
|
+
return this.components.get(name);
|
|
22
|
+
}
|
|
23
|
+
componentNames() {
|
|
24
|
+
return [...this.components.keys()];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Global registry indexing all available components (and, later, node tags). */
|
|
28
|
+
export const Registry = new EngineRegistry();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Component } from './Component.js';
|
|
2
|
+
import type { Engine } from './Engine.js';
|
|
3
|
+
/**
|
|
4
|
+
* An ECS-hybrid system: registered with `@system(SomeComponent)`, the engine
|
|
5
|
+
* auto-runs it while the loaded scene contains at least one component of that
|
|
6
|
+
* type and keeps `this.components` populated with the live set. The system owns
|
|
7
|
+
* the behaviour; the components are plain data (they keep their own lifecycle —
|
|
8
|
+
* this is additive, à la Unity DOTS hybrid).
|
|
9
|
+
*
|
|
10
|
+
* @system(RigidBody)
|
|
11
|
+
* class PhysicsSystem extends System<RigidBody> {
|
|
12
|
+
* override fixedStep(dt) { for (const rb of this.components) { … } }
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* The engine drives it alongside component lifecycles: `fixedStep` runs inside
|
|
16
|
+
* the fixed-timestep loop (after components' `fixedUpdate`), `step` once per
|
|
17
|
+
* frame (after `update`). Config comes from a singleton attachment, reachable
|
|
18
|
+
* via `this.engine.findAttachment(SomeAttachment)`.
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class System<T extends Component = Component> {
|
|
21
|
+
/** Owning engine. Assigned before onAttach. */
|
|
22
|
+
engine: Engine;
|
|
23
|
+
/** The live set of components of this system's type — maintained by the engine. */
|
|
24
|
+
readonly components: readonly T[];
|
|
25
|
+
/** Once, when the engine starts running the system (its component type appeared). */
|
|
26
|
+
onAttach?(): void;
|
|
27
|
+
/** Fixed-rate step, for deterministic simulation (physics world step, …). */
|
|
28
|
+
fixedStep?(fixedDt: number): void;
|
|
29
|
+
/** Variable-rate step, once per frame. */
|
|
30
|
+
step?(dt: number): void;
|
|
31
|
+
/** Once, when the system stops (scene unload / dispose). */
|
|
32
|
+
onDetach?(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ECS-hybrid system: registered with `@system(SomeComponent)`, the engine
|
|
3
|
+
* auto-runs it while the loaded scene contains at least one component of that
|
|
4
|
+
* type and keeps `this.components` populated with the live set. The system owns
|
|
5
|
+
* the behaviour; the components are plain data (they keep their own lifecycle —
|
|
6
|
+
* this is additive, à la Unity DOTS hybrid).
|
|
7
|
+
*
|
|
8
|
+
* @system(RigidBody)
|
|
9
|
+
* class PhysicsSystem extends System<RigidBody> {
|
|
10
|
+
* override fixedStep(dt) { for (const rb of this.components) { … } }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The engine drives it alongside component lifecycles: `fixedStep` runs inside
|
|
14
|
+
* the fixed-timestep loop (after components' `fixedUpdate`), `step` once per
|
|
15
|
+
* frame (after `update`). Config comes from a singleton attachment, reachable
|
|
16
|
+
* via `this.engine.findAttachment(SomeAttachment)`.
|
|
17
|
+
*/
|
|
18
|
+
export class System {
|
|
19
|
+
/** Owning engine. Assigned before onAttach. */
|
|
20
|
+
engine;
|
|
21
|
+
/** The live set of components of this system's type — maintained by the engine. */
|
|
22
|
+
components = [];
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
import { type PropertyMeta } from './Registry.js';
|
|
3
|
+
import type { ComponentCtor } from './Registry.js';
|
|
4
|
+
import { type AttachContext, type AttachTarget, type ChildRules } from './NodeRegistry.js';
|
|
5
|
+
export interface ComponentOptions {
|
|
6
|
+
/** XML tag name inside <Components>. */
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Declares an XML-exposed property on a field (Cocos `@property` style). Used by
|
|
12
|
+
* components, nodes, and attachments. Two forms:
|
|
13
|
+
* @property({ type: 'float', default: 1 }) — primitive/math property
|
|
14
|
+
* @property(SomeComponent) — typed reference (components only)
|
|
15
|
+
*/
|
|
16
|
+
export declare function property(meta: PropertyMeta | ComponentCtor): (proto: object, key: string | symbol) => void;
|
|
17
|
+
/** Registers a Component subclass under its XML tag name (Cocos `@ccclass` style). */
|
|
18
|
+
export declare function component(options: ComponentOptions): <T extends ComponentCtor>(target: T) => void;
|
|
19
|
+
export interface NodeOptions {
|
|
20
|
+
/** XML element name (mirrors the Three.js class). */
|
|
21
|
+
name: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
/** Override the default child rules (object3d children + <Components>). */
|
|
24
|
+
children?: Partial<ChildRules>;
|
|
25
|
+
/** `'scene'` only for the root; everything else (the default) is `'object3d'`. */
|
|
26
|
+
kind?: 'scene' | 'object3d';
|
|
27
|
+
}
|
|
28
|
+
/** Registers an Object3D-family node tag (lights, cameras, meshes, groups, …). */
|
|
29
|
+
export declare function node(options: NodeOptions): (target: new () => {
|
|
30
|
+
build(): Object3D;
|
|
31
|
+
}) => void;
|
|
32
|
+
export interface AttachmentOptions {
|
|
33
|
+
/** XML element name. */
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Node(s) this attaches to: an Object3D class (subclass-aware) or a node tag. */
|
|
37
|
+
target: AttachTarget;
|
|
38
|
+
/** Cardinality bucket on the target (e.g. 'geometry', 'material', 'fog'). */
|
|
39
|
+
group: string;
|
|
40
|
+
/** The target needs at least one attachment of this group. Default false. */
|
|
41
|
+
required?: boolean;
|
|
42
|
+
/** At most one attachment of this group per target. Default true. */
|
|
43
|
+
unique?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Registers an attachment tag — a leaf that configures its target node. */
|
|
46
|
+
export declare function attachment(options: AttachmentOptions): (target: new () => {
|
|
47
|
+
attach(ctx: AttachContext): void;
|
|
48
|
+
}) => void;
|
|
49
|
+
/** Registers an ECS-hybrid system that processes all components of `componentType`. */
|
|
50
|
+
export declare function system(componentType: abstract new (...args: never[]) => object): (target: new () => unknown) => void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Registry } from './Registry.js';
|
|
2
|
+
import { registerAttachment, registerNode, registerSystem, spatialChildren, } from './NodeRegistry.js';
|
|
3
|
+
// SceNoCo uses legacy (experimentalDecorators) decorators, like Cocos Creator.
|
|
4
|
+
// Rationale: as of 2026 the TC39 standard decorators are still not lowered by
|
|
5
|
+
// oxc (Vite 8 / Rolldown), so a library shipping them breaks consumers' builds.
|
|
6
|
+
//
|
|
7
|
+
// One authoring idiom for every registered, schema-bearing thing — a class with
|
|
8
|
+
// `@property` fields plus a class decorator declaring its role:
|
|
9
|
+
// @component — behaviour on a node (lifecycle, MonoBehaviour-style)
|
|
10
|
+
// @node — a scene-graph Object3D tag
|
|
11
|
+
// @attachment(target) — a leaf that configures a node (geometry/material/setting)
|
|
12
|
+
// @system(Component) — an ECS-hybrid batch processor for a component type
|
|
13
|
+
/** Key under which @property accumulates metadata on the constructor. */
|
|
14
|
+
const PROPS = Symbol.for('scenoco:properties');
|
|
15
|
+
/**
|
|
16
|
+
* Declares an XML-exposed property on a field (Cocos `@property` style). Used by
|
|
17
|
+
* components, nodes, and attachments. Two forms:
|
|
18
|
+
* @property({ type: 'float', default: 1 }) — primitive/math property
|
|
19
|
+
* @property(SomeComponent) — typed reference (components only)
|
|
20
|
+
*/
|
|
21
|
+
export function property(meta) {
|
|
22
|
+
const normalized = typeof meta === 'function' ? { type: meta } : meta;
|
|
23
|
+
return function (proto, key) {
|
|
24
|
+
if (typeof proto === 'function') {
|
|
25
|
+
throw new Error(`@property cannot decorate static field "${String(key)}"`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof key !== 'string') {
|
|
28
|
+
throw new Error('@property fields must have string (non-symbol) names');
|
|
29
|
+
}
|
|
30
|
+
const ctor = proto.constructor;
|
|
31
|
+
let bag = ctor[PROPS];
|
|
32
|
+
if (!Object.hasOwn(ctor, PROPS)) {
|
|
33
|
+
bag = Object.create(bag ?? null);
|
|
34
|
+
Object.defineProperty(ctor, PROPS, { value: bag, configurable: true });
|
|
35
|
+
}
|
|
36
|
+
bag[key] = normalized;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Collect every @property (own + inherited) declared on a class, in order. */
|
|
40
|
+
function collectProperties(target) {
|
|
41
|
+
const bag = target[PROPS];
|
|
42
|
+
const properties = new Map();
|
|
43
|
+
for (const key in bag)
|
|
44
|
+
properties.set(key, bag[key]);
|
|
45
|
+
return properties;
|
|
46
|
+
}
|
|
47
|
+
/** Registers a Component subclass under its XML tag name (Cocos `@ccclass` style). */
|
|
48
|
+
export function component(options) {
|
|
49
|
+
return function (target) {
|
|
50
|
+
assertTagName(options.name);
|
|
51
|
+
Registry.registerComponent({ name: options.name, description: options.description, properties: collectProperties(target) }, target);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Registers an Object3D-family node tag (lights, cameras, meshes, groups, …). */
|
|
55
|
+
export function node(options) {
|
|
56
|
+
return function (target) {
|
|
57
|
+
const { attrs, names } = readAttrs(target, options.name);
|
|
58
|
+
const build = (a) => {
|
|
59
|
+
const inst = new target();
|
|
60
|
+
assign(inst, names, a);
|
|
61
|
+
return inst.build();
|
|
62
|
+
};
|
|
63
|
+
// Probe the produced Three.js class (for attachment target matching). Build
|
|
64
|
+
// with no attrs so fields keep their initializers.
|
|
65
|
+
let produces;
|
|
66
|
+
try {
|
|
67
|
+
produces = build({}).constructor;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
produces = undefined;
|
|
71
|
+
}
|
|
72
|
+
registerNode({
|
|
73
|
+
tag: options.name,
|
|
74
|
+
kind: options.kind ?? 'object3d',
|
|
75
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
76
|
+
attrs,
|
|
77
|
+
children: spatialChildren(options.children),
|
|
78
|
+
build,
|
|
79
|
+
...(produces ? { produces } : {}),
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Registers an attachment tag — a leaf that configures its target node. */
|
|
84
|
+
export function attachment(options) {
|
|
85
|
+
return function (target) {
|
|
86
|
+
const { attrs, names } = readAttrs(target, options.name);
|
|
87
|
+
registerAttachment({
|
|
88
|
+
tag: options.name,
|
|
89
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
90
|
+
attrs,
|
|
91
|
+
target: options.target,
|
|
92
|
+
group: options.group,
|
|
93
|
+
required: options.required ?? false,
|
|
94
|
+
unique: options.unique ?? true,
|
|
95
|
+
make: (a) => {
|
|
96
|
+
const inst = new target();
|
|
97
|
+
assign(inst, names, a);
|
|
98
|
+
return inst;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// ---- systems ----------------------------------------------------------------
|
|
104
|
+
/** Registers an ECS-hybrid system that processes all components of `componentType`. */
|
|
105
|
+
export function system(componentType) {
|
|
106
|
+
return function (target) {
|
|
107
|
+
registerSystem({ ctor: target, componentType });
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ---- shared internals -------------------------------------------------------
|
|
111
|
+
/** Read a tag's `@property` fields into the registry's NodeAttr shape. */
|
|
112
|
+
function readAttrs(target, tag) {
|
|
113
|
+
assertTagName(tag);
|
|
114
|
+
const props = collectProperties(target);
|
|
115
|
+
const attrs = {};
|
|
116
|
+
for (const [name, meta] of props) {
|
|
117
|
+
if (typeof meta.type !== 'string' || meta.type === 'node') {
|
|
118
|
+
throw new Error(`<${tag}> @property "${name}" must use a scalar type (float/int/bool/string/vec2/vec3/euler/color); references are for components only`);
|
|
119
|
+
}
|
|
120
|
+
attrs[name] = {
|
|
121
|
+
type: meta.type,
|
|
122
|
+
...(meta.default !== undefined ? { default: meta.default } : {}),
|
|
123
|
+
...(meta.required ? { required: true } : {}),
|
|
124
|
+
...(meta.description !== undefined ? { description: meta.description } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { attrs, names: [...props.keys()] };
|
|
128
|
+
}
|
|
129
|
+
/** Assign compiled attr values onto a fresh instance, skipping undefined (keeps initializers). */
|
|
130
|
+
function assign(inst, names, attrs) {
|
|
131
|
+
const target = inst;
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
const v = attrs[name];
|
|
134
|
+
if (v !== undefined)
|
|
135
|
+
target[name] = v;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function assertTagName(name) {
|
|
139
|
+
if (name.includes('/')) {
|
|
140
|
+
throw new Error(`Tag/component name "${name}" may not contain "/" (reserved for prefab scope paths)`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
import type { SceneInstance } from '../core/Engine.js';
|
|
3
|
+
import type { CompiledScene } from './compiled.js';
|
|
4
|
+
/** How a prefab is instantiated — the Engine supplies this when wiring props. */
|
|
5
|
+
export type SpawnFn = (compiled: CompiledScene, parent?: Object3D) => SceneInstance;
|
|
6
|
+
export interface SpawnOptions {
|
|
7
|
+
/** Parent for the spawned subtree. Defaults to the owning component's node. */
|
|
8
|
+
parent?: Object3D;
|
|
9
|
+
/** Local position to apply to the spawned root after spawning. */
|
|
10
|
+
position?: [number, number, number];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The value a `@property({ type: 'prefab' })` field receives: a handle that
|
|
14
|
+
* spawns the build-time-embedded prefab at runtime — Unity/Cocos `Instantiate`,
|
|
15
|
+
* without the component touching the bundle/instantiate machinery.
|
|
16
|
+
*
|
|
17
|
+
* @property({ type: 'prefab' }) brick: PrefabRef | null = null;
|
|
18
|
+
* // …
|
|
19
|
+
* const instance = this.brick!.spawn({ position: [x, y, 0] });
|
|
20
|
+
* // later: instance.destroy();
|
|
21
|
+
*
|
|
22
|
+
* Spawned instances are owned by the engine; destroy them yourself (or let the
|
|
23
|
+
* owning component destroy them in onDestroy) — they are *not* torn down by the
|
|
24
|
+
* scene unload that removes the spawning component's subtree.
|
|
25
|
+
*/
|
|
26
|
+
export declare class PrefabRef {
|
|
27
|
+
private readonly compiled;
|
|
28
|
+
private readonly spawnFn;
|
|
29
|
+
/** Default parent — the node of the component that holds this reference. */
|
|
30
|
+
private readonly defaultParent?;
|
|
31
|
+
constructor(compiled: CompiledScene, spawnFn: SpawnFn,
|
|
32
|
+
/** Default parent — the node of the component that holds this reference. */
|
|
33
|
+
defaultParent?: Object3D | undefined);
|
|
34
|
+
/** Spawn one instance of the prefab. Returns its handle (call `.destroy()` to remove). */
|
|
35
|
+
spawn(options?: SpawnOptions): SceneInstance;
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value a `@property({ type: 'prefab' })` field receives: a handle that
|
|
3
|
+
* spawns the build-time-embedded prefab at runtime — Unity/Cocos `Instantiate`,
|
|
4
|
+
* without the component touching the bundle/instantiate machinery.
|
|
5
|
+
*
|
|
6
|
+
* @property({ type: 'prefab' }) brick: PrefabRef | null = null;
|
|
7
|
+
* // …
|
|
8
|
+
* const instance = this.brick!.spawn({ position: [x, y, 0] });
|
|
9
|
+
* // later: instance.destroy();
|
|
10
|
+
*
|
|
11
|
+
* Spawned instances are owned by the engine; destroy them yourself (or let the
|
|
12
|
+
* owning component destroy them in onDestroy) — they are *not* torn down by the
|
|
13
|
+
* scene unload that removes the spawning component's subtree.
|
|
14
|
+
*/
|
|
15
|
+
export class PrefabRef {
|
|
16
|
+
compiled;
|
|
17
|
+
spawnFn;
|
|
18
|
+
defaultParent;
|
|
19
|
+
constructor(compiled, spawnFn,
|
|
20
|
+
/** Default parent — the node of the component that holds this reference. */
|
|
21
|
+
defaultParent) {
|
|
22
|
+
this.compiled = compiled;
|
|
23
|
+
this.spawnFn = spawnFn;
|
|
24
|
+
this.defaultParent = defaultParent;
|
|
25
|
+
}
|
|
26
|
+
/** Spawn one instance of the prefab. Returns its handle (call `.destroy()` to remove). */
|
|
27
|
+
spawn(options = {}) {
|
|
28
|
+
const instance = this.spawnFn(this.compiled, options.parent ?? this.defaultParent);
|
|
29
|
+
if (options.position)
|
|
30
|
+
instance.root.position.fromArray(options.position);
|
|
31
|
+
return instance;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SceNoCo scene bundle — the optimized, XML-free format the runtime loads.
|
|
3
|
+
*
|
|
4
|
+
* Design follows the Cocos Creator compiled-bundle principles, adapted to the
|
|
5
|
+
* fact that SceNoCo's type system is *known to the runtime* (the registry), so
|
|
6
|
+
* we never ship class/schema definitions — only a string pool and per-instance
|
|
7
|
+
* data. Optimizations applied:
|
|
8
|
+
*
|
|
9
|
+
* - **Fixed-index arrays**, not keyed objects (no key strings on the wire).
|
|
10
|
+
* - **Frequency-sorted string pool**: tags, ids, and string values are interned
|
|
11
|
+
* once; instances reference them by integer (small ints in any varint codec).
|
|
12
|
+
* - **Default-value elision via bitmask**: only attributes that differ from the
|
|
13
|
+
* registry default are stored; a per-tag mask says which. A node at defaults
|
|
14
|
+
* carries almost no data.
|
|
15
|
+
* - **TRS special-case**: an identity transform is omitted entirely.
|
|
16
|
+
* - **Bitwise-NOT reference encoding**: a reference prop stores `i` for
|
|
17
|
+
* component[i] or `~i` for node[i] — one integer, no wrapper (unambiguous
|
|
18
|
+
* because the registry knows which props are references).
|
|
19
|
+
*
|
|
20
|
+
* v1 is JSON; the array-positional shape makes a later msgpack/binary wrapper a
|
|
21
|
+
* drop-in. The decoder lives in the runtime; the encoder in the compiler.
|
|
22
|
+
*/
|
|
23
|
+
import type { CompiledScalar } from './types.js';
|
|
24
|
+
/** A JSON-native wire value used positionally inside bundle records. */
|
|
25
|
+
export type BundleValue = CompiledScalar;
|
|
26
|
+
/**
|
|
27
|
+
* What a bundle is for: a `scene` is loaded as the whole scene
|
|
28
|
+
* (`engine.loadScene`); a `prefab` is a reusable subtree spawned at runtime
|
|
29
|
+
* (`engine.instantiate`). Absent (older 3-element bundles) means `scene`.
|
|
30
|
+
*/
|
|
31
|
+
export type BundleKind = 'scene' | 'prefab';
|
|
32
|
+
export type Bundle = [version: number, strings: string[], scene: BundleAsset, kind?: BundleKind];
|
|
33
|
+
/** The bundle's kind, defaulting to `scene` for bundles that predate the marker. */
|
|
34
|
+
export declare function bundleKind(bundle: Bundle): BundleKind;
|
|
35
|
+
export type BundleAsset = [
|
|
36
|
+
nodes: BundleNode[],
|
|
37
|
+
/** Content-addressed attachment pool (geometry/material/fog/…). */
|
|
38
|
+
attachments: BundleTag[],
|
|
39
|
+
components: BundleComponent[],
|
|
40
|
+
root: number,
|
|
41
|
+
/**
|
|
42
|
+
* Embedded prefab sub-bundles, referenced by `prefab` props (one index each).
|
|
43
|
+
* Appended only when the scene uses a prefab prop, so prefab-free bundles keep
|
|
44
|
+
* their 4-element asset shape unchanged.
|
|
45
|
+
*/
|
|
46
|
+
prefabs?: Bundle[]
|
|
47
|
+
];
|
|
48
|
+
/** Attachment: `[tagStringIndex, attrMask, ...nonDefaultValues]`. */
|
|
49
|
+
export type BundleTag = [number, number, ...BundleValue[]];
|
|
50
|
+
/**
|
|
51
|
+
* Node: `[tagStringIndex, flags, attrMask, ...nonDefaultAttrValues, ...extras]`.
|
|
52
|
+
* `extras` appear in flag-bit order: id, transform, attachments, components,
|
|
53
|
+
* children (only those whose flag bit is set).
|
|
54
|
+
*/
|
|
55
|
+
export type BundleNode = BundleValue[];
|
|
56
|
+
/**
|
|
57
|
+
* Component: `[typeStringIndex, flags, propMask, ...propValues, id?]`.
|
|
58
|
+
* Only the `id` flag bit is used in `flags`; `propMask` selects which of the
|
|
59
|
+
* component's registered properties are present.
|
|
60
|
+
*/
|
|
61
|
+
export type BundleComponent = BundleValue[];
|
|
62
|
+
export declare const BUNDLE_VERSION = 3;
|
|
63
|
+
/** Node flag bits (presence of optional sections). */
|
|
64
|
+
export declare const NodeFlag: {
|
|
65
|
+
readonly Id: number;
|
|
66
|
+
readonly Transform: number;
|
|
67
|
+
readonly Attachments: number;
|
|
68
|
+
readonly Components: number;
|
|
69
|
+
readonly Children: number;
|
|
70
|
+
};
|
|
71
|
+
/** Component flag bits. */
|
|
72
|
+
export declare const ComponentFlag: {
|
|
73
|
+
readonly Id: number;
|
|
74
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SceNoCo scene bundle — the optimized, XML-free format the runtime loads.
|
|
3
|
+
*
|
|
4
|
+
* Design follows the Cocos Creator compiled-bundle principles, adapted to the
|
|
5
|
+
* fact that SceNoCo's type system is *known to the runtime* (the registry), so
|
|
6
|
+
* we never ship class/schema definitions — only a string pool and per-instance
|
|
7
|
+
* data. Optimizations applied:
|
|
8
|
+
*
|
|
9
|
+
* - **Fixed-index arrays**, not keyed objects (no key strings on the wire).
|
|
10
|
+
* - **Frequency-sorted string pool**: tags, ids, and string values are interned
|
|
11
|
+
* once; instances reference them by integer (small ints in any varint codec).
|
|
12
|
+
* - **Default-value elision via bitmask**: only attributes that differ from the
|
|
13
|
+
* registry default are stored; a per-tag mask says which. A node at defaults
|
|
14
|
+
* carries almost no data.
|
|
15
|
+
* - **TRS special-case**: an identity transform is omitted entirely.
|
|
16
|
+
* - **Bitwise-NOT reference encoding**: a reference prop stores `i` for
|
|
17
|
+
* component[i] or `~i` for node[i] — one integer, no wrapper (unambiguous
|
|
18
|
+
* because the registry knows which props are references).
|
|
19
|
+
*
|
|
20
|
+
* v1 is JSON; the array-positional shape makes a later msgpack/binary wrapper a
|
|
21
|
+
* drop-in. The decoder lives in the runtime; the encoder in the compiler.
|
|
22
|
+
*/
|
|
23
|
+
/** The bundle's kind, defaulting to `scene` for bundles that predate the marker. */
|
|
24
|
+
export function bundleKind(bundle) {
|
|
25
|
+
return bundle[3] ?? 'scene';
|
|
26
|
+
}
|
|
27
|
+
// v3 unifies the old geometries/materials/settings pools into one `attachments`
|
|
28
|
+
// pool, and a node references its attachments by an index list.
|
|
29
|
+
export const BUNDLE_VERSION = 3;
|
|
30
|
+
/** Node flag bits (presence of optional sections). */
|
|
31
|
+
export const NodeFlag = {
|
|
32
|
+
Id: 1 << 0,
|
|
33
|
+
Transform: 1 << 1,
|
|
34
|
+
Attachments: 1 << 2,
|
|
35
|
+
Components: 1 << 3,
|
|
36
|
+
Children: 1 << 4,
|
|
37
|
+
};
|
|
38
|
+
/** Component flag bits. */
|
|
39
|
+
export const ComponentFlag = {
|
|
40
|
+
Id: 1 << 0,
|
|
41
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PropertyMeta } from '../core/Registry.js';
|
|
2
|
+
import { type NodeAttr } from './nodes.js';
|
|
3
|
+
import type { CompiledScalar } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Schema helpers shared by the bundle encoder and decoder, so both agree on the
|
|
6
|
+
* attribute/property *order* (which the bitmasks index into) and on which
|
|
7
|
+
* values are interned strings. Both derive the schema from the same registries.
|
|
8
|
+
*/
|
|
9
|
+
/** Ordered [name, meta] of a node tag's type-specific attributes. */
|
|
10
|
+
export declare function nodeAttrEntries(tag: string): [string, NodeAttr][];
|
|
11
|
+
/** Ordered [name, meta] of an attachment tag's attributes. */
|
|
12
|
+
export declare function attachmentAttrEntries(tag: string): [string, NodeAttr][];
|
|
13
|
+
/** Ordered [name, meta] of a component type's properties. */
|
|
14
|
+
export declare function componentPropEntries(type: string): [string, PropertyMeta][];
|
|
15
|
+
/** Number of set bits — how many values a mask selects. */
|
|
16
|
+
export declare function popcount(n: number): number;
|
|
17
|
+
/** Structural equality for compiled scalar values (numbers, bools, vec arrays). */
|
|
18
|
+
export declare function valueEquals(a: CompiledScalar | undefined, b: CompiledScalar | undefined): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Registry } from '../core/Registry.js';
|
|
2
|
+
import { getAttachmentDef, getNodeDef } from './nodes.js';
|
|
3
|
+
/**
|
|
4
|
+
* Schema helpers shared by the bundle encoder and decoder, so both agree on the
|
|
5
|
+
* attribute/property *order* (which the bitmasks index into) and on which
|
|
6
|
+
* values are interned strings. Both derive the schema from the same registries.
|
|
7
|
+
*/
|
|
8
|
+
/** Ordered [name, meta] of a node tag's type-specific attributes. */
|
|
9
|
+
export function nodeAttrEntries(tag) {
|
|
10
|
+
return Object.entries(getNodeDef(tag).attrs);
|
|
11
|
+
}
|
|
12
|
+
/** Ordered [name, meta] of an attachment tag's attributes. */
|
|
13
|
+
export function attachmentAttrEntries(tag) {
|
|
14
|
+
return Object.entries(getAttachmentDef(tag).attrs);
|
|
15
|
+
}
|
|
16
|
+
/** Ordered [name, meta] of a component type's properties. */
|
|
17
|
+
export function componentPropEntries(type) {
|
|
18
|
+
return [...Registry.getComponent(type).meta.properties];
|
|
19
|
+
}
|
|
20
|
+
/** Number of set bits — how many values a mask selects. */
|
|
21
|
+
export function popcount(n) {
|
|
22
|
+
let count = 0;
|
|
23
|
+
for (let v = n >>> 0; v !== 0; v >>= 1)
|
|
24
|
+
count += v & 1;
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
/** Structural equality for compiled scalar values (numbers, bools, vec arrays). */
|
|
28
|
+
export function valueEquals(a, b) {
|
|
29
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
30
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
31
|
+
}
|
|
32
|
+
return a === b;
|
|
33
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CompiledScalar } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compiled scene: a flat, indexed structure (Cocos Creator scene-format style).
|
|
4
|
+
* All cross-references are integer indices, so the runtime instantiates with no
|
|
5
|
+
* name lookups or string parsing. This is the ergonomic intermediate the
|
|
6
|
+
* compiler emits and the bundle decoder reconstructs.
|
|
7
|
+
*
|
|
8
|
+
* These are **pure types with no runtime imports**, so the runtime can use them
|
|
9
|
+
* without pulling in the XML parser/validator (which live in compile.ts).
|
|
10
|
+
*/
|
|
11
|
+
export interface CompiledScene {
|
|
12
|
+
/** Index of the root <Scene> node (always 0). */
|
|
13
|
+
root: number;
|
|
14
|
+
nodes: CompiledNode[];
|
|
15
|
+
/** Content-addressed pool of attachments (geometry/material/fog/…), shared by index. */
|
|
16
|
+
attachments: CompiledAsset[];
|
|
17
|
+
components: CompiledComponent[];
|
|
18
|
+
/**
|
|
19
|
+
* Embedded prefab subtrees referenced by `@property({ type: 'prefab' })`
|
|
20
|
+
* props, one self-contained CompiledScene each (resolved + compiled at build
|
|
21
|
+
* time). A prefab prop stores `{ prefab: i }`; the runtime hands the component
|
|
22
|
+
* a PrefabRef bound to `prefabs[i]`. Present only when the scene uses one.
|
|
23
|
+
*/
|
|
24
|
+
prefabs?: CompiledScene[];
|
|
25
|
+
}
|
|
26
|
+
/** A resolved reference: an index into nodes[], components[], or prefabs[]. */
|
|
27
|
+
export type CompiledRef = {
|
|
28
|
+
node: number;
|
|
29
|
+
} | {
|
|
30
|
+
component: number;
|
|
31
|
+
} | {
|
|
32
|
+
prefab: number;
|
|
33
|
+
};
|
|
34
|
+
export type CompiledValue = CompiledScalar | CompiledRef;
|
|
35
|
+
export interface CompiledTransform {
|
|
36
|
+
position: number[];
|
|
37
|
+
rotation: number[];
|
|
38
|
+
scale: number[];
|
|
39
|
+
visible: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface CompiledNode {
|
|
42
|
+
tag: string;
|
|
43
|
+
/** Author-assigned id (maps to Object3D.name at runtime). */
|
|
44
|
+
id?: string;
|
|
45
|
+
parent: number;
|
|
46
|
+
children: number[];
|
|
47
|
+
transform: CompiledTransform;
|
|
48
|
+
/** Type-specific scalar attrs (e.g. light color/intensity, camera fov). */
|
|
49
|
+
attrs: Record<string, CompiledScalar>;
|
|
50
|
+
/** Indices into the scene's attachments[] pool (geometry/material/settings/…). */
|
|
51
|
+
attachments: number[];
|
|
52
|
+
components: number[];
|
|
53
|
+
}
|
|
54
|
+
export interface CompiledAsset {
|
|
55
|
+
tag: string;
|
|
56
|
+
attrs: Record<string, CompiledScalar>;
|
|
57
|
+
}
|
|
58
|
+
export interface CompiledComponent {
|
|
59
|
+
type: string;
|
|
60
|
+
id?: string;
|
|
61
|
+
node: number;
|
|
62
|
+
/** Only explicitly-set props; omitted props keep the component's field defaults. */
|
|
63
|
+
props: Record<string, CompiledValue>;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Bundle } from './bundle-format.js';
|
|
2
|
+
import type { CompiledScene } from './compiled.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decode a bundle into a CompiledScene. This is the runtime's only "loader" —
|
|
5
|
+
* it contains no XML and no parser, just array indexing against the registry
|
|
6
|
+
* schema. The redundant back-pointers (`node.parent`, `component.node`) are
|
|
7
|
+
* reconstructed from the children/components arrays rather than stored.
|
|
8
|
+
*/
|
|
9
|
+
export declare function deserializeBundle(bundle: Bundle): CompiledScene;
|