Skip to content

feat(scene): decouple node name from path for animation binding. - #805

Open
qiuguohua wants to merge 3 commits into
cocos:mainfrom
qiuguohua:cocos_main_component_name
Open

feat(scene): decouple node name from path for animation binding.#805
qiuguohua wants to merge 3 commits into
cocos:mainfrom
qiuguohua:cocos_main_component_name

Conversation

@qiuguohua

@qiuguohua qiuguohua commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Background

After node name/path decoupling, node.name becomes a user-controlled display label that can duplicate among siblings, while the system path (managed by NodePathManager) remains unique with _001/_002 suffixes. This creates two independent path namespaces:

  • System path: Enemy, Enemy_001, Enemy_002 — unique, used for node addressing
  • Engine name path: Enemy, Enemy, Enemy — resolved by getChildByPath using Array.find (first match only)

The animation system builds track paths from node.name and resolves them via getChildByPath. Several code paths still assumed name === path segment, causing:

  1. property-curve.ts string-slicing produced system path segments (e.g. Enemy_001) that getChildByPath cannot resolve
  2. property-value.ts used queryNodePath which returns system paths, same mismatch
  3. node-proxy.ts silently returned stale paths after rename failure
  4. node.ts used getChildByName for path resolution, breaking when name differs from path segment
  5. _updateNameCount operated on display names instead of path segments, corrupting the path allocation table

Fix

Animation path resolution — Rewrite resolveRelativeNodePath (property-curve.ts) and resolveOperationRelativeNodePath (property-value.ts) to a UUID-first 3-step flow:

  1. Resolve target node UUID (via nodeUuid directly, or via getNodeByPath/getChildByPath from nodePath)
  2. Build name-based relative path via findRelativeNodePathByUuid (DFS using child.name), producing paths compatible with engine getChildByPath
  3. Detect same-name sibling ambiguity via hasSameNameSiblings — reject create/update operations when any path level has duplicate names

Lenient delete moderemovePropertyCurve and removePropertyKey skip ambiguity rejection but verify UUID match via getChildByPath, preventing accidental deletion of another node's track. When the target node no longer exists (orphan tracks), fall back to string-based path extraction so cleanup still works.

Node proxy — Throw on failed path resolution after rename instead of silently returning the old (now invalid) path.

Node manager — Remove _updateNameCount entirely (path segment lifecycle managed by NodePathManager), remove path-segment writeback to node.name after move, fix _ensurePathExists to resolve by accumulated system path.

Test Plan

  1. Open a scene with sibling nodes sharing the same name
  2. Verify animation track creation is rejected with a console warning for any node that has same-name siblings
  3. Verify existing animation tracks on same-name siblings can still be deleted
  4. Rename a node to conflict with a sibling, verify the returned path uses the system suffix (e.g. Enemy_001) while node.name shows the user input
  5. Delete a node that has animation tracks, verify the orphan tracks can be removed via removePropertyCurve
  6. Verify animation keyframe create/update operations work normally for nodes without name conflicts

@qiuguohua
qiuguohua requested review from bofeng-song, knoxHuang and star-e and removed request for bofeng-song July 29, 2026 11:08

@qiuguohua qiuguohua left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is a well-structured implementation. The UUID-first resolution with name-based path construction is the right pattern, lenient mode for deletions is a good compromise, and the test coverage is thorough. Two minor items below.

return normalizedNodePath.slice(normalizedRootPath.length + 1);
}
return rootNode.getChildByPath(normalizedNodePath) ? normalizedNodePath : null;
return findRelativeNodePathByUuid(rootNode, targetUuid);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveOperationRelativeNodePath calls findRelativeNodePathByUuid but does not check hasSameNameSiblings like property-curve.ts does.

If same-name siblings exist, the returned name-based path (e.g. Enemy) is ambiguous — queryAnimationPropertyMetadata would resolve it via getChildByPath which picks the first match, potentially returning the wrong sibling's component metadata. This could lead to incorrect value normalization (e.g. loading the wrong asset type).

Not a data-corruption risk since the downstream curve operation in property-curve.ts still has the ambiguity guard, but the metadata query could silently use the wrong node.

@@ -91,23 +93,26 @@ function resolveOperationRelativeNodePath(
operation: { nodeUuid?: string; nodePath?: string },
options: { queryNodeByUuid: (uuid: string) => Node | null; queryNodePath: (node: Node) => string },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The options parameter (queryNodeByUuid, queryNodePath) is no longer used after the UUID-first rewrite — the old code called options.queryNodeByUuid(operation.nodeUuid) and options.queryNodePath(node), but the new code resolves entirely via getNodeByPath/getChildByPath + findRelativeNodePathByUuid.

This makes options dead code in both resolveOperationRelativeNodePath and its caller normalizeProvidedAnimationPropertyOperationValue. Consider removing it (and updating call sites) to avoid confusing future readers.

@qiuguohua qiuguohua changed the title feat(scene): decouple node name from path for animation binding feat(scene): decouple node name from path for animation binding. Jul 30, 2026

@knoxHuang knoxHuang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

[P1] Preserve the display name when creating duplicate nodes

File: src/core/scene/scene-process/service/node.ts:177-182

This writeback still couples the user-facing name to the generated system path. When a second node named Enemy is created, NodeMgr.getNodePath(resultNode) may return Enemy_001, and this assignment changes node.name to Enemy_001. That violates the name/path separation described in the PR. Please remove this writeback and add a regression test verifying that the second node keeps name === 'Enemy' while its system path is Enemy_001.

[P2] Keep new code comments in English

The repository TypeScript style requires non-documentation comments to be written in English. This change adds or modifies Chinese comments in the new tests and schema declaration. Please translate the changed comments to English to keep the diff consistent with the documented coding standard.

const pathPart = pathParts[i];
let nextNode = currentParent.getChildByName(pathPart);
const accumulatedPath = pathParts.slice(0, i + 1).join('/');
let nextNode = NodeMgr.getNodeByPath(accumulatedPath) as Node | null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve the display name when creating duplicate nodes. Although this change correctly resolves the accumulated system path, the create path still writes the generated path segment back to node.name at lines 177-182. When a second node named Enemy is created, the system path may be Enemy_001 and the display name is consequently changed to Enemy_001. Please remove that writeback and add a regression test verifying name === Enemy while the system path is Enemy_001.

});
});

describe('10. name 与 path 解耦 - 允许同名节点', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Please keep the newly added code comments in English. The repository TypeScript style requires non-documentation comments to be written in English, but this new test block adds Chinese comments. The same issue also appears in the changed node-manager tests and schema declaration.

nodeId: z.string().describe('Node ID'), // 节点的 id
path: z.string().describe('Parent node path, full node path is parent path + node name; root node path is "/"'), // 父节点路径,完整节点路径为父路径+节点名;根节点路径为 "/"
name: z.string().describe('Node name'), // 节点名称
path: z.string().describe('Node addressing path (unique), may differ from node name when siblings share the same display name; root node path is "/"'), // 节点寻址路径(唯一),同名兄弟节点时可能与 name 不同;根节点路径为 "/"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parent node path 去除后,pink上的 ai 可能会把这个path 当成是该节点的路径,而不是他的父节点路径。或者现在的path意义有变了?

@qiuguohua qiuguohua Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个接口是代表获取node的path把?不能返回parentNodePath了把?如果返回的是parentNodePath,那应该就没有path的这个接口了。 这样后续的接口调用就会有问题(因为可能是同名的节点, 无法通过parentNodePath+name的拼接来定义nodepath)。 如果有需求要返回parentNodePath,那可能需要加个当前路径的path返回值

Decouple node.name (display label) from system path (unique address) so
that sibling nodes can share the same name while retaining stable,
unique paths via NodePathManager.

Changes:
- Remove _updateNameCount; path segment allocation is now fully managed
  by NodePathManager
- Remove path-segment writeback to node.name after move operations
- Fix _ensurePathExists to resolve by accumulated path instead of
  getChildByName
- Fix node-proxy to throw on failed path resolution after rename
  instead of silently returning stale paths
- Rewrite animation resolveRelativeNodePath to UUID-first 3-step flow:
  resolve UUID, build name-based relative path, detect same-name
  sibling ambiguity via hasSameNameSiblings
- Add lenient mode for delete operations: skip ambiguity rejection but
  verify UUID match; fall back to string-based path extraction for
  orphan track cleanup when node no longer exists
- Rewrite property-value resolveOperationRelativeNodePath to UUID-first
  approach, replacing string-slicing that produced _NNN suffixed paths
  incompatible with engine getChildByPath

Tests:
- Add 12 animation ambiguity detection tests (same-name siblings
  blocked, lenient delete allowed, orphan track cleanup)
- Add 2 property-value path resolution tests (UUID and nodePath
  branches produce name-based paths)
- Add 4 node-proxy rename path resolution tests (success, uuid missing,
  path resolution failure, no-rename passthrough)
- Expand node-path-manager and node-for-editor test suites for
  name/path decoupling scenarios
…erty-value

resolveOperationRelativeNodePath stopped using the options parameter
(queryNodeByUuid, queryNodePath) after the UUID-first rewrite but kept
it in the signature. Remove it from the function, its caller, and all
call sites.

Add hasSameNameSiblings check to match property-curve.ts — without it,
same-name siblings could cause queryAnimationPropertyMetadata to resolve
the wrong node's component metadata.
@qiuguohua
qiuguohua force-pushed the cocos_main_component_name branch from 33fb1e1 to 0bfbace Compare August 6, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants