@forgeax/engine-vfx-render 0.1.4 → 0.1.7

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.
@@ -1,4 +1,3 @@
1
- import type { RenderFeatureMaterialShaderBindingContract } from '@forgeax/engine-render';
2
1
  import {
3
2
  err,
4
3
  type MaterialAsset,
@@ -157,13 +156,6 @@ export function particleMaterialPass(
157
156
  };
158
157
  }
159
158
 
160
- /** Scene-depth is a shader binding contract, not a renderer-kind convention. */
161
- export function particleMaterialUsesSceneDepth(
162
- contract: RenderFeatureMaterialShaderBindingContract | 'view-with-resource',
163
- ): boolean {
164
- return contract === 'group-0-resource' || contract === 'view-with-resource';
165
- }
166
-
167
159
  /** True when the authored particle shader consumes the standard material bind group. */
168
160
  export function particleMaterialUsesBindings(material: MaterialAsset | undefined): boolean {
169
161
  return (material?.parameters?.length ?? 0) > 0;
@@ -1,5 +1,8 @@
1
- import type { RenderFeatureGpuBindingsRef, RenderFeatureGpuDispatch } from '@forgeax/engine-render';
2
1
  import type { VfxGpuStageReflection } from '@forgeax/engine-vfx';
2
+ import type {
3
+ RenderFeatureGpuBindingsRef,
4
+ RenderFeatureGpuDispatch,
5
+ } from '../../../render/src/features/prepared-gpu-work.js';
3
6
 
4
7
  const MANAGED_STAGES = new Set(['spawn', 'update', 'scan', 'compact']);
5
8
  const RESOURCE_NAMES = new Set([
@@ -1,10 +1,17 @@
1
- import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
2
- import {
3
- type AssetRegistryResolver,
4
- getAssetRegistryResolver,
5
- } from '@forgeax/engine-assets-runtime/internal';
1
+ import type {
2
+ AssetRegistryResolver,
3
+ AssetRegistry as LegacyAssetRegistry,
4
+ RuntimeAssetRegistry,
5
+ } from '@forgeax/engine-assets-runtime';
6
+ import { getAssetRegistryResolver } from '@forgeax/engine-assets-runtime';
6
7
  import { createWorldContext, type EntityHandle, type World } from '@forgeax/engine-ecs';
7
- import type { AssetDecoderLease, MaterialAsset, MeshAsset, Result } from '@forgeax/engine-types';
8
+ import type {
9
+ Asset,
10
+ AssetDecoderLease,
11
+ MaterialAsset,
12
+ MeshAsset,
13
+ Result,
14
+ } from '@forgeax/engine-types';
8
15
  import { err, ok } from '@forgeax/engine-types';
9
16
  import type {
10
17
  VfxDataInterfaceError,
@@ -21,6 +28,7 @@ import {
21
28
  VFX_GPU_RUNTIME_RESOURCE_KEY,
22
29
  type VfxGpuRuntime,
23
30
  vfxGpuEffectContribution,
31
+ vfxGpuEffectPackLoader,
24
32
  vfxGpuRuntimePlugin,
25
33
  } from '@forgeax/engine-vfx';
26
34
  import type { ParticleRenderCameraSource } from '../feature/camera.js';
@@ -30,6 +38,23 @@ import {
30
38
  type VfxDataInterfaceRegistry,
31
39
  } from './data-interface-providers.js';
32
40
 
41
+ /**
42
+ * Public authoring projection accepted by the VFX host. Editor owns the
43
+ * projection, while the Engine App still owns bytes/decoders/cache. Keeping
44
+ * this small structural seam public lets the Editor pass that projection
45
+ * without importing the runtime registry's internal module or fabricating a
46
+ * second registry identity.
47
+ */
48
+ export interface VfxAuthoringAssetRegistry {
49
+ readonly loaders: {
50
+ registerPackLoader(loader: unknown): void;
51
+ };
52
+ lookup<T extends Asset = Asset>(guid: string): T | undefined;
53
+ readonly load?: (guid: string, kind: string) => Promise<Result<unknown, unknown>>;
54
+ }
55
+
56
+ type AssetRegistry = LegacyAssetRegistry | RuntimeAssetRegistry | VfxAuthoringAssetRegistry;
57
+
33
58
  export interface VfxRuntimeHostOptions {
34
59
  readonly camera: ParticleRenderCameraSource;
35
60
  readonly maxQueuedTicks?: number;
@@ -108,8 +133,8 @@ export interface VfxRuntimeHost {
108
133
  }): Promise<Result<{ readonly state: 'detached' | 'not-attached' }, VfxRuntimeHostError>>;
109
134
  }
110
135
 
111
- /** Install the VFX owner decoder into the one host registry. */
112
- export function installVfxRuntimeDecoder(registry: AssetRegistry): AssetDecoderLease {
136
+ /** Install the VFX owner decoder into the core registry. */
137
+ export function installVfxRuntimeDecoder(registry: RuntimeAssetRegistry): AssetDecoderLease {
113
138
  return registry.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
114
139
  }
115
140
 
@@ -142,7 +167,7 @@ function controlFailure(
142
167
  }
143
168
 
144
169
  export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntimeHost {
145
- const decoderLeases = new WeakMap<AssetRegistry, AssetDecoderLease>();
170
+ const registries = new WeakSet<AssetRegistry>();
146
171
  const worlds = new WeakMap<
147
172
  World,
148
173
  {
@@ -169,15 +194,34 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
169
194
  const key = `${kind}:${guid.toLowerCase()}`;
170
195
  const cached = attached.renderAssets.get(key);
171
196
  if (cached?.kind === kind) return cached as Kind extends 'material' ? MaterialAsset : MeshAsset;
197
+ if ('lookup' in attached.assets) {
198
+ const lookup = attached.assets.lookup as (guid: string) => Asset | undefined;
199
+ // Preserve the registry receiver: the Engine-owned legacy class uses
200
+ // `this` to read its load-state/catalogue maps. The Editor facade may
201
+ // expose an arrow function, but calling through the owner is safe for
202
+ // both shapes and avoids turning a lookup into a swallowed plan error.
203
+ const legacy = lookup.call(attached.assets, guid);
204
+ if (legacy?.kind === kind) {
205
+ attached.renderAssets.set(key, legacy);
206
+ return legacy as Kind extends 'material' ? MaterialAsset : MeshAsset;
207
+ }
208
+ }
172
209
  const resolved = attached.resolver?.lookup<MaterialAsset | MeshAsset>(guid);
173
210
  if (resolved?.kind === kind) {
174
211
  attached.renderAssets.set(key, resolved);
175
212
  return resolved as Kind extends 'material' ? MaterialAsset : MeshAsset;
176
213
  }
177
- if (!attached.pendingRenderAssets.has(key)) {
214
+ if (
215
+ 'load' in attached.assets &&
216
+ typeof attached.assets.load === 'function' &&
217
+ !attached.pendingRenderAssets.has(key)
218
+ ) {
178
219
  const epoch = attached.catalogEpoch;
179
- const request = attached.assets
180
- .load(guid, kind)
220
+ const load = attached.assets.load as (
221
+ guid: string,
222
+ kind: string,
223
+ ) => Promise<Result<MaterialAsset | MeshAsset, unknown>>;
224
+ const request = load(guid, kind)
181
225
  .then((result) => {
182
226
  if (!result.ok || worlds.get(world) !== attached || attached.catalogEpoch !== epoch)
183
227
  return;
@@ -314,15 +358,27 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
314
358
  dataInterfaces.resolve(requirements, generation),
315
359
  attachWorld: async ({ world, assets }) => {
316
360
  if (worlds.has(world)) return ok({ state: 'already-attached' });
317
- if (!decoderLeases.has(assets)) {
361
+ if (!registries.has(assets)) {
318
362
  try {
319
- decoderLeases.set(assets, installVfxRuntimeDecoder(assets));
363
+ // The Editor facade deliberately exposes the legacy LoaderRegistry
364
+ // while projecting the Engine-owned App.assets instance. Prefer
365
+ // that public authoring seam before looking for the five-action
366
+ // runtime registry; otherwise a facade that happens to expose
367
+ // `load` would be misclassified as a decoder owner.
368
+ if ('loaders' in assets) {
369
+ assets.loaders.registerPackLoader(vfxGpuEffectPackLoader);
370
+ } else if ('installDecoder' in assets) {
371
+ assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
372
+ } else {
373
+ throw new TypeError('VFX assets registry exposes neither loaders nor installDecoder');
374
+ }
375
+ registries.add(assets);
320
376
  } catch (cause) {
321
377
  return err(
322
378
  failure(
323
379
  'vfx-host-loader-install-failed',
324
- 'the VFX owner decoder to be installed once in the runtime core',
325
- 'remove a conflicting particle-effect decoder and retry attachWorld',
380
+ 'the v2 VFX loader to be registered once',
381
+ 'remove a conflicting particle-effect loader and retry attachWorld',
326
382
  cause,
327
383
  ),
328
384
  );
@@ -346,10 +402,12 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
346
402
  );
347
403
  }
348
404
  let resolver: AssetRegistryResolver | undefined;
349
- try {
350
- resolver = getAssetRegistryResolver(assets);
351
- } catch {
352
- resolver = undefined;
405
+ if (!('loaders' in assets) && 'installDecoder' in assets) {
406
+ try {
407
+ resolver = getAssetRegistryResolver(assets);
408
+ } catch {
409
+ resolver = undefined;
410
+ }
353
411
  }
354
412
  const attached = {
355
413
  assets,
@@ -359,14 +417,13 @@ export function createVfxRuntimeHost(options: VfxRuntimeHostOptions): VfxRuntime
359
417
  renderAssets: new Map<string, MaterialAsset | MeshAsset>(),
360
418
  pendingRenderAssets: new Map<string, Promise<void>>(),
361
419
  catalogEpoch:
362
- typeof (assets as { readonly snapshot?: unknown }).snapshot === 'function'
420
+ 'snapshot' in assets && typeof assets.snapshot === 'function'
363
421
  ? assets.snapshot().epoch
364
422
  : 0,
365
423
  unsubscribeAssets: () => {},
366
424
  };
367
- const subscribe = (assets as { readonly subscribe?: AssetRegistry['subscribe'] }).subscribe;
368
- if (subscribe !== undefined) {
369
- attached.unsubscribeAssets = subscribe((snapshot) => {
425
+ if ('subscribe' in assets && typeof assets.subscribe === 'function') {
426
+ attached.unsubscribeAssets = assets.subscribe((snapshot) => {
370
427
  if (snapshot.epoch === attached.catalogEpoch) return;
371
428
  attached.catalogEpoch = snapshot.epoch;
372
429
  attached.renderAssets.clear();
package/src/index.ts CHANGED
@@ -10,7 +10,6 @@ export {
10
10
  VFX_EVENT_COUNTER_BYTES,
11
11
  VFX_EVENT_INPUT_BYTES,
12
12
  } from './feature/event-resources.js';
13
- export type { VfxGpuRenderObservation } from './feature/gpu-particle-feature.js';
14
13
  export {
15
14
  createVfxRenderInspectSnapshot,
16
15
  gpuParticleRenderFeature,
@@ -47,6 +46,7 @@ export {
47
46
  createVfxDataInterfaceRegistry,
48
47
  } from './host/data-interface-providers.js';
49
48
  export type {
49
+ VfxAuthoringAssetRegistry,
50
50
  VfxRuntimeHost,
51
51
  VfxRuntimeHostControl,
52
52
  VfxRuntimeHostControlError,