diff --git a/src/core/commands/require.ts b/src/core/commands/require.ts index f008b25e..869087b9 100644 --- a/src/core/commands/require.ts +++ b/src/core/commands/require.ts @@ -1,10 +1,10 @@ /** Lookups that throw a `CommandError` when the target does not exist. */ import { findNode, type GraphNode } from '../graph/Graph'; import { type NodeDef, nodeDef } from '../graph/NodeRegistry'; -import type { Asset, BehaviorAsset, SceneAsset } from '../model/Asset'; +import type { Asset, BehaviorAsset } from '../model/Asset'; import { findObject, type ObjectTree } from '../model/hierarchy'; import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance'; -import { findAsset, findObjectHolder, findScene, type ObjectHolder, type Project } from '../model/Project'; +import { findAsset, findObjectHolder, type ObjectHolder, type Project } from '../model/Project'; import type { SceneObject } from '../model/SceneObject'; import { propDef, type PropertyDef } from '../properties/PropertySchema'; import { CommandError } from './Command'; @@ -15,12 +15,6 @@ export function requireAsset(project: Project, assetId: string): Asset { return asset; } -export function requireScene(project: Project, sceneId: string): SceneAsset { - const scene = findScene(project, sceneId); - if (!scene) throw new CommandError(`Scene ${sceneId} not found`); - return scene; -} - export function requireHolder(project: Project, holderId: string): ObjectHolder { const holder = findObjectHolder(project, holderId); if (!holder) throw new CommandError(`Scene or container ${holderId} not found`); diff --git a/src/core/model/Project.ts b/src/core/model/Project.ts index b453fb3a..b6820553 100644 --- a/src/core/model/Project.ts +++ b/src/core/model/Project.ts @@ -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(); - 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; -} diff --git a/src/core/model/instance.ts b/src/core/model/instance.ts index 0793f426..1f028596 100644 --- a/src/core/model/instance.ts +++ b/src/core/model/instance.ts @@ -256,8 +256,6 @@ export function instanceTargetOf( }; } -// --- writing ----------------------------------------------------------------------------------- - /** * Read/write access to a placed object, whether it is stored in the holder or resolved from a * container template. `object` is the current state either way; the setters keep an instance edit @@ -421,8 +419,6 @@ export function overrideSites(project: Project, wanted: (assetId: string) => boo return sites; } -// --- unpacking --------------------------------------------------------------------------------- - /** * The resolved content of an instance as objects that can be stored. The derived ids are kept: * they are unique already, and anything pointing at one – the selection, a running game, a check diff --git a/src/core/model/usages.ts b/src/core/model/usages.ts new file mode 100644 index 00000000..3e4223a4 --- /dev/null +++ b/src/core/model/usages.ts @@ -0,0 +1,62 @@ +/** + * "Where is this asset used" – across scenes and container templates alike. Kept out of + * `Project.ts` so that module stays free of the property schema and of container instancing: + * both of those already import it, and answering this question needs both. + */ +import { schemaForAsset } from '../properties/PropertySchema'; +import type { Asset } from './Asset'; +import { overrideSites } from './instance'; +import { type ObjectHolder, objectHolders, type Project } from './Project'; +import type { SceneObject } from './SceneObject'; + +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(); + 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; +} diff --git a/src/editor/actions.ts b/src/editor/actions.ts index 23467548..17d3c175 100644 --- a/src/editor/actions.ts +++ b/src/editor/actions.ts @@ -8,8 +8,9 @@ import { CompoundCommand } from '../core/commands/CompoundCommand'; import { CreateContainerCommand } from '../core/commands/containerCommands'; import { RemoveNodeCommand } from '../core/commands/graphCommands'; import { RemoveSceneObjectsCommand } from '../core/commands/sceneObjectCommands'; -import { findAsset, findPropertyUsages, findUsages } from '../core/model/Project'; +import { findAsset } from '../core/model/Project'; import { textureRefIds } from '../core/model/texture'; +import { findPropertyUsages, findUsages } from '../core/model/usages'; import { Dialog } from '../ui'; import { type EditorState, objectSelection } from './state'; diff --git a/tests/core/holders.test.ts b/tests/core/holders.test.ts index db28b25b..d0bb0e78 100644 --- a/tests/core/holders.test.ts +++ b/tests/core/holders.test.ts @@ -9,14 +9,9 @@ import { CreateContainerCommand } from '../../src/core/commands/containerCommand import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands'; import { RemoveVariableCommand, RenameVariableCommand } from '../../src/core/commands/variableCommands'; import type { ContainerAsset } from '../../src/core/model/Asset'; -import { - findAsset, - findObjectHolder, - findUsages, - listContainers, - objectHolders, -} from '../../src/core/model/Project'; +import { findAsset, findObjectHolder, listContainers, objectHolders } from '../../src/core/model/Project'; import { instanceChildId } from '../../src/core/model/instance'; +import { findUsages } from '../../src/core/model/usages'; import { makeBehavior, makeSpriteScene, place } from '../helpers'; /** A behavior with a `speed` variable, placed and overridden to 42, then wrapped in a container. */ diff --git a/tests/core/texture.test.ts b/tests/core/texture.test.ts index 7d5654af..df3ba46a 100644 --- a/tests/core/texture.test.ts +++ b/tests/core/texture.test.ts @@ -1,7 +1,8 @@ import { describe, expect, it } from 'vitest'; import { CreateTextureCommand } from '../../src/core/commands/assetCommands'; import type { TextureAsset } from '../../src/core/model/Asset'; -import { findAsset, findPropertyUsages, findUsages } from '../../src/core/model/Project'; +import { findAsset } from '../../src/core/model/Project'; +import { findPropertyUsages, findUsages } from '../../src/core/model/usages'; import { DEFAULT_SLICE, isTextureFile,