Take the cross-cutting queries out of the model's base module

findPropertyUsages walks override sites, so Project.ts imported instance.ts
while instance.ts imported Project.ts – the repo's only value-level import
cycle. The queries now sit in usages.ts and Project.ts is a base type module
again. requireScene has had no caller since holderId replaced sceneId.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-10 07:14:28 +02:00
1 parent b13b130da7
commit a2f2f8465c
7 files changed
+70 -76

No files matched your search

-55
View File
@@ -1,7 +1,4 @@
import { schemaForAsset } from '../properties/PropertySchema';
import { type Asset, type ContainerAsset, isSceneAsset, type SceneAsset } from './Asset';
import { overrideSites } from './instance';
import type { SceneObject } from './SceneObject';
/**
* 2 since a placed container stopped copying its template into the scene. Old files are not
@@ -62,55 +59,3 @@ export function findObjectHolder(project: Project, holderId: string): ObjectHold
export function listContainers(project: Project): ContainerAsset[] {
return project.assets.filter((a): a is ContainerAsset => a.type === 'container');
}
export interface SceneObjectRef {
holder: ObjectHolder;
object: SceneObject;
}
export function objectsWhere(
project: Project,
predicate: (object: SceneObject) => boolean,
): SceneObjectRef[] {
const result: SceneObjectRef[] = [];
for (const holder of objectHolders(project)) {
for (const object of holder.objects) if (predicate(object)) result.push({ holder, object });
}
return result;
}
/** Every placed instance of an asset, in scenes and in container templates alike. */
export function findUsages(project: Project, assetId: string): SceneObjectRef[] {
return objectsWhere(project, (o) => o.assetId === assetId);
}
export type PropertyUsage =
| { on: 'asset'; asset: Asset; key: string }
/** `path` is empty for a placed object's own override, set for one a container instance keeps. */
| { on: 'object'; holder: ObjectHolder; object: SceneObject; path: string; key: string };
/**
* The other half of "where is this asset used": a texture is never placed, it is worn through a
* sprite's `texture` property, so counting uses needs this as well as `findUsages`. Several ids
* because every frame cut from a texture is a reference to the same picture (see `textureRefIds`).
*/
export function findPropertyUsages(project: Project, ids: string | readonly string[]): PropertyUsage[] {
const wanted = new Set((typeof ids === 'string' ? [ids] : ids).filter(Boolean));
if (wanted.size === 0) return [];
const result: PropertyUsage[] = [];
const refKeys = new Map<string, string[]>();
const matches = (value: unknown): boolean => typeof value === 'string' && wanted.has(value);
for (const asset of project.assets) {
const keys = schemaForAsset(asset)
.filter((def) => def.kind === 'asset' || def.kind === 'texture')
.map((def) => def.key);
refKeys.set(asset.id, keys);
for (const key of keys) if (matches(asset.props[key])) result.push({ on: 'asset', asset, key });
}
for (const site of overrideSites(project, () => true)) {
for (const key of refKeys.get(site.assetId) ?? [])
if (matches(site.bag[key]))
result.push({ on: 'object', holder: site.holder, object: site.object, path: site.path, key });
}
return result;
}