Skip to content

fix(camera): mark replacement shader fields as assignment clone#2988

Open
cptbtptpbcptdtptp wants to merge 2 commits into
galacean:dev/2.0from
cptbtptpbcptdtptp:pr/camera-replacement-clone-mode
Open

fix(camera): mark replacement shader fields as assignment clone#2988
cptbtptpbcptdtptp wants to merge 2 commits into
galacean:dev/2.0from
cptbtptpbcptdtptp:pr/camera-replacement-clone-mode

Conversation

@cptbtptpbcptdtptp
Copy link
Copy Markdown
Collaborator

@cptbtptpbcptdtptp cptbtptpbcptdtptp commented May 11, 2026

Summary

  • Add @assignmentClone to _replacementShader and _replacementSubShaderTag so cloned cameras share the same shader/tag reference instead of deep cloning them.

Background

Extracted from ca5252a9a on fix/shaderlab. Without this, cloning a camera that has a replacement shader set would deep-clone the Shader object, creating an unnecessary duplicate.

Test plan

  • New test case: clone preserves replacement shader by reference
  • CI green

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed camera component duplication to properly maintain shader state during cloning, ensuring consistent behavior when duplicating camera instances.

Review Change Stack

cptbtptpbcptdtptp and others added 2 commits May 11, 2026 18:22
Add @assignmentClone to _replacementShader and _replacementSubShaderTag
so cloned cameras share the same shader/tag reference instead of deep
cloning them (which would create unnecessary duplicates).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Walkthrough

Camera's internal replacement-shader state (_replacementShader and _replacementSubShaderTag) is now decorated with @assignmentClone, ensuring these fields preserve their object references during component cloning. Import statements and test coverage are updated accordingly.

Changes

Replacement Shader Clone Preservation

