@aethervtt/shard-core 0.0.1
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 +3 -0
- package/dist/ecs/commands.d.ts +35 -0
- package/dist/ecs/commands.js +96 -0
- package/dist/ecs/entity.d.ts +11 -0
- package/dist/ecs/entity.js +12 -0
- package/dist/ecs/events.d.ts +34 -0
- package/dist/ecs/events.js +65 -0
- package/dist/ecs/hierarchy.d.ts +7 -0
- package/dist/ecs/hierarchy.js +29 -0
- package/dist/ecs/index.d.ts +8 -0
- package/dist/ecs/index.js +38 -0
- package/dist/ecs/observers.d.ts +40 -0
- package/dist/ecs/observers.js +27 -0
- package/dist/ecs/query.d.ts +44 -0
- package/dist/ecs/query.js +67 -0
- package/dist/ecs/table.d.ts +91 -0
- package/dist/ecs/table.js +286 -0
- package/dist/ecs/world.d.ts +127 -0
- package/dist/ecs/world.js +513 -0
- package/dist/error.d.ts +26 -0
- package/dist/error.js +29 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -0
- package/dist/json.d.ts +15 -0
- package/dist/json.js +22 -0
- package/dist/math/aabb.d.ts +13 -0
- package/dist/math/aabb.js +78 -0
- package/dist/math/affine.d.ts +28 -0
- package/dist/math/affine.js +159 -0
- package/dist/math/affine64.d.ts +11 -0
- package/dist/math/affine64.js +58 -0
- package/dist/math/frustum.d.ts +11 -0
- package/dist/math/frustum.js +59 -0
- package/dist/math/hash.d.ts +14 -0
- package/dist/math/hash.js +38 -0
- package/dist/math/index.d.ts +14 -0
- package/dist/math/index.js +33 -0
- package/dist/math/mat3.d.ts +6 -0
- package/dist/math/mat3.js +37 -0
- package/dist/math/mat4.d.ts +23 -0
- package/dist/math/mat4.js +224 -0
- package/dist/math/quat.d.ts +22 -0
- package/dist/math/quat.js +233 -0
- package/dist/math/ray.d.ts +10 -0
- package/dist/math/ray.js +54 -0
- package/dist/math/rng.d.ts +41 -0
- package/dist/math/rng.js +102 -0
- package/dist/math/types.d.ts +8 -0
- package/dist/math/types.js +4 -0
- package/dist/math/vec2.d.ts +11 -0
- package/dist/math/vec2.js +52 -0
- package/dist/math/vec3.d.ts +23 -0
- package/dist/math/vec3.js +135 -0
- package/dist/math/vec4.d.ts +8 -0
- package/dist/math/vec4.js +47 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +39 -0
- package/dist/scheduler/profiler.d.ts +19 -0
- package/dist/scheduler/profiler.js +44 -0
- package/dist/scheduler/schedule.d.ts +52 -0
- package/dist/scheduler/schedule.js +235 -0
- package/dist/scheduler/system.d.ts +74 -0
- package/dist/scheduler/system.js +106 -0
- package/dist/schema/color.d.ts +7 -0
- package/dist/schema/color.js +34 -0
- package/dist/schema/component.d.ts +71 -0
- package/dist/schema/component.js +120 -0
- package/dist/schema/field.d.ts +145 -0
- package/dist/schema/field.js +591 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.js +40 -0
- package/dist/schema/names.d.ts +27 -0
- package/dist/schema/names.js +39 -0
- package/dist/schema/registry.d.ts +38 -0
- package/dist/schema/registry.js +58 -0
- package/dist/schema/resource.d.ts +48 -0
- package/dist/schema/resource.js +79 -0
- package/dist/test-env.d.ts +31 -0
- package/dist/test-env.js +37 -0
- package/package.json +38 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { JsonSchema } from '../json.js';
|
|
2
|
+
import type { ComponentDef } from './component.js';
|
|
3
|
+
import type { EventDef, ResourceDef } from './resource.js';
|
|
4
|
+
type Def = ComponentDef | ResourceDef<unknown> | EventDef<unknown>;
|
|
5
|
+
export interface RegistryDescription {
|
|
6
|
+
components: {
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
version: number;
|
|
10
|
+
tag: boolean;
|
|
11
|
+
requires: string[];
|
|
12
|
+
schema: JsonSchema;
|
|
13
|
+
}[];
|
|
14
|
+
resources: {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}[];
|
|
18
|
+
events: {
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Names in use by one world. Registering the same definition twice is fine; registering a
|
|
25
|
+
* different definition under a taken name is an error.
|
|
26
|
+
*/
|
|
27
|
+
export declare class Registry {
|
|
28
|
+
private readonly byName;
|
|
29
|
+
private readonly byId;
|
|
30
|
+
register(def: Def): void;
|
|
31
|
+
/** Swaps in a definition that reuses an existing one's id (hot reload). */
|
|
32
|
+
replace(def: Def): void;
|
|
33
|
+
isRegistered(def: Def): boolean;
|
|
34
|
+
get(name: string): Def | undefined;
|
|
35
|
+
component(name: string): ComponentDef | undefined;
|
|
36
|
+
describe(): RegistryDescription;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ShardError } from "../error.js";
|
|
2
|
+
class Registry {
|
|
3
|
+
byName = /* @__PURE__ */ new Map();
|
|
4
|
+
byId = [];
|
|
5
|
+
register(def) {
|
|
6
|
+
if (this.byId[def.id] === def) return;
|
|
7
|
+
const existing = this.byName.get(def.name);
|
|
8
|
+
if (existing && existing !== def) {
|
|
9
|
+
throw new ShardError("schema/duplicate-name", `"${def.name}" is already registered`, {
|
|
10
|
+
hint: "Two definitions share a name. Rename one, or import the existing definition."
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
this.byName.set(def.name, def);
|
|
14
|
+
this.byId[def.id] = def;
|
|
15
|
+
}
|
|
16
|
+
/** Swaps in a definition that reuses an existing one's id (hot reload). */
|
|
17
|
+
replace(def) {
|
|
18
|
+
const old = this.byId[def.id];
|
|
19
|
+
if (old && this.byName.get(old.name) === old) this.byName.delete(old.name);
|
|
20
|
+
this.byName.set(def.name, def);
|
|
21
|
+
this.byId[def.id] = def;
|
|
22
|
+
}
|
|
23
|
+
isRegistered(def) {
|
|
24
|
+
return this.byId[def.id] === def;
|
|
25
|
+
}
|
|
26
|
+
get(name) {
|
|
27
|
+
return this.byName.get(name);
|
|
28
|
+
}
|
|
29
|
+
component(name) {
|
|
30
|
+
const def = this.byName.get(name);
|
|
31
|
+
return def?.kind === "component" ? def : void 0;
|
|
32
|
+
}
|
|
33
|
+
describe() {
|
|
34
|
+
const out = { components: [], resources: [], events: [] };
|
|
35
|
+
const sorted = [...this.byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
36
|
+
for (const def of sorted) {
|
|
37
|
+
const description = def.description ? { description: def.description } : {};
|
|
38
|
+
if (def.kind === "component") {
|
|
39
|
+
out.components.push({
|
|
40
|
+
name: def.name,
|
|
41
|
+
...description,
|
|
42
|
+
version: def.version,
|
|
43
|
+
tag: def.isTag,
|
|
44
|
+
requires: def.requires.map((r) => r.name),
|
|
45
|
+
schema: def.jsonSchema()
|
|
46
|
+
});
|
|
47
|
+
} else if (def.kind === "resource") {
|
|
48
|
+
out.resources.push({ name: def.name, ...description });
|
|
49
|
+
} else {
|
|
50
|
+
out.events.push({ name: def.name, ...description });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
Registry
|
|
58
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ComponentDef } from './component.js';
|
|
2
|
+
export interface ResourceDef<T> {
|
|
3
|
+
readonly kind: 'resource';
|
|
4
|
+
readonly id: number;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string | undefined;
|
|
7
|
+
/** Creates the initial value for `world.initResource`. */
|
|
8
|
+
readonly init: (() => T) | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* What hot reload does with the value when the defining code reloads: 'keep' (default) keeps it,
|
|
11
|
+
* since it's game state; 'replace' takes whatever the new code inserts (configuration, such as
|
|
12
|
+
* action maps).
|
|
13
|
+
*/
|
|
14
|
+
readonly reload: 'keep' | 'replace';
|
|
15
|
+
/**
|
|
16
|
+
* The value's schema (`defineSchema`), for resources that are plain data: it validates,
|
|
17
|
+
* serializes, and migrates them. Settings and saved resources have one.
|
|
18
|
+
*/
|
|
19
|
+
readonly schema: ComponentDef | undefined;
|
|
20
|
+
/** Saved games include it (0038). Needs `schema`. */
|
|
21
|
+
readonly persist: boolean;
|
|
22
|
+
readonly __type?: T;
|
|
23
|
+
}
|
|
24
|
+
export interface ResourceOptions<T> {
|
|
25
|
+
description?: string;
|
|
26
|
+
init?: () => T;
|
|
27
|
+
reload?: 'keep' | 'replace';
|
|
28
|
+
schema?: ComponentDef;
|
|
29
|
+
persist?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function defineResource<T>(name: string, options?: ResourceOptions<T>): ResourceDef<T>;
|
|
32
|
+
/** Every resource defined in this process (latest definitions), sorted by name. */
|
|
33
|
+
export declare function allResources(): ResourceDef<unknown>[];
|
|
34
|
+
/** The resource defined under `name` (the latest definition), or undefined. */
|
|
35
|
+
export declare function findResource(name: string): ResourceDef<unknown> | undefined;
|
|
36
|
+
export interface EventDef<T> {
|
|
37
|
+
readonly kind: 'event';
|
|
38
|
+
readonly id: number;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly description: string | undefined;
|
|
41
|
+
readonly __type?: T;
|
|
42
|
+
}
|
|
43
|
+
export declare function defineEvent<T = undefined>(name: string, options?: {
|
|
44
|
+
description?: string;
|
|
45
|
+
}): EventDef<T>;
|
|
46
|
+
/** The event defined under `name` (the latest definition), or undefined. */
|
|
47
|
+
export declare function allEvents(): EventDef<unknown>[];
|
|
48
|
+
export declare function findEvent(name: string): EventDef<unknown> | undefined;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ShardError } from "../error.js";
|
|
2
|
+
import { allocateId, assertName, isRedefinable, recordRedefinition } from "./names.js";
|
|
3
|
+
const resources = /* @__PURE__ */ new Map();
|
|
4
|
+
function defineResource(name, options = {}) {
|
|
5
|
+
assertName("resource", name);
|
|
6
|
+
if (options.persist && !options.schema) {
|
|
7
|
+
throw new ShardError(
|
|
8
|
+
"schema/persist-needs-schema",
|
|
9
|
+
`Resource "${name}" persists but has no schema`,
|
|
10
|
+
{
|
|
11
|
+
hint: "Give it a schema (defineSchema) so saves can validate and migrate it."
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
const previous = isRedefinable(name) ? resources.get(name) : void 0;
|
|
16
|
+
const def = {
|
|
17
|
+
kind: "resource",
|
|
18
|
+
id: previous?.id ?? allocateId(),
|
|
19
|
+
name,
|
|
20
|
+
description: options.description,
|
|
21
|
+
init: options.init,
|
|
22
|
+
reload: options.reload ?? "keep",
|
|
23
|
+
schema: options.schema,
|
|
24
|
+
persist: options.persist ?? false
|
|
25
|
+
};
|
|
26
|
+
resources.set(name, def);
|
|
27
|
+
if (previous) {
|
|
28
|
+
recordRedefinition({
|
|
29
|
+
kind: "resource",
|
|
30
|
+
name,
|
|
31
|
+
previous,
|
|
32
|
+
next: def,
|
|
33
|
+
undo: () => resources.set(name, previous)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return def;
|
|
37
|
+
}
|
|
38
|
+
function allResources() {
|
|
39
|
+
return [...resources.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
}
|
|
41
|
+
function findResource(name) {
|
|
42
|
+
return resources.get(name);
|
|
43
|
+
}
|
|
44
|
+
function defineEvent(name, options = {}) {
|
|
45
|
+
assertName("event", name);
|
|
46
|
+
const previous = isRedefinable(name) ? events.get(name) : void 0;
|
|
47
|
+
const def = {
|
|
48
|
+
kind: "event",
|
|
49
|
+
id: previous?.id ?? allocateId(),
|
|
50
|
+
name,
|
|
51
|
+
description: options.description
|
|
52
|
+
};
|
|
53
|
+
events.set(name, def);
|
|
54
|
+
if (previous) {
|
|
55
|
+
recordRedefinition({
|
|
56
|
+
kind: "event",
|
|
57
|
+
name,
|
|
58
|
+
previous,
|
|
59
|
+
next: def,
|
|
60
|
+
undo: () => events.set(name, previous)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return def;
|
|
64
|
+
}
|
|
65
|
+
const events = /* @__PURE__ */ new Map();
|
|
66
|
+
function allEvents() {
|
|
67
|
+
return [...events.values()];
|
|
68
|
+
}
|
|
69
|
+
function findEvent(name) {
|
|
70
|
+
return events.get(name);
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
allEvents,
|
|
74
|
+
allResources,
|
|
75
|
+
defineEvent,
|
|
76
|
+
defineResource,
|
|
77
|
+
findEvent,
|
|
78
|
+
findResource
|
|
79
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How strict tests are about time, in one place.
|
|
3
|
+
*
|
|
4
|
+
* - `pnpm bench` (`SHARD_BENCH`) holds the exact budgets from the specs, one file at a time.
|
|
5
|
+
* - `pnpm test` allows 3×, for a machine busy running test files in parallel.
|
|
6
|
+
* - CI (`SHARD_CI`) checks correctness only. Shared runners render with a software GPU, so their
|
|
7
|
+
* timings say nothing about the engine; budgets are unlimited there and timeouts are 5× longer.
|
|
8
|
+
*/
|
|
9
|
+
export type TimingMode = 'bench' | 'test' | 'ci';
|
|
10
|
+
export declare const timingMode: TimingMode;
|
|
11
|
+
/** The factor budgets are multiplied by (and rate floors divided by) in this mode. */
|
|
12
|
+
export declare const slack: number;
|
|
13
|
+
/** A time budget in ms: exact under `pnpm bench`, 3× in `pnpm test`, unlimited in CI. */
|
|
14
|
+
export declare function budget(ms: number): number;
|
|
15
|
+
/** A test or hook timeout in ms: 5× in CI, unchanged everywhere else. */
|
|
16
|
+
export declare function timeout(ms: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Counts the garbage collections that start between now and `end()`, for "allocates nothing"
|
|
19
|
+
* checks. GC entries are delivered asynchronously, so `end()` waits for them, but it only counts
|
|
20
|
+
* the ones that started inside the window: what the test runner allocates while we wait isn't the
|
|
21
|
+
* code under test. Collect garbage (`gc()`, with `--expose-gc`) before opening the window.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gcWindow(): {
|
|
24
|
+
end(): Promise<number>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Whether "allocates nothing" checks run. They hold under `pnpm bench` and `pnpm test`; CI turns them
|
|
28
|
+
* off with the time budgets, since when V8 optimizes (and so whether a call boxes its result) depends
|
|
29
|
+
* on the runner's CPU and timing.
|
|
30
|
+
*/
|
|
31
|
+
export declare const allocationChecks: boolean;
|
package/dist/test-env.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const env = globalThis.process?.env ?? {};
|
|
2
|
+
const timingMode = env.SHARD_BENCH ? "bench" : env.SHARD_CI ? "ci" : "test";
|
|
3
|
+
const slack = timingMode === "bench" ? 1 : timingMode === "test" ? 3 : Number.POSITIVE_INFINITY;
|
|
4
|
+
function budget(ms) {
|
|
5
|
+
return ms * slack;
|
|
6
|
+
}
|
|
7
|
+
function timeout(ms) {
|
|
8
|
+
return timingMode === "ci" ? ms * 5 : ms;
|
|
9
|
+
}
|
|
10
|
+
const host = globalThis;
|
|
11
|
+
function gcWindow() {
|
|
12
|
+
const starts = [];
|
|
13
|
+
const observer = new host.PerformanceObserver((list) => {
|
|
14
|
+
for (const entry of list.getEntries()) starts.push(entry.startTime);
|
|
15
|
+
});
|
|
16
|
+
observer.observe({ entryTypes: ["gc"] });
|
|
17
|
+
const from = host.performance.now();
|
|
18
|
+
return {
|
|
19
|
+
async end() {
|
|
20
|
+
const to = host.performance.now();
|
|
21
|
+
await new Promise((resolve) => host.setTimeout(resolve, 50));
|
|
22
|
+
observer.disconnect();
|
|
23
|
+
let count = 0;
|
|
24
|
+
for (const start of starts) if (start >= from && start <= to) count++;
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const allocationChecks = timingMode !== "ci";
|
|
30
|
+
export {
|
|
31
|
+
allocationChecks,
|
|
32
|
+
budget,
|
|
33
|
+
gcWindow,
|
|
34
|
+
slack,
|
|
35
|
+
timeout,
|
|
36
|
+
timingMode
|
|
37
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aethervtt/shard-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./math": {
|
|
13
|
+
"types": "./dist/math/index.d.ts",
|
|
14
|
+
"default": "./dist/math/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./test-env": {
|
|
17
|
+
"types": "./dist/test-env.d.ts",
|
|
18
|
+
"default": "./dist/test-env.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/aethervtt/shard.git",
|
|
29
|
+
"directory": "packages/core"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"shard": {
|
|
36
|
+
"buildSha": "d6c5c6aef4675575c878b02cf2c8f738b4190229"
|
|
37
|
+
}
|
|
38
|
+
}
|