抽取自动分离素材状态变换

复用 SpriteAsset 校验并支持锁内构造下一份 State。

保留公开 addSpriteAssets 的锁保护与历史提交行为。
This commit is contained in:
2026-09-09 20:17:11 +08:00
parent 615d1a28cd
commit 5844afa14d
@@ -524,6 +524,40 @@ function spriteResourceValidationError(sprite: SpriteAsset) {
return border.ok ? null : border.message;
}
/**
* Apply the SpriteAsset resource checks without acquiring the editor mutation
* guard. Workflow adapters use this while a State lock is already held so
* resource insertion and component backfill can be committed atomically.
*/
export function addSpriteAssetsToState(
current: State,
assets: readonly SpriteAsset[],
): UiEditorOperationResult<State> {
const unique = new Map<SpriteAssetId, SpriteAsset>();
for (const asset of assets) {
const candidate =
unique.get(asset.asset_id) ?? current.sprite_assets[asset.asset_id];
if (candidate && !sameResource(candidate, asset)) {
return { ok: false, reason: 'duplicate' };
}
unique.set(asset.asset_id, asset);
}
const invalidSprite = [...unique.values()]
.map((asset) => ({ asset, error: spriteResourceValidationError(asset) }))
.find((item) => item.error);
if (invalidSprite?.error) {
return {
ok: false,
reason: `invalid:${invalidSprite.asset.asset_id}${invalidSprite.error}`,
};
}
const next = cloneState(current);
for (const asset of unique.values()) {
next.sprite_assets[asset.asset_id] = structuredClone(asset);
}
return { ok: true, value: next };
}
function fontResourceValidationError(font: FontAsset) {
if (font.asset_id.trim().length === 0) return '缺少字体 ID';
if (font.path.trim().length === 0) return '缺少字体路径';
@@ -782,32 +816,9 @@ export function useUiEditorState(initialState: State = EMPTY_UI_EDITOR_STATE) {
const blocked = guard();
if (blocked) return blocked;
const current = stateRef.current;
const unique = new Map<SpriteAssetId, SpriteAsset>();
for (const asset of assets) {
const candidate =
unique.get(asset.asset_id) ?? current.sprite_assets[asset.asset_id];
if (candidate && !sameResource(candidate, asset)) {
return { ok: false, reason: 'duplicate' };
}
unique.set(asset.asset_id, asset);
}
const invalidSprite = [...unique.values()]
.map((asset) => ({
asset,
error: spriteResourceValidationError(asset),
}))
.find((item) => item.error);
if (invalidSprite?.error) {
return {
ok: false,
reason: `invalid:${invalidSprite.asset.asset_id}${invalidSprite.error}`,
};
}
const next = cloneState(current);
for (const asset of unique.values()) {
next.sprite_assets[asset.asset_id] = structuredClone(asset);
}
commit(next);
const result = addSpriteAssetsToState(current, assets);
if (!result.ok) return result;
commit(result.value);
return { ok: true, value: undefined };
},
[commit, guard],