Skip to content

refactor(clone): type-driven default clone mode for container elements#3040

Draft
cptbtptpbcptdtptp wants to merge 12 commits into
galacean:dev/2.0from
cptbtptpbcptdtptp:fix/clone-opt-out-assignment
Draft

refactor(clone): type-driven default clone mode for container elements#3040
cptbtptpbcptdtptp wants to merge 12 commits into
galacean:dev/2.0from
cptbtptpbcptdtptp:fix/clone-opt-out-assignment

Conversation

@cptbtptpbcptdtptp

@cptbtptpbcptdtptp cptbtptpbcptdtptp commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Rework the clone system around a clear type-driven priority chain, so most objects clone correctly by their type with little to no per-field annotation.

How a field value is cloned — priority high → low

  1. Field decorator@deepClone / @assignmentClone / @ignoreClone (explicit per-field override).
  2. Container — Array / Map / Set / TypedArray / plain object → deep clone (each element re-enters the gate).
  3. Type default — the value type's @defaultCloneMode.
  4. FallbackAssignment (share the reference).

Primitives are copied by value; functions are skipped (the clone's constructor re-establishes bound handlers).

Type defaults (what most fields rely on)

  • ReferResource (Texture / Mesh / Material / Sprite / Font / …) → Assignment — assets are shared by reference.
  • Entity / ComponentRemap — rewired to the clone within the cloned subtree.
  • Math value types (Vector / Matrix / Quaternion / Color / Rect / Bounding* / …) → Deep — registered centrally in CloneManager (the math package can't depend on core's decorator).
  • Value-semantic data (RenderState, particle modules, joint config, PostProcess, ShaderData, Signal, …) → Deep.
  • UpdateFlagManagerIgnore — runtime notifier; the clone keeps its own.

Changes

  • Field decorators register as field-level modes (highest priority); CloneMode gains Ignore.
  • Removed 171 now-redundant @deepClone / @assignmentClone / @shallowClone field decorators — they're covered by the type-default network + container default; only @ignoreClone (and a few genuine overrides) remain.
  • srcRoot / targetRoot are threaded through the gate to _cloneTo, so types like Signal can remap entity/component references in their listeners.

Test plan

  • Clone unit tests across core (clone / entity / signal / camera / light / physics / particle / material / 2d …) pass
  • e2e visual regressions pass (Shadow / PhysX / GPUInstancing)

🤖 Generated with Claude Code

…ainer elements

- Add `@defaultCloneMode(mode)` class decorator for declaring how instances
  of a type should be cloned when encountered as field values
- Change container element cloning: array elements now use `undefined` as
  cloneMode so each element's type-level `_defaultCloneMode` is consulted.
  Unknown types (no _defaultCloneMode) default to Assignment (shared reference)
  instead of Deep, preventing crash on DOM/native objects.
- Add `_defaultCloneMode` to ICustomClone interface
- Mark RenderState system classes with @defaultCloneMode(Deep):
  DepthState, StencilState, RasterState, RenderTargetBlendState, BlendState, RenderState

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

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0aa89639-d6be-443a-988b-4776920d91e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a new defaultCloneMode(mode: CloneMode) class decorator that stores _defaultCloneMode on a class prototype. CloneManager.cloneProperty is updated to consult this type-level default when no per-field cloneMode is set, and array-element recursion passes undefined to allow the same fallback. Six shader render-state classes (BlendState, DepthState, RasterState, RenderState, RenderTargetBlendState, StencilState) are decorated with @defaultCloneMode(CloneMode.Deep).

Changes

Type-level Default Clone Mode

Layer / File(s) Summary
Decorator definition and ICustomClone contract
packages/core/src/clone/ComponentCloner.ts, packages/core/src/clone/CloneManager.ts
ICustomClone gains an optional readonly _defaultCloneMode?: CloneMode property. The defaultCloneMode exported decorator function is added to CloneManager, writing the mode onto the decorated class's prototype.
cloneProperty and array-element recursion updates
packages/core/src/clone/CloneManager.ts
cloneProperty reads _defaultCloneMode from the source object when no per-field mode is provided, applying Deep or Shallow accordingly. Array-element recursion now passes undefined for cloneMode so each element resolves its own type-level default.
Shader state classes decorated with @defaultCloneMode(Deep)
packages/core/src/shader/state/BlendState.ts, packages/core/src/shader/state/DepthState.ts, packages/core/src/shader/state/RasterState.ts, packages/core/src/shader/state/RenderState.ts, packages/core/src/shader/state/RenderTargetBlendState.ts, packages/core/src/shader/state/StencilState.ts
All six render-state classes import defaultCloneMode and CloneMode and are annotated with @defaultCloneMode(CloneMode.Deep).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • GuoLei1990

Poem

🐇 A decorator hops onto each class,
Deep clones assured as the states come to pass.
_defaultCloneMode whispers on the prototype's ear,
Arrays recurse without bias or fear.
No field-by-field marking needed anew —
The bunny stamped Deep and the whole state flew! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing type-driven default clone modes for container elements through the new @defaultCloneMode decorator.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@packages/core/src/clone/CloneManager.ts`:
- Around line 67-69: The defaultCloneMode decorator accepts any CloneMode value,
but the runtime logic at lines 141-149 only properly handles Deep and Shallow
modes, while Ignore is checked separately earlier at line 138. To fix this,
modify the defaultCloneMode function signature to restrict the mode parameter to
only accept CloneMode.Deep or CloneMode.Shallow (using a union type or
overload), preventing callers from incorrectly decorating with CloneMode.Ignore
which would not be honored at runtime.
🪄 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: 585fad6c-db8f-4c53-9512-ff3e147e9dde

📥 Commits

Reviewing files that changed from the base of the PR and between 5d74c1d and 79e9105.

📒 Files selected for processing (8)
  • packages/core/src/clone/CloneManager.ts
  • packages/core/src/clone/ComponentCloner.ts
  • packages/core/src/shader/state/BlendState.ts
  • packages/core/src/shader/state/DepthState.ts
  • packages/core/src/shader/state/RasterState.ts
  • packages/core/src/shader/state/RenderState.ts
  • packages/core/src/shader/state/RenderTargetBlendState.ts
  • packages/core/src/shader/state/StencilState.ts

Comment on lines +67 to +69
export function defaultCloneMode(mode: CloneMode) {
return function (target: Function): void {
Object.defineProperty(target.prototype, "_defaultCloneMode", { value: mode });

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 | 🟠 Major | ⚡ Quick win

@defaultCloneMode accepts modes that are not fully honored at runtime

At Line 67 the decorator accepts any CloneMode, but Lines 141-149 only promote Deep/Shallow, and the Ignore fast-path is checked earlier at Line 138. So @defaultCloneMode(CloneMode.Ignore) will not actually ignore the value.

Suggested fix
-    if (cloneMode === CloneMode.Ignore) return;
-
-    // If no per-field decorator, consult the value's type-level default clone mode
-    if (cloneMode === undefined && sourceProperty instanceof Object) {
-      const typeDefault = (<ICustomClone>sourceProperty)._defaultCloneMode;
-      if (typeDefault === CloneMode.Deep) {
-        cloneMode = CloneMode.Deep;
-      } else if (typeDefault === CloneMode.Shallow) {
-        cloneMode = CloneMode.Shallow;
-      }
-      // typeDefault === undefined or Assignment → fall through to assignment below
-    }
+    // If no per-field decorator, consult the value's type-level default clone mode
+    if (cloneMode === undefined && sourceProperty instanceof Object) {
+      cloneMode = (<ICustomClone>sourceProperty)._defaultCloneMode;
+    }
+    if (cloneMode === CloneMode.Ignore) return;

Also applies to: 138-149

🤖 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 `@packages/core/src/clone/CloneManager.ts` around lines 67 - 69, The
defaultCloneMode decorator accepts any CloneMode value, but the runtime logic at
lines 141-149 only properly handles Deep and Shallow modes, while Ignore is
checked separately earlier at line 138. To fix this, modify the defaultCloneMode
function signature to restrict the mode parameter to only accept CloneMode.Deep
or CloneMode.Shallow (using a union type or overload), preventing callers from
incorrectly decorating with CloneMode.Ignore which would not be honored at
runtime.

cptbtptpbcptdtptp and others added 7 commits June 17, 2026 19:31
…+ type-driven deep clone

Core changes:
- Clone is now opt-out: all enumerable fields are cloned unless @ignoreClone
- Default clone mode for unknown types is Assignment (shared reference) — safe
  for DOM elements, native handles, and any unrecognized constructor
- Types that need independent copies declare @defaultCloneMode(CloneMode.Deep)
- Identity-map based deep clone with cycle/shared-subgraph dedup
- 3-stage lifecycle: Construct → Populate (copyFrom or for...in) → Finalize (_cloneTo)
- Container elements (Array/Map/Set) go through the type-driven gate individually
- @deepClone/@assignmentClone/@shallowClone kept as no-op for backward compat
- @ignoreClone remains functional

RenderState classes marked @defaultCloneMode(Deep):
  DepthState, StencilState, RasterState, RenderTargetBlendState, BlendState, RenderState

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eep)

Particle system:
  ParticleGeneratorModule (+ all subclasses via inheritance),
  MainModule, BaseShape (+ all shape subclasses), ParticleGenerator,
  ParticleCompositeCurve, ParticleCurve, CurveKey,
  ParticleCompositeGradient, ParticleGradient, GradientColorKey,
  GradientAlphaKey, Burst

Post-process:
  PostProcessEffect (+ BloomEffect, TonemappingEffect via inheritance),
  PostProcessEffectParameter (+ Float/Bool/Color/Vector/Enum/Texture)

Physics:
  JointLimits, JointMotor

Other:
  VirtualCamera, Skin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mark Entity/Component with @defaultCloneMode(Remap) and split Entity.clone() into a
register-then-copy two-pass: pre-register every source Entity/Component to its clone in
the identity map across the whole subtree, so references nested in arrays/maps/objects
are remapped through the clone gate, not just top-level fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Re-register @deepClone/@assignmentClone/@ignoreClone as field-level modes (highest priority).
- Containers (Array/Map/Set/TypedArray/plain object) default to deep clone.
- Type defaults: ReferResource -> Assignment, math types -> Deep, UpdateFlagManager -> Ignore.
- Thread srcRoot/targetRoot through the gate so Signal._cloneTo can remap listeners.
- Add CloneMode.Ignore; drop erroneous @deepClone on Joint limits/motor _updateFlagManager.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…efaults

- Remove @deepClone/@assignmentClone/@shallowClone field decorators (171) across core & ui.
- Fields resolve via container default deep + type-level @defaultCloneMode + Assignment fallback.
- Mark ShaderData & Signal Deep; ShaderData adds _cloneTo delegating to cloneTo.
- Clean up now-unused clone decorator imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t-assignment

# Conflicts:
#	packages/core/src/particle/ParticleGenerator.ts
…l run

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.40559% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.44%. Comparing base (4967220) to head (3cdd18c).
⚠️ Report is 1 commits behind head on dev/2.0.

Files with missing lines Patch % Lines
packages/core/src/clone/CloneManager.ts 90.43% 24 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #3040      +/-   ##
===========================================
- Coverage    79.49%   79.44%   -0.05%     
===========================================
  Files          919      919              
  Lines       102523   102651     +128     
  Branches     11364     8696    -2668     
===========================================
+ Hits         81500    81555      +55     
- Misses       20837    20913      +76     
+ Partials       186      183       -3     
Flag Coverage Δ
unittests 79.44% <94.40%> (-0.05%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 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.

cptbtptpbcptdtptp and others added 4 commits June 23, 2026 15:40
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…low)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant