@forgeax/engine-render-graph 0.1.2

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 (78) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +184 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/graph-pass-kind-owner.test-d.d.ts +2 -0
  5. package/dist/__tests__/graph-pass-kind-owner.test-d.d.ts.map +1 -0
  6. package/dist/__tests__/observation-lifetime-state-owner.test-d.d.ts +2 -0
  7. package/dist/__tests__/observation-lifetime-state-owner.test-d.d.ts.map +1 -0
  8. package/dist/__tests__/observation.unit.test.d.ts +2 -0
  9. package/dist/__tests__/observation.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/render-graph-alias-source.unit.test.d.ts +2 -0
  11. package/dist/__tests__/render-graph-alias-source.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/render-graph-builder.unit.test.d.ts +2 -0
  13. package/dist/__tests__/render-graph-builder.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/render-graph-capability-owner.test-d.d.ts +2 -0
  15. package/dist/__tests__/render-graph-capability-owner.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/render-graph-errors.test-d.d.ts +2 -0
  17. package/dist/__tests__/render-graph-errors.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/render-graph-regressions.unit.test.d.ts +2 -0
  19. package/dist/__tests__/render-graph-regressions.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/render-graph-rhi-null.integration.test.d.ts +2 -0
  21. package/dist/__tests__/render-graph-rhi-null.integration.test.d.ts.map +1 -0
  22. package/dist/__tests__/render-graph.unit.test.d.ts +2 -0
  23. package/dist/__tests__/render-graph.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/resource-declaration-collision.test.d.ts +2 -0
  25. package/dist/__tests__/resource-declaration-collision.test.d.ts.map +1 -0
  26. package/dist/__tests__/resource-kind-owner.test-d.d.ts +2 -0
  27. package/dist/__tests__/resource-kind-owner.test-d.d.ts.map +1 -0
  28. package/dist/builder.d.ts +42 -0
  29. package/dist/builder.d.ts.map +1 -0
  30. package/dist/compiled-graph.d.ts +23 -0
  31. package/dist/compiled-graph.d.ts.map +1 -0
  32. package/dist/errors.d.ts +148 -0
  33. package/dist/errors.d.ts.map +1 -0
  34. package/dist/graph.d.ts +404 -0
  35. package/dist/graph.d.ts.map +1 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.mjs +2256 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/kernel-internal.d.ts +84 -0
  41. package/dist/kernel-internal.d.ts.map +1 -0
  42. package/dist/observation.d.ts +38 -0
  43. package/dist/observation.d.ts.map +1 -0
  44. package/dist/pass-registry.d.ts +12 -0
  45. package/dist/pass-registry.d.ts.map +1 -0
  46. package/dist/pipeline/__tests__/color-value-domain.test.d.ts +2 -0
  47. package/dist/pipeline/__tests__/color-value-domain.test.d.ts.map +1 -0
  48. package/dist/pipeline/color-value-domain.d.ts +35 -0
  49. package/dist/pipeline/color-value-domain.d.ts.map +1 -0
  50. package/dist/resource-registry.d.ts +61 -0
  51. package/dist/resource-registry.d.ts.map +1 -0
  52. package/dist/types.d.ts +171 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/package.json +61 -0
  55. package/src/__tests__/graph-pass-kind-owner.test-d.ts +35 -0
  56. package/src/__tests__/observation-lifetime-state-owner.test-d.ts +39 -0
  57. package/src/__tests__/observation.unit.test.ts +103 -0
  58. package/src/__tests__/render-graph-alias-source.unit.test.ts +118 -0
  59. package/src/__tests__/render-graph-builder.unit.test.ts +664 -0
  60. package/src/__tests__/render-graph-capability-owner.test-d.ts +32 -0
  61. package/src/__tests__/render-graph-errors.test-d.ts +169 -0
  62. package/src/__tests__/render-graph-regressions.unit.test.ts +126 -0
  63. package/src/__tests__/render-graph-rhi-null.integration.test.ts +86 -0
  64. package/src/__tests__/render-graph.unit.test.ts +2549 -0
  65. package/src/__tests__/resource-declaration-collision.test.ts +151 -0
  66. package/src/__tests__/resource-kind-owner.test-d.ts +38 -0
  67. package/src/builder.ts +1009 -0
  68. package/src/compiled-graph.ts +383 -0
  69. package/src/errors.ts +205 -0
  70. package/src/graph.ts +1270 -0
  71. package/src/index.ts +97 -0
  72. package/src/kernel-internal.ts +148 -0
  73. package/src/observation.ts +134 -0
  74. package/src/pass-registry.ts +43 -0
  75. package/src/pipeline/__tests__/color-value-domain.test.ts +43 -0
  76. package/src/pipeline/color-value-domain.ts +139 -0
  77. package/src/resource-registry.ts +173 -0
  78. package/src/types.ts +217 -0
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { DuplicateResourceDetail } from '../errors.js';
3
+ import { RenderGraph } from '../graph.js';
4
+ import { ResourceRegistry } from '../resource-registry.js';
5
+
6
+ const firstDescriptor = {
7
+ format: 'rgba8unorm',
8
+ size: { w: 4, h: 4 },
9
+ usage: 0,
10
+ } as const;
11
+
12
+ const replacementDescriptor = {
13
+ format: 'rgba16float',
14
+ size: { w: 8, h: 8 },
15
+ usage: 0,
16
+ } as const;
17
+
18
+ type DeclarationResult = {
19
+ readonly ok: boolean;
20
+ readonly value?: unknown;
21
+ readonly error?: {
22
+ readonly code: string;
23
+ readonly detail?: DuplicateResourceDetail;
24
+ };
25
+ };
26
+
27
+ function expectDuplicate(result: unknown, resourceKey: string): void {
28
+ const declaration = result as DeclarationResult;
29
+ expect(declaration.ok).toBe(false);
30
+ expect(declaration.error?.code).toBe('duplicate-resource');
31
+ expect(declaration.error?.detail).toEqual({ resourceKey });
32
+ }
33
+
34
+ function makeDevice(): {
35
+ readonly device: unknown;
36
+ readonly counts: { created: number; views: number; destroyed: number };
37
+ } {
38
+ const counts = { created: 0, views: 0, destroyed: 0 };
39
+ const device = {
40
+ createTexture: () => {
41
+ counts.created += 1;
42
+ return { ok: true as const, value: { id: `texture-${counts.created}` } };
43
+ },
44
+ createTextureView: () => {
45
+ counts.views += 1;
46
+ return { ok: true as const, value: { id: `view-${counts.views}` } };
47
+ },
48
+ destroyTexture: () => {
49
+ counts.destroyed += 1;
50
+ return { ok: true as const, value: undefined };
51
+ },
52
+ queue: { onSubmittedWorkDone: () => Promise.resolve(undefined) },
53
+ };
54
+ return { device, counts };
55
+ }
56
+
57
+ const compileOptions = {
58
+ backendKind: 'webgpu' as const,
59
+ caps: { backendKind: 'webgpu', compute: true, storageBuffer: true } as never,
60
+ };
61
+
62
+ describe('resource declaration collision preflight', () => {
63
+ it.each([
64
+ ['target/target', 'target-target'],
65
+ ['resource/target', 'resource-target'],
66
+ ['source/reused-alias-key', 'source-alias'],
67
+ ['alias/target', 'alias-target'],
68
+ ])('ResourceRegistry rejects %s without replacing the first entry', (kind, key) => {
69
+ const registry = new ResourceRegistry();
70
+ let first: unknown;
71
+ let duplicate: unknown;
72
+
73
+ if (kind === 'target/target') {
74
+ first = registry.addColorTarget(key, firstDescriptor);
75
+ duplicate = registry.addColorTarget(key, replacementDescriptor);
76
+ } else if (kind === 'resource/target') {
77
+ first = registry.add(key, { kind: 'texture', lifetime: 'persistent' });
78
+ duplicate = registry.addColorTarget(key, replacementDescriptor);
79
+ } else if (kind === 'source/reused-alias-key') {
80
+ first = registry.addColorTarget(key, firstDescriptor);
81
+ duplicate = registry.addColorTargetAlias(key, key);
82
+ } else {
83
+ registry.addColorTarget('source', firstDescriptor);
84
+ first = registry.addColorTargetAlias(key, 'source');
85
+ duplicate = registry.addColorTarget(key, replacementDescriptor);
86
+ }
87
+
88
+ expectDuplicate(duplicate, key);
89
+ const firstEntry = (first as { readonly value?: unknown }).value ?? first;
90
+ expect(registry.get(key)).toBe(firstEntry);
91
+ });
92
+
93
+ it.each([
94
+ ['target/target', 'target-target'],
95
+ ['resource/target', 'resource-target'],
96
+ ['source/reused-alias-key', 'source-alias'],
97
+ ['alias/target', 'alias-target'],
98
+ ])('RenderGraph rejects %s before allocation and keeps the active graph', (kind, key) => {
99
+ const graph = new RenderGraph();
100
+ const { device, counts } = makeDevice();
101
+
102
+ if (kind === 'target/target') {
103
+ graph.addColorTarget(key, firstDescriptor);
104
+ } else if (kind === 'resource/target') {
105
+ graph.addResource(key, { kind: 'texture', lifetime: 'persistent' });
106
+ } else if (kind === 'source/reused-alias-key') {
107
+ graph.addColorTarget(key, firstDescriptor);
108
+ } else {
109
+ graph.addColorTarget('source', firstDescriptor);
110
+ graph.addColorTargetAlias(key, 'source');
111
+ }
112
+
113
+ const executed: string[] = [];
114
+ graph.addPass('first-pass', {
115
+ reads: [],
116
+ writes: [key],
117
+ execute: () => executed.push('first-pass'),
118
+ });
119
+ const initial = graph.compile({ ...compileOptions, device: device as never });
120
+ expect(initial.ok).toBe(true);
121
+ const initialResources = graph.listResources();
122
+ const initialPasses = graph.listPasses();
123
+ const initialTexture = graph.getColorTargetTexture(key);
124
+ const initialView = graph.getColorTargetView(key);
125
+ const countsBeforeCollision = { ...counts };
126
+
127
+ let duplicate: unknown;
128
+ if (kind === 'target/target' || kind === 'alias/target') {
129
+ duplicate = graph.addColorTarget(key, replacementDescriptor);
130
+ } else if (kind === 'resource/target') {
131
+ duplicate = graph.addColorTarget(key, replacementDescriptor);
132
+ } else {
133
+ duplicate = graph.addColorTargetAlias(key, key);
134
+ }
135
+
136
+ expectDuplicate(duplicate, key);
137
+ expect(counts).toEqual(countsBeforeCollision);
138
+
139
+ const repaired = graph.compile({ ...compileOptions, device: device as never });
140
+ expect(repaired.ok).toBe(true);
141
+ expect(graph.listResources()).toEqual(initialResources);
142
+ expect(graph.listPasses()).toEqual(initialPasses);
143
+ expect(graph.getColorTargetTexture(key)).toBe(initialTexture);
144
+ expect(graph.getColorTargetView(key)).toBe(initialView);
145
+
146
+ graph.execute(undefined);
147
+ expect(executed).toEqual(['first-pass']);
148
+ graph.drain();
149
+ expect(counts.destroyed).toBe(countsBeforeCollision.created);
150
+ });
151
+ });
@@ -0,0 +1,38 @@
1
+ /// <reference types="node" />
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import { describe, expect, expectTypeOf, it } from 'vitest';
5
+ import type {
6
+ GraphResourceKind,
7
+ ResourceDescriptor,
8
+ ResourceInfo,
9
+ ResourceKind,
10
+ } from '../index.js';
11
+
12
+ const graphSource = readFileSync(new URL('../graph.ts', import.meta.url), 'utf8');
13
+
14
+ describe('render-graph ResourceKind owner', () => {
15
+ it('keeps exact membership and projects the public resource shapes', () => {
16
+ expectTypeOf<GraphResourceKind>().toEqualTypeOf<'texture' | 'buffer'>();
17
+ expectTypeOf<ResourceKind>().toEqualTypeOf<GraphResourceKind>();
18
+ expectTypeOf<GraphResourceKind>().toEqualTypeOf<ResourceKind>();
19
+ expectTypeOf<ResourceDescriptor['kind']>().toEqualTypeOf<GraphResourceKind>();
20
+ expectTypeOf<ResourceInfo['kind']>().toEqualTypeOf<GraphResourceKind>();
21
+
22
+ const acceptResourceKind = (kind: ResourceKind): ResourceKind => kind;
23
+ const texture: GraphResourceKind = 'texture';
24
+ const buffer: GraphResourceKind = 'buffer';
25
+ acceptResourceKind(texture);
26
+ acceptResourceKind(buffer);
27
+ const graphKind: GraphResourceKind = acceptResourceKind('texture');
28
+ void graphKind;
29
+
30
+ // @ts-expect-error unknown resource kinds remain outside the closed union.
31
+ acceptResourceKind('resource-kind-not-real');
32
+ });
33
+
34
+ it('keeps ResourceKind as a type-only projection of GraphResourceKind', () => {
35
+ expect(graphSource).toContain('export type ResourceKind = GraphResourceKind;');
36
+ expect(graphSource).not.toContain("export type ResourceKind = 'texture' | 'buffer';");
37
+ });
38
+ });