Layer / File(s) Summary
Import decorator
packages/core/src/Camera.ts
CloneManager imports include assignmentClone function alongside existing clone utilities.
Apply decorator to fields
packages/core/src/Camera.ts
@assignmentClone decorator applied to _replacementShader and _replacementSubShaderTag, changing clone semantics for these internal fields.
Test coverage
tests/src/core/Camera.test.ts
Existing clone test assertions reformatted for consistency; new test validates cloned camera retains shader object reference and tag value when replacement shaders are set.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A shader shines so bright,
Clone it right, preserve the light!
With decorators, references stay true,
Two fields now know just what to do. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding @assignmentClone decorator to camera replacement shader fields to fix cloning behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/src/core/Camera.test.ts`:
- Around line 432-453: Replace the string assignment and string comparison with
a proper ShaderTagKey: create a ShaderTagKey via
ShaderTagKey.getByName("TestTag"), assign that to
camera._replacementSubShaderTag (instead of the raw string), and update the
assertion to compare the cloned camera's _replacementSubShaderTag to the same
ShaderTagKey (or compare their names via .getName()) so types and semantics
match; references: camera._replacementSubShaderTag, ShaderTagKey.getByName,
cloneCamera.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1688ce5f-7744-4d70-9cf9-41db4bc2fbf8

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc2b10 and 9845234.

📒 Files selected for processing (2)
  • packages/core/src/Camera.ts
  • tests/src/core/Camera.test.ts

Comment on lines +432 to +453
it("clone preserves replacement shader by reference (assignmentClone)", () => {
const shader = Shader.create("TestCloneReplaceShader", [
new SubShader("Default", [new ShaderPass("Default", [], [], ShaderLanguage.GLSLES100)])
]);

// @ts-ignore
camera._replacementShader = shader;
// @ts-ignore
camera._replacementSubShaderTag = "TestTag";

const cloneCamera = camera.entity.clone().getComponent(Camera);

// @ts-ignore - assignmentClone copies reference, not deep clone
expect(cloneCamera._replacementShader).to.eq(shader);
// @ts-ignore
expect(cloneCamera._replacementSubShaderTag).to.eq("TestTag");

// @ts-ignore - cleanup
camera._replacementShader = null;
// @ts-ignore
camera._replacementSubShaderTag = null;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix type violations in replacement shader tag assignment and assertion.

The test has two critical correctness issues:

  1. Line 440: Assigns a string "TestTag" directly to _replacementSubShaderTag, but this field is typed as ShaderTagKey, not string. While @ts-ignore suppresses the type error, this violates the actual type contract.

  2. Line 447: Compares _replacementSubShaderTag to the string "TestTag". If line 440 is corrected to use a proper ShaderTagKey object, this assertion will fail since you'd be comparing an object to a string.

The proper usage pattern is shown in Camera.ts line 719: ShaderTagKey.getByName(replacementTag).

🔧 Proposed fix using ShaderTagKey.getByName()
+  const testTag = ShaderTagKey.getByName("TestTag");
+
   // `@ts-ignore`
   camera._replacementShader = shader;
   // `@ts-ignore`
-  camera._replacementSubShaderTag = "TestTag";
+  camera._replacementSubShaderTag = testTag;

   const cloneCamera = camera.entity.clone().getComponent(Camera);

   // `@ts-ignore` - assignmentClone copies reference, not deep clone
   expect(cloneCamera._replacementShader).to.eq(shader);
   // `@ts-ignore`
-  expect(cloneCamera._replacementSubShaderTag).to.eq("TestTag");
+  expect(cloneCamera._replacementSubShaderTag).to.eq(testTag);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("clone preserves replacement shader by reference (assignmentClone)", () => {
const shader = Shader.create("TestCloneReplaceShader", [
new SubShader("Default", [new ShaderPass("Default", [], [], ShaderLanguage.GLSLES100)])
]);
// @ts-ignore
camera._replacementShader = shader;
// @ts-ignore
camera._replacementSubShaderTag = "TestTag";
const cloneCamera = camera.entity.clone().getComponent(Camera);
// @ts-ignore - assignmentClone copies reference, not deep clone
expect(cloneCamera._replacementShader).to.eq(shader);
// @ts-ignore
expect(cloneCamera._replacementSubShaderTag).to.eq("TestTag");
// @ts-ignore - cleanup
camera._replacementShader = null;
// @ts-ignore
camera._replacementSubShaderTag = null;
});
it("clone preserves replacement shader by reference (assignmentClone)", () => {
const shader = Shader.create("TestCloneReplaceShader", [
new SubShader("Default", [new ShaderPass("Default", [], [], ShaderLanguage.GLSLES100)])
]);
const testTag = ShaderTagKey.getByName("TestTag");
// `@ts-ignore`
camera._replacementShader = shader;
// `@ts-ignore`
camera._replacementSubShaderTag = testTag;
const cloneCamera = camera.entity.clone().getComponent(Camera);
// `@ts-ignore` - assignmentClone copies reference, not deep clone
expect(cloneCamera._replacementShader).to.eq(shader);
// `@ts-ignore`
expect(cloneCamera._replacementSubShaderTag).to.eq(testTag);
// `@ts-ignore` - cleanup
camera._replacementShader = null;
// `@ts-ignore`
camera._replacementSubShaderTag = null;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/core/Camera.test.ts` around lines 432 - 453, Replace the string
assignment and string comparison with a proper ShaderTagKey: create a
ShaderTagKey via ShaderTagKey.getByName("TestTag"), assign that to
camera._replacementSubShaderTag (instead of the raw string), and update the
assertion to compare the cloned camera's _replacementSubShaderTag to the same
ShaderTagKey (or compare their names via .getName()) so types and semantics
match; references: camera._replacementSubShaderTag, ShaderTagKey.getByName,
cloneCamera.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.30%. Comparing base (1bc2b10) to head (9845234).

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2988      +/-   ##
===========================================
+ Coverage    78.25%   78.30%   +0.04%     
===========================================
  Files          900      900              
  Lines        99234    99236       +2     
  Branches     10172    10192      +20     
===========================================
+ Hits         77657    77705      +48     
+ Misses       21406    21360      -46     
  Partials       171      171              
Flag Coverage Δ
unittests 78.30% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

@GuoLei1990 GuoLei1990 mentioned this pull request May 11, 2026
3 tasks
GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

所有历史问题均已关闭。

总结

Camera._replacementShader_replacementSubShaderTag 添加 @assignmentClone,修复 clone camera 时对 Shader 全局资源做不必要深拷贝的问题。改动精准,方向正确,测试验证了 clone 后引用相等性。

