Skip to content
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
79e9105
refactor(clone): add @defaultCloneMode and type-driven clone for cont…
Jun 17, 2026
a22e83c
refactor(clone): rewrite clone system — opt-out + default Assignment …
Jun 17, 2026
afce33d
refactor(clone): mark value-semantic classes with @defaultCloneMode(D…
Jun 17, 2026
94e6a03
fix(clone): remap nested Entity/Component refs via two-pass clone
Jun 22, 2026
a0e5fa9
refactor(clone): three-tier mode resolution + container default deep
Jun 22, 2026
297508f
refactor(clone): drop 171 field clone-mode decorators, rely on type d…
Jun 22, 2026
c5d6257
Merge remote-tracking branch 'upstream/dev/2.0' into fix/clone-opt-ou…
Jun 23, 2026
00498a1
ci: shard codecov unit tests (4 shards) to avoid browser crash on ful…
Jun 23, 2026
1561b72
docs(clone): drop CLONE_OPT_IN_AUDIT.md; design moved to PR description
Jun 23, 2026
d196914
ci: split codecov into 8 shards (vitest browser WebGL resource ceiling)
Jun 23, 2026
3cdd18c
test(clone): regression for texture refCount balance on clone/destroy
Jun 23, 2026
b8e754c
ci: revert codecov sharding experiment (let GitHub run the original f…
Jun 23, 2026
2139275
refactor(clone): decouple ref counting from the clone gate and unify …
Jul 2, 2026
13f3d95
Merge branch 'dev/2.0' into fix/clone-opt-out-assignment
Jul 2, 2026
9c5469c
refactor(clone): slot-ownership ref counting — gate acquires, owner r…
Jul 3, 2026
c2790dd
test(clone): refCount lifecycle suites + fix particle MeshShape mesh …
Jul 3, 2026
60135e9
refactor(clone)!: remove the deprecated shallowClone decorator
Jul 3, 2026
2a4309e
style(clone): order CloneManager members by visibility
Jul 3, 2026
88625db
refactor(clone): exempt user scripts from gate ref counting
Jul 3, 2026
2239fbd
refactor(clone): one uniform slot contract — no script special-casing
Jul 3, 2026
c7df2af
refactor(clone): move slot-ownership acquisition out of the gate
Jul 3, 2026
84e1b76
docs(clone): clarify the @deepClone-on-Remap branch is error recovery
Jul 3, 2026
3c26c2a
docs(clone): condense the CloneManager class comment
Jul 3, 2026
5b8da5a
test(clone): pin aliasing topology — shared instance clones into one …
Jul 3, 2026
e5894ae
refactor(clone): drop _cloneTo logic the type-driven gate already covers
Jul 3, 2026
8ef2a64
fix(mesh): type _onSkinUpdated's value as the union it actually carries
Jul 3, 2026
158fbdc
refactor(math): add a MathValue base class carrying the family clone …
Jul 3, 2026
1be50b0
Revert "refactor(math): add a MathValue base class carrying the famil…
Jul 3, 2026
5e3738d
test(clone): guard math value-type registration completeness
Jul 3, 2026
7dba7c1
feat(math): implement IClone/ICopy on Ray and register it Deep
Jul 3, 2026
de460e8
fix(clone): treat null-prototype objects as data containers
Jul 3, 2026
6c4cbb7
fix(clone): @deepClone on a registered asset recovers to sharing
Jul 3, 2026
56fb595
docs(clone): tighten comments in CloneManager and ComponentCloner
Jul 3, 2026
6a8688e
feat(clone): register runtime containers @defaultCloneMode(Ignore)
Jul 3, 2026
a3510d4
refactor(clone): drop @ignoreClone on runtime-container typed fields
Jul 3, 2026
887bfcc
docs(clone): define Deep as structure-fresh, member-semantic cloning
Jul 3, 2026
83e8328
docs(clone): correct stale or vacuous comments in gate and cloner
Jul 3, 2026
bdcc635
refactor(ui): centralize transition state ref counting in the base class
Jul 3, 2026
81c7991
test(math): cover Ray clone and copyFrom
Jul 3, 2026
e696681
fix(clone): release an owned counted preset displaced by an uncounted…
Jul 3, 2026
b0064ab
fix(clone): register typed-array clones in the identity map and harde…
Jul 3, 2026
d095812
fix(clone): route copyFrom dispatch after containers and close review…
Jul 3, 2026
6d0c7fe
test(clone): cover the rewritten Skin, PostProcess and TrailRenderer …
Jul 3, 2026
78ec199
docs(clone): align clone guides with the type-driven mechanism
Jul 3, 2026
9fa3cc2
refactor(clone): settle slot ownership unconditionally and fold dupli…
Jul 4, 2026
23f0459
test(clone): rename the suite after CloneManager and fix stale attrib…
Jul 4, 2026
2a91917
perf(clone): iterate Map/Set with for-of to avoid per-clone closure a…
Jul 4, 2026
c13dd08
docs(clone): drop a change-narrating comment on the Map branch
Jul 4, 2026
6d724c7
docs(clone): drop tense-relative wording from a lifecycle test comment
Jul 4, 2026
828008f
fix(particle): allow bare construction of ParticleCompositeGradient
Jul 4, 2026
16d95d6
test(clone): enforce the bare-construction contract for Deep-register…
Jul 4, 2026
f58d380
feat(clone): name the bare-construction contract when a deep clone ca…
Jul 4, 2026
843ab78
test(clone): pin the host-bound-in-container rejection as intended se…
Jul 4, 2026
9db6c66
test(clone): pin both host-bound-in-container behaviors by component …
Jul 4, 2026
f8ad0d7
fix(shader): cascade texture-array entries through the ref-count cont…
Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 61 additions & 15 deletions docs/en/core/clone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const cloneEntity = entity.clone();
```

## Script Cloning
Scripts are essentially components, so when we call the entity's [clone()](/apis/design/#IClone-clone) function, the engine will not only clone the built-in components but also clone custom scripts. The cloning rules for built-in components have been customized by the official team, and similarly, we have also opened up the cloning capabilities and rules for scripts to developers. The default cloning method for script fields is shallow copy. For example, if we modify the field values of the script and then clone it, the cloned script will retain the modified values without any additional coding. Below is an example of custom script cloning:
Scripts are essentially components, so when we call the entity's [clone()](/apis/design/#IClone-clone) function, the engine will not only clone the built-in components but also clone custom scripts. The cloning rules for built-in components have been customized by the official team, and similarly, we have also opened up the cloning capabilities and rules for scripts to developers. Script fields are cloned automatically, and how each field value is cloned is resolved by the value's type (see the default rules below). For example, if we modify the field values of the script and then clone it, the cloned script will retain the modified values without any additional coding. Below is an example of custom script cloning:
```typescript
// define a custom script
class CustomScript extends Script{
Expand All @@ -40,19 +40,62 @@ const cloneEntity = entity.clone();
const cloneScript = cloneEntity.getComponent(CustomScript);
console.log(cloneScript.a); // output is true.
console.log(cloneScript.b); // output is 2.
console.log(cloneScript.c); // output is (1,1,1).
console.log(cloneScript.c); // output is (1,1,1), and it is an independent copy — Vector3 is a math value type and is deep cloned by default.
```
### Default Cloning Rules
When a field has no clone decorator, the engine resolves how to clone its value by the value's type:

| Value Type | Default Cloning Behavior |
| :--- | :--- |
| Primitive types (`number`, `string`, `boolean`, ...) | Copy the value. |
| Assets (`Texture`, `Mesh`, `Material`, `Sprite`, `Font` and other `ReferResource` types) | Share the reference — the clone uses the same asset as the source. |
| `Entity` / `Component` references | Automatically remapped to the corresponding clone within the cloned subtree; references pointing outside the subtree keep the original reference. |
| Math value types (`Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Matrix`, `Color`, ...) and other value-semantic data | Deep cloned — the clone gets a fully independent copy. |
| Containers (`Array`, `Map`, `Set`, TypedArray, `DataView`, plain objects) | Deep cloned — a fresh container is created, and each member is cloned again according to its own type semantics. |
| Runtime containers (internal transient state such as `UpdateFlagManager`) | Ignored — the clone keeps its own constructor-built value. |
| Other objects | Share the reference (assignment). |

So without any decorator, plain data objects and arrays get independent deep copies, assets remain shared, and entity references are automatically remapped:
```typescript
// define a custom script
class CustomScript extends Script{
/** Entity reference.*/
target: Entity;

/** Plain data object.*/
config = { speed: 1, offsets: [0, 1, 2] };

/** Asset.*/
texture: Texture2D;
}

// Init entity and script
const entity = engine.createEntity();
const child = entity.createChild("child");
const script = entity.addComponent(CustomScript);
script.target = child;

// Clone logic
const cloneEntity = entity.clone();
const cloneScript = cloneEntity.getComponent(CustomScript);
console.log(cloneScript.target === cloneEntity.findByName("child")); // output is true, entity references are remapped to the corresponding clone in the cloned subtree.
console.log(cloneScript.config === script.config); // output is false, plain data objects are deep cloned into independent copies.
console.log(cloneScript.texture === script.texture); // output is true, assets are shared between the source and the clone.
```
### Clone Decorators
In addition to the default cloning method, the engine also provides "clone decorators" to customize the cloning method for script fields. The engine has four built-in clone decorators:
In addition to the default type-driven rules, the engine also provides "clone decorators" to customize the cloning method for script fields. Field decorators have the highest priority — they override the value type's default cloning behavior and take effect at any depth of the cloned object graph. The engine has three built-in clone decorators:

| Decorator Name | Decorator Description |
| :--- | :--- |
| [ignoreClone](/apis/core/#ignoreClone) | Ignore the field during cloning. |
| [assignmentClone](/apis/core/#assignmentClone) | (Default value, equivalent to not adding any clone decorator) Assign the field during cloning. If it is a basic type, the value will be copied; if it is a reference type, the reference address will be copied. |
| [shallowClone](/apis/core/#shallowClone) | Shallow clone the field during cloning. After cloning, it will maintain its own independent reference and clone all its internal fields by assignment (if the internal field is a basic type, the value will be copied; if the internal field is a reference type, the reference address will be copied). |
| [deepClone](/apis/core/#deepClone) | Deep clone the field during cloning. After cloning, it will maintain its own independent reference, and all its internal deep fields will remain completely independent. |
| [ignoreClone](/apis/core/#ignoreClone) | Ignore the field during cloning; the clone keeps its own constructor-built value. |
| [assignmentClone](/apis/core/#assignmentClone) | Assign the field during cloning. If it is a basic type, the value will be copied; if it is a reference type, the clone will share the reference with the source. |
| [deepClone](/apis/core/#deepClone) | Deep clone the field during cloning. The field gets a fresh, independent structure, and each inner member is cloned again according to its own semantics. Engine-bound values cannot be deep cloned: `Entity` / `Component` references fall back to remapping with a warning, and assets fall back to sharing with a warning. |

<Callout type="negative">
`@shallowClone` has been removed. Its old semantics — copy the container itself but share its members — no longer exist. Migrate by intent: use `@deepClone` if the field should be independent, or `@assignmentClone` if it should be shared.
</Callout>

We slightly modify the above example and add different "clone decorators" to the four fields in `CustomScript`. Since `shallowClone` and `deepClone` are more complex, we add additional print output to the fields `c` and `d` for further explanation.
We slightly modify the above example and add different "clone decorators" to the four fields in `CustomScript`. Since arrays are already deep cloned by default, we use `assignmentClone` on field `c` to force sharing, and `deepClone` on field `d` to make the default behavior explicit, with additional print output for further explanation.
```typescript
// define a custom script
class CustomScript extends Script{
Expand All @@ -65,7 +108,7 @@ class CustomScript extends Script{
b:number = 1;

/** class type.*/
@shallowClone
@assignmentClone
c:Vector3[] = [new Vector3(0,0,0)];

/** class type.*/
Expand All @@ -84,20 +127,23 @@ script.d[0].set(1,1,1);
// Clone logic
const cloneEntity = entity.clone();
const cloneScript = cloneEntity.getComponent(CustomScript);
console.log(cloneScript.a); // output is false,ignoreClone will ignore the value.
console.log(cloneScript.a); // output is false,ignoreClone keeps the clone's own constructor-built value.
console.log(cloneScript.b); // output is 2,assignmentClone is just assignment the origin value.
console.log(cloneScript.c[0]); // output is Vector3(1,1,1),shallowClone clone the array shell,but use the same element.
console.log(cloneScript.c[0]); // output is Vector3(1,1,1),assignmentClone shares the same array instance with the source.
console.log(cloneScript.d[0]); // output is Vector3(1,1,1),deepClone clone the array shell and also clone the element.

cloneScript.c[0].set(2,2,2); // change the field c[0] value to (2,2,2).
cloneScript.d[0].set(2,2,2); // change the field d[0] value to (2,2,2).

console.log(script.c[0]); // output is (2,2,2). bacause shallowClone let c[0] use the same reference with cloneScript's c[0].
console.log(script.c[0]); // output is (2,2,2). bacause assignmentClone let c use the same array reference with cloneScript's c.
console.log(script.d[0]); // output is (1,1,1). bacause deepClone let d[0] use the different reference with cloneScript's d[0].
Comment on lines +138 to 139

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Typo: "bacause" → "because".

✏️ Proposed fix
-console.log(script.c[0]); // output is (2,2,2). bacause assignmentClone let c use the same array reference with cloneScript's c.
-console.log(script.d[0]); // output is (1,1,1). bacause deepClone let d[0] use the different reference with cloneScript's d[0].
+console.log(script.c[0]); // output is (2,2,2). because assignmentClone let c use the same array reference with cloneScript's c.
+console.log(script.d[0]); // output is (1,1,1). because deepClone let d[0] use the different reference with cloneScript's d[0].
📝 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
console.log(script.c[0]); // output is (2,2,2). bacause assignmentClone let c use the same array reference with cloneScript's c.
console.log(script.d[0]); // output is (1,1,1). bacause deepClone let d[0] use the different reference with cloneScript's d[0].
console.log(script.c[0]); // output is (2,2,2). because assignmentClone let c use the same array reference with cloneScript's c.
console.log(script.d[0]); // output is (1,1,1). because deepClone let d[0] use the different reference with cloneScript's d[0].
🤖 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 `@docs/en/core/clone.mdx` around lines 138 - 139, The typo in the example
comments uses “bacause” instead of “because”; update the inline explanatory text
in the clone documentation example so the wording is correct and consistent. Fix
the two comment lines associated with the script.c and script.d examples,
keeping the rest of the example unchanged.

```
- Note:

- `shallowClone` and `deepClone` are usually used for *Object*, *Array*, and *Class* types.
- `shallowClone` will maintain its own independent reference after cloning and clone all its internal fields by assignment (if the internal field is a basic type, the value will be copied; if the internal field is a reference type, the reference address will be copied).
- `deepClone` is a deep clone that will recursively clone the properties deeply. How the sub-properties of the properties are cloned depends on the decorators of the sub-properties.
- Field decorators have the highest priority and also take effect on the fields of nested classes at any depth of the cloned object graph.
- `deepClone` recursively clones the structure, while each inner member is still cloned according to its own semantics: assets stay shared, entity references are remapped, and nested field decorators still apply.
- `deepClone` cannot deep clone engine-bound objects: `Entity` / `Component` references fall back to remapping and assets fall back to sharing, both with a warning. If you need a real copy of an asset, use the asset's own clone API.
- If the clone decorators do not meet the requirements, you can implement the [_cloneTo()](/apis/design/#IClone-cloneTo) method to add custom cloning.

## Cloning and Asset Reference Counting
When a component's top-level field shares a ref-counted asset (a `ReferResource` such as `Texture`, `Mesh`, or `Material`) during cloning, the engine automatically adds one reference for the clone. The class holding the field is responsible for releasing that reference when it is destroyed — built-in components already handle this; for custom scripts, release the resources you hold in `onDestroy`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 2 additions & 2 deletions docs/en/how-to-contribute.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class CustomScript extends Script {
a:boolean = false;
@assignmentClone
b:number = 1;
@shallowClone
@deepClone
c:Vector3[] = [new Vector3(0,0,0)];
}
```
Expand All @@ -133,7 +133,7 @@ class CustomScript extends Script{
a:boolean = false;
@assignmentClone
b:number = 1;
@shallowClone
@deepClone
c:Vector3[] = [new Vector3(0,0,0)];
}

Expand Down
Loading
Loading