[P2] Camera.test.ts — 测试用裸字符串 "TestTag" 赋值给 _replacementSubShaderTag(类型应为 ShaderTagKey

当前测试通过 @ts-ignore 压制类型错误,验证的是字符串的 reference 语义,无法反映真实使用中 ShaderTagKey 对象的 clone 行为。建议改为:

import { ShaderTagKey } from "@galacean/engine-core";
const tagKey = ShaderTagKey.getByName("TestTag");
// @ts-ignore
camera._replacementSubShaderTag = tagKey;
// ...
// @ts-ignore
expect(cloneCamera._replacementSubShaderTag).to.eq(tagKey);

不阻塞合并,LGTM。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

所有历史问题均已关闭。

总结

Camera._replacementShader_replacementSubShaderTag 两个字段加 @assignmentClone,修复克隆时对 Shader 对象的无意义深克隆。测试验证了 clone 后引用保持一致。修复点精准,代码改动最小。

无新问题。可以合并。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

单行修复:给 Camera 的 _replacementShader_replacementSubShaderTag@assignmentClone,避免 clone 时深拷贝 Shader 对象。修复正确,测试覆盖了 reference equality。无问题,LGTM。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

修复方向正确。Shader 是引擎共享资产,克隆时应保持引用相同而非创建空壳。加 @assignmentClone 语义明确,测试覆盖完整。

问题

[P2] _replacementFailureStrategy 同样无装饰器,需确认类型

_replacementFailureStrategy: ReplacementFailureStrategy = null 没有装饰器。若 ReplacementFailureStrategy 是 TypeScript enum(数字),sourceProperty instanceof Object 判断为 false,走直接赋值,行为正确;若是 class/object,则在 PR #2992_inferCloneMode 引入之前需要补 @assignmentClone。请确认类型或补加装饰器。

[P2] 需对齐与 PR #2992 的合并顺序

PR #2992 引入 _inferCloneMode,其 fallback 已能正确处理 class 实例的 assignment 语义(Shader 会走 Assignment 路径)。若 #2992 先于本 PR 合入,本 PR 的修复成为"防御性标注"(非必需但有益于可读性);若本 PR 先合入,#2992 合入后需回归验证不冲突(理论上显式装饰器优先于推断,不存在冲突)。两个 PR 需对齐合并顺序。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

为 Camera._replacementShader 和 _replacementSubShaderTag 两个字段加 @assignmentClone,修复克隆时对 Shader 全局资源做不必要深克隆的问题。Shader 是引擎全局单例(通过 Shader.find(name) 统一管理),clone 时应共享引用而非创建空壳。测试验证了 clone 后引用相等性。

问题

[P2] Camera.test.ts 中 _replacementSubShaderTag 赋字符串而非 ShaderTagKey

测试直接将字符串 "TestTag" 赋给 _replacementSubShaderTag,该字段类型为 ShaderTagKey,通过 @ts-ignore 压制类型错误。这能验证字符串的引用保持,但无法反映真实使用中 ShaderTagKey 对象的 clone 行为。建议改为:

const tagKey = ShaderTagKey.getByName("TestTag");
// @ts-ignore
camera._replacementSubShaderTag = tagKey;
// @ts-ignore
expect(cloneCamera._replacementSubShaderTag).to.eq(tagKey);

P2,不阻塞合并。整体修复精准干净。LGTM。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

所有历史问题均已关闭。

总结

Camera._replacementShader_replacementSubShaderTag 添加 @assignmentClone,修复 clone 时对 Shader 全局资产做不必要深克隆的问题。Shader 通过 Shader.find(name) 统一管理,clone 时应共享引用。测试验证了 clone 后引用相等性。

[P2] Camera.ts:134_replacementFailureStrategy 无装饰器,确认正确

_replacementFailureStrategy: ReplacementFailureStrategy = nullReplacementFailureStrategy 是 enum(本质为 number),原始类型会被 cloneProperty 直接 assign,无需装饰器。已确认,行为正确。

[P2] Camera.test.ts:442_replacementSubShaderTag 赋字符串而非 ShaderTagKey

建议改为 ShaderTagKey.getByName("TestTag") 以测试真实对象类型的 reference equality(而非字符串,通过 === 比较时字符串字面量可能因 interning 而意外通过)。不阻塞合并。

无新 P0/P1,LGTM,可合入。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

Camera._replacementShader_replacementSubShaderTag 添加 @assignmentClone 装饰器,避免 clone 时深拷贝 Shader 对象。修复精准,改动最小(2 行 + 1 个测试),语义正确:Shader 是引擎全局注册的共享资产,clone 时保持同一引用是正确行为。

问题

无 P0/P1,LGTM,可合入。

[P3] 测试用 new SubShader(...) / new ShaderPass(...) 直接构造 Shader 对象验证 assignment,逻辑没有问题,但若后续 ShaderPass 构造签名变化测试会随之失效,如果有 Shader.find(name) 可用的简单 fixture 会更稳定(非阻塞)。

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.

2 participants