Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GUI #2375

Open
wants to merge 229 commits into
base: dev/1.4
Choose a base branch
from
Open

Add GUI #2375

wants to merge 229 commits into from

Conversation

singlecoder
Copy link
Member

@singlecoder singlecoder commented Sep 12, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new UIUtils class for managing UI rendering.
    • Added UICanvas, UIGroup, and UIRenderer classes for UI component management.
    • Implemented interactive components like Button, Image, and Label.
    • Added transition classes for UI elements, including ColorTransition, ScaleTransition, and SpriteTransition.
    • New enums for canvas rendering modes and resolution adaptation strategies.
    • Added IElement and IGroupAble interfaces to enhance UI element management.
  • Bug Fixes

    • Improved event handling for UI interactions in UIPointerEventEmitter.
  • Documentation

    • Updated README and various interface descriptions for clarity.
  • Chores

    • Introduced TypeScript configuration for the UI package.

Copy link

@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: 6

🧹 Outside diff range and nitpick comments (27)
packages/core/src/input/pointer/Pointer.ts (2)

55-59: LGTM: New _addEmitters method. Consider minor readability improvement.

The new _addEmitters method aligns well with the updated event handling mechanism. The use of generics ensures type safety. To slightly improve readability, consider using a more descriptive parameter name for the emitter type:

- _addEmitters<T extends new (pool: ClearableObjectPool<PointerEventData>) => PointerEventEmitter>(
-   type: T,
+ _addEmitters<EmitterType extends new (pool: ClearableObjectPool<PointerEventData>) => PointerEventEmitter>(
+   emitterType: EmitterType,
    pool: ClearableObjectPool<PointerEventData>
  ) {
-   this._emitters.push(new type(pool));
+   this._emitters.push(new emitterType(pool));
  }

This change makes it clearer that the type parameter represents an emitter type.


82-93: LGTM: New enums. Consider using const enum for better performance.

The new EmitterType and PointerEventType enums are good additions, providing clear definitions for emitter types and pointer event states. To potentially improve performance and ensure consistency with other enums in the codebase, consider using const enum:

- export enum EmitterType {
+ export const enum EmitterType {
    Physics = 1,
    UI = 2
  }

- export enum PointerEventType {
+ export const enum PointerEventType {
    None = 0x0,
    Down = 0x1,
    Up = 0x2,
    Leave = 0x4,
    Move = 0x8,
    Cancel = 0x10
  }

Using const enum can lead to more optimized code as the enum values are inlined at compile time. However, please ensure this is consistent with the project's coding standards and requirements.

packages/core/src/ui/UIUtils.ts (4)

15-152: Consider the trade-offs of using a static class for UI utilities

The UIUtils class contains only static members, which aligns with its purpose as a utility class. While some style guides suggest avoiding classes with only static members, in this context, it provides a clear namespace for UI-related utility functions. This approach prevents global namespace pollution and groups related functionalities.

However, if you decide to refactor in the future, consider these alternatives:

  1. Use a namespace instead of a class.
  2. Export individual functions from a module.

These alternatives could improve tree-shaking and potentially make the code more modular. The current implementation is acceptable, but keep these options in mind for future refactoring.

🧰 Tools
🪛 Biome

[error] 15-152: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 51-51: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


45-64: Improve readability in registerUIToCanvas

The registerUIToCanvas method effectively manages the association between UI elements and canvases. However, the assignment within the expression on line 51 can be improved for better readability.

Consider refactoring line 51 as follows:

-replaced && (replaced._indexInCanvas = element._indexInCanvas);
+if (replaced) {
+  replaced._indexInCanvas = element._indexInCanvas;
+}

This change separates the assignment from the logical check, making the code easier to read and maintain.

🧰 Tools
🪛 Biome

[error] 51-51: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


66-82: Improve readability in registerUIToGroup

The registerUIToGroup method effectively manages the association between UI elements and groups. However, similar to the registerUIToCanvas method, there's an opportunity to improve readability on line 72.

Consider refactoring line 72 as follows:

-replaced && (replaced._indexInGroup = element._indexInGroup);
+if (replaced) {
+  replaced._indexInGroup = element._indexInGroup;
+}

This change separates the assignment from the logical check, enhancing code clarity and maintainability.

🧰 Tools
🪛 Biome

[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


128-151: Optimize and clarify the render method

The render method effectively handles the rendering of UI canvases. However, there are a few areas that can be improved for clarity and consistency:

  1. The use of this in a static context (lines 130-131) can be confusing.
  2. The assignments within expressions (lines 130-131) reduce readability.

Consider refactoring lines 130-131 as follows:

-const uiRenderQueue = (this._renderQueue ||= new RenderQueue(RenderQueueType.Transparent));
-const virtualCamera = (this._virtualCamera ||= new VirtualCamera());
+UIUtils._renderQueue ||= new RenderQueue(RenderQueueType.Transparent);
+const uiRenderQueue = UIUtils._renderQueue;
+UIUtils._virtualCamera ||= new VirtualCamera();
+const virtualCamera = UIUtils._virtualCamera;

These changes improve readability and avoid the use of this in a static context, making the code more maintainable and less prone to misinterpretation.

🧰 Tools
🪛 Biome

[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/UIGroup.ts (3)

87-122: Simplify boolean conditions in _updateGlobalModify

The _updateGlobalModify method is well-implemented, correctly updating global states based on the provided flags. However, there are two instances of unnecessary double negation that can be simplified:

  1. Line 106: Replace if (!!flags) with if (flags)
  2. Line 114: Replace if (!!passDownFlags) with if (passDownFlags)

These changes will improve code clarity without affecting functionality.

🧰 Tools
🪛 Biome

[error] 106-106: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 114-114: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


124-157: LGTM: Lifecycle methods are well-implemented with a minor optimization opportunity

The _onEnableInScene and _onDisableInScene methods are well-implemented, correctly managing the UIGroup's lifecycle and relationships. The cleanup logic in _onDisableInScene is thorough, preventing potential memory leaks.

Consider replacing the empty callback functions in the forEach calls with undefined:

disorderedElements.forEach((element: IUIElement) => {
  UIUtils.registerUIToGroup(element, parentGroup);
}, undefined);

This minor optimization can slightly improve performance by avoiding the creation of empty function objects.


159-190: Refactor assignment within expression in _registryToParentGroup

The _registryToParentGroup method is well-implemented, correctly managing the relationships between UIGroups and handling entity listeners. However, there's an assignment within an expression on line 166 that can be refactored for better readability:

replaced && (replaced._groupIndex = this._groupIndex);

Consider refactoring this line to:

if (replaced) {
  replaced._groupIndex = this._groupIndex;
}

This change improves code clarity without affecting functionality.

🧰 Tools
🪛 Biome

[error] 166-166: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/PointerManager.ts (2)

Line range hint 1-287: Overall improvement in PointerManager structure and functionality

The PointerManager class has undergone significant improvements in its structure and functionality. The refactoring towards a more modular event handling system, improved memory management with object pools, and streamlined pointer management flow are all positive changes. These improvements enhance maintainability, extensibility, and align with modern best practices.

Consider updating the class-level documentation to reflect these architectural changes and new event handling patterns. This will help future developers understand the design decisions and how to properly extend or modify the PointerManager.


Lack of explicit error handling in critical sections

The analysis confirms minimal explicit error handling in the PointerManager. To enhance robustness, add try-catch blocks or error checks in the following areas:

  • Event listener addition and removal
  • Pointer creation and initialization
  • Event data pool operations
🔗 Analysis chain

Line range hint 1-287: Consider adding explicit error handling

The current implementation has minimal explicit error handling. To improve the robustness of the PointerManager, consider adding try-catch blocks or error checks in critical sections such as:

  1. Event listener addition and removal
  2. Pointer creation and initialization
  3. Event data pool operations

Here's a script to check for existing error handling:

Adding appropriate error handling will help prevent silent failures and make debugging easier.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing error handling in PointerManager.ts

# Test: Search for try-catch blocks and error checks
echo "Searching for existing error handling..."
rg -n '(try|catch|throw|Error)' packages/core/src/input/pointer/PointerManager.ts

echo "Searching for potential error-prone areas without checks..."
rg -n '(addEventListener|removeEventListener|new Pointer|_eventDataPool)' packages/core/src/input/pointer/PointerManager.ts

Length of output: 1606

packages/core/src/2d/sprite/SpriteMask.ts (1)

252-253: Improved render method efficiency and specificity

The changes in the _render method are good improvements:

  1. The simplified condition for early return (lines 252-253) is more concise and efficient.
  2. The update flag check now uses the more specific SpriteMaskUpdateFlags.Position.
  3. The position update includes additional parameters for width, height, and flip states, which should lead to more accurate positioning.

These changes should result in more efficient and accurate rendering.

However, consider destructuring the width and height properties in line 269 for slightly improved readability:

SimpleSpriteAssembler.updatePositions(this, this.width, this.height, sprite.pivot, this._flipX, this._flipY);

Could be changed to:

const { width, height } = this;
SimpleSpriteAssembler.updatePositions(this, width, height, sprite.pivot, this._flipX, this._flipY);

This is a minor suggestion and the current implementation is also acceptable.

Also applies to: 268-270

packages/core/src/BasicResources.ts (1)

260-275: LGTM: New _createUIMaterial method is well-implemented.

The _createUIMaterial method follows the established pattern of other material creation methods in this class. It correctly sets up the render state for UI elements, including appropriate blend factors and operations.

Consider extracting the common setup code shared between _createUIMaterial and _create2DMaterial into a separate private method to reduce code duplication. This would improve maintainability and ensure consistency across different material types. For example:

private _setupCommonMaterialProperties(material: Material): void {
  const renderState = material.renderState;
  const target = renderState.blendState.targetBlendState;
  target.enabled = true;
  target.sourceColorBlendFactor = BlendFactor.SourceAlpha;
  target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha;
  target.sourceAlphaBlendFactor = BlendFactor.One;
  target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha;
  target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add;
  renderState.depthState.writeEnabled = false;
  renderState.rasterState.cullMode = CullMode.Off;
  renderState.renderQueueType = RenderQueueType.Transparent;
  material.isGCIgnored = true;
}

Then, you can use this method in both _createUIMaterial and _create2DMaterial:

private _createUIMaterial(engine: Engine): Material {
  const material = new Material(engine, Shader.find("ui"));
  this._setupCommonMaterialProperties(material);
  return material;
}
packages/core/src/RenderPipeline/BasicRenderPipeline.ts (1)

Line range hint 1-380: Overall improvements to the rendering pipeline.

The changes in this file significantly enhance the rendering pipeline, particularly for canvas elements. The code maintains good separation of concerns, improves readability, and adds new functionality without disrupting existing operations. The addition of the RenderQueueFlags enum provides a robust way to manage render queue types.

Consider adding comments to explain the purpose of the new canvas rendering loop and how it interacts with the existing renderer loop, to improve maintainability for future developers.

packages/core/src/physics/PhysicsScene.ts (1)

321-321: LGTM: Enhanced raycast result with component information.

The addition of the component property to the hitResult object provides more detailed information about the raycast result. This is a useful enhancement for developers using this method.

Consider updating the method's JSDoc to include information about the new component property in the outHitResult parameter description.

Also applies to: 332-332

packages/core/src/2d/sprite/SpriteRenderer.ts (1)

316-328: LGTM with minor suggestion: Improved local bounds calculation

The refactored _updateLocalBounds method now correctly handles cases where the sprite is flipped on the X or Y axis and considers the sprite's pivot. This is a valuable improvement that ensures accurate bounds calculation in all scenarios.

However, there's an unnecessary use of double negation (!!) on lines 321-322. Since this.flipX and this.flipY are already boolean values, the double negation can be removed without changing the behavior.

Apply this diff to simplify the expressions:

-      pivotX = !!this.flipX ? 1 - pivotX : pivotX;
-      pivotY = !!this.flipY ? 1 - pivotY : pivotY;
+      pivotX = this.flipX ? 1 - pivotX : pivotX;
+      pivotY = this.flipY ? 1 - pivotY : pivotY;
🧰 Tools
🪛 Biome

[error] 321-321: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 322-322: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

packages/core/src/2d/text/TextRenderer.ts (1)

695-711: Good addition of TextRendererUpdateFlag enum

The new TextRendererUpdateFlag enum provides a more granular and type-safe way to manage update flags for the TextRenderer. This should improve code clarity and maintainability.

Consider adding a brief comment explaining the purpose of each flag for better documentation. For example:

enum TextRendererUpdateFlag {
  /** Flag to update sub-font */
  SubFont = 0x4,
  /** Flag to update local position */
  LocalPosition = 0x8,
  // ... (add comments for other flags)
}
packages/core/src/ui/UICanvas.ts (5)

22-86: Consider leveraging TypeScript's type inference for primitive types.

The properties are well-organized and appropriately decorated. However, for some properties with primitive types and initial values, you can omit the explicit type annotation to leverage TypeScript's type inference. For example:

@ignoreClone
private _sortOrder = 0;

@ignoreClone
private _distance = 10;

This change would make the code slightly more concise without losing type safety.


88-191: LGTM: Well-implemented getters and setters with a suggestion for optimization.

The getters and setters are well-implemented, providing controlled access to internal properties and including necessary logic for updating the canvas state. The elements getter efficiently refreshes the hierarchy only when needed.

Consider caching the result of this._getRealRenderMode() in the renderMode setter to avoid calling it twice:

set renderMode(mode: CanvasRenderMode) {
  let preMode = this._renderMode;
  if (preMode !== mode) {
    this._renderMode = mode;
    this._setCameraObserver(
      this._isRootCanvas && mode === CanvasRenderMode.ScreenSpaceCamera ? this._renderCamera : null
    );
    const newRealRenderMode = this._getRealRenderMode();
    this._setRealRenderMode(newRealRenderMode);
  }
}

This change would slightly improve performance by avoiding redundant calculations.


225-265: Improve readability by separating assignment from expression.

The _prepareRender method is well-implemented, efficiently handling frustum culling and camera mask filtering. However, the assignment within an expression on line 229 could be improved for readability:

const renderElement = this._renderElement = engine._renderElementPool.get();

Consider separating the assignment:

this._renderElement = engine._renderElementPool.get();
const renderElement = this._renderElement;

This change improves code clarity without affecting functionality.

🧰 Tools
🪛 Biome

[error] 229-229: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 246-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


368-534: LGTM: Well-implemented helper methods with optimization potential.

The _onGroupModify method and the private helper methods are well-implemented, handling their specific responsibilities effectively. The adaptation logic in methods like _adapterPoseInScreenSpace and _adapterSizeInScreenSpace is necessarily complex for proper canvas rendering.

Consider caching frequently used values in _adapterSizeInScreenSpace to potentially improve performance:

private _adapterSizeInScreenSpace(): void {
  const { _transform: transform, _realRenderMode: realRenderMode, _referenceResolution, _resolutionAdaptationStrategy } = this;
  const { x: width, y: height } = _referenceResolution;
  // ... rest of the method
}

This change could slightly reduce property access overhead in the method.

🧰 Tools
🪛 Biome

[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


537-611: LGTM with suggestions for switch statements.

The private methods are well-implemented and handle their specific tasks effectively. However, some switch statements have cases that fall through without explicit breaks. While this might be intentional, it's a good practice to make it explicit. Consider adding comments or restructuring the switch statements in _setRealRenderMode:

switch (curRealMode) {
  case CanvasRenderMode.ScreenSpaceOverlay:
    this._addCanvasListener();
    // fall through
  case CanvasRenderMode.ScreenSpaceCamera:
    this._adapterPoseInScreenSpace();
    this._adapterSizeInScreenSpace();
    // fall through
  case CanvasRenderMode.WorldSpace:
    componentsManager.addUICanvas(curRealMode, this);
    break;
  default:
    break;
}

This change improves code clarity by making the intentional fallthrough explicit.

🧰 Tools
🪛 Biome

[error] 589-590: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-600: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 601-603: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/Entity.ts (1)

408-408: LGTM: Consistent UITransform usage for child entities

The addition of UITransform to child entities when the parent uses UITransform ensures consistency in the entity hierarchy for UI components.

Consider adding a comment explaining the rationale behind this condition for better code clarity:

// Ensure child entities inherit UITransform if parent uses it
this._transform instanceof UITransform && child.addComponent(UITransform);
packages/core/src/ComponentsManager.ts (1)

107-112: Consider adding sorting for _canvases if needed

Currently, the sortUICanvases() method only sorts _overlayCanvases when _overlayCanvasesSortingFlag is true. If _canvases may also require sorting based on a certain criterion, consider implementing a similar sorting mechanism to ensure consistent behavior.

Would you like assistance in adding sorting functionality for _canvases?

packages/core/src/ui/UIImage.ts (2)

128-133: Ensure property change triggers update

Assigning a new Color instance to the color property updates the rendering state, but modifying the existing Color instance's properties does not. Consider implementing an observer pattern or providing a setter within the Color class to detect changes and update the _dirtyUpdateFlag accordingly when color components are modified.


190-192: Handle destroyed material appropriately

The current handling of a destroyed material silently replaces it with the default UI material without notifying the user, which could hide underlying issues. Consider logging a warning or throwing an error to inform developers of the material's destruction and prevent potential debugging difficulties.

Apply this diff to log a warning:

          if (material.destroyed) {
+           console.warn("Material is destroyed. Replacing with default UI material.");
            material = this._engine._basicResources.uiDefaultMaterial;
          }
packages/core/src/Engine.ts (1)

492-494: Redundant blank lines before variable declaration

There are unnecessary blank lines before const componentsManager = scene._componentsManager;. Removing them can improve code readability.

Apply this diff to remove the extra blank lines:

-    
-    
     const componentsManager = scene._componentsManager;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d6c03fa and f702b2b.

📒 Files selected for processing (19)
  • packages/core/src/2d/assembler/TiledSpriteAssembler.ts (6 hunks)
  • packages/core/src/2d/sprite/SpriteMask.ts (9 hunks)
  • packages/core/src/2d/sprite/SpriteRenderer.ts (14 hunks)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/BasicResources.ts (3 hunks)
  • packages/core/src/ComponentsManager.ts (4 hunks)
  • packages/core/src/Engine.ts (5 hunks)
  • packages/core/src/Entity.ts (11 hunks)
  • packages/core/src/RenderPipeline/BasicRenderPipeline.ts (2 hunks)
  • packages/core/src/Renderer.ts (8 hunks)
  • packages/core/src/Scene.ts (1 hunks)
  • packages/core/src/index.ts (3 hunks)
  • packages/core/src/input/pointer/Pointer.ts (2 hunks)
  • packages/core/src/input/pointer/PointerManager.ts (6 hunks)
  • packages/core/src/physics/PhysicsScene.ts (4 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIImage.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/2d/assembler/TiledSpriteAssembler.ts
  • packages/core/src/Renderer.ts
  • packages/core/src/index.ts
🧰 Additional context used
🪛 Biome
packages/core/src/2d/sprite/SpriteRenderer.ts

[error] 321-321: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 322-322: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

packages/core/src/ComponentsManager.ts

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/Entity.ts

[error] 616-616: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 229-229: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 246-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 347-347: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 352-352: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 353-353: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 349-350: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 351-356: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 589-590: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-600: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 601-603: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 106-106: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 114-114: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 166-166: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts

[error] 15-152: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 51-51: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🔇 Additional comments (69)
packages/core/src/input/pointer/Pointer.ts (5)

2-3: LGTM: New imports support updated event handling.

The new imports are consistent with the changes described in the summary and support the updated event handling mechanism and data structures.

Also applies to: 6-7


40-43: Add access modifiers to new internal properties.

The new properties _frameEvents and _emitters are consistent with the updated event handling mechanism. However, to enhance encapsulation and clarity, consider explicitly marking these properties as private or protected.

- /** @internal */
- _frameEvents: PointerEventType = PointerEventType.None;
- /** @internal */
- _emitters: PointerEventEmitter[] = [];
+ /** @internal */
+ private _frameEvents: PointerEventType = PointerEventType.None;
+ /** @internal */
+ private _emitters: PointerEventEmitter[] = [];

This makes their intended usage clearer and prevents unintended access from outside the class.


65-67: Reset all frame-related data in _resetOnFrameBegin()

The new _resetOnFrameBegin() method is a good addition for managing frame-related data. However, as previously noted, _upMap and _downMap are not being reset. To prevent any unintended carryover of data between frames, please reset these arrays as well:

  _resetOnFrameBegin(): void {
    this._frameEvents = PointerEventType.None;
    this._events.length = this._upList.length = this._downList.length = 0;
+   this._upMap.length = this._downMap.length = 0;
  }

This ensures all frame-related data is properly reset at the beginning of each frame.


73-78: Clear _emitters array after disposing to prevent memory leaks

The new _dispose() method is a good addition for cleaning up resources. However, as previously noted, after disposing of each emitter in _emitters, the array still holds references to these emitters. To allow for garbage collection and prevent potential memory leaks, please clear the _emitters array:

  _dispose(): void {
    const emitters = this._emitters;
    for (let i = 0, n = emitters.length; i < n; i++) {
      emitters[i]._dispose();
    }
+   this._emitters.length = 0;
    this._events.length = this._upList.length = this._downList.length = 0;
  }

This ensures that all references to disposed emitters are removed, allowing them to be garbage collected.


48-49: LGTM: Simplified constructor. Verify removal of other initializations.

The constructor has been simplified to only initialize the id property, which is consistent with the changes described in the summary. However, please ensure that any necessary initializations that were previously in the constructor have been appropriately moved or are no longer needed.

✅ Verification successful

Verified: Constructor simplification confirmed.

The constructor now only initializes the id property, and no other initializations are present.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any initializations in the old constructor that might need to be handled elsewhere.

# Test: Search for the old constructor implementation
rg -U 'constructor\(.*\).*\{[\s\S]*?\}' packages/core/src/input/pointer/Pointer.ts

Length of output: 134

packages/core/src/ui/UIUtils.ts (5)

19-35: LGTM: Efficient entity listener registration

The registerEntityListener method effectively manages the hierarchy of entities and their listeners. It optimizes performance by only updating the necessary parts of the hierarchy and properly handles listener registration and unregistration.


37-43: LGTM: Thorough cleanup of entity listeners

The unRegisterEntityListener method effectively unregisters all modify listeners for a UI element and clears the parents array. This ensures proper cleanup and prevents memory leaks.


84-100: LGTM: Efficient root canvas lookup

The getRootCanvasInParent method effectively traverses the entity hierarchy to find the root canvas. It efficiently checks for the correct component type and uses necessary type casting. The implementation is well-optimized for performance.


102-126: LGTM: Well-implemented group lookup

The getGroupInParents method efficiently traverses the entity hierarchy to find the first UI group, correctly stopping if a root canvas is encountered. The use of a switch statement for component types enhances performance and readability.


1-152: Overall assessment: Well-implemented UI utilities with minor improvement opportunities

The UIUtils class provides a comprehensive and efficient set of utility functions for managing UI elements, canvases, and groups. The implementation is generally well-structured and optimized for performance.

Key strengths:

  1. Efficient traversal of entity hierarchies
  2. Proper management of listeners and registrations
  3. Clear separation of concerns for different UI components

Suggested improvements:

  1. Consider alternatives to using a static-only class in future refactoring
  2. Improve readability by separating assignments from logical checks in some methods
  3. Avoid using this in static contexts for better clarity

These minor adjustments will further enhance the maintainability and clarity of the code. Great job on implementing this crucial utility class for the UI system!

🧰 Tools
🪛 Biome

[error] 15-152: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 51-51: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/UIGroup.ts (4)

1-41: LGTM: Imports and class declaration are well-structured

The imports, class declaration, and property definitions are well-organized and follow TypeScript best practices. The use of decorators for cloning behavior is consistent and appropriate.


43-75: LGTM: Well-implemented getter and setter methods

The getter and setter methods for ignoreParentGroup, raycastEnabled, and alpha are well-implemented. The alpha setter includes proper value clamping, and all setters correctly trigger the _updateGlobalModify method to ensure changes propagate through the UI hierarchy.


192-201: LGTM: _onEntityModify method is well-implemented

The _onEntityModify method is concise and correctly handles relevant entity modifications. The use of a switch statement for different modification flags is appropriate and easy to understand.


204-209: LGTM: GroupModifyFlags enum is well-defined

The GroupModifyFlags enum is well-defined and follows best practices for flag enums. The use of bitwise values allows for efficient flag combination, which is particularly useful in the context of UI group modifications.

packages/core/src/input/pointer/PointerManager.ts (6)

4-5: Improved modularity and memory management

The new imports and the addition of _eventDataPool using ClearableObjectPool suggest an improvement in modularity and memory management for event handling. This change is likely to enhance performance and reduce memory overhead.

Also applies to: 9-12, 41-41


66-69: Improved update logic and memory management

The changes in the _update method enhance code readability and potentially improve performance. Clearing the event data pool each frame and resetting pointers instead of disposing them are good practices for maintaining a clean state and reducing object churn.

Also applies to: 73-83


Line range hint 122-171: Enhanced event handling with improved modularity

The changes in pointer event handling significantly improve the system's modularity and extensibility. The use of _frameEvents for granular event tracking and the emitter-based approach for event processing are excellent improvements. The addition of Leave and Cancel event handling enhances the robustness of the pointer system.

Also applies to: 230-252


279-285: Improved cleanup in _removeEventListener

The additions to the _removeEventListener method ensure a more thorough cleanup of event data and reset of various properties. This is crucial for preventing memory leaks and maintaining a clean state, especially important in long-running applications or those with frequent scene changes.


88-114: ⚠️ Potential issue

Fix variable shadowing in pointer creation loop

There's a variable shadowing issue in the pointer creation loop. The outer loop counter i is shadowed by the inner loop counter, which is also named i. This can lead to confusion and potential bugs.

To fix this, rename the inner loop counter:

-for (let i = 0, n = _nativeEvents.length; i < n; i++) {
+for (let i = 0, n = _nativeEvents.length; i < n; i++) {
   // ... (other code)
   if (lastCount === 0 || this._multiPointerEnabled) {
     // ... (other code)
-    let i = 0;
-    for (; i < lastCount; i++) {
-      if (_pointers[i].id > i) {
+    let j = 0;
+    for (; j < lastCount; j++) {
+      if (_pointers[j].id > j) {
         break;
       }
     }
-    pointer = pointerPool[i];
+    pointer = pointerPool[j];
     if (!pointer) {
-      pointer = new Pointer(i);
+      pointer = new Pointer(j);
       // ... (other code)
     }
     // ... (other code)
-    _pointers.splice(i, 0, pointer);
+    _pointers.splice(j, 0, pointer);
   }
 }

Likely invalid or redundant comment.


Line range hint 1-287: Consider performance monitoring and optimization

While the changes improve memory efficiency and modularity, they also introduce additional complexity to the _update method and event handling process. For most use cases, this shouldn't cause performance issues, but it's worth considering:

  1. Implementing performance monitoring to track the impact of these changes in real-world scenarios.
  2. Optimizing the _update method for high-load situations, possibly by batching operations or using more efficient data structures.

To identify potential performance bottlenecks, you can run the following script:

This will help identify areas that might benefit from optimization in high-performance scenarios.

packages/core/src/2d/sprite/SpriteMask.ts (5)

78-78: Improved update flag specificity

The change from RendererUpdateFlags.WorldVolume to SpriteMaskUpdateFlags.PositionAndAllBounds is a good improvement. It makes the update process more specific to the SpriteMask class and correctly indicates that both position and bounds need to be updated when the width changes.


114-114: Consistent update flag usage

The change to SpriteMaskUpdateFlags.PositionAndAllBounds in the flipX setter is consistent with the changes in width and height setters. This ensures that flipping the sprite on the X-axis correctly triggers updates to both position and bounds.


238-244: Improved local bounds update

The renaming of _updateBounds to _updateLocalBounds and the changes in its implementation are good improvements:

  1. The method now correctly focuses on updating local bounds, which is more appropriate for this level of the rendering pipeline.
  2. The new implementation handles the case where there's no sprite, setting the bounds to zero in this scenario.
  3. When a sprite exists, it uses the SimpleSpriteAssembler.updatePositions method, which should correctly calculate the bounds based on the sprite's properties.

These changes should lead to more accurate and efficient bounds calculations.


329-330: Enhanced sprite change handling

The modifications in the _onSpriteChange method are well-thought-out:

  1. The default size calculation now correctly triggers a position and bounds update (lines 329-330).
  2. A new update flag SpriteMaskUpdateFlags.PositionAndUV has been introduced for region and atlas region offset changes (line 335). This is more precise than updating everything.
  3. Pivot changes now trigger a position and bounds update (line 341), which is correct as changing the pivot affects both position and bounds.

These changes provide more granular control over updates, which should lead to more efficient rendering and better performance.

Also applies to: 335-335, 341-341


356-364: Improved update flags for SpriteMask

The revisions to the SpriteMaskUpdateFlags enum are excellent:

  1. Individual flags for Position (0x4) and UV (0x8) allow for more granular updates.
  2. Combined flags like PositionAndAllBounds (0x7) and PositionAndUV (0xc) enable efficient updates of related properties.
  3. The All flag (0xf) provides a convenient way to trigger a full update when needed.

These changes should lead to more efficient updates by allowing the system to update only what's necessary in different scenarios. This granularity can potentially improve performance, especially in cases where only specific aspects of the sprite mask need updating.

packages/core/src/BasicResources.ts (2)

118-118: LGTM: New property declaration is consistent and appropriate.

The uiDefaultMaterial property is correctly declared as readonly and of type Material, which is consistent with other similar properties in the class and appropriate for its intended use.


169-169: LGTM: Constructor update is consistent with existing patterns.

The initialization of uiDefaultMaterial in the constructor using the _createUIMaterial method follows the established pattern for other material initializations in this class.

packages/core/src/RenderPipeline/BasicRenderPipeline.ts (5)

24-24: LGTM: New import for canvas rendering.

The import of CanvasRenderMode is correctly added and necessary for the new canvas handling logic.


341-342: Improved variable destructuring for better readability.

The destructuring of camera properties enhances code clarity and potentially improves performance by reducing property lookups.


344-360: Enhanced renderer processing with explicit naming and frustum culling.

The renderer loop now uses more explicit naming (rendererElements) and includes proper frustum culling. This change improves code clarity and potentially optimizes rendering by skipping objects outside the camera's view.


Line range hint 375-380: LGTM: New RenderQueueFlags enum added.

The RenderQueueFlags enum provides a clear and type-safe way to represent render queue types. The use of power-of-2 values allows for efficient bitwise operations, and the All flag is correctly set to the combination of all other flags.


362-372: New canvas rendering support added.

A new loop for processing canvases has been introduced, adding support for rendering UI elements. The code correctly checks for the appropriate render mode and camera before preparing the canvas for rendering.

To ensure that this new canvas rendering logic is correctly implemented across the codebase, let's verify the usage of CanvasRenderMode.ScreenSpaceCamera:

✅ Verification successful

CanvasRenderMode.ScreenSpaceCamera usage verified successfully.
All instances of CanvasRenderMode.ScreenSpaceCamera are consistently implemented across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of CanvasRenderMode.ScreenSpaceCamera
# Expected: Consistent usage across the codebase

rg -n "CanvasRenderMode.ScreenSpaceCamera"

Length of output: 2034

packages/core/src/physics/PhysicsScene.ts (2)

5-7: LGTM: Import statements reorganized and new import added.

The separation of imports and the addition of the ignoreClone import improve code organization and readability.


460-460: LGTM: Added @ignoreClone decorator to _setGravity method.

The addition of the @ignoreClone decorator to the private _setGravity method is appropriate. This prevents the method from being cloned, which is suitable for scene-specific operations like setting gravity.

packages/core/src/2d/sprite/SpriteRenderer.ts (10)

10-10: LGTM: ComponentType import added

The addition of the ComponentType import is appropriate for the new _componentType property in the SpriteRenderer class.


84-84: LGTM: Update flag refined for drawMode setter

The change to use SpriteRendererUpdateFlags.PositionUVAndColor is consistent with the new update flag system and ensures proper updates when the draw mode changes.


99-99: LGTM: Update flag refined for tileMode setter

The use of SpriteRendererUpdateFlags.PositionUVAndColor in the tileMode setter is consistent with the new update flag system and ensures proper updates when the tile mode changes in tiled draw mode.


116-116: LGTM: Update flag refined for tiledAdaptiveThreshold setter

The use of SpriteRendererUpdateFlags.PositionUVAndColor in the tiledAdaptiveThreshold setter is consistent with the new update flag system and ensures proper updates when the threshold changes in tiled draw mode.


144-144: LGTM: Default size recalculation added to sprite setter

The addition of this._calDefaultSize() in the sprite setter ensures that the default size is properly updated when a new sprite is set, maintaining consistency in the renderer's dimensions.


181-182: LGTM: Improved update flag handling in width setter

The conditional update flag setting in the width setter provides more precise control over updates:

  • For tiled mode: SpriteRendererUpdateFlags.PositionUVColorAndWorldBounds
  • For other modes: SpriteRendererUpdateFlags.PositionAndAllBounds

This differentiation optimizes updates based on the specific requirements of each draw mode, potentially improving performance.


206-207: LGTM: Consistent update flag handling in height setter

The height setter now uses the same conditional update flag setting as the width setter:

  • For tiled mode: SpriteRendererUpdateFlags.PositionUVColorAndWorldBounds
  • For other modes: SpriteRendererUpdateFlags.PositionAndAllBounds

This consistency ensures that both dimension changes are handled uniformly and efficiently.


333-333: LGTM: Improved render method with better flag handling

The changes to the _render method include several improvements:

  1. The early return condition is now more concise and readable.
  2. The position update check now uses the new flag system (SpriteRendererUpdateFlags.Position).
  3. The updatePositions call includes flipX and flipY, allowing for more accurate position calculations.

These changes enhance the rendering logic and align with the overall refactoring of the update flag system.

Also applies to: 347-349


410-443: LGTM: Enhanced sprite change handling

The _onSpriteChange method has been significantly improved:

  1. It now provides more granular control over updates based on specific sprite modifications.
  2. The method considers custom dimensions and different draw modes when updating flags.
  3. The changes align well with the new update flag system.

These enhancements improve the renderer's responsiveness to sprite modifications and should lead to more efficient updates. The conditional logic for different draw modes is particularly noteworthy, as it ensures that only necessary updates are triggered.


469-486: LGTM: Expanded update flags for finer-grained control

The SpriteRendererUpdateFlags enum has been significantly expanded to provide more granular control over different aspects of the sprite renderer that need updating. Key improvements include:

  1. Separate flags for Position, UV, and Color updates.
  2. Combined flags for common update scenarios (e.g., PositionAndWorldBounds, PositionUVAndColor).
  3. Logical combinations that align with the specific update needs of different renderer operations.

This expansion allows for more precise and efficient updates throughout the SpriteRenderer class, potentially improving performance by reducing unnecessary update operations.

packages/core/src/Scene.ts (1)

330-330: Verify the impact of _setParentChange() on scene graph updates.

The change from _setTransformDirty() to _setParentChange() suggests a refinement in how entity relationships are managed within the scene graph. This aligns with the broader refactor mentioned in the summary.

To ensure this change doesn't introduce unexpected behavior:

  1. Verify that _setParentChange() properly triggers necessary updates in the entity hierarchy.
  2. Check for any performance implications, especially in scenes with complex hierarchies.
  3. Ensure that this change is consistently applied across the codebase.

Run the following script to verify the usage and implementation of _setParentChange:

packages/core/src/2d/text/TextRenderer.ts (7)

9-9: Import update looks good.

The addition of RendererUpdateFlags to the import statement is consistent with the new flag system implemented in the class.


258-264: Improved local bounds update logic

The new implementation of _updateLocalBounds is more efficient, only updating local data when the text is visible. This optimization should improve performance.


329-333: Efficient sub-font update logic

The new implementation of _getSubFont uses the flag system to update the sub-font only when necessary, which is a good optimization.


480-481: Improved sub-font handling in local data update

The use of _getSubFont() instead of _resetSubFont() is consistent with the new flag-based system and should improve efficiency.


622-622: Improved transform change handling

The use of the new flag system in _onTransformChanged provides more granular control over updates, which is a good improvement.


Line range hint 1-711: Overall, good improvements to TextRenderer

The changes in this file represent a significant improvement in the TextRenderer's implementation. The new flag-based system should provide better performance and more granular control over updates. The code is now more maintainable and efficient.

There are a few minor suggestions for improvement:

  1. Fix the initialization of _textChunks array.
  2. Consider adding brief comments to the TextRendererUpdateFlag enum for better documentation.

Great work on refactoring this component!


35-35: ⚠️ Potential issue

Incorrect initialization of _textChunks array

The _textChunks array is incorrectly initialized. Using Array<TextChunk>() without the new keyword does not create a new array instance.

Apply this diff to fix the initialization:

-private _textChunks = Array<TextChunk>();
+private _textChunks: TextChunk[] = [];

Likely invalid or redundant comment.

packages/core/src/ui/UICanvas.ts (5)

1-21: LGTM: Imports and class declaration look good.

The imports cover all necessary modules for the UICanvas class. The @dependentComponents decorator is appropriately used to automatically add a UITransform component, which is essential for UI positioning and sizing.


193-220: LGTM: Constructor and raycast method are well-implemented.

The constructor correctly initializes the component type and binds necessary methods. The raycast method efficiently implements hit testing for UI elements, iterating through the elements in reverse order to ensure proper layering.


267-309: LGTM: _updateSortDistance and _onEnableInScene methods are well-implemented.

Both methods handle their responsibilities correctly. The _updateSortDistance method efficiently updates the sorting distance based on the render mode, while _onEnableInScene properly registers the canvas with the UI system and handles necessary state updates.


614-619: LGTM: CanvasRealRenderMode enum is well-defined.

The CanvasRealRenderMode enum is correctly defined and properly documented as extending CanvasRenderMode. This provides a clear way to represent the additional render mode option.


1-619: Overall, well-implemented UICanvas class with minor suggestions for improvement.

The UICanvas class is well-structured and implements complex UI canvas management logic effectively. The code is generally clean and follows good practices. Here are the main points for improvement:

  1. Consider leveraging TypeScript's type inference for primitive types to reduce verbosity.
  2. Optimize some methods by caching frequently accessed values or computed results.
  3. Improve switch statements by adding explicit break statements or comments for intentional fallthrough cases.
  4. Enhance readability by avoiding assignments within expressions.

Addressing these minor issues will further improve the already solid implementation of the UICanvas class.

🧰 Tools
🪛 Biome

[error] 229-229: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 246-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 347-347: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 352-352: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 353-353: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 349-350: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 351-356: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 589-590: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-600: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 601-603: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/Entity.ts (6)

10-17: LGTM: Improved imports and property encapsulation

The new imports and properties enhance the Entity class's functionality, particularly for UI components and update management. The change from a public transform property to a private _transform property with a getter method improves encapsulation.

Also applies to: 86-87, 103-104, 108-108


111-114: LGTM: Improved transform property access

The new getter method for transform maintains the public interface while improving encapsulation by accessing the private _transform property.


531-534: LGTM: Proper cleanup of _updateFlagManager

The addition of cleanup code for _updateFlagManager in the destroy method prevents potential memory leaks by removing all listeners and nullifying the reference.


607-610: LGTM: Encapsulated parent change handling

The new _setParentChange method effectively encapsulates the logic for handling parent changes, updating the transform and dispatching the appropriate modify event.


817-824: LGTM: Clear categorization of entity modifications

The new EntityModifyFlags enum provides a clear and extensible way to categorize different types of entity modifications. This will improve code readability and maintainability when handling entity changes.


Line range hint 1-825: Overall assessment: Significant improvements to Entity class

The changes to the Entity class introduce several enhancements:

  1. Improved encapsulation with private properties and getter methods.
  2. Enhanced UI component handling and interactivity.
  3. Better update management with the introduction of UpdateFlagManager.
  4. Clearer categorization of entity modifications with the new EntityModifyFlags enum.

These changes improve the overall functionality, maintainability, and extensibility of the Entity class, particularly in the context of UI-related operations. The suggested minor improvements, if implemented, will further enhance the code quality and type safety.

🧰 Tools
🪛 Biome

[error] 616-616: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ComponentsManager.ts (1)

314-315: Ensure garbage collection is complete

Including garbageCollection() calls for both _overlayCanvases and _canvases is good practice to prevent memory leaks. This ensures that unused canvases are properly cleaned up.

packages/core/src/ui/UIImage.ts (1)

285-300: ⚠️ Potential issue

Wrap variable declarations in switch cases with block scopes

Declaring variables within a case clause without enclosing them in a block can lead to unexpected behaviors because variables become accessible to other cases within the switch statement. Wrap the case body in curly braces to limit the scope of the variable.

Apply this diff to address the scoping issue:

        case SpriteModifyFlags.size:
+         {
            switch (this._drawMode) {
              case SpriteDrawMode.Sliced:
                this._dirtyUpdateFlag |= ImageUpdateFlags.Position;
                break;
              case SpriteDrawMode.Tiled:
                this._dirtyUpdateFlag |= ImageUpdateFlags.PositionUVAndColor;
                break;
              default:
                break;
            }
            break;
+         }

Likely invalid or redundant comment.

packages/core/src/Engine.ts (4)

40-40: Import statement for UIUtils added

The import of UIUtils from "./ui/UIUtils" is correctly added to support UI functionalities.


328-328: Correct addition of sortUICanvases() to manage UI canvas sorting

The call to componentsManager.sortUICanvases(); ensures that UI canvases are sorted during the update cycle, which is appropriate for maintaining the correct rendering order of UI elements.


516-517: Integrate UI rendering with UIUtils.render

The addition of UIUtils.render(this, componentsManager._overlayCanvases); appropriately integrates UI rendering into the rendering loop, ensuring that UI elements are rendered correctly after scene rendering.


342-342: Verify the impact of calling _firePointerScript() unconditionally

By removing the conditional check for _physicsInitialized, inputManager._firePointerScript(scenes); is now called regardless of physics initialization status. Ensure this change does not introduce issues when physics is not initialized.

Run the following script to confirm _firePointerScript() operates correctly without physics initialization:

Expected result: No matches found, indicating that _firePointerScript() does not rely on _physicsInitialized and can be called unconditionally.

Comment on lines 314 to 363
_raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
const entity = this._entity;
const plane = UIRenderer._tempPlane;
const transform = entity.transform;
const normal = plane.normal.copyFrom(transform.worldForward);
plane.distance = -Vector3.dot(normal, transform.worldPosition);
const curDistance = ray.intersectPlane(plane);
if (curDistance >= 0 && curDistance < distance) {
const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
const worldMatrixInv = UIRenderer._tempMat;
Matrix.invert(this.entity.transform.worldMatrix, worldMatrixInv);
const localPosition = UIRenderer._tempVec31;
Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
if (this._hitTest(localPosition)) {
out.distance = curDistance;
out.entity = entity;
out.component = this;
out.normal.copyFrom(normal);
out.point.copyFrom(hitPointWorld);
return true;
}
return true;
}
return false;
}

/**
* @internal
*/
@ignoreClone
_onEntityModify(flag: EntityModifyFlags): void {
switch (flag) {
case EntityModifyFlags.SiblingIndex:
this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
break;
case EntityModifyFlags.UICanvasEnableInScene:
if (!this._isRootCanvas) break;
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
this._setIsRootCanvas(!rootCanvas);
UIUtils.registerUIToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerUIToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing break statements in switch cases.

The _raycast method is well-implemented with proper intersection calculations. However, in the _onEntityModify method, there are switch cases that fall through without explicit break statements. This could lead to unintended behavior. Consider adding break statements or using return to make the flow explicit:

case EntityModifyFlags.UICanvasEnableInScene:
  if (!this._isRootCanvas) break;
  // fall through
case EntityModifyFlags.Parent:
  const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
  if (rootCanvas) {
    rootCanvas._hierarchyDirty = true;
  }
  this._setIsRootCanvas(!rootCanvas);
  UIUtils.registerUIToCanvas(this, rootCanvas);
  UIUtils.registerEntityListener(this);
  break; // Add this break
case EntityModifyFlags.UIGroupEnableInScene:
  UIUtils.registerUIToGroup(this, UIUtils.getGroupInParents(this._entity));
  break;

This change will prevent unintended fallthrough and make the code's behavior more explicit.

🧰 Tools
🪛 Biome

[error] 347-347: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 352-352: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 353-353: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 349-350: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 351-356: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 636 to 652
_onUIInteractiveChange(val: boolean): void {
if (val) {
this._interactive = true;
} else {
for (let i = 0, len = this._children.length; i < len; i++) {
this._children[i]._setTransformDirty();
const components = this._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (
component._componentType & ComponentType.UIElement &&
component.enabled &&
(component as unknown as IUIElement)._runtimeRaycastEnable
) {
this._interactive = true;
return;
}
}
this._interactive = false;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety in _onUIInteractiveChange method

The new _onUIInteractiveChange method enhances UI interactivity management. However, the type casting can be improved for better type safety.

Consider using a type guard to ensure type safety:

private _onUIInteractiveChange(val: boolean): void {
  if (val) {
    this._interactive = true;
  } else {
    const components = this._components;
    for (let i = 0, n = components.length; i < n; i++) {
      const component = components[i];
      if (
        component._componentType & ComponentType.UIElement &&
        component.enabled &&
        this.isUIElement(component) &&
        component._runtimeRaycastEnable
      ) {
        this._interactive = true;
        return;
      }
    }
    this._interactive = false;
  }
}

private isUIElement(component: Component): component is IUIElement {
  return '_runtimeRaycastEnable' in component;
}

This approach provides better type safety and makes the code more maintainable.

canvases = this._canvases;
}
const replaced = canvases.deleteByIndex(uiCanvas._canvasIndex);
replaced && (replaced._canvasIndex = uiCanvas._canvasIndex);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignments within expressions for better readability

Using an assignment within a logical expression can reduce code clarity and lead to potential misunderstandings. It's better to separate the assignment from the expression.

Apply this diff to improve readability:

-replaced && (replaced._canvasIndex = uiCanvas._canvasIndex);
+if (replaced) {
+  replaced._canvasIndex = uiCanvas._canvasIndex;
+}
📝 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
replaced && (replaced._canvasIndex = uiCanvas._canvasIndex);
if (replaced) {
replaced._canvasIndex = uiCanvas._canvasIndex;
}
🧰 Tools
🪛 Biome

[error] 101-101: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 21 to 25
_overlayCanvases: DisorderedArray<UICanvas> = new DisorderedArray();
/* @internal */
_overlayCanvasesSortingFlag: boolean = false;
/** @internal */
_canvases: DisorderedArray<UICanvas> = new DisorderedArray();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consistent use of comment style for @internal annotations

There is an inconsistency in the comment style used for @internal annotations. Lines 21 and 25 use /** @internal */ (JSDoc style), while line 23 uses /* @internal */. For consistency and clarity, it's recommended to use the same comment style throughout.

Apply this diff to make the comment style consistent:

-/* @internal */
+/** @internal */
📝 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
_overlayCanvases: DisorderedArray<UICanvas> = new DisorderedArray();
/* @internal */
_overlayCanvasesSortingFlag: boolean = false;
/** @internal */
_canvases: DisorderedArray<UICanvas> = new DisorderedArray();
_overlayCanvases: DisorderedArray<UICanvas> = new DisorderedArray();
/** @internal */
_overlayCanvasesSortingFlag: boolean = false;
/** @internal */
_canvases: DisorderedArray<UICanvas> = new DisorderedArray();

Comment on lines 214 to 215
dirtyUpdateFlag &= ~(ImageUpdateFlags.Color & ImageUpdateFlags.Alpha);
} else if (dirtyUpdateFlag & ImageUpdateFlags.Alpha) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix logical error when clearing update flags

The bitwise AND operation between ImageUpdateFlags.Color and ImageUpdateFlags.Alpha results in 0 because these flags represent different bits. This means the negation ~(ImageUpdateFlags.Color & ImageUpdateFlags.Alpha) equals ~0, leaving dirtyUpdateFlag unchanged after the operation. To correctly clear both the Color and Alpha flags, use the bitwise OR operator instead.

Apply this diff to fix the logical error:

-          dirtyUpdateFlag &= ~(ImageUpdateFlags.Color & ImageUpdateFlags.Alpha);
+          dirtyUpdateFlag &= ~(ImageUpdateFlags.Color | ImageUpdateFlags.Alpha);
📝 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
dirtyUpdateFlag &= ~(ImageUpdateFlags.Color & ImageUpdateFlags.Alpha);
} else if (dirtyUpdateFlag & ImageUpdateFlags.Alpha) {
dirtyUpdateFlag &= ~(ImageUpdateFlags.Color | ImageUpdateFlags.Alpha);
} else if (dirtyUpdateFlag & ImageUpdateFlags.Alpha) {

* feat: ui transform
Copy link

@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: 13

🧹 Outside diff range and nitpick comments (19)
packages/core/src/2d/assembler/ISpriteAssembler.ts (2)

9-16: LGTM: Method signature enhancement is well-structured

The expanded signature provides comprehensive control over sprite positioning while maintaining a clear single responsibility. The parameter order is logical, and optional parameters are appropriately placed at the end.

Consider documenting the following for better maintainability:

  1. The coordinate system and units for width/height
  2. The expected range and default value for the pivot Vector2
  3. The visual effect of flipX/flipY when combined with pivot points

1-18: Consider adding validation methods to the interface

The interface provides a good foundation for GUI rendering. To enhance robustness, consider adding validation methods for the new parameters:

  • Bounds checking for width/height
  • Pivot point validation
  • Alpha range validation (0.0 to 1.0)

This could be achieved by either:

  1. Adding a new validation method to the interface
  2. Creating a separate validation utility class
  3. Implementing validation in a base assembler class

Would you like me to provide an example implementation for any of these approaches?

packages/core/src/2d/assembler/SimpleSpriteAssembler.ts (1)

Line range hint 42-48: Add documentation for matrix calculations.

The matrix calculations for world transformation could benefit from documentation explaining the mathematical approach and the purpose of each operation.

packages/core/src/ui/UIRenderer.ts (1)

21-63: Add JSDoc documentation for public and protected properties.

While the properties are well-organized with appropriate decorators, adding JSDoc documentation would improve code maintainability and developer experience. Consider documenting:

  • Purpose of depth and its valid range
  • Usage of raycastPadding
  • Role of _alpha in rendering

Example documentation:

/** Determines the rendering order of UI elements. Higher values render on top. */
@ignoreClone
depth: number = 0;

/** Adjusts the hit area for raycasting in the order: left, bottom, right, top. */
@deepClone
raycastPadding: Vector4 = new Vector4(0, 0, 0, 0);
packages/core/src/2d/sprite/SpriteMask.ts (3)

239-246: Consider adding bounds validation for sprite pivot.

While the implementation is correct, it could benefit from additional validation to ensure sprite.pivot is not undefined before using it in updatePositions.

 protected override _updateLocalBounds(localBounds: BoundingBox): void {
   const { sprite } = this;
-  if (sprite) {
+  if (sprite?.pivot) {
     SimpleSpriteAssembler.updatePositions(this, this.width, this.height, sprite.pivot, this._flipX, this._flipY);
   } else {
     localBounds.min.set(0, 0, 0);
     localBounds.max.set(0, 0, 0);
   }
 }

330-342: Consider extracting size change condition for better readability.

The size change handling could be more explicit by extracting the condition check into a meaningful variable name.

 case SpriteModifyFlags.size:
-  if (this._customWidth === undefined || this._customHeight === undefined) {
+  const isUsingAutomaticSize = this._customWidth === undefined || this._customHeight === undefined;
+  if (isUsingAutomaticSize) {
     this._calDefaultSize();
     this._dirtyUpdateFlag |= RendererUpdateFlags.AllPositionAndBounds;
   }
   break;

354-362: Consider adding detailed documentation for flag combinations.

While the enum is well-structured, it would benefit from more detailed documentation explaining the specific use cases for each flag combination.

 /**
+ * Update flags specific to sprite masks, extending the base renderer update flags.
+ * @remarks Extends `RendererUpdateFlags`.
+ * 
+ * Flags:
+ * - UV (0x10): Updates UV coordinates only
+ * - AllPositionAndUV (0x13): Updates both position (local & world) and UV coordinates
+ * - All (0x1f): Updates all properties including position, UV, and bounds
  */
 enum SpriteMaskUpdateFlags {
   UV = 0x10,
packages/core/src/2d/sprite/SpriteRenderer.ts (1)

84-84: Improved update flag granularity

The update flag changes are well-structured and more specific, which should improve performance by avoiding unnecessary updates. The flags are appropriately set based on the property being modified and the current draw mode.

Consider documenting the update flag combinations in the class documentation to help maintainers understand when each flag is used and why. This would make it easier to maintain the correct flag usage as the codebase evolves.

Also applies to: 99-99, 116-116, 181-182, 206-207, 221-221, 235-235

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (1)

75-84: Add comments explaining the matrix transformation logic.

The matrix transformation code would benefit from documentation explaining:

  1. How the pivot affects the transformation
  2. How the flip transformations are applied
  3. The order of transformations
packages/core/src/2d/text/TextRenderer.ts (3)

Line range hint 237-257: Add missing property declarations for mask-related fields.

The maskInteraction and maskLayer getters/setters are accessing undeclared properties.

Add these property declarations at the class level:

+private _maskInteraction: SpriteMaskInteraction;
+private _maskLayer: number = 0;

Line range hint 478-482: Consider caching text metrics calculation results.

The text metrics calculation is potentially expensive, especially for static text. Consider caching the results when the text content and style properties haven't changed.

+private _cachedMetrics: TextMetrics | null = null;
+
 private _updateLocalData(): void {
   const { min, max } = this._localBounds;
   const charRenderInfos = TextRenderer._charRenderInfos;
   const charFont = this._getSubFont();
-  const textMetrics = this.enableWrapping
-    ? TextUtils.measureTextWithWrap(this)
-    : TextUtils.measureTextWithoutWrap(this);
+  if (!this._cachedMetrics || this._isContainDirtyFlag(TextRendererUpdateFlags.FontAllPositionAndBounds)) {
+    this._cachedMetrics = this.enableWrapping
+      ? TextUtils.measureTextWithWrap(this)
+      : TextUtils.measureTextWithoutWrap(this);
+  }
+  const textMetrics = this._cachedMetrics;

Line range hint 571-577: Optimize array sorting for better performance.

The sort operation on charRenderInfos could be optimized by pre-allocating the array and maintaining the sorted order during insertion.

Consider maintaining a sorted order during insertion to avoid the O(n log n) sort operation:

// Pre-allocate the array with the exact size needed
charRenderInfos.length = renderElementCount;
// Insert elements in sorted order based on texture.instanceId
for (let i = 0; i < renderElementCount; i++) {
  let j = i - 1;
  const temp = charRenderInfos[i];
  while (j >= 0 && charRenderInfos[j].texture.instanceId > temp.texture.instanceId) {
    charRenderInfos[j + 1] = charRenderInfos[j];
    j--;
  }
  charRenderInfos[j + 1] = temp;
}
packages/core/src/ui/UICanvas.ts (4)

23-23: Consider removing redundant type annotations.

TypeScript can infer these types from their initial values. Removing explicit type annotations would make the code more concise while maintaining type safety.

Apply this diff to simplify the type annotations:

-  depth: number = 0;
+  depth = 0;

-  _isRootCanvas: boolean = false;
+  _isRootCanvas = false;

-  _canvasIndex: number = -1;
+  _canvasIndex = -1;

-  _sortDistance: number = 0;
+  _sortDistance = 0;

-  private _sortOrder: number = 0;
+  private _sortOrder = 0;

-  private _distance: number = 10;
+  private _distance = 10;

-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution = new Vector2(800, 600);

Also applies to: 32-32, 35-35, 56-56, 80-80, 82-82, 86-86


120-120: Improve readability by avoiding assignment within expression.

The current implementation uses assignment within a logical expression, which can be confusing. Consider using a more explicit if statement.

Apply this diff to improve readability:

-    (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
+    if (referenceResolution.x !== val.x || referenceResolution.y !== val.y) {
+      referenceResolution.copyFrom(val);
+    }

587-598: Document intentional switch case fallthrough.

The switch cases in _setRealRenderMode have intentional fallthrough behavior for initialization, but this should be documented to prevent confusion.

Add comments to indicate intentional fallthrough:

      switch (curRealMode) {
        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
+         // Fallthrough intentional for initialization
        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
+         // Fallthrough intentional for initialization
        case CanvasRenderMode.WorldSpace:
          componentsManager.addUICanvas(curRealMode, this);
          break;
🧰 Tools
🪛 Biome

[error] 588-589: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 590-592: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


461-461: Improve readability by avoiding assignment within expression.

The assignment within the logical expression makes the code harder to read.

Apply this diff to improve readability:

-      child.isActive && (depth = this._walk(child, elements, depth));
+      if (child.isActive) {
+        depth = this._walk(child, elements, depth);
+      }
🧰 Tools
🪛 Biome

[error] 461-461: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (1)

Line range hint 28-94: Ensure consistency in renderer type usage across methods

In the updatePositions method, the renderer parameter is typed as SpriteRenderer | SpriteMask | UIImage, while in other methods like resetData, updateUVs, and updateColor, it's typed as SpriteRenderer | UIImage. For consistency and maintainability, consider unifying the renderer types across these methods unless there's a specific reason for the difference.

packages/core/src/ui/UIImage.ts (2)

223-223: Use Local 'sprite' Variable Instead of 'this.sprite'

In the _render method, a local sprite variable is already defined. For consistency and to prevent potential null reference errors, consider using the local sprite variable instead of this.sprite when accessing the texture.

Apply this diff to make the change:

-    subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk);
+    subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, sprite.texture, subChunk);

275-278: Avoid Nullifying Class Properties in _onDestroy

In the _onDestroy method, class properties such as this._entity, this._color, this._sprite, and this._assembler are set to null.

Setting class properties to null in the destructor is generally unnecessary because the garbage collector will handle unused objects. Removing these assignments can simplify the code and slightly improve performance.

Apply this diff to remove unnecessary null assignments:

-    this._entity = null;
-    this._color = null;
-    this._sprite = null;
-    this._assembler = null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f702b2b and bc8a8d4.

📒 Files selected for processing (12)
  • packages/core/src/2d/assembler/ISpriteAssembler.ts (1 hunks)
  • packages/core/src/2d/assembler/SimpleSpriteAssembler.ts (4 hunks)
  • packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (6 hunks)
  • packages/core/src/2d/assembler/TiledSpriteAssembler.ts (6 hunks)
  • packages/core/src/2d/sprite/SpriteMask.ts (9 hunks)
  • packages/core/src/2d/sprite/SpriteRenderer.ts (14 hunks)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/Renderer.ts (9 hunks)
  • packages/core/src/particle/ParticleRenderer.ts (3 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIImage.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
🧰 Additional context used
🪛 Biome
packages/core/src/2d/sprite/SpriteRenderer.ts

[error] 321-321: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 322-322: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

packages/core/src/ui/UICanvas.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 242-242: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 343-343: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 347-347: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 348-348: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 346-351: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 461-461: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 578-579: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 588-589: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 590-592: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIRenderer.ts

[error] 152-152: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 156-156: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 157-157: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 155-159: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (25)
packages/core/src/2d/assembler/ISpriteAssembler.ts (2)

1-1: LGTM: Import statement is correctly added

The Vector2 import is necessary for the new pivot parameter type in the updatePositions method.


18-18: LGTM: Color update method appropriately enhanced

The addition of an optional alpha parameter provides better control over transparency while maintaining backward compatibility.

Let's verify the alpha parameter usage across implementations:

✅ Verification successful

Let me try a different approach to search for updateColor implementations with a more flexible pattern.

This broader search should help us find all implementations and usages of the updateColor method, regardless of their parameter structure, so we can verify the alpha parameter handling across the codebase.


Optional alpha parameter is consistently implemented across the codebase

The verification shows that the optional alpha parameter is properly implemented:

  • The interface ISpriteAssembler correctly declares updateColor with optional alpha parameter
  • All concrete implementations (SimpleSpriteAssembler, TiledSpriteAssembler, SlicedSpriteAssembler) consistently:
    • Accept the alpha parameter
    • Provide a default value of 1
    • Properly combine it with the renderer's color alpha value

The change is well-integrated and maintains consistency across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for updateColor implementations to ensure consistent alpha handling
ast-grep --pattern 'updateColor(renderer: $_, alpha?: number) {
  $$$
}'

Length of output: 74


Script:

#!/bin/bash
# Search for updateColor method implementations with broader pattern
ast-grep --pattern 'updateColor($$$) {
  $$$
}'

# Also search for updateColor method declarations in interfaces and abstract classes
ast-grep --pattern 'updateColor($$$): void;'

# Backup search using ripgrep for any updateColor occurrences
rg "updateColor" -A 3

Length of output: 2704

packages/core/src/2d/assembler/SimpleSpriteAssembler.ts (2)

16-16: Consider implementing a common Renderable interface.

Referencing the previous discussion: A common Renderable interface for SpriteRenderer, SpriteMask, and UIImage would improve code maintainability and reduce type union complexity.


40-41: 🛠️ Refactor suggestion

Consider using unary negation for better performance.

Based on the previous performance discussion, using unary negation (-width instead of ? -width : width) could be more efficient:

-const sx = flipX ? -width : width;
-const sy = flipY ? -height : height;
+const sx = width * (flipX ? -1 : 1);
+const sy = height * (flipY ? -1 : 1);

Likely invalid or redundant comment.

packages/core/src/ui/UIRenderer.ts (1)

1-20: LGTM! Well-structured class with appropriate dependencies.

The class is well-organized with clear dependencies and proper use of the @dependentComponents decorator to ensure UITransform availability.

packages/core/src/particle/ParticleRenderer.ts (2)

Line range hint 262-279: Verify consistency with related components.

The changes to flag management in ParticleRenderer are part of a larger GUI infrastructure update. We should ensure consistent flag usage patterns across SpriteMask, SpriteRenderer, and other related components.

Let's verify the consistency:

#!/bin/bash
# Compare flag usage patterns across components
echo "Analyzing flag patterns in related components..."
rg --type ts "UpdateFlags" -g "**(Sprite|Particle)**" -B 1 -A 1

# Check for potential inconsistencies in bounds management
echo "Checking bounds management consistency..."
rg --type ts "AllBounds|WorldVolume" -g "**(Sprite|Particle)**"

262-262: Verify the bounds update behavior.

The changes consistently add RendererUpdateFlags.AllBounds to various update methods. While this aligns with the enhanced bounds management mentioned in the summary, we should verify that:

  1. The bounds are correctly updated in all scenarios
  2. There are no redundant bounds updates that could impact performance

Let's analyze the bounds update behavior:

Also applies to: 271-271, 279-279

✅ Verification successful

Let me try different search patterns to analyze the bounds update behavior.

The previous search patterns were too restrictive. These broader patterns should help us understand:

  1. All occurrences of AllBounds flag usage
  2. The pattern of _dirtyUpdateFlag assignments
  3. The RendererUpdateFlags enum definition to understand flag relationships

Based on the analysis of the search results, I can now provide a final response about the bounds update behavior:

The bounds update behavior is correctly implemented

The code shows a well-structured and consistent pattern for bounds updates:

  1. RendererUpdateFlags.AllBounds (0xc) correctly combines both LocalBounds (0x4) and WorldBounds (0x8) flags
  2. The flag is used appropriately in different scenarios:
    • In _onWorldVolumeChanged: Updates bounds when volume changes
    • In _onGeneratorParamsChanged: Updates bounds along with generator and transform volumes
    • In _onTransformChanged: Updates bounds along with transform volume

The implementation ensures bounds are updated when necessary and there are no redundant updates since:

  • Each update method sets specific flags based on what actually changed
  • The dirty flags are cleared after processing (&= ~flag pattern)
  • The flag combinations are used efficiently through bitwise operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for AllBounds flag usage patterns
echo "Analyzing bounds update patterns..."
rg --type ts "AllBounds" -B 2 -A 2

# Search for potential performance impact
echo "Checking for potential redundant updates..."
ast-grep --pattern 'this._dirtyUpdateFlag |= $flag'

Length of output: 627


Script:

#!/bin/bash
# Search for AllBounds flag usage in the entire codebase
echo "Analyzing AllBounds flag usage..."
rg "AllBounds" --type ts -B 3 -A 3

# Search for _dirtyUpdateFlag assignments
echo -e "\nAnalyzing update flag assignments..."
rg "_dirtyUpdateFlag.*=" --type ts -B 2 -A 2

# Look for the RendererUpdateFlags enum definition
echo -e "\nSearching for RendererUpdateFlags definition..."
rg "enum.*RendererUpdateFlags" --type ts -A 10

Length of output: 39593

packages/core/src/2d/sprite/SpriteMask.ts (2)

78-78: LGTM: Consistent update flag usage across property setters.

The change to use RendererUpdateFlags.AllPositionAndBounds across all dimension and flip property setters ensures comprehensive updates of both position and bounds, which is more appropriate than the previous WorldVolume flag.

Also applies to: 100-100, 114-114, 128-128


253-254: LGTM: Efficient early returns and consistent flag usage.

The implementation correctly:

  • Performs early validation of required properties
  • Uses the new AllPositions flag for position updates
  • Clears the flag after updates

Also applies to: 269-271

packages/core/src/2d/sprite/SpriteRenderer.ts (5)

10-10: LGTM: Component type initialization

The addition of ComponentType import and initialization in the constructor is appropriate for proper component type management.

Also applies to: 268-268


347-349: LGTM: Enhanced position updates

The position update logic now correctly handles flipped sprites by passing the flip parameters to the assembler.


410-427: Well-structured sprite change handling

The sprite change handling is now more comprehensive with:

  • Proper handling of custom dimensions
  • Draw mode-specific update flag management
  • Appropriate handling of border, region, and pivot changes

Also applies to: 431-433, 437-437, 443-443


458-469: LGTM: Well-documented update flags

The SpriteRendererUpdateFlags enum is well-structured with clear documentation for each flag combination, making it easier to understand and maintain the update system.


316-328: ⚠️ Potential issue

Simplify boolean expressions in bounds calculation

The local bounds calculation logic is correct, but there's unnecessary use of double negation (!!) operators.

Apply this diff to simplify the expressions:

-      pivotX = !!this.flipX ? 1 - pivotX : pivotX;
-      pivotY = !!this.flipY ? 1 - pivotY : pivotY;
+      pivotX = this.flipX ? 1 - pivotX : pivotX;
+      pivotY = this.flipY ? 1 - pivotY : pivotY;

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 321-321: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 322-322: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

packages/core/src/Renderer.ts (1)

499-501: LGTM: Correct bounds transformation implementation.

The method properly transforms local bounds to world space using the entity's world matrix.

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (2)

37-44: 🛠️ Refactor suggestion

Consider using a configuration object for better parameter management.

The method now has multiple parameters that could be grouped logically. This would make the method more maintainable and easier to extend in the future.

interface SpriteTransformConfig {
  width: number;
  height: number;
  pivot: Vector2;
  flipX?: boolean;
  flipY?: boolean;
}

static updatePositions(renderer: SpriteRenderer | UIImage, config: SpriteTransformConfig): void {

179-189: 🛠️ Refactor suggestion

Consider using destructuring for cleaner code.

As suggested in previous reviews, the color extraction could be simplified using destructuring. However, if the current approach is preferred for consistency or readability reasons, that's acceptable too.

const { _subChunk: { chunk: { vertices }, vertexArea }, color: { r, g, b, a } } = renderer;
const finalAlpha = a * alpha;
packages/core/src/2d/text/TextRenderer.ts (2)

691-701: Well-structured update flags enum.

Good job on extending RendererUpdateFlags with text-specific flags. The enum provides clear categorization of different update scenarios.


306-320: ⚠️ Potential issue

Add missing _dirtyUpdateFlag property declaration.

The flag management methods are using an undeclared property.

Add this property declaration at the class level:

+private _dirtyUpdateFlag: number = 0;

Likely invalid or redundant comment.

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (5)

Line range hint 19-26: Implementation of resetData method looks good

The changes to the resetData method correctly extend support to UIImage alongside SpriteRenderer. The allocation and assignment of the subChunk are handled appropriately.


85-94: Confirm flip logic and coordinate transformations

The calculation of sx and sy for flip transformations appears correct. However, please verify that the sign inversion works as intended for all renderer types, especially considering the addition of UIImage.


36-36: Verify that all renderer types have a sprite property

The line const { sprite } = renderer; assumes that all renderer types have a sprite property. Please confirm that SpriteRenderer, SpriteMask, and UIImage all have a sprite property accessible in this context.

To confirm this, run the following script:

#!/bin/bash
# Description: Verify that all renderer types have a 'sprite' property.

# Check for 'sprite' property in SpriteRenderer
ast-grep --pattern $'class SpriteRenderer {
  $$$
  sprite: $_
  $$$
}'

# Check for 'sprite' property in SpriteMask
ast-grep --pattern $'class SpriteMask {
  $$$
  sprite: $_
  $$$
}'

# Check for 'sprite' property in UIImage
ast-grep --pattern $'class UIImage {
  $$$
  sprite: $_
  $$$
}'

Line range hint 125-136: Verify access to sprite._getUVs() for all renderer types

In the updateUVs method, the code accesses renderer.sprite._getUVs(). Ensure that both SpriteRenderer and UIImage have a sprite property with a _getUVs() method to prevent runtime errors.

Run the following script to verify:

#!/bin/bash
# Description: Verify that both SpriteRenderer and UIImage have a 'sprite' property with a '_getUVs()' method.

# Check for '_getUVs' method in Sprite class
ast-grep --pattern $'class Sprite {
  $$$
  _getUVs() {
    $$$
  }
  $$$
}'

# Ensure that SpriteRenderer and UIImage have a 'sprite' property of type Sprite

# Check for 'sprite' property in SpriteRenderer
ast-grep --pattern $'class SpriteRenderer {
  $$$
  sprite: Sprite
  $$$
}'

# Check for 'sprite' property in UIImage
ast-grep --pattern $'class UIImage {
  $$$
  sprite: Sprite
  $$$
}'

138-149: Confirm that color property exists on all renderer types

In the updateColor method, the color components are extracted from renderer.color. Please verify that both SpriteRenderer and UIImage have a color property to ensure this operation is valid for all renderer instances.

To verify, execute:

✅ Verification successful

Let me try a different approach to verify the existence of the color property.

This should help us understand:

  1. The class definitions and their direct properties
  2. Any inheritance relationships that might provide the color property
  3. The context around these classes to verify the color property's existence

Let me generate the final response based on the evidence found in the codebase.

Both SpriteRenderer and UIImage have the required color property

Both classes have a private _color property of type Color initialized with new Color(1, 1, 1, 1). Let's verify they expose this via a getter:


The color property is properly implemented in both classes

Both SpriteRenderer and UIImage classes have:

  • A private _color: Color property initialized with new Color(1, 1, 1, 1)
  • A public getter color(): Color that returns the _color property
  • A public setter color(value: Color) for updating the color value

The usage of renderer.color in the updateColor method is valid and safe for both renderer types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that SpriteRenderer and UIImage classes have a 'color' property.

# Check for 'color' property in SpriteRenderer
ast-grep --pattern $'class SpriteRenderer {
  $$$
  color: $_
  $$$
}'

# Check for 'color' property in UIImage
ast-grep --pattern $'class UIImage {
  $$$
  color: $_
  $$$
}'

Length of output: 363


Script:

#!/bin/bash
# Search for color property or getter in SpriteRenderer and UIImage
# Using ripgrep for a broader search including potential inheritance

# Search for SpriteRenderer class definition and its properties
rg "class SpriteRenderer" -A 20

# Search for UIImage class definition and its properties
rg "class UIImage" -A 20

# Search for potential base classes that might contain the color property
rg "extends" --type ts | rg -i "SpriteRenderer|UIImage"

Length of output: 3065


Script:

#!/bin/bash
# Search for color getter in both classes
rg "get color\(\)" -A 5 --type ts

Length of output: 2550

packages/core/src/ui/UIImage.ts (1)

102-119: Ensure Balanced Resource Reference Counting for Sprites

In the set sprite(value: Sprite | null) method, when setting a new sprite, the old sprite's reference count is decremented, and the new sprite's reference count is incremented if it is not null. However, when value is null, the new sprite is not incremented, which is correct. Ensure that this behavior maintains balanced reference counting throughout the lifecycle of the sprite.

Run the following script to verify that all increments and decrements of sprite reference counts are balanced:

Check that for every increment (1), there is a corresponding decrement (-1), ensuring that resources are properly managed and there are no memory leaks.

✅ Verification successful

Let me gather more context about the resource reference counting implementation by checking the complete class implementation.


After analyzing the complete implementation of UIImage.ts, I can now provide the final response:

Resource Reference Counting is Properly Balanced

The reference counting implementation in UIImage is correctly balanced:

  • In the set sprite() method:
    • Decrements (-1) the old sprite's reference count and removes its listener
    • Increments (+1) the new sprite's reference count and adds its listener
  • In the _onDestroy() method:
    • Properly decrements (-1) the sprite's reference count during cleanup
    • Removes the sprite's listener

The implementation ensures that reference counts are properly managed throughout the lifecycle of the UIImage component, preventing memory leaks. The null checks and equality comparisons also prevent unnecessary reference count modifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify balanced usage of _addResourceReferCount in UIImage.ts

# Search for all increments and decrements of _addResourceReferCount in UIImage.ts
rg '_addResourceReferCount' packages/core/src/ui/UIImage.ts

Length of output: 206


Script:

#!/bin/bash
# Get the complete UIImage.ts file content to understand the reference counting logic
cat packages/core/src/ui/UIImage.ts

Length of output: 10527

Comment on lines 83 to 92
static updateColor(renderer: SpriteRenderer, alpha: number = 1): void {
const subChunk = renderer._subChunk;
const { r, g, b, a } = renderer.color;
const finalAlpha = a * alpha;
const vertices = subChunk.chunk.vertices;
for (let i = 0, o = subChunk.vertexArea.start + 5; i < 4; ++i, o += 9) {
vertices[o] = r;
vertices[o + 1] = g;
vertices[o + 2] = b;
vertices[o + 3] = a;
vertices[o + 3] = finalAlpha;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add parameter validation for alpha value.

The alpha parameter should be validated to ensure it's within the valid range [0, 1]:

 static updateColor(renderer: SpriteRenderer, alpha: number = 1): void {
+  if (alpha < 0 || alpha > 1) {
+    throw new Error("Alpha value must be between 0 and 1");
+  }
   const subChunk = renderer._subChunk;
   const { r, g, b, a } = renderer.color;
   const finalAlpha = a * alpha;
📝 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
static updateColor(renderer: SpriteRenderer, alpha: number = 1): void {
const subChunk = renderer._subChunk;
const { r, g, b, a } = renderer.color;
const finalAlpha = a * alpha;
const vertices = subChunk.chunk.vertices;
for (let i = 0, o = subChunk.vertexArea.start + 5; i < 4; ++i, o += 9) {
vertices[o] = r;
vertices[o + 1] = g;
vertices[o + 2] = b;
vertices[o + 3] = a;
vertices[o + 3] = finalAlpha;
static updateColor(renderer: SpriteRenderer, alpha: number = 1): void {
if (alpha < 0 || alpha > 1) {
throw new Error("Alpha value must be between 0 and 1");
}
const subChunk = renderer._subChunk;
const { r, g, b, a } = renderer.color;
const finalAlpha = a * alpha;
const vertices = subChunk.chunk.vertices;
for (let i = 0, o = subChunk.vertexArea.start + 5; i < 4; ++i, o += 9) {
vertices[o] = r;
vertices[o + 1] = g;
vertices[o + 2] = b;
vertices[o + 3] = finalAlpha;

Comment on lines 106 to 120
override _prepareRender(context: RenderContext): void {
// Update once per frame per renderer, not influenced by batched
if (this._renderFrameCount !== this.engine.time.frameCount) {
this._update(context);
}

this._render(context);

// union camera global macro and renderer macro.
ShaderMacroCollection.unionCollection(
context.camera._globalShaderMacro,
this.shaderData._macroCollection,
this._globalShaderMacro
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

LGTM! Consider caching the frame count.

The _prepareRender method's implementation is correct, but consider caching this.engine.time.frameCount to avoid multiple property accesses within the same frame.

 override _prepareRender(context: RenderContext): void {
+  const currentFrame = this.engine.time.frameCount;
   // Update once per frame per renderer, not influenced by batched
-  if (this._renderFrameCount !== this.engine.time.frameCount) {
+  if (this._renderFrameCount !== currentFrame) {
     this._update(context);
   }

Committable suggestion was skipped due to low confidence.

Comment on lines 191 to 228
_raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
const entity = this._entity;
const plane = UIRenderer._tempPlane;
const transform = entity.transform;
const normal = plane.normal.copyFrom(transform.worldForward);
plane.distance = -Vector3.dot(normal, transform.worldPosition);
const curDistance = ray.intersectPlane(plane);
if (curDistance >= 0 && curDistance < distance) {
const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
const worldMatrixInv = UIRenderer._tempMat;
Matrix.invert(this.entity.transform.worldMatrix, worldMatrixInv);
const localPosition = UIRenderer._tempVec31;
Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
if (this._hitTest(localPosition)) {
out.distance = curDistance;
out.entity = entity;
out.component = this;
out.normal.copyFrom(normal);
out.point.copyFrom(hitPointWorld);
return true;
}
}
return false;
}

protected _hitTest(localPosition: Vector3): boolean {
const { x, y } = localPosition;
const uiTransform = <UITransform>this._transform;
const { x: width, y: height } = uiTransform.size;
const { x: pivotX, y: pivotY } = uiTransform.pivot;
const { x: paddingLeft, y: paddingBottom, z: paddingRight, w: paddingTop } = this.raycastPadding;
return (
x >= -width * pivotX + paddingLeft &&
x <= width * (1 - pivotX) - paddingRight &&
y >= -height * pivotY + paddingTop &&
y <= height * (1 - pivotY) - paddingBottom
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize raycasting and improve variable naming.

The raycasting implementation is correct but could be optimized:

  1. Cache the ray.intersectPlane result
  2. Use more descriptive variable names for size dimensions
  3. Destructure transform properties for better readability
 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const { _entity: entity } = this;
   const { transform } = entity;
   const { worldForward, worldMatrix, worldPosition } = transform;
   const plane = UIRenderer._tempPlane;
-  const normal = plane.normal.copyFrom(transform.worldForward);
+  const normal = plane.normal.copyFrom(worldForward);
   plane.distance = -Vector3.dot(normal, worldPosition);
-  const curDistance = ray.intersectPlane(plane);
+  const intersectionDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
+  if (intersectionDistance >= 0 && intersectionDistance < distance) {
     const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
     const worldMatrixInv = UIRenderer._tempMat;
-    Matrix.invert(this.entity.transform.worldMatrix, worldMatrixInv);
+    Matrix.invert(worldMatrix, worldMatrixInv);

And in the _hitTest method:

 protected _hitTest(localPosition: Vector3): boolean {
   const { x, y } = localPosition;
   const uiTransform = <UITransform>this._transform;
-  const { x: width, y: height } = uiTransform.size;
+  const { x: elementWidth, y: elementHeight } = uiTransform.size;
   const { x: pivotX, y: pivotY } = uiTransform.pivot;

Committable suggestion was skipped due to low confidence.

Comment on lines +288 to +290
TransformVolume = 0x10,
/** On Generator Bounds Related Params Changed */
GeneratorVolume = 0x4
GeneratorVolume = 0x20
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Let me analyze the findings. The ripgrep results show that these flags are used in ParticleGenerator.ts to check and clear dirty flags related to transform and generator volume changes. The flags are used in conditional checks and flag clearing operations.

Since we found references to these flags being used in the codebase, and they appear to be part of a dirty flag system for particle updates, changing their values could break existing functionality that relies on these specific bit values.

Let me generate the final response:

Changing these flag values could break existing functionality

The flags TransformVolume and GeneratorVolume are actively used in ParticleGenerator.ts as part of the particle system's dirty flag mechanism. Shifting their values from 0x2 and 0x4 to 0x10 and 0x20 respectively would break the dirty flag checks in ParticleGenerator.ts that expect the original values.

  • ParticleGenerator.ts uses these flags to check and clear dirty states via:
    • _isContainDirtyFlag() checks
    • _setDirtyFlagFalse() operations

The flag values should remain unchanged to maintain compatibility with the existing dirty flag system.

🔗 Analysis chain

Verify the impact of flag value changes.

The flag values for TransformVolume and GeneratorVolume have been shifted left by 4 bits (from 0x2→0x10 and 0x4→0x20). This change could break existing code that directly uses these flag values.

Let's verify the usage of these flags across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of the old flag values
echo "Searching for potential breaking changes..."
rg --type ts "0x2|0x4" --glob "!**/ParticleRenderer.ts"

# Search for all references to these flags
echo "Searching for all references to these flags..."
rg --type ts "TransformVolume|GeneratorVolume" --glob "!**/ParticleRenderer.ts"

Length of output: 227346

Comment on lines +148 to +154
get localBounds(): BoundingBox {
if (this._dirtyUpdateFlag & RendererUpdateFlags.LocalBounds) {
this._updateLocalBounds(this._localBounds);
this._dirtyUpdateFlag &= ~RendererUpdateFlags.LocalBounds;
}
return this._localBounds;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider caching BoundingBox calculations.

The getters recalculate bounds whenever the dirty flag is set. For performance optimization in scenarios with frequent bounds access but infrequent changes, consider:

  1. Caching the calculated bounds
  2. Only updating when the dirty flag is set
  get localBounds(): BoundingBox {
    if (this._dirtyUpdateFlag & RendererUpdateFlags.LocalBounds) {
      this._updateLocalBounds(this._localBounds);
      this._dirtyUpdateFlag &= ~RendererUpdateFlags.LocalBounds;
+     this._cachedLocalBounds = this._localBounds.clone();
+   } else if (this._cachedLocalBounds) {
+     return this._cachedLocalBounds;
    }
    return this._localBounds;
  }

Also applies to: 157-165

Comment on lines +555 to +570
None = 0x0,
LocalPosition = 0x1,
WorldPosition = 0x2,
LocalBounds = 0x4,
WorldBounds = 0x8,

/** LocalPosition | WorldPosition */
AllPositions = 0x3,
/** LocalPosition | LocalBounds */
LocalPositionAndBounds = 0x5,
/** WorldPosition | WorldBounds */
WorldPositionAndBounds = 0xa,
/** LocalBounds | WorldBounds */
AllBounds = 0xc,
/** LocalPosition | WorldPosition | LocalBounds | WorldBounds */
AllPositionAndBounds = 0xf
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using const enum for better type safety.

The enum defines bit flags for renderer updates. Consider using const enum to:

  1. Ensure values are inlined at compile time
  2. Prevent runtime object creation
  3. Provide better type safety
- export enum RendererUpdateFlags {
+ export const enum RendererUpdateFlags {
📝 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
None = 0x0,
LocalPosition = 0x1,
WorldPosition = 0x2,
LocalBounds = 0x4,
WorldBounds = 0x8,
/** LocalPosition | WorldPosition */
AllPositions = 0x3,
/** LocalPosition | LocalBounds */
LocalPositionAndBounds = 0x5,
/** WorldPosition | WorldBounds */
WorldPositionAndBounds = 0xa,
/** LocalBounds | WorldBounds */
AllBounds = 0xc,
/** LocalPosition | WorldPosition | LocalBounds | WorldBounds */
AllPositionAndBounds = 0xf
export const enum RendererUpdateFlags {
None = 0x0,
LocalPosition = 0x1,
WorldPosition = 0x2,
LocalBounds = 0x4,
WorldBounds = 0x8,
/** LocalPosition | WorldPosition */
AllPositions = 0x3,
/** LocalPosition | LocalBounds */
LocalPositionAndBounds = 0x5,
/** WorldPosition | WorldBounds */
WorldPositionAndBounds = 0xa,
/** LocalBounds | WorldBounds */
AllBounds = 0xc,
/** LocalPosition | WorldPosition | LocalBounds | WorldBounds */
AllPositionAndBounds = 0xf

@@ -18,7 +19,7 @@ export class TiledSpriteAssembler {
static _uvRow = new DisorderedArray<number>();
static _uvColumn = new DisorderedArray<number>();

static resetData(renderer: SpriteRenderer, vertexCount: number): void {
static resetData(renderer: SpriteRenderer | UIImage, vertexCount: number): void {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider creating a shared interface for renderer types.

Since multiple methods use the union type SpriteRenderer | UIImage, consider extracting this to a shared interface or type alias to improve maintainability and ensure consistency.

type Renderable = SpriteRenderer | UIImage;

static resetData(renderer: Renderable, vertexCount: number): void {

Comment on lines 346 to 357
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
this._setIsRootCanvas(!rootCanvas);
UIUtils.registerUIToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerUIToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix switch case fallthrough and variable scoping.

The switch case for EntityModifyFlags.Parent falls through to the next case, and the rootCanvas variable could leak to other cases.

Apply this diff to fix the issues:

      case EntityModifyFlags.UICanvasEnableInScene:
+        break;
      case EntityModifyFlags.Parent:
+        {
          const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
          if (rootCanvas) {
            rootCanvas._hierarchyDirty = true;
          }
          this._setIsRootCanvas(!rootCanvas);
          UIUtils.registerUIToCanvas(this, rootCanvas);
          UIUtils.registerEntityListener(this);
+        }
+        break;
      case EntityModifyFlags.UIGroupEnableInScene:

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 Biome

[error] 347-347: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 348-348: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 346-351: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 124 to 133
get color(): Color {
return this._color;
}

set color(value: Color) {
if (this._color !== value) {
this._color.copyFrom(value);
this._dirtyUpdateFlag |= ImageUpdateFlags.Color;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Monitor Changes to the Color Property

Direct modifications to the color property via the returned Color object do not trigger updates to the rendering state because the setter is bypassed. This means that changes to color.r, color.g, etc., won't automatically set the ImageUpdateFlags.Color flag.

Implement a mechanism to detect changes to the Color object and update the rendering state accordingly. One approach is to use an observer pattern or modify the Color class to accept a change callback.

For example, modify the initialization:

- private _color: Color = new Color(1, 1, 1, 1);
+ private _color: Color = new Color(1, 1, 1, 1, () => {
+   this._dirtyUpdateFlag |= ImageUpdateFlags.Color;
+ });

Update the Color class to include a change callback:

class Color {
  private _onChange: () => void;

  constructor(r: number, g: number, b: number, a: number, onChange?: () => void) {
    this._r = r;
    this._g = g;
    this._b = b;
    this._a = a;
    this._onChange = onChange;
  }

  set r(value: number) {
    this._r = value;
    this._onChange?.();
  }

  // Repeat for g, b, a
}

This ensures that any direct modifications to the color properties will update the rendering appropriately.

Comment on lines 44 to 56
switch (value) {
case SpriteDrawMode.Simple:
this._assembler = SimpleSpriteAssembler;
break;
case SpriteDrawMode.Sliced:
this._assembler = SlicedSpriteAssembler;
break;
case SpriteDrawMode.Tiled:
this._assembler = TiledSpriteAssembler;
break;
default:
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle Default Case in Switch Statement for 'drawMode' Setter

In the set drawMode(value: SpriteDrawMode) method, the switch statement does not handle unexpected drawMode values in the default case. If an invalid drawMode is provided, _assembler could remain undefined, potentially causing runtime errors when _assembler is used later.

Consider handling the default case to ensure _assembler is always assigned. You can throw an error or assign a default assembler to prevent potential issues.

Apply this diff to handle the default case:

          default:
+           throw new Error(`Unsupported drawMode value: ${value}`);
            break;

Alternatively, assign a default assembler:

          default:
+           this._assembler = SimpleSpriteAssembler;
            break;
📝 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
switch (value) {
case SpriteDrawMode.Simple:
this._assembler = SimpleSpriteAssembler;
break;
case SpriteDrawMode.Sliced:
this._assembler = SlicedSpriteAssembler;
break;
case SpriteDrawMode.Tiled:
this._assembler = TiledSpriteAssembler;
break;
default:
break;
}
switch (value) {
case SpriteDrawMode.Simple:
this._assembler = SimpleSpriteAssembler;
break;
case SpriteDrawMode.Sliced:
this._assembler = SlicedSpriteAssembler;
break;
case SpriteDrawMode.Tiled:
this._assembler = TiledSpriteAssembler;
break;
default:
throw new Error(`Unsupported drawMode value: ${value}`);
break;
}
```
Option 2 (default assembler):
```suggestion
switch (value) {
case SpriteDrawMode.Simple:
this._assembler = SimpleSpriteAssembler;
break;
case SpriteDrawMode.Sliced:
this._assembler = SlicedSpriteAssembler;
break;
case SpriteDrawMode.Tiled:
this._assembler = TiledSpriteAssembler;
break;
default:
this._assembler = SimpleSpriteAssembler;
break;
}

*/
@ignoreClone
_onEntityModify(flag: EntityModifyFlags): void {
switch (flag) {
Copy link
Contributor

Choose a reason for hiding this comment

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

When UIGroupEnableInScene , root canvas should be dirty and walk again.

);
}

private _adapterPoseInScreenSpace(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

adapter -> adapt

}
}

private _adapterSizeInScreenSpace(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

adapter -> adapt

const { frameCount } = engine.time;
const renderElement = (this._renderElement = engine._renderElementPool.get());
this._updateSortDistance(context.virtualCamera.position);
renderElement.set(this.sortOrder, this._sortDistance);
Copy link
Contributor

Choose a reason for hiding this comment

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

In 3d renderer , renderElement sets priority and distanceSort generally within the life cycle of _render. Is it appropriate to put it here?

rootCanvas && (rootCanvas._hierarchyDirty = true);
this._setIsRootCanvas(!rootCanvas);
UIUtils.registerUIToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

These lines of logic are duplicated with those in _onEnableInScene. Could we encapsulate an additional method to optimize this?

* feat: support alphaHitTestMinimumThreshold
Copy link

@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: 26

🧹 Outside diff range and nitpick comments (16)
packages/core/src/2d/assembler/ISpriteRenderer.ts (2)

8-16: Consider separating public and internal interface members.

The interface currently mixes public API members (sprite, color, etc.) with internal implementation details (_transform, _subChunk, _getChunkManager). This could make it harder to maintain API compatibility and might expose internal implementation details to consumers.

Consider splitting this into two interfaces:

export interface ISpriteRenderer {
  sprite: Sprite;
  color?: Color;
  tileMode?: SpriteTileMode;
  tiledAdaptiveThreshold?: number;
}

interface ISpriteRendererInternal extends ISpriteRenderer {
  _transform: Transform;
  _subChunk: SubPrimitiveChunk;
  _getChunkManager(): PrimitiveChunkManager;
}

10-12: Document the optional properties' default behavior.

The interface includes optional properties (color, tileMode, tiledAdaptiveThreshold) without clear documentation about their default behavior when undefined.

Consider adding JSDoc comments to clarify the default behavior:

/** The color to tint the sprite with. If undefined, no tinting is applied. */
color?: Color;
/** The tiling mode for the sprite. If undefined, no tiling is applied. */
tileMode?: SpriteTileMode;
/** The threshold for adaptive tiling. Only used when tileMode is set. */
tiledAdaptiveThreshold?: number;
packages/core/src/2d/assembler/ISpriteAssembler.ts (2)

17-18: Consider documenting the alpha parameter's valid range.

The methods have been properly updated to use ISpriteRenderer. The new alpha parameter in updateColor enhances control over transparency.

Consider adding JSDoc to document the expected range for the alpha parameter (typically 0-1).


19-26: Document the boolean return value's meaning.

The new getUVByLocalPosition method is well-structured with appropriate parameter types.

Consider adding JSDoc to clarify:

  • The meaning of the boolean return value
  • The coordinate space for the position parameter
  • The expected values in the out parameter
packages/core/src/mesh/MeshRenderer.ts (2)

102-108: Consider using a shared zero bounds constant

The implementation correctly handles local bounds, but consider defining a shared ZERO_BOUNDS constant for the zero initialization case to improve maintainability and reduce object allocations.

private static readonly ZERO_BOUNDS = Object.freeze(new BoundingBox(new Vector3(0, 0, 0), new Vector3(0, 0, 0)));

protected override _updateLocalBounds(localBounds: BoundingBox): void {
  const mesh = this._mesh;
  if (mesh) {
    localBounds.copyFrom(mesh.bounds);
  } else {
    localBounds.copyFrom(MeshRenderer.ZERO_BOUNDS);
  }
}

191-199: Enhance enum documentation with bit composition

Consider adding detailed documentation for each flag value to explain their bit composition. This would help maintainers understand the relationship between flags.

enum MeshRendererUpdateFlags {
  /** Indicates vertex element changes requiring shader macro updates (0x10) */
  VertexElementMacro = 0x10,
  
  /** 
   * Combined flag for vertex and bounds updates (0x1c)
   * Composition:
   * - VertexElementMacro     (0x10)
   * - LocalBounds           (0x04)
   * - WorldBounds          (0x08)
   */
  VertexElementMacroAndAllBounds = 0x1c,
  
  /** 
   * All possible update flags (0x1f)
   * Includes all renderer and mesh-specific flags
   */
  All = 0x1f
}
packages/core/src/mesh/SkinnedMeshRenderer.ts (1)

191-220: Add JSDoc documentation for the method.

The implementation looks solid, but would benefit from documentation explaining:

  • The purpose of the method
  • The different scenarios it handles
  • The parameters and return value

Add documentation like this:

/**
 * Updates the local bounds of the skinned mesh.
 * Handles three scenarios:
 * 1. Root bone exists and is in the bones array - uses exact inverse bind matrix
 * 2. Root bone exists but not in bones array - computes approximate matrix from children
 * 3. No root bone - falls back to base class behavior
 * @param localBounds The bounding box to update
 */
protected override _updateLocalBounds(localBounds: BoundingBox): void
packages/core/src/2d/assembler/TiledSpriteAssembler.ts (3)

1-19: Consider enhancing class-level documentation.

While the class is marked as @internal, it would be beneficial to add JSDoc comments describing:

  • The purpose and responsibility of the TiledSpriteAssembler
  • Key algorithms or techniques used
  • Example usage scenarios

Line range hint 169-337: Consider extracting common UV calculation patterns.

The switch cases for both row and column calculations contain similar patterns for UV coordinate calculation. Consider extracting these into helper methods to reduce code duplication and improve maintainability.

Example helper method:

private static calculateUVCoordinate(
  localPos: number,
  pos0: number,
  pos1: number,
  uv0: number,
  uv1: number
): number {
  return ((localPos - pos0) / (pos1 - pos0)) * (uv1 - uv0) + uv0;
}

413-413: Enhance warning message with more context.

The warning message could be more helpful by including:

  • The actual vertex count that exceeded the limit
  • Suggestions for potential solutions
-Logger.warn(`The number of vertices exceeds the upper limit(${maxVertexCount}).`);
+Logger.warn(
+  `Vertex count (${vertexCount}) exceeds the maximum limit (${maxVertexCount}). ` +
+  `Consider reducing the tile count or using a simpler sprite configuration.`
+);
packages/core/src/2d/text/TextRenderer.ts (2)

Line range hint 234-246: Add missing property declarations for mask-related properties.

The maskInteraction and maskLayer getters/setters are using undeclared properties.

Add these property declarations at the class level:

+private _maskInteraction: SpriteMaskInteraction;
+private _maskLayer: number = 0;

Line range hint 474-611: Consider implementing object pooling for temporary calculations.

The text layout calculation creates numerous temporary objects for measurements and positions. Consider implementing an object pool for frequently created objects like Vector2 for positions and temporary arrays for line calculations.

This would help reduce garbage collection pressure, especially for frequently updating text.

Key areas for pooling:

  1. Line width calculations
  2. Character position vectors
  3. Temporary arrays for line metrics
packages/core/src/ui/UICanvas.ts (2)

23-23: Consider removing redundant type declarations.

TypeScript can infer these types from their initial values. This makes the code more concise and easier to maintain.

Apply this diff to simplify the declarations:

-  depth: number = 0;
+  depth = 0;
-  _isRootCanvas: boolean = false;
+  _isRootCanvas = false;
-  _canvasIndex: number = -1;
+  _canvasIndex = -1;
-  _sortDistance: number = 0;
+  _sortDistance = 0;
-  private _sortOrder: number = 0;
+  private _sortOrder = 0;
-  private _distance: number = 10;
+  private _distance = 10;
-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution = new Vector2(800, 600);

Also applies to: 32-32, 35-35, 56-56, 80-80, 82-82, 86-86


387-387: Rename 'adapter' methods to 'adapt' for better naming.

The prefix 'adapter' is typically used for objects that implement the Adapter pattern. For methods that perform adaptation, 'adapt' is more appropriate.

Apply this diff to improve method naming:

-  private _adapterPoseInScreenSpace(): void {
+  private _adaptPoseInScreenSpace(): void {
-  private _adapterSizeInScreenSpace(): void {
+  private _adaptSizeInScreenSpace(): void {

Also applies to: 406-406

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (2)

36-36: Ensure consistency in renderer properties

Destructuring sprite from renderer enhances readability. Ensure consistent use of destructuring across the codebase for cleaner code.


71-75: Simplify assignment statements for better readability

Similar to earlier, separating combined assignments can enhance code clarity.

Apply this diff:

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between bc8a8d4 and 53d136e.

📒 Files selected for processing (15)
  • packages/core/src/2d/assembler/ISpriteAssembler.ts (1 hunks)
  • packages/core/src/2d/assembler/ISpriteRenderer.ts (1 hunks)
  • packages/core/src/2d/assembler/SimpleSpriteAssembler.ts (4 hunks)
  • packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (5 hunks)
  • packages/core/src/2d/assembler/TiledSpriteAssembler.ts (8 hunks)
  • packages/core/src/2d/sprite/SpriteMask.ts (10 hunks)
  • packages/core/src/2d/sprite/SpriteRenderer.ts (15 hunks)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/Renderer.ts (8 hunks)
  • packages/core/src/mesh/MeshRenderer.ts (4 hunks)
  • packages/core/src/mesh/SkinnedMeshRenderer.ts (4 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIImage.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/2d/sprite/SpriteMask.ts
  • packages/core/src/Renderer.ts
🧰 Additional context used
🪛 Biome
packages/core/src/mesh/MeshRenderer.ts

[error] 185-185: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 186-186: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/mesh/SkinnedMeshRenderer.ts

[error] 256-256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/ui/UICanvas.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 242-242: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 342-342: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 346-346: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 347-347: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 345-350: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 460-460: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 574-575: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 584-585: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 586-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 106-106: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 111-111: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 154-154: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 152-152: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 156-156: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 157-157: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 155-159: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (31)
packages/core/src/2d/assembler/ISpriteRenderer.ts (1)

14-15: Verify the necessity of exposing internal chunk management.

The _subChunk property and _getChunkManager() method suggest tight coupling with the rendering implementation. This might make it difficult to change the underlying rendering system in the future.

Let's check if these members are used across different implementations:

✅ Verification successful

Based on the search results, I can now generate a final response as there's clear evidence of the usage patterns of these members across the codebase.

The internal chunk management exposure is necessary for the current architecture

The _subChunk and _getChunkManager() members are integral parts of the sprite rendering system, used consistently across multiple implementations:

  • Core sprite renderers (SpriteRenderer, SpriteMask) use them for chunk management
  • Various sprite assemblers (SimpleSpriteAssembler, SlicedSpriteAssembler, TiledSpriteAssembler) rely on these members for efficient vertex allocation
  • UI components (UIRenderer) and text rendering (TextRenderer) also utilize this chunk management system
  • Test files validate the chunk management behavior

The underscore prefix correctly denotes these as protected members, and their exposure is a deliberate design choice for the rendering implementation hierarchy rather than a coupling issue.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of _subChunk and _getChunkManager
echo "Checking _subChunk usage:"
rg "_subChunk" --type ts

echo -e "\nChecking _getChunkManager usage:"
rg "_getChunkManager" --type ts

Length of output: 5157

packages/core/src/2d/assembler/ISpriteAssembler.ts (2)

1-7: LGTM! Clean imports and proper internal API marking.

The imports are well-organized and the interface is correctly marked as internal.


8-16: Verify the ISpriteRenderer type compatibility across implementations.

The method signatures have been updated to use ISpriteRenderer, which is more specific than the previous Renderer type. The updatePositions method's additional parameters enhance its flexibility while maintaining its core purpose of updating vertex positions.

✅ Verification successful

All implementations correctly use ISpriteRenderer and match the interface signature

The verification shows that all three implementations (SimpleSpriteAssembler, SlicedSpriteAssembler, and TiledSpriteAssembler) correctly:

  • Use ISpriteRenderer type in their resetData and updatePositions methods
  • Implement the required method signatures with matching parameters
  • Handle the width, height, pivot, and flip parameters consistently

The type changes are properly propagated across all implementations, maintaining interface compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all implementations properly implement the new interface
# Test: Search for classes implementing ISpriteAssembler
ast-grep --pattern 'class $_ implements ISpriteAssembler {
  $$$
}'

Length of output: 69


Script:

#!/bin/bash
# Let's try a different approach to find implementations
# First, find all TypeScript files that import ISpriteAssembler
rg -l "ISpriteAssembler" --type ts

# Then look for actual class implementations with more context
rg "class.*implements.*ISpriteAssembler" -A 10

Length of output: 379


Script:

#!/bin/bash
# Let's examine the implementation files directly to verify the method signatures
echo "=== SimpleSpriteAssembler ===="
cat packages/core/src/2d/assembler/SimpleSpriteAssembler.ts

echo -e "\n=== SlicedSpriteAssembler ===="
cat packages/core/src/2d/assembler/SlicedSpriteAssembler.ts

echo -e "\n=== TiledSpriteAssembler ===="
cat packages/core/src/2d/assembler/TiledSpriteAssembler.ts

Length of output: 31413

packages/core/src/2d/assembler/SimpleSpriteAssembler.ts (4)

1-4: LGTM! Good improvement in type safety.

The switch to ISpriteRenderer interface aligns well with the previous suggestion about having a common type for renderable components.


11-12: LGTM! Good encapsulation.

Making these static properties private improves encapsulation by preventing external access to internal implementation details.


81-92: LGTM! Alpha blending implementation looks correct.

The alpha blending calculation and vertex color updates are implemented correctly.


38-39: 🛠️ Refactor suggestion

Optimize flip calculations for better performance.

Based on previous benchmarks, using unary negation would be more performant than the current ternary approach.

-const sx = flipX ? -width : width;
-const sy = flipY ? -height : height;
+const sx = flipX ? -width : width;
+const sy = flipY ? -height : height;

Likely invalid or redundant comment.

packages/core/src/mesh/MeshRenderer.ts (2)

36-36: LGTM: Efficient flag combination for mesh updates

The use of VertexElementMacroAndAllBounds efficiently combines vertex element and bounds updates, which typically occur together when the mesh changes.


185-186: Refer to previous review comment about assignments in expressions

🧰 Tools
🪛 Biome

[error] 185-185: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 186-186: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIGroup.ts (2)

1-41: LGTM! Well-structured class definition with proper property management

The class structure follows TypeScript best practices with appropriate use of decorators and clear documentation.


147-178: 🛠️ Refactor suggestion

Add error handling for edge cases

The method could benefit from additional error handling for edge cases.

Consider adding validation for circular parent-child relationships:

Add validation at the start of the method:

private _registryToParentGroup(parentGroup: UIGroup): void {
  if (parentGroup === this) {
    throw new Error('Cannot register UIGroup as its own parent');
  }
  // ... rest of the method
}
🧰 Tools
🪛 Biome

[error] 154-154: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts (2)

125-132: LGTM! Clean lifecycle management.

The lifecycle methods properly handle component registration and cleanup, ensuring UI elements are correctly managed within the scene.

Also applies to: 137-143


229-249: LGTM! Proper resource cleanup and transform handling.

The cleanup and transform change handling are well-implemented, ensuring proper resource management and UI updates.

packages/core/src/mesh/SkinnedMeshRenderer.ts (1)

256-259: Variable scope issue in switch case.

The variable scope issue identified in the previous review is still present.

🧰 Tools
🪛 Biome

[error] 256-256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/2d/sprite/SpriteRenderer.ts (4)

10-13: LGTM: Interface implementation enhances type safety

The addition of ISpriteRenderer interface and related imports improves type safety and contract enforcement.

Also applies to: 26-26


269-269: LGTM: Proper component type initialization

Correctly initializes the component type to SpriteRenderer.


317-329: LGTM: Improved local bounds calculation

The bounds calculation correctly handles sprite dimensions and pivot points, including flipped states.


460-462: LGTM: Clean color change handling

The color change handler correctly sets the appropriate update flag.

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (1)

Line range hint 1-476: Implementation looks solid overall.

The TiledSpriteAssembler implementation is well-structured and handles edge cases appropriately. The code demonstrates good attention to detail in handling various tiling scenarios and coordinate transformations.

packages/core/src/2d/text/TextRenderer.ts (2)

35-35: Property initialization issue already reported.

Skipping as this was already reported in a previous review.


689-699: Well-structured enum implementation for update flags.

The TextRendererUpdateFlags enum effectively extends RendererUpdateFlags and provides clear, well-documented flag combinations for different update scenarios.

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (8)

1-4: Good use of ISpriteRenderer interface

Importing ISpriteRenderer and updating the parameter types enhances flexibility by allowing different sprite renderer implementations to use these methods.


11-17: Encapsulation of static properties

Changing _rectangleTriangles and _worldMatrix to private scopes improves encapsulation and prevents unintended external access.


19-19: Updating resetData to accept ISpriteRenderer

Modifying the resetData method to accept ISpriteRenderer broadens its applicability and aligns with the use of a common interface for renderers.


28-35: Consider renaming updatePositions to reflect expanded functionality

As previously mentioned, since updatePositions now accepts additional parameters like width, height, pivot, flipX, and flipY, consider renaming the method to better reflect its role, such as updateTransform or updateDimensionsAndPosition.


119-122: Updating updateUVs to accept ISpriteRenderer

Using ISpriteRenderer for updateUVs increases method flexibility and allows for different renderer types.


132-135: Updated updateColor method to accept ISpriteRenderer

Changing the parameter type to ISpriteRenderer ensures that both SpriteRenderer and UIImage instances can utilize this method, addressing previous concerns about consistency.


135-141: 🛠️ Refactor suggestion

Optimize color assignment in the loop

To enhance performance, consider caching the color values before the loop to avoid repeated property accesses.

Apply this diff:

+ const rValue = r;
+ const gValue = g;
+ const bValue = b;
+ const alphaValue = finalAlpha;
  for (let i = 0, o = subChunk.vertexArea.start + 5; i < 16; ++i, o += 9) {
-   vertices[o] = r;
-   vertices[o + 1] = g;
-   vertices[o + 2] = b;
-   vertices[o + 3] = finalAlpha;
+   vertices[o] = rValue;
+   vertices[o + 1] = gValue;
+   vertices[o + 2] = bValue;
+   vertices[o + 3] = alphaValue;
  }

Likely invalid or redundant comment.


145-200: Verify the correctness of getUVByLocalPosition method

Ensure that the calculations within getUVByLocalPosition correctly map local positions to UV coordinates, especially regarding the handling of borders and scaling factors.

Run the following script to check for potential edge cases:

✅ Verification successful

Let me gather more information about the implementation details and edge cases.

This will help us understand:

  1. The base implementation in SimpleSpriteAssembler
  2. Any existing test coverage
  3. Documentation and related comments that might explain the expected behavior

Let me check the test files to understand how these UV calculations are tested.

This will help us:

  1. Understand how UV calculations are tested
  2. Compare with the base implementation in SimpleSpriteAssembler
  3. Verify against any interface requirements

Based on the extensive test coverage and implementation details revealed in the test files, I can now provide a final response about the correctness of the getUVByLocalPosition method.

UV mapping calculations in SlicedSpriteAssembler are correctly implemented

The implementation is verified to be correct based on the following evidence:

  1. The test suite thoroughly validates UV calculations for various scenarios:

    • Different sprite sizes and borders
    • Both adaptive and continuous tile modes
    • Edge cases with very small (0.01) and very large (100000) dimensions
    • Various border configurations
  2. The method correctly handles scaling when fixed borders exceed dimensions:

    if (fixedLeft + fixedRight > width) {
      const widthScale = width / (fixedLeft + fixedRight);
      // Scaling is properly applied
    }
  3. The UV calculations are consistent with the sprite's border and region properties, as demonstrated by the extensive test assertions comparing expected and actual UV coordinates.

The implementation successfully maps local positions to UV coordinates while properly handling sprite borders, scaling, and different drawing modes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for calls to `getUVByLocalPosition` and ensure they handle edge cases properly.

# Test: Find all usages of `getUVByLocalPosition` in the codebase.
# Expect: Identify call sites to review their implementations.
rg 'getUVByLocalPosition' -A 5

Length of output: 4146


Script:

#!/bin/bash
# Check the implementation of SimpleSpriteAssembler's getUVByLocalPosition
ast-grep --pattern 'static getUVByLocalPosition($_) {
  $$$
}'

# Check for any test files related to these sprite assemblers
fd -e test.ts -e spec.ts sprite

# Look for any related documentation or comments
rg -B 3 "getUVByLocalPosition|SlicedSpriteAssembler" --type ts

Length of output: 6593


Script:

#!/bin/bash
# Check the content of sprite test files for UV-related tests
cat tests/src/core/Sprite.test.ts tests/src/core/SpriteRenderer.test.ts

# Look for the implementation of SimpleSpriteAssembler's getUVByLocalPosition for comparison
ast-grep --pattern 'static getUVByLocalPosition($params) {
  $$$
}' packages/core/src/2d/assembler/SimpleSpriteAssembler.ts

# Check for any sprite-related interfaces that might define UV calculation requirements
rg -A 5 "interface.*Sprite" --type ts

Length of output: 109958

packages/core/src/ui/UIImage.ts (2)

58-70: ⚠️ Potential issue

Ensure this._assembler is assigned for all drawMode values

In the set drawMode method, the default case in the switch statement does not assign an assembler to this._assembler. This could lead to this._assembler being undefined if an invalid drawMode is set, causing runtime errors when methods on this._assembler are called.

Consider handling the default case to assign a default assembler or throw an error:

default:
+   throw new Error(`Unsupported drawMode value: ${value}`);
    break;

This ensures that this._assembler is always valid after setting the drawMode.

Likely invalid or redundant comment.


248-267: Verify that all dirty flags are properly cleared

After updating positions, UVs, and colors, it's important to ensure that all corresponding flags in _dirtyUpdateFlag are cleared. Failure to do so may result in unnecessary updates and performance issues.

Run the following script to check for any flags that may not be cleared:

✅ Verification successful

Let me gather more information about the flag definitions and their usage to ensure proper clearing.


Let me search for the complete enum definitions and renderer flags to fully understand the bitmask operations.


All dirty flags are properly cleared

Looking at the code and flag definitions, the clearing of flags is handled correctly:

  1. Position updates clear RendererUpdateFlags.AllPositions (0x3)
  2. UV updates clear ImageUpdateFlags.UV (0x10)
  3. Color updates clear ImageUpdateFlags.Color (0x20)

Each update operation properly clears its specific bits using the bitwise AND NOT operation (&= ~flag), and the final state is correctly stored back in this._dirtyUpdateFlag. The flags are mutually exclusive in their bit positions, ensuring no unintended interference.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all bits in _dirtyUpdateFlag are cleared after updates.

# Search for where _dirtyUpdateFlag is modified.
rg '_dirtyUpdateFlag\s*[\&\|\^]?=\s*' packages/core/src/ui/UIImage.ts

# Check that after each update, the corresponding flag is cleared.
# Expected: All update methods properly clear their flags in _dirtyUpdateFlag.

Length of output: 1285


Script:

#!/bin/bash
# Search for flag definitions to understand what bits are used
rg -A 5 'enum (Image|Renderer)UpdateFlags' packages/core/src/ui/

# Search for any other places where flags are cleared (using &= ~)
rg '_dirtyUpdateFlag.*&=\s*~' packages/core/src/ui/

Length of output: 437


Script:

#!/bin/bash
# Search for the complete ImageUpdateFlags enum
rg -A 10 'enum ImageUpdateFlags' packages/core/src/ui/UIImage.ts

# Search for RendererUpdateFlags enum in the codebase
rg -A 10 'enum RendererUpdateFlags' packages/core/src/

Length of output: 1024

Comment on lines +94 to +117
static getUVByLocalPosition(
renderer: ISpriteRenderer,
width: number,
height: number,
pivot: Vector2,
position: Vector3,
out: Vector2
): boolean {
const sprite = renderer.sprite;
const normalizedX = position.x / width + pivot.x;
const normalizedY = position.y / height + pivot.y;
const positions = sprite._getPositions();
const { x: left, y: bottom } = positions[0];
const { x: right, y: top } = positions[3];
if (normalizedX >= left && normalizedX <= right && normalizedY >= bottom && normalizedY <= top) {
const uvs = sprite._getUVs();
const { x: uvLeft, y: uvBottom } = uvs[0];
const { x: uvRight, y: uvTop } = uvs[3];
const factorX = (normalizedX - left) / (right - left);
const factorY = (normalizedY - bottom) / (top - bottom);
out.set(uvLeft + (uvRight - uvLeft) * factorX, uvBottom + (uvTop - uvBottom) * factorY);
return true;
} else {
return false;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation to prevent potential runtime errors.

The method should validate input parameters to prevent division by zero and null/undefined errors.

 static getUVByLocalPosition(
   renderer: ISpriteRenderer,
   width: number,
   height: number,
   pivot: Vector2,
   position: Vector3,
   out: Vector2
 ): boolean {
+  if (!renderer?.sprite || !position || !pivot || !out) {
+    return false;
+  }
+  if (width === 0 || height === 0) {
+    return false;
+  }
   const sprite = renderer.sprite;
   const normalizedX = position.x / width + pivot.x;
📝 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
static getUVByLocalPosition(
renderer: ISpriteRenderer,
width: number,
height: number,
pivot: Vector2,
position: Vector3,
out: Vector2
): boolean {
const sprite = renderer.sprite;
const normalizedX = position.x / width + pivot.x;
const normalizedY = position.y / height + pivot.y;
const positions = sprite._getPositions();
const { x: left, y: bottom } = positions[0];
const { x: right, y: top } = positions[3];
if (normalizedX >= left && normalizedX <= right && normalizedY >= bottom && normalizedY <= top) {
const uvs = sprite._getUVs();
const { x: uvLeft, y: uvBottom } = uvs[0];
const { x: uvRight, y: uvTop } = uvs[3];
const factorX = (normalizedX - left) / (right - left);
const factorY = (normalizedY - bottom) / (top - bottom);
out.set(uvLeft + (uvRight - uvLeft) * factorX, uvBottom + (uvTop - uvBottom) * factorY);
return true;
} else {
return false;
static getUVByLocalPosition(
renderer: ISpriteRenderer,
width: number,
height: number,
pivot: Vector2,
position: Vector3,
out: Vector2
): boolean {
if (!renderer?.sprite || !position || !pivot || !out) {
return false;
}
if (width === 0 || height === 0) {
return false;
}
const sprite = renderer.sprite;
const normalizedX = position.x / width + pivot.x;
const normalizedY = position.y / height + pivot.y;
const positions = sprite._getPositions();
const { x: left, y: bottom } = positions[0];
const { x: right, y: top } = positions[3];
if (normalizedX >= left && normalizedX <= right && normalizedY >= bottom && normalizedY <= top) {
const uvs = sprite._getUVs();
const { x: uvLeft, y: uvBottom } = uvs[0];
const { x: uvRight, y: uvTop } = uvs[3];
const factorX = (normalizedX - left) / (right - left);
const factorY = (normalizedY - bottom) / (top - bottom);
out.set(uvLeft + (uvRight - uvLeft) * factorX, uvBottom + (uvTop - uvBottom) * factorY);
return true;
} else {
return false;

Comment on lines 69 to 75
set alpha(val: number) {
val = Math.max(0, Math.min(val, 1));
if (this._alpha !== val) {
this._alpha = val;
this._updateGlobalModify(GroupModifyFlags.Alpha);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract value clamping logic into a utility function

The alpha value clamping logic could be reused across the codebase.

Consider extracting the clamping logic into a utility function:

// Add to UIUtils.ts
export function clampAlpha(value: number): number {
  return Math.max(0, Math.min(value, 1));
}

Then simplify the setter:

  set alpha(val: number) {
-   val = Math.max(0, Math.min(val, 1));
+   val = UIUtils.clampAlpha(val);
    if (this._alpha !== val) {
      this._alpha = val;
      this._updateGlobalModify(GroupModifyFlags.Alpha);
    }
  }

Comment on lines 87 to 116
_updateGlobalModify(flags: GroupModifyFlags): void {
let passDownFlags = GroupModifyFlags.None;
const parentGroup = this._parentGroup;
if (flags & GroupModifyFlags.Alpha) {
const alpha = this._alpha * (!this._ignoreParentGroup && parentGroup ? parentGroup._globalAlpha : 1);
if (this._globalAlpha !== alpha) {
this._globalAlpha = alpha;
passDownFlags |= GroupModifyFlags.Alpha;
}
}
if (flags & GroupModifyFlags.RaycastEnable) {
const raycastEnable =
this._raycastEnabled &&
(!this._ignoreParentGroup && parentGroup ? parentGroup._getGlobalRaycastEnable() : true);
if (this._globalRaycastEnable !== raycastEnable) {
this._globalRaycastEnable = raycastEnable;
passDownFlags |= GroupModifyFlags.RaycastEnable;
}
}
if (!!flags) {
this._disorderedElements.forEach((element: IUIElement) => {
element._onGroupModify(passDownFlags);
});
}
if (!!passDownFlags) {
this._disorderedGroups.forEach((element: UIGroup) => {
element._updateGlobalModify(passDownFlags);
});
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider breaking down _updateGlobalModify into smaller functions

The method handles multiple concerns and could be more maintainable if split into separate methods.

Consider refactoring into smaller, focused methods:

private _updateGlobalAlpha(): GroupModifyFlags {
  const alpha = this._alpha * (!this._ignoreParentGroup && this._parentGroup ? this._parentGroup._globalAlpha : 1);
  if (this._globalAlpha !== alpha) {
    this._globalAlpha = alpha;
    return GroupModifyFlags.Alpha;
  }
  return GroupModifyFlags.None;
}

private _updateGlobalRaycast(): GroupModifyFlags {
  const raycastEnable = this._raycastEnabled && 
    (!this._ignoreParentGroup && this._parentGroup ? this._parentGroup._getGlobalRaycastEnable() : true);
  if (this._globalRaycastEnable !== raycastEnable) {
    this._globalRaycastEnable = raycastEnable;
    return GroupModifyFlags.RaycastEnable;
  }
  return GroupModifyFlags.None;
}

private _updateGlobalModify(flags: GroupModifyFlags): void {
  let passDownFlags = GroupModifyFlags.None;
  
  if (flags & GroupModifyFlags.Alpha) {
    passDownFlags |= this._updateGlobalAlpha();
  }
  if (flags & GroupModifyFlags.RaycastEnable) {
    passDownFlags |= this._updateGlobalRaycast();
  }

  if (flags) {
    this._disorderedElements.forEach((element: IUIElement) => {
      element._onGroupModify(passDownFlags);
    });
  }
  if (passDownFlags) {
    this._disorderedGroups.forEach((element: UIGroup) => {
      element._updateGlobalModify(passDownFlags);
    });
  }
}
🧰 Tools
🪛 Biome

[error] 106-106: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 111-111: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

this._parentGroup = parentGroup;
if (preParentGroup) {
const replaced = preParentGroup._disorderedGroups.deleteByIndex(this._groupIndex);
replaced && (replaced._groupIndex = this._groupIndex);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignment within expression

The assignment within the logical AND expression reduces code readability.

Refactor to make the assignment more explicit:

- replaced && (replaced._groupIndex = this._groupIndex);
+ if (replaced) {
+   replaced._groupIndex = this._groupIndex;
+ }
📝 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
replaced && (replaced._groupIndex = this._groupIndex);
if (replaced) {
replaced._groupIndex = this._groupIndex;
}
🧰 Tools
🪛 Biome

[error] 154-154: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 22 to 28
static _tempVec30: Vector3 = new Vector3();
/** @internal */
static _tempVec31: Vector3 = new Vector3();
/** @internal */
static _tempMat: Matrix = new Matrix();
/** @internal */
static _tempPlane: Plane = new Plane();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider pooling temporary objects for better memory efficiency.

The static temporary objects could benefit from object pooling to reduce garbage collection pressure during frequent UI updates.

Consider implementing an object pool:

class UIRendererObjectPool {
  private static _vec3Pool: Vector3[] = [];
  private static _matrixPool: Matrix[] = [];
  
  static acquireVector3(): Vector3 {
    return this._vec3Pool.pop() || new Vector3();
  }
  
  static releaseVector3(vec: Vector3): void {
    vec.set(0, 0, 0);
    this._vec3Pool.push(vec);
  }
  // Similar methods for Matrix
}

Comment on lines 139 to 146
return this._color;
}

set color(value: Color) {
if (this._color !== value) {
this._color.copyFrom(value);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update rendering state when setting a new Color object

In the set color method, when assigning a new Color object to this._color, the _dirtyUpdateFlag is not updated. This may prevent the renderer from recognizing the color change.

Apply this diff to ensure the rendering state is updated:

set color(value: Color) {
  if (this._color !== value) {
    this._color.copyFrom(value);
+   this._dirtyUpdateFlag |= ImageUpdateFlags.Color;
  }
}

This makes sure that any changes to the color property will trigger a rendering update.

📝 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
return this._color;
}
set color(value: Color) {
if (this._color !== value) {
this._color.copyFrom(value);
}
}
return this._color;
}
set color(value: Color) {
if (this._color !== value) {
this._color.copyFrom(value);
this._dirtyUpdateFlag |= ImageUpdateFlags.Color;
}
}

Comment on lines 298 to 308
switch (this._drawMode) {
case SpriteDrawMode.Simple:
case SpriteDrawMode.Sliced:
this._dirtyUpdateFlag |= RendererUpdateFlags.AllPositionAndBounds;
break;
case SpriteDrawMode.Tiled:
this._dirtyUpdateFlag |= ImageUpdateFlags.All;
break;
default:
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle unexpected drawMode values in _onTransformChanged

In the _onTransformChanged method, the default case in the switch statement is empty. If an unexpected drawMode value is encountered, it might lead to silent failures or undefined behavior.

Consider handling the default case to ensure that any unsupported drawMode values are properly managed:

default:
+   console.warn(`Unsupported drawMode value: ${this._drawMode} in _onTransformChanged.`);
    break;

This will help in debugging if an invalid drawMode is ever set.

📝 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
switch (this._drawMode) {
case SpriteDrawMode.Simple:
case SpriteDrawMode.Sliced:
this._dirtyUpdateFlag |= RendererUpdateFlags.AllPositionAndBounds;
break;
case SpriteDrawMode.Tiled:
this._dirtyUpdateFlag |= ImageUpdateFlags.All;
break;
default:
break;
}
switch (this._drawMode) {
case SpriteDrawMode.Simple:
case SpriteDrawMode.Sliced:
this._dirtyUpdateFlag |= RendererUpdateFlags.AllPositionAndBounds;
break;
case SpriteDrawMode.Tiled:
this._dirtyUpdateFlag |= ImageUpdateFlags.All;
break;
default:
console.warn(`Unsupported drawMode value: ${this._drawMode} in _onTransformChanged.`);
break;
}

Comment on lines 387 to 388
case SpriteDrawMode.Tiled:
return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect assembler method called for Tiled draw mode

In the _getUVByLocalPosition method, when drawMode is SpriteDrawMode.Tiled, the code calls SlicedSpriteAssembler.getUVByLocalPosition. This is likely a mistake and should instead call TiledSpriteAssembler.getUVByLocalPosition to correctly handle tiled sprites.

Apply this diff to fix the issue:

case SpriteDrawMode.Tiled:
-   return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
+   return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
📝 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
case SpriteDrawMode.Tiled:
return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
case SpriteDrawMode.Tiled:
return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);

Comment on lines 203 to 205
texture.getPixelBuffer(Math.floor(uv.x * texture.width), Math.floor(uv.y * texture.height), 1, 1, 0, pixel);
if (pixel[3] >= alphaHitTestMinimumThreshold * 255) {
return true;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check for out-of-bounds texture coordinates in _hitTest

In the _hitTest method, the texture pixel is sampled using UV coordinates multiplied by the texture dimensions. If uv.x or uv.y are outside the range [0, 1], this could result in out-of-bounds access.

Ensure that uv.x and uv.y are clamped between 0 and 1 before sampling the texture:

texture.getPixelBuffer(
+   Math.floor(MathUtil.clamp(uv.x, 0, 1) * texture.width),
+   Math.floor(MathUtil.clamp(uv.y, 0, 1) * texture.height),
    1, 1, 0, pixel);

This prevents potential errors when the UV coordinates are slightly outside the expected range due to numerical inaccuracies.

Committable suggestion was skipped due to low confidence.

Comment on lines 316 to 329
protected override _onDestroy(): void {
const sprite = this._sprite;
if (sprite) {
this._addResourceReferCount(sprite, -1);
sprite._updateFlagManager.removeListener(this._onSpriteChange);
}

super._onDestroy();

this._entity = null;
this._color = null;
this._sprite = null;
this._assembler = null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Set properties to null before calling super._onDestroy()

In the _onDestroy method, properties like this._entity, this._color, this._sprite, and this._assembler are set to null after calling super._onDestroy(). If the parent class relies on these properties during its own cleanup, this could cause errors.

Consider setting these properties to null before calling super._onDestroy():

protected override _onDestroy(): void {
+   this._entity = null;
+   this._color = null;
+   this._sprite = null;
+   this._assembler = null;

    super._onDestroy();
}

This ensures that the parent class does not access properties that have been reset.

Committable suggestion was skipped due to low confidence.

}
}

/**
Copy link
Member Author

Choose a reason for hiding this comment

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

Merge with CanvasRenderMode?

const { elements: projectE } = virtualCamera.projectionMatrix;
const { elements: viewE } = virtualCamera.viewMatrix;
(projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0);
rhi.activeRenderTarget(null, new Vector4(0, 0, 1, 1), renderContext.flipProjection, 0);
Copy link
Member Author

Choose a reason for hiding this comment

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

Will create Vector4 every frame

Comment on lines +62 to +66
(row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
row[3] = width - expectWidth * (1 - right) * widthScale;
} else {
row = [expectWidth * left, fixedLeft, width - fixedRight, width - expectWidth * (1 - right)];
(row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
row[3] = width - expectWidth * (1 - right);
Copy link
Member Author

Choose a reason for hiding this comment

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

can not agree more

import { IUIElement } from "./interface/IUIElement";

@dependentComponents(UITransform, DependentMode.AutoAdd)
export class UIRenderer extends Renderer implements IUIElement {
Copy link
Member Author

Choose a reason for hiding this comment

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

should add implements ISpriteRenderer, as follows:

export class UIRenderer extends Renderer implements IUIElement, ISpriteRenderer {}

const texture = this.sprite?.texture;
if (!texture) {
return false;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

It might be more appropriate to check the texture first

Copy link

@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: 7

🧹 Outside diff range and nitpick comments (4)
packages/core/src/2d/text/TextUtils.ts (2)

274-274: LGTM with documentation suggestion

The method signature change is correct. Consider adding JSDoc to document the new parameter type.

+/**
+ * Measures text without wrapping for both TextRenderer and UIText.
+ * @param renderer - The text renderer (TextRenderer or UIText)
+ * @returns TextMetrics containing measurement information
+ */
 static measureTextWithoutWrap(renderer: TextRenderer | UIText): TextMetrics {

Line range hint 100-274: Consider adding integration tests for UIText measurements

Since these methods now support both TextRenderer and UIText, it would be valuable to add integration tests that verify:

  1. Text measurement consistency between TextRenderer and UIText
  2. Edge cases with different text styles and overflow modes
  3. Performance implications of supporting both types

This will help ensure compatibility and prevent regressions.

packages/core/src/ui/UIText.ts (2)

323-323: Address the TODO: Optimize world positions buffer update

There's a TODO comment at line 323 indicating that optimization is needed: //@todo: Always update world positions to buffer, should opt. Would you like assistance in implementing this optimization or opening a GitHub issue to track it?


514-514: Consider splitting assignments for better readability

At line 514, the line combines multiple assignments in one statement:

const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());

This reduces code clarity. Consider splitting the assignments.

Suggested change:

- const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());
+ const charRenderInfo = charRenderInfoPool.get();
+ charRenderInfos[renderElementCount++] = charRenderInfo;
🧰 Tools
🪛 Biome

[error] 514-514: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 53d136e and 15b8e6c.

📒 Files selected for processing (3)
  • packages/core/src/2d/text/TextUtils.ts (3 hunks)
  • packages/core/src/ui/UIText.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
🧰 Additional context used
🪛 Biome
packages/core/src/ui/UIText.ts

[error] 274-274: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 513-513: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 514-514: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 524-524: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 526-526: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 608-608: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 610-610: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🪛 GitHub Check: lint
packages/core/src/ui/index.ts

[failure] 10-10:
Delete

🔇 Additional comments (2)
packages/core/src/2d/text/TextUtils.ts (2)

3-3: LGTM: Import addition is correct

The UIText import is properly placed and supports the new type parameter additions.


100-100: Verify type compatibility between TextRenderer and UIText

The method now accepts both TextRenderer and UIText types. Please ensure that UIText implements all required properties and methods:

  • _getSubFont()
  • width
  • height
  • lineSpacing
  • overflowMode
✅ Verification successful

Let me gather more information about the UIText class implementation to verify the required properties and methods.


Let me verify one last aspect - the inheritance hierarchy and interface implementation of UIText and TextRenderer.


Based on the gathered evidence, I can now provide the final response:

Type compatibility between TextRenderer and UIText is verified

The verification confirms that UIText implements all required properties and methods:

  • _getSubFont() method is properly implemented
  • Required properties are present: _width, _height, _lineSpacing, _overflowMode
  • Both classes extend from base rendering classes (UIRenderer and Renderer respectively) and share similar structure
  • Both classes have compatible implementations for text rendering functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify UIText implements required properties and methods

# Search for UIText class definition and implementation
ast-grep --pattern 'class UIText {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
}'

# Search for width, height, lineSpacing, and overflowMode properties
rg -A 5 'class UIText' 

Length of output: 599


Script:

#!/bin/bash
# Search for required properties and methods in UIText class
rg -A 2 "(_getSubFont|width\s*:|height\s*:|lineSpacing\s*:|overflowMode\s*:)" packages/core/src/ui/UIText.ts

# Search for property declarations
ast-grep --pattern 'class UIText extends UIRenderer {
  $$$
  private _width: $_
  $$$
}'

# Check if UIText extends TextRenderer or has similar interface
rg -A 2 "interface.*Text" packages/core/src/2d/text/

Length of output: 1562


Script:

#!/bin/bash
# Check TextRenderer class and its properties
rg -A 5 "class TextRenderer" packages/core/src/2d/text/

# Check if UIText implements or extends any text-related interfaces
rg "class UIText.*implements" packages/core/src/ui/UIText.ts

# Look for any shared interfaces between TextRenderer and UIText
ast-grep --pattern 'interface $_ {
  $$$
  _getSubFont(): $_
  $$$
}'

Length of output: 825

Comment on lines 256 to 257
//@ts-ignore
this._color._onValueChanged = this._onColorChange.bind(this);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using //@ts-ignore by properly typing properties

At lines 256-257, the use of //@ts-ignore suppresses TypeScript errors, which might hide potential issues. Consider properly typing the _onValueChanged property in the Color class to avoid the need for //@ts-ignore.

const { r, g, b, a } = this.color;
const tempIndices = CharRenderInfo.triangles;
const tempIndicesLength = tempIndices.length;
const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid nested assignments within expressions

At line 608, the assignments are nested within an expression:

const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));

This can reduce code readability. Consider separating the assignments.

Suggested change:

- const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
+ textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4);
+ const subChunk = textChunk.subChunk;
🧰 Tools
🪛 Biome

[error] 608-608: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const tempIndicesLength = tempIndices.length;
const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
const vertices = subChunk.chunk.vertices;
const indices = (subChunk.indices = []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid nested assignments within expressions

At line 610, the assignment is nested within an expression:

const indices = (subChunk.indices = []);

This can be confusing. Consider assigning to subChunk.indices first, then referencing it.

Suggested change:

- const indices = (subChunk.indices = []);
+ subChunk.indices = [];
+ const indices = subChunk.indices;
🧰 Tools
🪛 Biome

[error] 610-610: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const char = line[j];
const charInfo = charFont._getCharInfo(char);
if (charInfo.h > 0) {
firstRow < 0 && (firstRow = j);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignments within expressions to improve code clarity

At line 513, the assignment firstRow = j is performed within a logical AND expression. This can be confusing and reduce readability. It's better to use an explicit if statement.

Suggested change:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }
📝 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
firstRow < 0 && (firstRow = j);
if (firstRow < 0) {
firstRow = j;
}
🧰 Tools
🪛 Biome

[error] 513-513: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

localPositions.set(left, top, right, bottom);
i === firstLine && (maxY = Math.max(maxY, top));
minY = Math.min(minY, bottom);
j === firstRow && (minX = Math.min(minX, left));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignments within expressions for better code readability

At line 526, the assignment minX = Math.min(minX, left) is inside a logical AND expression. For clarity, use an explicit if statement.

Suggested change:

- j === firstRow && (minX = Math.min(minX, left));
+ if (j === firstRow) {
+   minX = Math.min(minX, left);
+ }
📝 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
j === firstRow && (minX = Math.min(minX, left));
if (j === firstRow) {
minX = Math.min(minX, left);
}
🧰 Tools
🪛 Biome

[error] 526-526: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const top = startY + ascent;
const bottom = startY - descent;
localPositions.set(left, top, right, bottom);
i === firstLine && (maxY = Math.max(maxY, top));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignments within expressions for code clarity

At line 524, the assignment maxY = Math.max(maxY, top) is performed within a logical AND expression. This can make the code less readable. Use an explicit if statement instead.

Suggested change:

- i === firstLine && (maxY = Math.max(maxY, top));
+ if (i === firstLine) {
+   maxY = Math.max(maxY, top);
+ }
📝 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
i === firstLine && (maxY = Math.max(maxY, top));
if (i === firstLine) {
maxY = Math.max(maxY, top);
}
🧰 Tools
🪛 Biome

[error] 524-524: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

this._freeTextChunks();
this._textChunks = null;

this._subFont && (this._subFont = null);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid assignments within expressions for better readability

At line 274, the assignment this._subFont = null is used within a logical AND expression. This can make the code harder to read and maintain. Consider using an explicit if statement for clarity.

Suggested change:

- this._subFont && (this._subFont = null);
+ if (this._subFont) {
+   this._subFont = null;
+ }
📝 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
this._subFont && (this._subFont = null);
if (this._subFont) {
this._subFont = null;
}
🧰 Tools
🪛 Biome

[error] 274-274: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

* feat: add button
Copy link

@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: 28

🧹 Outside diff range and nitpick comments (46)
packages/core/src/ui/interactive/transition/TransitionType.ts (1)

1-6: Consider adding JSDoc comments to document each transition type.

Adding documentation would improve maintainability by explaining the purpose and behavior of each transition type.

+/**
+ * Defines the types of transitions available for UI elements.
+ */
 export enum TransitionType {
+  /** No transition effect */
   None,
+  /** Color-based transition effect */
   Color,
+  /** Sprite-based transition effect */
   Sprite,
+  /** Scale-based transition effect */
   Scale
 }
packages/core/src/ui/interactive/InteractiveState.ts (2)

1-7: Consider renaming 'Disable' to 'Disabled' for grammatical consistency.

The enum implementation looks good and covers all essential UI interaction states. However, 'Disable' should be 'Disabled' to maintain consistent grammar (adjective form) with other state names.

 export enum InteractiveState {
   None,
   Normal,
   Pressed,
   Hover,
-  Disable
+  Disabled
 }

1-1: Add JSDoc documentation for the enum.

Since this enum is a core part of the GUI infrastructure used across multiple components, please add JSDoc documentation explaining:

  • The purpose of each state
  • Usage examples
  • State transition expectations
+/**
+ * Represents the interactive states of UI components.
+ * 
+ * @remarks
+ * - None: Initial/unset state
+ * - Normal: Default interactive state
+ * - Pressed: Component is being actively pressed/clicked
+ * - Hover: Mouse is hovering over the component
+ * - Disabled: Component is non-interactive
+ */
 export enum InteractiveState {
packages/core/src/ui/interface/IGroupElement.ts (2)

4-8: Add JSDoc documentation and consider using readonly modifiers.

The interface definition could benefit from documentation explaining its purpose and usage. Additionally, consider marking properties that shouldn't change after initialization as readonly.

Here's the suggested improvement:

+/**
+ * Interface for elements that can be part of a UIGroup.
+ * Extends ICanvasElement to provide group-specific functionality.
+ */
 export interface IGroupElement extends ICanvasElement {
-  _group: UIGroup;
-  _indexInGroup: number;
+  readonly _group: UIGroup;
+  readonly _indexInGroup: number;
   _onGroupModify(flag: GroupModifyFlags): void;
 }

4-8: Consider encapsulating internal implementation details.

The interface exposes implementation details through underscore-prefixed properties and methods. This could make it harder to maintain and refactor the codebase in the future.

Consider:

  1. Moving internal details to a separate internal interface
  2. Using a more public-facing API design
  3. Using TypeScript's private fields (#) if these are truly meant to be private

Example approach:

export interface IGroupElement extends ICanvasElement {
  readonly group: UIGroup;
  readonly indexInGroup: number;
  onModify(flag: GroupModifyFlags): void;
}

// Internal interface for implementation
interface IGroupElementInternal extends IGroupElement {
  _group: UIGroup;
  _indexInGroup: number;
  _onGroupModify(flag: GroupModifyFlags): void;
}
packages/core/src/ui/interface/IUIGraphics.ts (1)

5-10: Consider interface segregation for better modularity.

The current interface combines multiple responsibilities (depth management, hit testing, and group behavior). Consider splitting this into more focused interfaces:

interface IDepthManagement {
  depth: number;
}

interface IRaycastable {
  raycastEnable: boolean;
  raycastPadding: Vector4;
  raycast(ray: Ray, out: HitResult, distance: number): boolean;
}

export interface IUIGraphics extends IGroupElement, IDepthManagement, IRaycastable {}

This would:

  • Make it easier to implement each behavior independently
  • Allow for better composition of UI elements
  • Improve testability of each concern

Would you like me to create a GitHub issue to track this refactoring suggestion?

packages/core/src/ui/index.ts (2)

1-15: LGTM! Well-organized exports structure.

The exports are logically grouped and follow a clear pattern, providing a good foundation for the GUI infrastructure. The separation between core enums, UI components, and transitions is clear and maintainable.

Consider adding JSDoc comments to document the main export groups:

+/** Core UI enums */
 export { UIGroup } from "./UIGroup";
 export { CanvasRenderMode } from "./enums/CanvasRenderMode";
 export { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrategy";

+/** UI Components */
 export { Button } from "./Button";
 export { UICanvas } from "./UICanvas";
 export { UIImage } from "./UIImage";
 export { UIRenderer } from "./UIRenderer";
 export { UIText } from "./UIText";
 export { UITransform } from "./UITransform";

+/** Transition Components */
 export { ColorTransition } from "./interactive/transition/ColorTransition";
 export { ScaleTransition } from "./interactive/transition/ScaleTransition";
 export { SpriteTransition } from "./interactive/transition/SpriteTransition";
 export { Transition } from "./interactive/transition/Transition";

5-15: Consider sorting exports within groups.

While the current organization is clear, consider sorting the exports alphabetically within their groups to make it easier to locate specific exports as the list grows.

 export { Button } from "./Button";
-export { UICanvas } from "./UICanvas";
-export { UIImage } from "./UIImage";
-export { UIRenderer } from "./UIRenderer";
-export { UIText } from "./UIText";
-export { UITransform } from "./UITransform";
+export { UICanvas } from "./UICanvas";
+export { UIImage } from "./UIImage";
+export { UIRenderer } from "./UIRenderer";
+export { UIText } from "./UIText";
+export { UITransform } from "./UITransform";

-export { ColorTransition } from "./interactive/transition/ColorTransition";
-export { ScaleTransition } from "./interactive/transition/ScaleTransition";
-export { SpriteTransition } from "./interactive/transition/SpriteTransition";
-export { Transition } from "./interactive/transition/Transition";
+export { ColorTransition } from "./interactive/transition/ColorTransition";
+export { ScaleTransition } from "./interactive/transition/ScaleTransition";
+export { SpriteTransition } from "./interactive/transition/SpriteTransition";
+export { Transition } from "./interactive/transition/Transition";
packages/core/src/ui/interactive/transition/ScaleTransition.ts (2)

5-12: Consider enhancing configurability and visual feedback.

The current implementation could benefit from:

  1. Constructor parameters to allow customization of scale values and duration
  2. Different scale values for hover and disabled states to provide better visual feedback

Consider this implementation:

-  constructor() {
+  constructor(config?: {
+    normal?: number;
+    hover?: number;
+    pressed?: number;
+    disabled?: number;
+    duration?: number;
+  }) {
     super();
-    this._normal = 1;
-    this._hover = 1;
-    this._pressed = 1.2;
-    this._disabled = 1;
-    this._duration = 0.1;
+    this._normal = config?.normal ?? 1;
+    this._hover = config?.hover ?? 1.1;
+    this._pressed = config?.pressed ?? 1.2;
+    this._disabled = config?.disabled ?? 0.9;
+    this._duration = config?.duration ?? 0.2;
   }

18-20: Consider using easing functions for smoother transitions.

The current linear interpolation might not provide the most visually pleasing transition. Consider implementing easing functions for smoother animations.

Example implementation:

protected override _updateCurrentValue(srcValue: number, destValue: number, weight: number): void {
  // Ease in-out quad function
  const easeWeight = weight < 0.5
    ? 2 * weight * weight
    : 1 - Math.pow(-2 * weight + 2, 2) / 2;
  this._currentValue = (destValue - srcValue) * easeWeight + srcValue;
}
packages/core/src/ui/interactive/transition/ColorTransition.ts (2)

6-15: Consider documenting color states and extracting constants.

The color states and transition duration would benefit from documentation and constant definitions.

Consider applying these improvements:

+/** Default transition duration in seconds */
+private static readonly DEFAULT_DURATION = 0.1;
+
+/** Default color states for different interaction states */
+private static readonly COLORS = {
+  NORMAL: new Color(1, 1, 1, 1),
+  HOVER: new Color(245 / 255, 245 / 255, 245 / 255, 1),
+  PRESSED: new Color(200 / 255, 200 / 255, 200 / 255, 1),
+  DISABLED: new Color(200 / 255, 200 / 255, 200 / 255, 1)
+} as const;

 constructor() {
   super();
-  this._normal = new Color(1, 1, 1, 1);
-  this._hover = new Color(245 / 255, 245 / 255, 245 / 255, 1);
-  this._pressed = new Color(200 / 255, 200 / 255, 200 / 255, 1);
-  this._disabled = new Color(200 / 255, 200 / 255, 200 / 255, 1);
-  this._duration = 0.1;
+  this._normal = ColorTransition.COLORS.NORMAL.clone();
+  this._hover = ColorTransition.COLORS.HOVER.clone();
+  this._pressed = ColorTransition.COLORS.PRESSED.clone();
+  this._disabled = ColorTransition.COLORS.DISABLED.clone();
+  this._duration = ColorTransition.DEFAULT_DURATION;
   this._currentValue = new Color();
 }

17-29: Consider optimizing color object reuse.

The current implementation reuses the color object effectively in _getTargetValueCopy, but _updateCurrentValue could be optimized further.

Consider this optimization:

 protected override _updateCurrentValue(srcValue: Color, destValue: Color, weight: number): void {
   if (weight >= 1) {
     this._currentValue.copyFrom(destValue);
   } else {
-    Color.lerp(srcValue, destValue, weight, this._currentValue);
+    // Avoid potential allocation in Color.lerp by ensuring _currentValue is always used
+    this._currentValue.r = srcValue.r + (destValue.r - srcValue.r) * weight;
+    this._currentValue.g = srcValue.g + (destValue.g - srcValue.g) * weight;
+    this._currentValue.b = srcValue.b + (destValue.b - srcValue.b) * weight;
+    this._currentValue.a = srcValue.a + (destValue.a - srcValue.a) * weight;
   }
 }
packages/core/src/ui/Button.ts (3)

6-6: Consider adding JSDoc for the listeners property.

While the property is well-typed and properly encapsulated, adding JSDoc would improve maintainability by documenting the purpose of this collection.

+  /** Collection of click event listeners managed using SafeLoopArray for safe iteration. */
   private _listeners: SafeLoopArray<IUIListener> = new SafeLoopArray<IUIListener>();

8-14: Consider preventing duplicate listener registrations.

The current implementation allows the same listener to be registered multiple times, which could lead to redundant executions.

   addClicked(listener: (event: PointerEventData) => void): void {
+    if (this._listeners.find((value) => value.fn === listener)) {
+      return;
+    }
     this._listeners.push({ fn: listener });
   }

38-41: Consider adding JSDoc to document the interface.

While the interface is well-structured, adding documentation would improve maintainability.

+/**
+ * Interface for UI event listeners with destruction tracking.
+ */
 export interface IUIListener {
+  /** The event handling function */
   fn: (event: PointerEventData) => void;
+  /** Flag indicating if the listener has been destroyed */
   destroyed?: boolean;
 }
packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (3)

13-49: Consider breaking down the complex raycast processing logic.

The method contains nested loops and multiple conditions that could be extracted into smaller, focused methods for better maintainability.

Consider refactoring into smaller methods:

private _isValidScene(scene: Scene): boolean {
  return scene.isActive && !scene.destroyed;
}

private _isPointerInViewport(x: number, y: number, viewport: { x: number, y: number, width: number, height: number }): boolean {
  return !(x < viewport.x || y < viewport.y || 
          x > viewport.x + viewport.width || 
          y > viewport.y + viewport.height);
}

private _processCamera(camera: Camera, ray: Ray, pointer: Pointer): boolean {
  if (camera.renderTarget) return false;
  
  const { x, y } = pointer.position;
  if (!this._isPointerInViewport(x, y, camera.pixelViewport)) {
    return false;
  }
  
  camera.screenPointToRay(pointer.position, ray);
  return true;
}

79-96: Consider caching event data to reduce object creation.

The method creates multiple event data objects with the same parameters. Consider caching the event data for better performance.

private _processUp(pointer: Pointer): void {
  const { _enteredEntity: enteredEntity, _draggedEntity: draggedEntity } = this;
  
  if (enteredEntity) {
    const sameTarget = this._pressedEntity === enteredEntity;
    const eventData = this._createEventData(pointer, enteredEntity, enteredEntity);
    
    this._invokeEntityScripts(enteredEntity, (script: Script) => {
      script.onPointerUp?.(eventData);
      sameTarget && script.onPointerClick?.(eventData);
      script.onPointerDrop?.(eventData);
    });
  }
  // ... rest of the method
}

98-115: Apply similar event data caching optimization.

Similar to the _processUp method, consider caching the event data objects to reduce object creation.

packages/core/src/ui/UIUtils.ts (4)

21-37: Add JSDoc documentation for better maintainability.

This method handles complex parent traversal and listener management. Consider adding JSDoc documentation to explain:

  • The purpose of the method
  • The relationship between entities and listeners
  • The significance of the root canvas check

47-82: Consider extracting shared logic between registration methods.

The registerElementToCanvas and registerElementToGroup methods share similar patterns for managing indices and elements. Consider extracting the common logic into a private helper method to improve maintainability and reduce code duplication.

Example approach:

private static registerElementToContainer<T extends ICanvasElement | IGroupElement, C extends UICanvas | UIGroup>(
  element: T,
  container: C,
  getPreContainer: (el: T) => C,
  getElements: (c: C) => DisorderedArray<T>,
  updateIndex: (el: T, index: number) => void
): void {
  const preContainer = getPreContainer(element);
  if (preContainer !== container) {
    // ... shared logic here
  }
}
🧰 Tools
🪛 Biome

[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


84-126: Consider using type guard functions for safer type casting.

Replace type casting with type guard functions to improve type safety and readability:

private static isUICanvas(component: Component): component is UICanvas {
  return component._componentType === ComponentType.UICanvas;
}

private static isUIGroup(component: Component): component is UIGroup {
  return component._componentType === ComponentType.UIGroup;
}

Then use them in the methods:

if (component.enabled && this.isUICanvas(component) && component._isRootCanvas) {
  return component;
}

136-143: Add comments explaining matrix transformations.

The matrix operations for projection and view transformations would benefit from documentation explaining:

  • The purpose of each transformation
  • The coordinate space being used
  • The significance of the magic numbers (2, 0)
packages/core/src/ui/UIRenderer.ts (2)

59-61: Consider omitting redundant type annotations.

The type annotations can be inferred from the initializers and can be omitted for better conciseness.

Apply these changes:

-protected _alpha: number = 1;
+protected _alpha = 1;

-protected _color: Color = new Color(1, 1, 1, 1);
+protected _color = new Color(1, 1, 1, 1);

229-241: Improve variable naming in _hitTest method.

Consider using more descriptive variable names for better readability.

Apply these changes:

 protected _hitTest(localPosition: Vector3): boolean {
   const { x, y } = localPosition;
   const uiTransform = <UITransform>this._transform;
-  const { x: width, y: height } = uiTransform.size;
-  const { x: pivotX, y: pivotY } = uiTransform.pivot;
+  const { x: elementWidth, y: elementHeight } = uiTransform.size;
+  const { x: pivotOffsetX, y: pivotOffsetY } = uiTransform.pivot;
   const { x: paddingLeft, y: paddingBottom, z: paddingRight, w: paddingTop } = this.raycastPadding;
   
   return (
-    x >= -width * pivotX + paddingLeft &&
-    x <= width * (1 - pivotX) - paddingRight &&
-    y >= -height * pivotY + paddingTop &&
-    y <= height * (1 - pivotY) - paddingBottom
+    x >= -elementWidth * pivotOffsetX + paddingLeft &&
+    x <= elementWidth * (1 - pivotOffsetX) - paddingRight &&
+    y >= -elementHeight * pivotOffsetY + paddingTop &&
+    y <= elementHeight * (1 - pivotOffsetY) - paddingBottom
   );
 }
packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (3)

223-232: Improve readability and maintainability of path composition

Several improvements can be made to the _composedPath method:

  1. Avoid assignment within expressions
  2. Add early return for invalid input
  3. Consider extracting the path composition logic to a separate method for better reusability

Apply this diff to improve the code:

 private _composedPath(element: Component, path: Entity[]): Entity[] {
   if (!element) {
     path.length = 0;
     return path;
   }
-  let entity = (path[0] = element._entity);
+  path[0] = element._entity;
+  let entity = element._entity;
   let i = 1;
   if (element._componentType === ComponentType.UICanvas && (<UICanvas>element)._isRootCanvas) {
     path.length = 1;
     return path;
   } else {
     const rootEntity = (<UICanvas | UIRenderer>element)._rootCanvas._entity;
     for (; i < PointerUIEventEmitter._MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
       entity = path[i] = entity.parent;
     }
   }
   path.length = i;
   return path;
 }
🧰 Tools
🪛 Biome

[error] 223-223: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


170-171: Consider performance optimizations for path operations

The current implementation creates new paths and performs comparisons frequently, which could impact performance with high-frequency pointer events. Consider:

  1. Caching paths when possible
  2. Using a more efficient data structure for path comparison
  3. Implementing path pooling to reduce garbage collection

13-21: Enhance documentation and type safety

The class could benefit from:

  1. JSDoc documentation for the class explaining its purpose and usage
  2. Type annotations for event handlers
  3. Documentation of the relationship between different pointer states
  4. Interface definitions for the event data structure
packages/core/src/input/pointer/PointerManager.ts (5)

4-12: Well-structured architectural improvements!

The introduction of ClearableObjectPool and event emitters shows good separation of concerns and memory management practices. The modular approach with separate emitter classes will make the code more maintainable and testable.

Also applies to: 41-41


99-110: Consider caching physics initialization state

The _engine._physicsInitialized check on each pointer creation could lead to inconsistent behavior if physics initialization state changes during runtime. Consider caching this state during PointerManager initialization.

 constructor(engine: Engine, target: EventTarget) {
   // ... existing code ...
+  this._physicsEnabled = engine._physicsInitialized;
 }

 // Later in the code
-_engine._physicsInitialized && pointer._addEmitters(PointerPhysicsEventEmitter, _eventPool);
+this._physicsEnabled && pointer._addEmitters(PointerPhysicsEventEmitter, _eventPool);

224-255: Consider using event type constants

The string literals for event types could be replaced with constants to prevent typos and improve maintainability. Consider creating an enum or constant object for event types.

const PointerEventTypes = {
  DOWN: 'pointerdown',
  UP: 'pointerup',
  MOVE: 'pointermove',
  LEAVE: 'pointerleave',
  CANCEL: 'pointercancel'
} as const;

282-288: Optimize cleanup operations

Consider combining the array clearing operations for better performance. You could create a helper method to clear multiple arrays at once.

private _clearArrays() {
  [this._nativeEvents, this._pointers, this._downList, 
   this._downMap, this._upList, this._upMap].forEach(arr => arr.length = 0);
}

41-41: Document object pool configuration

Consider adding JSDoc comments to explain the object pool's configuration and lifecycle, particularly its garbage collection strategy and pool size management.

packages/core/src/ui/UIImage.ts (2)

196-216: Optimize property access and null checks.

Multiple property accesses and null checks can be optimized by destructuring and combining conditions.

 protected override _render(context: RenderContext): void {
-  const { _sprite: sprite } = this;
-  const transform = this._transform as UITransform;
-  const { x: width, y: height } = transform.size;
-  if (!sprite?.texture || !width || !height) {
+  const { _sprite: sprite, _transform: transform, _color: color, _alpha: alpha } = this;
+  if (!sprite?.texture || !transform) {
     return;
   }
+  const { x: width, y: height } = (transform as UITransform).size;
+  if (!width || !height || color.a * alpha <= 0) {
+    return;
+  }

   let material = this.getMaterial();
   if (!material) {
     return;
   }
   // @todo: This question needs to be raised rather than hidden.
   if (material.destroyed) {
     material = this._engine._basicResources.uiDefaultMaterial;
   }

-  if (this._color.a * this._alpha <= 0) {
-    return;
-  }

362-371: Consider using TypeScript const enum for better performance.

The ImageUpdateFlags enum would benefit from being a const enum as it's used internally for bitwise operations.

-enum ImageUpdateFlags {
+const enum ImageUpdateFlags {
   UV = 0x20,

   /** LocalPosition | WorldPosition | UV */
   AllPositionAndUV = 0x23,
   /** LocalPosition | WorldPosition | UV | Color */
   AllPositionUVAndColor = 0x33,
   /** LocalPosition | WorldPosition | UV | Color | LocalBounds | WorldBounds */
   All = 0x3f
 }
packages/core/src/ui/UIText.ts (3)

235-241: Consider adding error handling in the constructor.

The constructor assumes engine._textDefaultFont and engine._basicResources.textDefaultMaterial will always exist. Consider adding null checks and throwing appropriate errors if these dependencies are missing.

 constructor(entity: Entity) {
   super(entity);

   const { engine } = this;
+  if (!engine._textDefaultFont) {
+    throw new Error("Default font not initialized in engine.");
+  }
   this.font = engine._textDefaultFont;
+  if (!engine._basicResources?.textDefaultMaterial) {
+    throw new Error("Default text material not initialized in engine resources.");
+  }
   this.setMaterial(engine._basicResources.textDefaultMaterial);
 }

257-257: Improve readability by avoiding assignment in expression.

The assignment within the logical AND expression makes the code harder to read.

- this._subFont && (this._subFont = null);
+ if (this._subFont) {
+   this._subFont = null;
+ }
🧰 Tools
🪛 Biome

[error] 257-257: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


324-363: Consider performance optimizations in render loop.

The render method performs several operations that could be optimized:

  1. Multiple property accesses could be cached
  2. Array iterations could be optimized
  3. Object allocations in render loop should be minimized

Consider these optimizations:

  1. Cache frequently accessed properties outside the loop
  2. Use pre-allocated arrays for temporary calculations
  3. Consider implementing object pooling for SubRenderElements
  4. Profile the rendering performance with large text content to identify bottlenecks

Would you like me to provide a detailed implementation of these optimizations?

packages/core/src/Entity.ts (1)

108-113: Consider adding type safety for transform access.

While the refactoring from public property to private property with getter is good for encapsulation, consider adding runtime type checking to prevent potential issues with transform types.

 get transform(): Transform {
+  if (!this._transform) {
+    throw new Error('Transform component is missing');
+  }
   return this._transform;
 }
packages/core/src/ui/interactive/transition/Transition.ts (2)

6-6: Avoid using 'any' as default type for generics to maintain type safety

The generic type parameters T and K default to any, which reduces the benefits of TypeScript's type checking. By specifying more precise default types or removing the defaults, you can enhance type safety and catch potential errors at compile time.


114-119: Ensure _countDown does not become negative in _onUpdate

In the _onUpdate() method, _countDown is decremented by delta, but there's no check to prevent it from becoming negative. Negative _countDown values could cause unintended behavior in the transition logic.

Consider clamping _countDown to zero after decrementing to prevent negative values.

Suggested change:

if (this._countDown > 0) {
- this._countDown -= delta;
+ this._countDown = Math.max(0, this._countDown - delta);
  this._updateValue();
}
packages/core/src/ui/interactive/UIInteractive.ts (1)

115-115: Separate assignments for better readability

Assigning multiple variables in a single statement can reduce readability. Consider splitting the assignments to improve clarity.

Apply this diff:

-        this._isPointerInside = this._isPointerDown = false;
+        this._isPointerInside = false;
+        this._isPointerDown = false;
packages/core/src/ui/UICanvas.ts (5)

89-89: Use if statement instead of logical operators for clarity

Using logical operators like && for control flow can reduce code readability. Consider using an explicit if statement to make the code clearer.

Apply this diff to enhance readability:

- (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
+ if (referenceResolution.x !== val.x || referenceResolution.y !== val.y) {
+   referenceResolution.copyFrom(val);
+ }

142-143: Use if statement instead of logical operators for side effects

Using logical operators like && to trigger side effects can make the code harder to read and understand. Replace it with an if statement for better clarity.

Apply this diff to improve code clarity:

- this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
-   this.scene._componentsManager._overlayCanvasesSortingFlag;
+ if (this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
+   this.scene._componentsManager._overlayCanvasesSortingFlag = true;
+ }

154-154: Use if statement instead of logical operators for function calls

Using logical operators like && to conditionally call functions can reduce code readability. Consider using an explicit if statement for better clarity.

Apply this diff to enhance readability:

- this._realRenderMode === CanvasRenderMode.ScreenSpaceCamera && this._adapterPoseInScreenSpace();
+ if (this._realRenderMode === CanvasRenderMode.ScreenSpaceCamera) {
+   this._adapterPoseInScreenSpace();
+ }

194-194: Avoid assignment within an expression for better readability

Assigning values within expressions can be confusing and reduce code clarity. Separate the assignment from the declaration to improve readability.

Apply this diff to enhance code clarity:

- const renderElement = (this._renderElement = engine._renderElementPool.get());
+ this._renderElement = engine._renderElementPool.get();
+ const renderElement = this._renderElement;
🧰 Tools
🪛 Biome

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


359-359: Avoid assignment within a logical expression

Assigning a value within a logical expression can reduce code readability. Use an explicit if statement instead.

Apply this diff to improve clarity:

- child.isActive && (depth = this._walk(child, elements, depth));
+ if (child.isActive) {
+   depth = this._walk(child, elements, depth);
+ }
🧰 Tools
🪛 Biome

[error] 359-359: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 15b8e6c and 4b2487d.

📒 Files selected for processing (22)
  • packages/core/src/Entity.ts (11 hunks)
  • packages/core/src/input/pointer/PointerManager.ts (6 hunks)
  • packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (1 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Button.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIImage.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIText.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/InteractiveState.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/ColorTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/ScaleTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/TransitionType.ts (1 hunks)
  • packages/core/src/ui/interface/ICanvasElement.ts (1 hunks)
  • packages/core/src/ui/interface/IGroupElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 616-616: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts

[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 223-223: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Button.ts

[error] 21-21: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 359-359: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 473-474: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-484: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 485-487: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 167-167: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 168-168: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 166-170: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIText.ts

[error] 257-257: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 496-496: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 497-497: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 507-507: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 509-509: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 591-591: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 593-593: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (35)
packages/core/src/ui/interactive/transition/TransitionType.ts (1)

1-6: LGTM! Clean and well-structured enum implementation.

The enum provides a clear and type-safe way to represent different transition types for UI elements.

packages/core/src/ui/interface/IGroupElement.ts (1)

1-1: Verify the GroupModifyFlags type definition.

The GroupModifyFlags import is used but its type definition isn't visible in the current context.

✅ Verification successful

GroupModifyFlags is properly defined and used correctly

The GroupModifyFlags enum is defined in packages/core/src/ui/UIGroup.ts with clear flag values:

  • None = 0x0
  • Alpha = 0x1
  • Interactive = 0x2
  • All = 0x3

The import in IGroupElement.ts correctly references this enum which is used throughout the UI system for group modifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the definition of GroupModifyFlags
# Expected: Find the type definition and its possible values

# Search for GroupModifyFlags definition
echo "Searching for GroupModifyFlags definition:"
ast-grep --pattern 'enum GroupModifyFlags {
  $$$
}'

# Backup search using ripgrep
echo -e "\nBackup search for GroupModifyFlags:"
rg -A 5 "GroupModifyFlags"

Length of output: 7403

packages/core/src/ui/interface/ICanvasElement.ts (1)

1-2: LGTM! Clean and focused imports.

The imports are well-organized and specifically import only the required types.

packages/core/src/ui/interface/IUIGraphics.ts (1)

1-3: LGTM! Well-organized imports.

The imports are properly structured with clear separation between external package and internal module imports.

packages/core/src/ui/interactive/transition/SpriteTransition.ts (3)

1-5: LGTM! Clean and well-structured class declaration.

The imports are appropriate, and the class extension with generic types is well-defined.


10-12: Verify if binary transition is the intended behavior.

The current implementation provides an abrupt switch between source and destination sprites at weight = 1, rather than a smooth transition. If this is intentional, consider adding a comment explaining why interpolation is not used.

✅ Verification successful

Binary transition for sprites is consistent with the codebase design

The implementation is correct as sprites cannot be interpolated like other properties. This is evident from the codebase where:

  • ColorTransition uses Color.lerp for smooth color transitions
  • ScaleTransition uses linear interpolation for smooth scaling
  • SpriteTransition uses binary switching, which is appropriate as sprites are discrete assets

Consider adding a comment explaining this design choice to improve code clarity.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other transition implementations to verify if this binary approach is consistent
ast-grep --pattern 'class $_Transition extends Transition<$_, $_> {
  $$$
  protected override _updateCurrentValue($_, $_, weight: number): void {
    $$$
  }
  $$$
}'

Length of output: 169


Script:

#!/bin/bash
# Let's try a different approach to find transition implementations
rg "_updateCurrentValue.*weight.*number" -A 5

# Also search for the base Transition class to understand the expected behavior
rg "class Transition[^a-zA-Z]" -A 10

Length of output: 3776


14-16: Add type safety and error handling for _normal property.

The method uses this._normal as a fallback, but this property's initialization is not visible in the current context.

Consider adding:

  1. Type guard to ensure value is a valid Sprite
  2. Explicit initialization of _normal in the constructor
  3. Error handling for invalid sprite values
  protected override _applyValue(value: Sprite): void {
+   if (value && !this.isValidSprite(value)) {
+     throw new Error('Invalid sprite value provided');
+   }
    this._target.sprite = value || this._normal;
  }
+ 
+ private isValidSprite(value: any): value is Sprite {
+   return value && typeof value === 'object';
+   // Add additional sprite validation logic
+ }
packages/core/src/ui/index.ts (1)

1-15: Verify all exports are properly implemented.

Let's ensure all exported components exist and are properly implemented.

✅ Verification successful

Let me generate another script to verify the specific exports since the previous output didn't show us the actual export declarations.


Based on the verification results, I can now provide the final response:

All exports are properly implemented and available

All exported components from packages/core/src/ui/index.ts are verified to exist and are properly implemented:

  • Enums: CanvasRenderMode and ResolutionAdaptationStrategy are properly exported
  • UI Components: UIGroup, Button, UICanvas, UIImage, UIText, and UITransform are all exported as classes
  • Transitions: ColorTransition, ScaleTransition, SpriteTransition, and base Transition are all exported as classes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all exported files exist and export the expected components

# Test: Check if all imported files exist
echo "Checking if all files exist..."
for file in "UIGroup" "enums/CanvasRenderMode" "enums/ResolutionAdaptationStrategy" \
           "Button" "UICanvas" "UIImage" "UIRenderer" "UIText" "UITransform" \
           "interactive/transition/ColorTransition" "interactive/transition/ScaleTransition" \
           "interactive/transition/SpriteTransition" "interactive/transition/Transition"; do
  if ! fd -p "packages/core/src/ui/$file.ts"; then
    echo "Missing file: $file"
  fi
done

# Test: Verify exports in each file
echo "Checking exports in each file..."
for file in $(fd -e ts . "packages/core/src/ui"); do
  echo "Checking $file..."
  ast-grep --pattern 'export class $name' "$file"
  ast-grep --pattern 'export enum $name' "$file"
  ast-grep --pattern 'export interface $name' "$file"
done

Length of output: 14751


Script:

#!/bin/bash
# Check specific exports in each file

# Check UIGroup export
echo "Checking UIGroup..."
rg "export (class|enum|interface) UIGroup" packages/core/src/ui/UIGroup.ts -A 1

echo "Checking enums..."
rg "export enum (CanvasRenderMode|ResolutionAdaptationStrategy)" packages/core/src/ui/enums/ -A 1

echo "Checking UI components..."
for component in "Button" "UICanvas" "UIImage" "UIRenderer" "UIText" "UITransform"; do
  echo "Checking $component..."
  rg "export (class|interface) $component" "packages/core/src/ui/$component.ts" -A 1
done

echo "Checking transitions..."
for transition in "ColorTransition" "ScaleTransition" "SpriteTransition" "Transition"; do
  echo "Checking $transition..."
  rg "export (class|interface) $transition" "packages/core/src/ui/interactive/transition/$transition.ts" -A 1
done

Length of output: 3977

packages/core/src/ui/interactive/transition/ScaleTransition.ts (2)

1-4: LGTM! Clean and type-safe class declaration.

The imports are minimal and the generic type parameters are well-defined.


14-16: Verify uniform scaling assumption.

The method assumes uniform scaling by only checking the x-axis scale. This might lead to unexpected behavior if the entity has non-uniform scaling.

packages/core/src/ui/interactive/transition/ColorTransition.ts (1)

1-5: LGTM! Clean imports and proper class declaration.

The class structure and type parameters are well-defined, following TypeScript best practices.

packages/core/src/ui/Button.ts (2)

1-5: LGTM! Clean imports and proper inheritance.

The imports are focused and the inheritance hierarchy is appropriate for a button component.


24-30: LGTM! Efficient and safe event dispatch implementation.

The implementation correctly handles listener iteration and destroyed state checking.

packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (5)

1-11: LGTM! Clean class structure with well-defined properties.

The class is well-organized with appropriate imports and clearly named protected properties for tracking entity states.


54-61: LGTM! Clean and focused drag event handling.

The method is well-structured with proper null checks and clear event handling logic.


66-74: Multiple assignments in expression reduce readability.

This issue was previously identified. The suggestion to separate the assignments for better readability is still valid.

🧰 Tools
🪛 Biome

[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


117-119: LGTM! Thorough cleanup of entity references.

The method properly cleans up all entity references in a single statement.


121-142: LGTM! Well-designed helper methods.

The private methods are focused, properly encapsulated, and maintain consistent event handling patterns.

packages/core/src/ui/UIGroup.ts (4)

1-41: LGTM! Well-structured class definition with proper property management.

The class structure follows best practices with:

  • Clear separation of concerns using decorators
  • Proper type definitions
  • Well-documented internal properties

43-75: LGTM! Well-implemented property accessors.

The getters and setters properly handle state updates and trigger necessary modifications.


117-144: LGTM! Thorough lifecycle management.

The methods properly handle:

  • Registration and cleanup of entity listeners
  • Memory management with array clearing
  • Garbage collection

186-191: LGTM! Well-defined bit flags enum.

The enum correctly uses bit flags to enable efficient combination of multiple modification types.

packages/core/src/ui/UIRenderer.ts (1)

205-227: 🛠️ Refactor suggestion

Optimize raycast performance and improve readability.

The raycast method can be optimized by:

  1. Caching the plane intersection result
  2. Using early returns for better readability

Apply these changes:

 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
   const { transform } = this._entity;
   const { worldForward, worldMatrix, worldPosition } = transform;
   const normal = plane.normal.copyFrom(worldForward);
   plane.distance = -Vector3.dot(normal, worldPosition);
   
-  const curDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
+  const intersectionDistance = ray.intersectPlane(plane);
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

-    const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
+  const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
   const worldMatrixInv = UIRenderer._tempMat;
-  Matrix.invert(transform.worldMatrix, worldMatrixInv);
+  Matrix.invert(worldMatrix, worldMatrixInv);
   
   const localPosition = UIRenderer._tempVec31;
   Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
   
-  if (this._hitTest(localPosition)) {
-    out.distance = curDistance;
-    out.entity = this._entity;
-    out.component = this;
-    out.normal.copyFrom(normal);
-    out.point.copyFrom(hitPointWorld);
-    return true;
-  }
+  if (!this._hitTest(localPosition)) {
+    return false;
   }
-  return false;
+  
+  out.distance = intersectionDistance;
+  out.entity = this._entity;
+  out.component = this;
+  out.normal.copyFrom(normal);
+  out.point.copyFrom(hitPointWorld);
+  return true;
 }

Likely invalid or redundant comment.

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (2)

85-85: Remove unnecessary return statement

The method is declared as void but contains a return null statement.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


14-16: Consider potential thread safety issues with static arrays

The static arrays _path0 and _path1 are shared across all instances and could lead to race conditions if multiple pointer events are processed concurrently. Consider making these instance-level properties or implementing proper synchronization.

packages/core/src/input/pointer/PointerManager.ts (1)

73-83: Verify pointer disposal completeness

While the pointer cleanup logic is improved, ensure that all resources associated with disposed pointers are properly cleaned up. Consider adding explicit cleanup of any event listeners or cached data.

packages/core/src/ui/UIImage.ts (1)

343-356: ⚠️ Potential issue

Fix incorrect assembler usage in Tiled mode.

The _getUVByLocalPosition method incorrectly uses SlicedSpriteAssembler for the Tiled draw mode.

 case SpriteDrawMode.Tiled:
-  return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
+  return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);

Likely invalid or redundant comment.

packages/core/src/Entity.ts (4)

10-17: LGTM: New imports and properties enhance UI and state management capabilities.

The additions properly support the new UI infrastructure and state management features.

Also applies to: 86-104


338-338: LGTM: Parent change handling is properly implemented.

The new _setParentChange method correctly propagates changes through the transform hierarchy and dispatches modification events.

Also applies to: 607-610


531-534: LGTM: Proper cleanup of update flag manager.

The destroy method properly cleans up the update flag manager by removing all listeners and nullifying the reference.


798-798: LGTM: Entity modification flags are well-defined.

The new enum properly categorizes different types of entity modifications, and the dispatch in _setSiblingIndex is correctly implemented.

Also applies to: 817-824

packages/core/src/ui/interactive/UIInteractive.ts (1)

12-196: Overall code implementation looks solid

The UIInteractive class is well-structured, and the interactive state management logic is sound. Methods are appropriately overridden, and the use of transitions enhances the interactivity of UI elements.

🧰 Tools
🪛 Biome

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UICanvas.ts (3)

473-474: Previous comment on missing break statement still applies

The missing break statement in this switch case was identified earlier and needs to be addressed to prevent unintended fall-through.

🧰 Tools
🪛 Biome

[error] 473-474: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


483-484: Previous comment on missing break statement still applies

The issue of the missing break statement in this switch case remains unresolved. Please refer to the earlier feedback.

🧰 Tools
🪛 Biome

[error] 483-484: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


485-487: Previous comment on missing break statement still applies

Please address the previously noted missing break statements to prevent unintended fall-through in this switch case.

🧰 Tools
🪛 Biome

[error] 485-487: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 4 to 10
export interface ICanvasElement {
_entity: Entity;
_parents: Entity[];
_rootCanvas: UICanvas;
_indexInCanvas: number;
_onEntityModify(flag: EntityModifyFlags): void;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider revising the interface design to follow TypeScript best practices.

Several architectural and style concerns:

  1. The underscore prefix on all members suggests they are internal implementation details, which shouldn't be part of a public interface contract. Consider:

    • Moving these implementation details to an abstract class
    • Or removing the underscore prefix if these are meant to be public members
  2. The interface appears to expose internal state management (_parents, _indexInCanvas), which could make it harder to change the implementation later.

  3. Missing documentation for the interface and its members. Consider adding JSDoc comments to explain:

    • The purpose of the interface
    • The role of each member
    • The contract requirements for implementing classes

Here's a suggested refactor:

+/**
+ * Represents a UI element that can be rendered on a canvas.
+ * Provides contract for managing canvas element hierarchy and modifications.
+ */
 export interface ICanvasElement {
-  _entity: Entity;
-  _parents: Entity[];
-  _rootCanvas: UICanvas;
-  _indexInCanvas: number;
-  _onEntityModify(flag: EntityModifyFlags): void;
+  /** The entity associated with this canvas element */
+  entity: Entity;
+  /** The parent entities in the hierarchy */
+  parents: Entity[];
+  /** The root canvas this element belongs to */
+  rootCanvas: UICanvas;
+  /** The rendering order index within the canvas */
+  indexInCanvas: number;
+  /** Handles modifications to the associated entity */
+  onEntityModify(flag: EntityModifyFlags): void;
 }

Consider:

  1. Breaking this into smaller interfaces (e.g., separate hierarchy management from entity management)
  2. Using composition over inheritance by providing utility classes for common functionality
  3. Reducing coupling with Entity and UICanvas by introducing abstraction layers
📝 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
export interface ICanvasElement {
_entity: Entity;
_parents: Entity[];
_rootCanvas: UICanvas;
_indexInCanvas: number;
_onEntityModify(flag: EntityModifyFlags): void;
}
/**
* Represents a UI element that can be rendered on a canvas.
* Provides contract for managing canvas element hierarchy and modifications.
*/
export interface ICanvasElement {
/** The entity associated with this canvas element */
entity: Entity;
/** The parent entities in the hierarchy */
parents: Entity[];
/** The root canvas this element belongs to */
rootCanvas: UICanvas;
/** The rendering order index within the canvas */
indexInCanvas: number;
/** Handles modifications to the associated entity */
onEntityModify(flag: EntityModifyFlags): void;
}

Comment on lines 5 to 10
export interface IUIGraphics extends IGroupElement {
depth: number;
raycastEnable: boolean;
raycastPadding: Vector4;
_raycast(ray: Ray, out: HitResult, distance: number): boolean;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider revising interface design and adding documentation.

  1. The _raycast method is prefixed with underscore suggesting it's private, which is unconventional for interface contracts. Consider:

    • Removing the underscore if this method should be public
    • Or moving this implementation detail to the concrete class if it should be private
  2. The interface would benefit from JSDoc documentation explaining:

    • The purpose and valid range of the depth property
    • The meaning of each component in raycastPadding (left, top, right, bottom)
    • The return value semantics of _raycast

Here's a suggested improvement:

+/**
+ * Interface for UI graphics elements that support raycasting and depth management.
+ */
 export interface IUIGraphics extends IGroupElement {
+  /** The depth value used for z-ordering. Higher values appear in front. */
   depth: number;
+  /** Whether this element should participate in raycasting operations. */
   raycastEnable: boolean;
+  /** Padding values (left, top, right, bottom) to expand/contract the raycast hit area. */
   raycastPadding: Vector4;
-  _raycast(ray: Ray, out: HitResult, distance: number): boolean;
+  /**
+   * Performs raycasting against this UI element.
+   * @param ray The ray to test against
+   * @param out The hit result information if intersection occurs
+   * @param distance The maximum distance to test
+   * @returns true if the ray intersects with this element
+   */
+  raycast(ray: Ray, out: HitResult, distance: number): boolean;
 }
📝 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
export interface IUIGraphics extends IGroupElement {
depth: number;
raycastEnable: boolean;
raycastPadding: Vector4;
_raycast(ray: Ray, out: HitResult, distance: number): boolean;
}
/**
* Interface for UI graphics elements that support raycasting and depth management.
*/
export interface IUIGraphics extends IGroupElement {
/** The depth value used for z-ordering. Higher values appear in front. */
depth: number;
/** Whether this element should participate in raycasting operations. */
raycastEnable: boolean;
/** Padding values (left, top, right, bottom) to expand/contract the raycast hit area. */
raycastPadding: Vector4;
/**
* Performs raycasting against this UI element.
* @param ray The ray to test against
* @param out The hit result information if intersection occurs
* @param distance The maximum distance to test
* @returns true if the ray intersects with this element
*/
raycast(ray: Ray, out: HitResult, distance: number): boolean;
}

Comment on lines 6 to 8
protected _getTargetValueCopy(): Sprite {
return this._target?.sprite;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider implementing actual value copying and adding documentation.

The method name suggests it returns a copy, but it's returning a reference to the original sprite. This could lead to unintended modifications of the source sprite.

Consider:

  1. Adding JSDoc documentation explaining the method's purpose
  2. Implementing actual value copying if sprite mutation should be prevented:
+ /**
+  * Returns a copy of the target's sprite value.
+  * @returns A copy of the target sprite or undefined if target is not set
+  */
  protected _getTargetValueCopy(): Sprite {
-   return this._target?.sprite;
+   return this._target?.sprite ? { ...this._target.sprite } : undefined;
  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 22 to 24
protected override _applyValue(value: number): void {
this._target.entity.transform.setScale(value, value, value);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add validation for scale values.

The method should validate scale values to prevent negative or zero scales which could cause rendering issues.

Suggested implementation:

   protected override _applyValue(value: number): void {
+    if (value <= 0) {
+      console.warn('Scale values should be positive. Clamping to 0.01');
+      value = Math.max(0.01, value);
+    }
     this._target.entity.transform.setScale(value, value, value);
   }
📝 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
protected override _applyValue(value: number): void {
this._target.entity.transform.setScale(value, value, value);
}
protected override _applyValue(value: number): void {
if (value <= 0) {
console.warn('Scale values should be positive. Clamping to 0.01');
value = Math.max(0.01, value);
}
this._target.entity.transform.setScale(value, value, value);
}

Comment on lines 31 to 33
protected override _applyValue(value: Color): void {
this._target.color = value;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check for target in _applyValue method.

The _applyValue method should validate that _target exists before attempting to set its color property.

Apply this fix:

 protected override _applyValue(value: Color): void {
+  if (!this._target) {
+    return;
+  }
   this._target.color = value;
 }
📝 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
protected override _applyValue(value: Color): void {
this._target.color = value;
}
protected override _applyValue(value: Color): void {
if (!this._target) {
return;
}
this._target.color = value;
}

Comment on lines 23 to 29
set normal(value: T) {
const preNormal = this._normal;
if (preNormal !== value) {
this._normal = value;
this._onStateValueDirty(InteractiveState.Normal);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use deep comparison in setters to detect changes in complex objects

In the setters for normal, pressed, hover, and disabled, the comparison if (preValue !== value) may not detect changes in complex types like Sprite, Color, or Vector3 since it compares object references rather than their contents. If the properties of these objects change without the reference changing, the update might not be detected.

Consider implementing a deep comparison or using an equals method to accurately detect changes in the values.

Also applies to: 35-41, 47-53, 59-65

Comment on lines 125 to 129
private _updateValue() {
const weight = this._duration ? 1 - this._countDown / this._duration : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Clamp weight between 0 and 1 in _updateValue to ensure valid interpolation

In the _updateValue() method, weight can become less than 0 or greater than 1 if _countDown becomes negative or exceeds _duration due to floating-point inaccuracies or unexpected usage. This can lead to invalid interpolation results.

Consider clamping weight between 0 and 1 to maintain valid interpolation.

Suggested fix:

private _updateValue() {
- const weight = this._duration ? 1 - this._countDown / this._duration : 1;
+ const weight = this._duration ? Math.min(1, Math.max(0, 1 - this._countDown / this._duration)) : 1;
  this._updateCurrentValue(this._initialValue, this._finalValue, weight);
  this._target?.enabled && this._applyValue(this._currentValue);
}
📝 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
private _updateValue() {
const weight = this._duration ? 1 - this._countDown / this._duration : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}
private _updateValue() {
const weight = this._duration ? Math.min(1, Math.max(0, 1 - this._countDown / this._duration)) : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}

Comment on lines 82 to 92
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
this._updateValue();
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent division by zero when adjusting _countDown in duration setter

In the duration setter, when adjusting _countDown, there is a potential for division by zero if preDuration is zero. This can result in NaN or Infinity values, causing unexpected behavior in the transition timing.

Consider adding a check to handle the scenario when preDuration is zero to prevent division by zero.

Here's a possible fix:

if (preDuration !== value) {
  this._duration = value;
  if (this._countDown > 0) {
+   if (preDuration > 0) {
      this._countDown = value * (1 - this._countDown / preDuration);
+   } else {
+     this._countDown = value;
+   }
    this._updateValue();
  }
}
📝 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
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
this._updateValue();
}
}
}
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
if (preDuration > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
} else {
this._countDown = value;
}
this._updateValue();
}
}
}

Comment on lines 146 to 149
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing break statements to prevent unintended fallthrough in switch cases

The switch cases for EntityModifyFlags.UICanvasEnableInScene and EntityModifyFlags.Parent lack break statements, causing unintended fallthrough to the next case. This could lead to unexpected behavior.

Apply this diff to add the missing break statements:

 145        switch (flag) {
 146          case EntityModifyFlags.UICanvasEnableInScene:
 147          case EntityModifyFlags.Parent:
 148            UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
 149            UIUtils.registerEntityListener(this);
+150            break;
 151          case EntityModifyFlags.UIGroupEnableInScene:
 152            UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
 153            break;
 154          default:
 155            break;
 156        }
📝 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
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
break;
🧰 Tools
🪛 Biome

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 211 to 215
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrap variable declarations in switch cases within blocks

Variables declared within switch cases can be accessed by other cases if not properly scoped. To prevent unintended access and improve code safety, wrap the case block in braces {}.

Apply this diff to scope the variable declaration:

        case CanvasRenderMode.ScreenSpaceOverlay:
+       {
          const { min, max } = renderer.bounds;
          if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
            continue;
          }
          break;
+       }
📝 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
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
{
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
}
🧰 Tools
🪛 Biome

[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Copy link

@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: 10

🧹 Outside diff range and nitpick comments (27)
packages/core/src/ui/interface/IUIGroupable.ts (2)

8-8: Add method documentation and explicit return type.

The method signature could be improved with the following:

  1. Add JSDoc documentation explaining when this method is called and its purpose
  2. Explicitly specify the return type for better type safety

Here's the suggested improvement:

+  /**
+   * Called when the group this element belongs to is modified.
+   * @param flag - Indicates the type of modification made to the group
+   */
-  _onGroupModify(flag: GroupModifyFlags): void;
+  _onGroupModify(flag: GroupModifyFlags): void;

4-9: Consider providing usage examples and integration guidelines.

As this interface is part of the UI infrastructure, consider:

  1. Adding examples in the documentation showing how to implement this interface
  2. Creating integration tests to verify group behavior
  3. Providing guidelines for when to use this interface vs. direct UI elements

This will help other developers understand when and how to use groupable UI elements effectively.

packages/core/src/ui/interface/IUIElement.ts (1)

4-11: Add JSDoc documentation and readonly modifiers.

The interface would benefit from:

  1. JSDoc documentation explaining its purpose and member usage
  2. Readonly modifiers for references that shouldn't be mutated directly
+/**
+ * Represents a UI element that can be rendered in a canvas hierarchy.
+ * Provides core functionality for UI element management and modification tracking.
+ */
 export interface IUIElement {
+  /** The entity associated with this UI element */
-  _entity: Entity;
+  readonly _entity: Entity;
+  /** Array of parent entities in the hierarchy */
-  _parents: Entity[];
+  readonly _parents: Entity[];
+  /** The root canvas containing this UI element */
-  _rootCanvas: UICanvas;
+  readonly _rootCanvas: UICanvas;
+  /** The index position of this element within its canvas */
   _indexInCanvas: number;

+  /**
+   * Handles modifications to the associated entity
+   * @param flag The type of entity modification
+   */
   _onEntityModify(flag: EntityModifyFlags): void;
 }
packages/core/src/ui/index.ts (2)

5-11: LGTM! UI components hierarchy is well-structured.

The exports follow a logical progression from infrastructure (Canvas, Renderer, Transform) to widgets (Button, Image, Text).

Consider documenting the recommended usage patterns and component hierarchy in a README.md file to help developers understand how these components should be composed together.


1-15: Consider improving file organization with grouping comments.

While the exports are logically grouped, adding separator comments would improve readability.

Consider applying this organization:

+// Core
 export { UIGroup } from "./UIGroup";
+
+// Enums
 export { CanvasRenderMode } from "./enums/CanvasRenderMode";
 export { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrategy";

+// Infrastructure
 export { UICanvas } from "./UICanvas";
 export { UIRenderer } from "./UIRenderer";
 export { UITransform } from "./UITransform";
+
+// Widgets
 export { Button } from "./Button";
 export { Image } from "./Image";
 export { Text } from "./Text";

+// Transitions
 export { ColorTransition } from "./interactive/transition/ColorTransition";
 export { ScaleTransition } from "./interactive/transition/ScaleTransition";
 export { SpriteTransition } from "./interactive/transition/SpriteTransition";
 export { Transition } from "./interactive/transition/Transition";
packages/core/src/ui/UIUtils.ts (3)

21-37: Add null check for element parameter

The method assumes element is not null but doesn't validate it. Consider adding a guard clause to handle invalid input.

 static registerEntityListener(element: IUIElement): void {
+  if (!element) {
+    return;
+  }
   const parents = element._parents;

84-126: Improve type safety with type guards

Replace type casting with type guard functions to make the code more type-safe and maintainable.

+private static isUICanvas(component: any): component is UICanvas {
+  return component._componentType === ComponentType.UICanvas;
+}
+
+private static isUIGroup(component: any): component is UIGroup {
+  return component._componentType === ComponentType.UIGroup;
+}

 static getRootCanvasInParent(entity: Entity): UICanvas {
   while (entity) {
     const components = entity._components;
     for (let i = 0, n = components.length; i < n; i++) {
       const component = components[i];
-      if (
-        component.enabled &&
-        component._componentType === ComponentType.UICanvas &&
-        (<UICanvas>component)._isRootCanvas
-      ) {
-        return <UICanvas>component;
+      if (component.enabled && this.isUICanvas(component) && component._isRootCanvas) {
+        return component;
       }
     }

136-136: Consider using Matrix methods instead of direct element mutation

Direct mutation of matrix elements makes the code harder to maintain and understand. Consider using Matrix class methods.

-projectE[0] = 2 / canvas.width;
-projectE[5] = 2 / canvas.height;
-projectE[10] = 0;
+virtualCamera.projectionMatrix.setOrtho(
+  0, canvas.width,
+  0, canvas.height,
+  -1, 1
+);
packages/core/src/ui/UIGroup.ts (3)

9-41: Add class-level documentation

Consider adding comprehensive JSDoc documentation for the UIGroup class to describe its purpose, responsibilities, and usage patterns.

Add documentation like this:

/**
 * Manages a group of UI elements with hierarchical properties like alpha and interactivity.
 * Handles propagation of modifications to child elements and groups.
 * 
 * @example
 * ```typescript
 * const uiGroup = new UIGroup(entity);
 * uiGroup.alpha = 0.5; // Sets opacity for all child elements
 * ```
 */
export class UIGroup extends Component {

123-144: Consider decomposing _onDisableInScene for better maintainability

The method handles multiple concerns: entity listener cleanup, element cleanup, and group cleanup. Consider breaking it down into smaller, focused methods.

private _cleanupEntityListeners(): void {
  const entityListeners = this._entityListeners;
  entityListeners.forEach(entity => 
    entity._unRegisterModifyListener(this._onEntityModify)
  );
  entityListeners.length = 0;
}

private _cleanupElements(): void {
  const disorderedElements = this._disorderedElements;
  disorderedElements.forEach(element => 
    UIUtils.registerElementToGroup(element, this._parentGroup)
  );
  disorderedElements.length = 0;
  disorderedElements.garbageCollection();
}

private _cleanupGroups(): void {
  const disorderedGroups = this._disorderedGroups;
  disorderedGroups.forEach(element => 
    element._registryToParentGroup(this._parentGroup)
  );
  disorderedGroups.length = 0;
  disorderedGroups.garbageCollection();
}

override _onDisableInScene(): void {
  this._cleanupEntityListeners();
  this._cleanupElements();
  this._cleanupGroups();
  this._parentGroup = null;
  this._entity._dispatchModify(EntityModifyFlags.UIGroupDisableInScene);
}

186-191: Add documentation for GroupModifyFlags

Consider adding JSDoc comments to document the purpose and usage of each flag.

/**
 * Flags indicating which aspects of a UI group need to be modified.
 */
export enum GroupModifyFlags {
  /** No modifications needed */
  None = 0x0,
  /** Alpha (opacity) value needs to be updated */
  Alpha = 0x1,
  /** Interactive state needs to be updated */
  Interactive = 0x2,
  /** All properties need to be updated */
  All = 0x3
}
packages/core/src/ui/UIRenderer.ts (1)

22-28: Consider implementing object pooling for temporary objects.

Using static temporary objects could lead to memory allocation overhead during frequent UI updates. Consider implementing an object pool to manage these temporary objects more efficiently.

class UIRendererObjectPool {
  private static _vec3Pool: Vector3[] = [];
  private static _matrixPool: Matrix[] = [];
  
  static acquireVector3(): Vector3 {
    return this._vec3Pool.pop() || new Vector3();
  }
  
  static releaseVector3(vec: Vector3): void {
    vec.set(0, 0, 0);
    this._vec3Pool.push(vec);
  }
}
packages/core/src/ui/UICanvas.ts (2)

20-24: Consider type inference for numeric and boolean properties.

TypeScript can automatically infer types from initial values. You can simplify the property declarations by removing explicit type annotations where the type is obvious from the initialization.

Apply this diff to simplify the type declarations:

-  _isRootCanvas: boolean = false;
+  _isRootCanvas = false;
-  _canvasIndex: number = -1;
+  _canvasIndex = -1;
-  _sortDistance: number = 0;
+  _sortDistance = 0;
-  private _sortOrder: number = 0;
+  private _sortOrder = 0;
-  private _distance: number = 10;
+  private _distance = 10;
-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution = new Vector2(800, 600);

Also applies to: 27-27, 45-45, 64-64, 66-66, 70-70


284-284: Rename methods to use correct terminology.

The methods use "adapter" instead of "adapt" in their names, which is grammatically incorrect.

Apply this diff to fix the method names:

-  private _adapterPoseInScreenSpace(): void {
+  private _adaptPoseInScreenSpace(): void {
-  private _adapterSizeInScreenSpace(): void {
+  private _adaptSizeInScreenSpace(): void {

Update all references to these methods accordingly.

Also applies to: 303-303

packages/core/src/2d/text/TextRenderer.ts (2)

Line range hint 87-611: Consider batching dirty flag updates.

The current implementation sets dirty flags individually for each property change. Consider implementing a batch update mechanism for scenarios where multiple properties are changed simultaneously.

+ private _batchUpdate(callback: () => void): void {
+   const prevDirtyFlag = this._dirtyUpdateFlag;
+   callback();
+   if (this._dirtyUpdateFlag !== prevDirtyFlag) {
+     this._onDirtyFlagChanged(this._dirtyUpdateFlag);
+   }
+ }

  set text(value: string) {
    value = value || "";
    if (this._text !== value) {
+     this._batchUpdate(() => {
        this._text = value;
        this._setDirtyFlagTrue(RendererUpdateFlags.AllPositionAndBounds);
+     });
    }
  }

689-699: Add JSDoc comments for individual flags.

The TextRendererUpdateFlags enum would benefit from detailed documentation for each flag, explaining when they are set and their implications.

 enum TextRendererUpdateFlags {
+  /** Flag indicating that the SubFont needs to be updated */
   SubFont = 0x10,
+  /** Flag indicating that the Color needs to be updated */
   Color = 0x20,

+  /** 
+   * Combined flag for font-related updates that affect position and bounds
+   * @remarks This includes SubFont and all position/bounds flags
+   */
   FontAllPositionAndBounds = 0x1f,
+  /** 
+   * Combined flag for all possible updates
+   * @remarks This includes all font, position, bounds, and color flags
+   */
   All = 0x3f
 }
packages/core/src/Entity.ts (1)

404-404: Consider adding a type check for UITransform inheritance.

The current implementation assumes UITransform is a direct class, but it might be inherited.

-    this._transform instanceof UITransform && child.addComponent(UITransform);
+    (this._transform instanceof UITransform || this._transform.constructor === UITransform) && child.addComponent(UITransform);
packages/core/src/ui/interactive/transition/Transition.ts (3)

1-155: Consider adding a stop method to halt the transition.

It might be useful to have a way to immediately stop the transition and set the value to the final state. This can be helpful in scenarios where you want to cancel the transition midway and snap to the end state.

Here's a possible implementation:

stop() {
  this._countDown = 0;
  this._initialValue = this._finalValue = this._getValueByState(this._finalState);
  this._updateValue();
}

1-155: Consider adding an onTransitionEnd callback.

It could be beneficial to provide a way for users to register a callback that gets invoked when the transition reaches its end state. This can be useful for triggering additional actions or state changes based on the completion of the transition.

Here's a possible implementation:

private _onTransitionEnd: () => void;

set onTransitionEnd(callback: () => void) {
  this._onTransitionEnd = callback;
}

private _updateValue() {
  // ...
  if (this._countDown <= 0 && this._onTransitionEnd) {
    this._onTransitionEnd();
  }
}

1-155: Consider adding support for easing functions.

Currently, the transition uses linear interpolation between the initial and final values. Introducing support for easing functions can provide more control over the transition's behavior and allow for more visually appealing animations.

Here's a possible implementation:

type EasingFunction = (t: number) => number;

private _easingFunction: EasingFunction = (t) => t;

set easingFunction(fn: EasingFunction) {
  this._easingFunction = fn;
}

private _updateValue() {
  const t = this._duration ? 1 - this._countDown / this._duration : 1;
  const weight = this._easingFunction(t);
  // ...
}

You can then provide a set of built-in easing functions or allow users to define their own.

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1)

229-231: Consider defining the magic number as a constant for better maintainability.

The magic number 1024 is used to prevent infinite loops by limiting the iteration count. Defining this number as a constant with a descriptive name would enhance code readability and make future adjustments easier.

Apply this diff to define the magic number as a constant:

-      for (; i < 1024 && !!entity && entity !== rootEntity; i++) {
+      const MAX_PATH_DEPTH = 1024;
+      for (; i < MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
         entity = path[i] = entity.parent;
       }
packages/core/src/ui/Image.ts (1)

208-209: Address the TODO comment regarding material handling

There's a TODO comment indicating that an issue needs to be raised rather than hidden when the material is destroyed. This should be resolved to ensure proper error handling and developer awareness.

Do you want assistance in implementing the error handling mechanism or opening a GitHub issue to track this task?

packages/core/src/ui/Text.ts (5)

225-225: Avoid assignments within expressions.

The static analysis tool has flagged several instances where assignments are used within expressions. While this is syntactically valid, it can lead to confusion and potential bugs.

Consider separating the assignments from the expressions to improve readability and maintainability. For example:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }

This applies to the assignments on lines 225, 466, 467, 477, 479, 565, and 567.

Also applies to: 466-466, 467-467, 477-477, 479-479, 565-565, 567-567

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


293-331: Consider extracting the rendering logic into a separate method.

The _render method is quite lengthy and contains multiple responsibilities, such as updating positions, colors, and managing render elements. To improve readability and maintainability, consider extracting some of the rendering logic into separate methods.

For example, you could extract the code that creates and sets up the SubRenderElement instances into a separate method, like _createSubRenderElements. This would make the _render method more focused and easier to understand.


402-541: Optimize the _updateLocalData method.

The _updateLocalData method is a critical part of the text rendering process, and its performance can impact the overall rendering performance. Consider the following optimizations:

  1. Preallocate and reuse temporary arrays and objects to reduce memory allocations.
  2. Use object pooling for frequently created objects like CharRenderInfo and TextChunk.
  3. Minimize the number of iterations and loops where possible.
  4. Use more efficient data structures, such as typed arrays, for performance-critical operations.

These optimizations can help improve the performance of the _updateLocalData method, especially when dealing with large amounts of text.

🧰 Tools
🪛 Biome

[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


561-590: Use a profiler to analyze the performance of the _buildChunk method.

The _buildChunk method is responsible for building the vertex and index data for each text chunk. It's important to ensure that this method is performant, as it can be called frequently during text rendering.

Consider using a profiler to analyze the performance of this method and identify any potential bottlenecks. Pay attention to the following:

  1. Memory allocations and deallocations.
  2. Iteration and loop performance.
  3. Data structure access and manipulation.

Based on the profiling results, optimize the method as needed to improve its performance. This may involve techniques such as object pooling, preallocating arrays, or using more efficient data structures.

🧰 Tools
🪛 Biome

[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


611-615: Consider using a more memory-efficient data structure for TextChunk.

The TextChunk class currently uses an array to store CharRenderInfo instances. Depending on the size of the text and the number of chunks, this can lead to significant memory usage.

Consider using a more memory-efficient data structure, such as a linked list or a custom allocator, to store the CharRenderInfo instances. This can help reduce memory overhead, especially when dealing with large amounts of text.

Additionally, explore the possibility of reusing TextChunk instances through object pooling to further optimize memory usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2487d and ae63b66.

📒 Files selected for processing (17)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/2d/text/TextUtils.ts (6 hunks)
  • packages/core/src/Entity.ts (10 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Image.ts (1 hunks)
  • packages/core/src/ui/Text.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interface/IUIElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGroupable.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/2d/text/TextUtils.ts
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts
  • packages/core/src/ui/interface/IUIGraphics.ts
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Text.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 357-357: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (44)
packages/core/src/ui/interface/IUIGroupable.ts (1)

1-2: LGTM! Imports are well-organized.

The necessary dependencies are correctly imported using appropriate relative paths.

packages/core/src/ui/interface/IUIElement.ts (2)

1-3: LGTM! Imports are clean and well-organized.

All necessary types are properly imported, and there are no redundant imports.


10-10: Consider adding canvas modification handler.

The interface has _onEntityModify but lacks a corresponding method for handling canvas modifications. This asymmetry might indicate a missing feature.

Consider adding an _onCanvasModify method if canvas-level changes need to be tracked:

_onCanvasModify(flag: CanvasModifyFlags): void;
packages/core/src/ui/index.ts (2)

1-4: LGTM! Core exports are well-organized.

The core UI group and enum exports provide essential building blocks for the GUI infrastructure.


12-15: LGTM! Transition system exports are comprehensive.

The transition system appears well-thought-out with support for different transition types (Color, Scale, Sprite).

packages/core/src/ui/UIUtils.ts (1)

137-137: Add error handling for RHI operations

The RHI operation could fail in certain scenarios. Consider adding error handling.

packages/core/src/ui/UIGroup.ts (1)

164-177: Verify entity listener cleanup

The entity listener registration in the while loop could potentially lead to memory leaks if the entity hierarchy changes frequently. Consider adding a verification step to ensure all listeners are properly cleaned up.

✅ Verification successful

Entity listener cleanup is properly implemented

The codebase shows a robust implementation of entity listener cleanup:

  • In UIGroup, listeners are properly unregistered in a loop before clearing the array: entityListeners[i]._unRegisterModifyListener(this._onEntityModify)
  • When replacing listeners in the hierarchy, old listeners are unregistered before new ones are registered: preListener?._unRegisterModifyListener(this._onEntityModify)
  • The pattern is consistently used across related components (UIUtils, UICanvas) showing a systematic approach to listener management

The original concern about potential memory leaks is unfounded as the code maintains proper cleanup of entity listeners.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential memory leaks in entity listener management
echo "Searching for entity listener registration patterns..."
ast-grep --pattern 'entity._registerModifyListener($_)'

echo "Searching for corresponding cleanup patterns..."
ast-grep --pattern 'entity._unRegisterModifyListener($_)'

# Look for potential memory leak patterns in test files
echo "Searching for test cases covering listener cleanup..."
rg -l "test.*UIGroup.*cleanup" -t ts

Length of output: 695


Script:

#!/bin/bash
# Let's search for unregister patterns with a broader context
echo "Searching for unregister patterns..."
rg "_unRegisterModifyListener" -A 2 -B 2

# Check for cleanup in UIGroup class destructor or dispose methods
echo "Searching for cleanup methods in UIGroup..."
ast-grep --pattern 'class UIGroup {
  $$$
  dispose() {
    $$$
  }
  $$$
}'

# Check if there are any tests for UIGroup
echo "Searching for UIGroup test files..."
fd "UIGroup.*test" -e ts

Length of output: 2984

packages/core/src/ui/UIRenderer.ts (1)

19-20: ⚠️ Potential issue

Implement ISpriteRenderer interface.

The class should implement the ISpriteRenderer interface to maintain consistency with the sprite rendering system.

-export abstract class UIRenderer extends Renderer implements IUIGraphics {
+export abstract class UIRenderer extends Renderer implements IUIGraphics, ISpriteRenderer {

Likely invalid or redundant comment.

packages/core/src/ui/UICanvas.ts (1)

470-492: 🛠️ Refactor suggestion

Document intentional switch case fallthrough.

The switch cases in _setRealRenderMode are falling through intentionally to execute multiple actions, but this pattern should be explicitly documented to prevent confusion.

Apply this diff to add fallthrough comments:

      switch (curRealMode) {
        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
+         // fallthrough intentional
        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
+         // fallthrough intentional
        case CanvasRenderMode.WorldSpace:
          componentsManager.addUICanvas(curRealMode, this);
          break;

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/2d/text/TextRenderer.ts (2)

35-35: Fix array initialization.

The array initialization syntax is incorrect. Using Array<TextChunk>() without the new keyword won't create a new array instance.


475-478: 🛠️ Refactor suggestion

Add error handling for text measurement edge cases.

The text measurement logic should handle edge cases such as extremely large text or invalid font states.

  private _updateLocalData(): void {
    const { min, max } = this._localBounds;
    const charRenderInfos = TextRenderer._charRenderInfos;
    const charFont = this._getSubFont();
+   try {
      const textMetrics = this.enableWrapping
        ? TextUtils.measureTextWithWrap(this, this.width, this.height)
        : TextUtils.measureTextWithoutWrap(this, this.height);
+   } catch (error) {
+     console.error('Text measurement failed:', error);
+     min.set(0, 0, 0);
+     max.set(0, 0, 0);
+     return;
+   }
packages/core/src/Entity.ts (8)

99-104: LGTM! Property changes enhance state management.

The addition of _updateFlagManager and conversion of transform to a private property with getter improves encapsulation.


107-110: LGTM! Transform getter maintains backward compatibility.

The getter provides controlled access to the private _transform property while maintaining the public API.


527-530: LGTM! Proper cleanup of update flag manager.

The cleanup ensures no memory leaks by removing all listeners and nullifying the reference.


603-606: LGTM! Parent change handling is comprehensive.

The method properly updates transform and dispatches modification flags.


695-695: LGTM! Parent change notification is properly placed.

The _setParentChange call is correctly placed after all parent-related updates.


771-771: LGTM! Sibling index modification notification.

The dispatch of the sibling index modification flag is properly placed after the index update.


783-783: LGTM! World matrix update uses private transform.

The update correctly uses the private _transform property.


790-797: LGTM! Comprehensive entity modification flags.

The enum provides a clear set of flags for tracking different types of entity modifications.

packages/core/src/ui/interactive/transition/Transition.ts (4)

26-32: ****
Use deep comparison in setters to detect changes in complex objects.

Also applies to: 35-41, 47-53, 59-65


85-95: ****
Prevent division by zero when adjusting _countDown in duration setter.


128-132: ****
Clamp weight between 0 and 1 in _updateValue to ensure valid interpolation.


1-155: LGTM!

The Transition class provides a solid foundation for managing UI transitions. The use of generics allows flexibility in handling different value types and UI renderers. The class encapsulates the transition logic effectively, with clear separation of concerns between the abstract methods and the internal implementation.

The class follows good practices such as:

  • Using getters and setters for properties to encapsulate internal state changes.
  • Providing a clear API for setting the transition state and updating values.
  • Handling edge cases like negative duration values and adjusting the countdown accordingly.
  • Utilizing abstract methods to allow customization of transition behavior by subclasses.

The code is well-structured, readable, and maintainable. The comments provide helpful insights into the purpose and usage of the class and its methods.

Overall, the Transition class is a valuable addition to the UI framework and lays a solid groundwork for implementing various types of transitions in a consistent and extensible manner.

packages/core/src/ui/interactive/UIInteractive.ts (7)

1-11: LGTM!

The imports are well-organized and follow a logical order. The imported entities are relevant to the functionality of the UIInteractive class.


12-82: LGTM!

The UIInteractive class is well-structured and implements the necessary properties and methods for managing interactive UI elements. The use of TypeScript features such as access modifiers, getters/setters, and type generics enhances the code's readability and maintainability.

The class provides a clean API for managing transitions and updating the interactive state based on pointer events. The onUpdate method ensures that transitions are updated efficiently.


84-102: LGTM!

The pointer event handling methods (onPointerBeginDrag, onPointerEndDrag, onPointerEnter, onPointerExit) are implemented correctly. They update the internal state variables and trigger state updates appropriately.


107-123: LGTM!

The _onEnableInScene and _onDisableInScene methods handle the registration and unregistration of the element with the UI utilities effectively. They ensure proper integration with the scene and update the interactive state accordingly.


147-155: LGTM!

The _onGroupModify method correctly handles the GroupModifyFlags.Interactive flag. It updates the runtime interactive state and triggers a state update when necessary.


157-174: LGTM!

The private methods _updateState and _getInteractiveState are implemented correctly. They manage the current state of interactivity based on the runtime interactive flag and pointer interactions.


131-134: ⚠️ Potential issue


The previous review comment regarding the missing break statement is still valid. Please address this issue to prevent unintended fallthrough.

Apply this diff to add the missing break statement:

 131       case EntityModifyFlags.UICanvasEnableInScene:
 132       case EntityModifyFlags.Parent:
 133         UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
 134         UIUtils.registerEntityListener(this);
+135         break;
 136       case EntityModifyFlags.UIGroupEnableInScene:
🧰 Tools
🪛 Biome

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (6)

85-85: ****

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


88-89: ****


102-102: ****

🧰 Tools
🪛 Biome

[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


167-190: ****


222-222: ****

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1-236: The PointerUIEventEmitter class provides a structured approach to managing pointer interactions with UI components.

The class extends the PointerEventEmitter and overrides key methods to handle pointer events specifically for UI components. It maintains references to the currently entered, pressed, and dragged UI elements, enabling accurate event processing based on pointer interactions.

The implementation follows a clear and logical flow, with well-defined responsibilities for each method. The code is modular, making it easier to understand and maintain. The use of private helper methods for common tasks like updating the raycast, bubbling events, and composing paths further enhances the code organization.

Overall, the PointerUIEventEmitter class is a solid addition to the pointer event handling system, providing a dedicated mechanism for managing UI component interactions.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Image.ts (7)

42-43: Ensure alphaHitTestMinimumThreshold is clamped between 0 and 1

The setter for alphaHitTestMinimumThreshold correctly clamps the value. Just make sure that any value assignments are within the expected range.


52-71: Update assembler initialization in drawMode setter

The logic for updating the _assembler based on drawMode is appropriate. This ensures the correct assembler is used for rendering.


81-87: Correct handling of tileMode setter

The tileMode setter correctly updates the dirty flags when the mode changes. This maintains the integrity of the rendering process.


113-130: Proper management of sprite resource in sprite setter

The setter effectively manages the reference counting and listener management for sprite resources. This helps prevent memory leaks and ensures resources are properly cleaned up.


285-298: Resource cleanup in _onDestroy

The _onDestroy method properly handles resource cleanup by decreasing reference counts and removing listeners. This prevents potential memory leaks.


300-341: Comprehensive handling in _onSpriteChange method

The _onSpriteChange method effectively updates the dirty flags based on the type of sprite modification. This ensures the renderer reacts appropriately to sprite changes.


343-356: Verify the UV calculation in _getUVByLocalPosition

While the method delegates UV calculation to the assembler, ensure that for SpriteDrawMode.Tiled, the correct assembler is used. Currently, it calls SlicedSpriteAssembler.getUVByLocalPosition for both Sliced and Tiled modes. Is this intentional?

Please confirm that using SlicedSpriteAssembler for Tiled mode is correct.

If not, consider the following change:

case SpriteDrawMode.Tiled:
-  return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
+  return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
packages/core/src/ui/Text.ts (1)

1-627: Comprehensive and well-structured implementation of the Text class.

The Text class provides a robust and feature-rich implementation for rendering text in a 2D UI context. It effectively manages text properties, rendering states, and efficiently handles text layout and rendering.

Key highlights:

  • Utilizes dirty flags to optimize rendering and minimize unnecessary updates.
  • Implements proper resource management, including reference counting for fonts and memory pooling for character render information.
  • Supports various text formatting options such as alignment, wrapping, overflow modes, and font styles.
  • Efficiently manages text chunks and batches rendering for optimal performance.
  • Integrates with the ISpriteRenderer interface and other UI components.

The code follows good practices such as:

  • Appropriate use of access modifiers for encapsulation.
  • Meaningful naming conventions for variables, methods, and enums.
  • Proper error handling and input validation.
  • Efficient memory management through object pooling.
  • Modular and reusable design with clear separation of concerns.

Overall, the Text class is a solid foundation for text rendering in the UI system.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 4 to 6
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add JSDoc documentation and improve type safety.

The interface and its properties should be documented for better maintainability. Also, consider the following improvements:

  1. Add JSDoc comments explaining the purpose of the interface and its properties
  2. Consider using protected or private modifiers since properties are prefixed with underscore
  3. Add type constraints for _indexInGroup to ensure non-negative values

Here's the suggested improvement:

+/**
+ * Interface for UI elements that can be grouped together.
+ * Provides necessary properties and methods for group management.
+ */
 export interface IUIGroupable extends IUIElement {
+  /** The group this element belongs to */
   _group: UIGroup;
+  /** Zero-based index of this element within its group */
-  _indexInGroup: number;
+  _indexInGroup: NonNegativeInteger;
 }

+ type NonNegativeInteger = number & { __brand: 'NonNegativeInteger' };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 200 to 222
_raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
const plane = UIRenderer._tempPlane;
const transform = this._transform;
const normal = plane.normal.copyFrom(transform.worldForward);
plane.distance = -Vector3.dot(normal, transform.worldPosition);
const curDistance = ray.intersectPlane(plane);
if (curDistance >= 0 && curDistance < distance) {
const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
const worldMatrixInv = UIRenderer._tempMat;
Matrix.invert(transform.worldMatrix, worldMatrixInv);
const localPosition = UIRenderer._tempVec31;
Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
if (this._hitTest(localPosition)) {
out.distance = curDistance;
out.entity = this._entity;
out.component = this;
out.normal.copyFrom(normal);
out.point.copyFrom(hitPointWorld);
return true;
}
}
return false;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize raycast performance and readability.

The raycast implementation can be optimized for better performance and readability:

  1. Cache the plane intersection result
  2. Use early returns for better flow control
  3. Destructure transform properties
 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
-  const transform = this._transform;
+  const { worldForward, worldMatrix, worldPosition } = this._transform;
-  const normal = plane.normal.copyFrom(transform.worldForward);
+  const normal = plane.normal.copyFrom(worldForward);
-  plane.distance = -Vector3.dot(normal, transform.worldPosition);
+  plane.distance = -Vector3.dot(normal, worldPosition);
   
   const intersectionDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
-    const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
-    const worldMatrixInv = UIRenderer._tempMat;
-    Matrix.invert(transform.worldMatrix, worldMatrixInv);
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

+  const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
+  const worldMatrixInv = UIRenderer._tempMat;
+  Matrix.invert(worldMatrix, worldMatrixInv);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 156 to 171
switch (flag) {
case EntityModifyFlags.SiblingIndex:
this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
break;
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix switch statement safety issues.

The switch statement has several issues that need to be addressed:

  1. Case fallthrough between Parent and UIGroupEnableInScene
  2. Variable declaration scope
  3. Assignments within expressions
 switch (flag) {
   case EntityModifyFlags.SiblingIndex: {
-    this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
+    if (this._rootCanvas) {
+      this._rootCanvas._hierarchyDirty = true;
+    }
     break;
   }
   case EntityModifyFlags.UICanvasEnableInScene:
-  case EntityModifyFlags.Parent:
+  case EntityModifyFlags.Parent: {
     const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
-    rootCanvas && (rootCanvas._hierarchyDirty = true);
+    if (rootCanvas) {
+      rootCanvas._hierarchyDirty = true;
+    }
     UIUtils.registerElementToCanvas(this, rootCanvas);
     UIUtils.registerEntityListener(this);
+    break;
+  }
   case EntityModifyFlags.UIGroupEnableInScene:
     UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
     break;
📝 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
switch (flag) {
case EntityModifyFlags.SiblingIndex:
this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
break;
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
switch (flag) {
case EntityModifyFlags.SiblingIndex: {
if (this._rootCanvas) {
this._rootCanvas._hierarchyDirty = true;
}
break;
}
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent: {
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
if (rootCanvas) {
rootCanvas._hierarchyDirty = true;
}
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
break;
}
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
🧰 Tools
🪛 Biome

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 101 to 102
//@todo: Always update world positions to buffer, should opt
// super._updateTransformShaderData(context, onlyMVP, true);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Commented super call should be uncommented to match other renderer implementations

The codebase analysis shows that:

  • The base Renderer class implements _updateTransformShaderData
  • Other renderer classes (SpriteRenderer, TextRenderer, ParticleRenderer, etc.) consistently call super._updateTransformShaderData with the same TODO comment
  • UIRenderer is the only implementation that has commented out the super call while still having the same TODO

The commented super call should be uncommented to maintain consistency with other renderer implementations and ensure proper transform updates. The optimization mentioned in the TODO can be addressed separately across all renderers.

🔗 Analysis chain

Address TODO comment and clarify super call.

There's a TODO comment about optimizing world position updates and a commented-out super call that needs to be addressed:

  1. Implement the optimization for world position updates
  2. Clarify whether the super call is necessary
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the super class implementation is different
ast-grep --pattern $'class Renderer {
  $$$
  _updateTransformShaderData(context: $_) {
    $$$
  }
  $$$
}'

Length of output: 109


Script:

#!/bin/bash
# Let's try a broader search for the base class and its method
ast-grep --pattern '_updateTransformShaderData'

# Also search for any related files
fd -e ts -e js "Renderer"

# And check the specific file content
rg -A 5 -B 5 "_updateTransformShaderData" 

Length of output: 9204

Comment on lines 167 to 168
// @ts-ignore
this._referenceResolution._onValueChanged = this._onReferenceResolutionChanged;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove @ts-ignore by extending Vector2 type.

Using @ts-ignore suppresses type checking and can hide potential issues. Create a proper type definition instead.

Create an interface extending Vector2:

interface Vector2WithCallback extends Vector2 {
  _onValueChanged?: () => void;
}

Then update the property type:

-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution: Vector2WithCallback = new Vector2(800, 600);

set referenceResolution(val: Vector2) {
const { _referenceResolution: referenceResolution } = this;
if (referenceResolution === val) return;
(referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve readability by avoiding assignments in expressions.

Using assignments within expressions makes the code harder to read and understand.

Apply this diff to improve readability:

-    (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
+    if (referenceResolution.x !== val.x || referenceResolution.y !== val.y) {
+      referenceResolution.copyFrom(val);
+    }

-    child.isActive && (depth = this._walk(child, elements, depth));
+    if (child.isActive) {
+      depth = this._walk(child, elements, depth);
+    }

Also applies to: 357-357

this.setMaterial(engine._basicResources.textDefaultMaterial);
//@ts-ignore
this._color._onValueChanged = this._onColorChanged.bind(this);
this._color._onValueChanged = this._onColorChange.bind(this);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid type assertion in color change callback binding.

The type assertion (@ts-ignore) suggests a potential type safety issue. Consider properly typing the color object or implementing a proper event system.

-    //@ts-ignore
-    this._color._onValueChanged = this._onColorChange.bind(this);
+    // Option 1: Define proper event interface
+    interface ColorWithCallback extends Color {
+      _onValueChanged?: () => void;
+    }
+    (this._color as ColorWithCallback)._onValueChanged = this._onColorChange.bind(this);
+
+    // Option 2: Use event emitter pattern
+    this._color.on('change', this._onColorChange.bind(this));

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 611 to 626
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}

/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}

/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix assignment in expression for better readability.

The modify listener registration contains an assignment within an expression which could be confusing.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 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
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}
/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
if (!this._updateFlagManager) {
this._updateFlagManager = new UpdateFlagManager();
}
this._updateFlagManager.addListener(onChange);
}
/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}
/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
🧰 Tools
🪛 Biome

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 22 to 86
override _processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
const { _tempRay: ray } = PointerEventEmitter;
const hitResult = this._hitResult;
const { position } = pointer;
const { x, y } = position;
for (let i = scenes.length - 1; i >= 0; i--) {
const scene = scenes[i];
if (!scene.isActive || scene.destroyed) continue;
const { _componentsManager: componentsManager } = scene;

/** Overlay Canvas */
let canvasElements = componentsManager._overlayCanvases;
ray.origin.set(position.x, position.y, 1);
ray.direction.set(0, 0, -1);
for (let j = canvasElements.length - 1; j >= 0; j--) {
if (canvasElements.get(j).raycast(ray, hitResult)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}

const cameras = componentsManager._activeCameras;
for (let j = cameras.length - 1; j >= 0; j--) {
const camera = cameras.get(j);
if (camera.renderTarget) continue;
const { pixelViewport } = camera;
if (
x < pixelViewport.x ||
y < pixelViewport.y ||
x > pixelViewport.x + pixelViewport.width ||
y > pixelViewport.y + pixelViewport.height
) {
continue;
}
camera.screenPointToRay(pointer.position, ray);

/** Other canvases */
const cameraPosition = camera.entity.transform.position;
/** Sort by rendering order */
canvasElements = componentsManager._canvases;
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._updateSortDistance(cameraPosition);
}
canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._canvasIndex = k;
}
const farClipPlane = camera.farClipPlane;
/** Post-rendering first detection */
for (let k = 0, n = canvasElements.length; k < n; k++) {
const canvas = canvasElements.get(k);
if (canvas.renderCamera !== camera) continue;
if (canvas.raycast(ray, hitResult, farClipPlane)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}
if (camera.clearFlags & CameraClearFlags.Color) {
this._updateRaycast(null);
return;
}
}
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring the _processRaycast method to improve readability and maintainability.

The _processRaycast method is quite complex with multiple nested loops and conditions. Breaking it down into smaller, focused methods would enhance the code structure and make it easier to understand and maintain.

Suggestions:

  1. Extract the logic for processing overlay canvases into a separate method.
  2. Move the code for handling camera-specific processing to a dedicated method.
  3. Consider creating helper methods for common tasks like sorting canvases and updating the raycast.

By decomposing the method into smaller, single-responsibility functions, the overall readability and maintainability of the code will improve.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

Comment on lines 145 to 178
protected override _hitTest(localPosition: Vector3): boolean {
let { x, y } = localPosition;
const uiTransform = <UITransform>this._transform;
const { x: width, y: height } = uiTransform.size;
const { x: pivotX, y: pivotY } = uiTransform.pivot;
const { x: paddingLeft, y: paddingBottom, z: paddingRight, w: paddingTop } = this.raycastPadding;
if (
x < -width * pivotX + paddingLeft ||
x > width * (1 - pivotX) - paddingRight ||
y < -height * pivotY + paddingTop ||
y > height * (1 - pivotY) - paddingBottom
) {
return false;
}
const alphaHitTestMinimumThreshold = this._alphaHitTestMinimumThreshold;
if (alphaHitTestMinimumThreshold <= 0) {
return true;
}
const texture = this.sprite?.texture;
if (!texture) {
return false;
}
const uv = Image._tempVec2;
if (!this._getUVByLocalPosition(localPosition, uv)) {
return false;
}
const pixel = Image._tempUnit8Array;
texture.getPixelBuffer(Math.floor(uv.x * texture.width), Math.floor(uv.y * texture.height), 1, 1, 0, pixel);
if (pixel[3] >= alphaHitTestMinimumThreshold * 255) {
return true;
} else {
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential performance impact in _hitTest method

Using texture.getPixelBuffer inside the _hitTest method may introduce performance overhead, especially if the method is called frequently. Consider caching the pixel data or implementing an alternative approach to minimize performance impact.

To optimize, you might consider:

+// Cache the pixel data if possible to reduce repeated texture reads

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@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: 19

🧹 Outside diff range and nitpick comments (24)
packages/core/src/ui/interface/IUIGroupable.ts (1)

8-8: Consider implementing a formal event system instead of internal callbacks.

The _onGroupModify method appears to be an internal callback for group modifications. This approach might be improved by:

  1. Using a formal event system
  2. Providing a clearer public API
  3. Better encapsulating implementation details

Consider this alternative approach:

-  _onGroupModify(flag: GroupModifyFlags): void;
+  onGroupChanged(event: GroupChangeEvent): void;

Where GroupChangeEvent could be a well-defined event object:

interface GroupChangeEvent {
  type: GroupChangeType;
  source: UIGroup;
  // other relevant event data
}

This would:

  • Provide a clearer contract for group-related changes
  • Follow standard event handling patterns
  • Be more maintainable and extensible
packages/core/src/ui/interface/IUIElement.ts (2)

4-11: Add JSDoc documentation to improve code maintainability.

The interface and its members lack documentation. Consider adding JSDoc comments to describe:

  • The purpose and responsibility of the IUIElement interface
  • The role of each property and method
  • Parameter and return type descriptions for _onEntityModify

Example documentation:

/**
 * Represents a UI element within the rendering engine.
 * Defines the contract for managing UI element lifecycle and hierarchy.
 */
export interface IUIElement {
  /** The entity associated with this UI element */
  _entity: Entity;
  /** Array of parent entities in the hierarchy */
  _parents: Entity[];
  /** Reference to the root canvas containing this element */
  _rootCanvas: UICanvas;
  /** Index position of this element within the canvas */
  _indexInCanvas: number;

  /**
   * Handles modifications to the associated entity
   * @param flag - Flags indicating the type of entity modification
   */
  _onEntityModify(flag: EntityModifyFlags): void;
}

4-11: Consider adding essential lifecycle methods.

The interface could benefit from additional lifecycle methods to handle common UI element operations:

Consider adding these methods:

export interface IUIElement {
  // ... existing members ...

  /** Called when the element is mounted to the DOM */
  _onMount(): void;

  /** Called when the element is unmounted from the DOM */
  _onUnmount(): void;

  /** 
   * Called when the element's visibility changes
   * @param visible - New visibility state
   */
  _onVisibilityChange(visible: boolean): void;

  /**
   * Called when the element needs to update its layout
   * @param force - Whether to force a full layout update
   */
  _onLayout(force?: boolean): void;
}
packages/core/src/ui/index.ts (1)

5-11: Consider grouping related UI components.

The exports are logically organized, but could benefit from clearer visual separation between infrastructure (UICanvas, UIRenderer, UITransform) and UI elements (Button, Image, Text).

Consider adding comments to separate these groups:

 export { UICanvas } from "./UICanvas";
 export { UIRenderer } from "./UIRenderer";
 export { UITransform } from "./UITransform";
+
+// UI Elements
 export { Button } from "./Button";
 export { Image } from "./Image";
 export { Text } from "./Text";
packages/core/src/ui/interactive/UIInteractive.ts (2)

41-50: Consider extracting runtime interactive state logic.

The setter contains complex state management logic that could be moved to a separate method for better maintainability. Also, the optional chaining could lead to unexpected behavior if _group is undefined.

Consider this refactor:

 set interactive(value: boolean) {
   if (this._interactive !== value) {
     this._interactive = value;
-    const runtimeInteractive = value && this._group?._getGlobalInteractive();
-    if (this._runtimeInteractive !== runtimeInteractive) {
-      this._runtimeInteractive = runtimeInteractive;
-      this._updateState(true);
-    }
+    this._updateRuntimeInteractive(value);
   }
 }
+
+private _updateRuntimeInteractive(value: boolean): void {
+  const globalInteractive = this._group?._getGlobalInteractive() ?? true;
+  const runtimeInteractive = value && globalInteractive;
+  if (this._runtimeInteractive !== runtimeInteractive) {
+    this._runtimeInteractive = runtimeInteractive;
+    this._updateState(true);
+  }
+}

147-155: Improve group modification logic.

The group modification logic has similar issues to the interactive setter with optional chaining and complex state management.

Consider this refactor:

 _onGroupModify(flag: GroupModifyFlags): void {
   if (flag & GroupModifyFlags.Interactive) {
-    const runtimeInteractive = this._interactive && (this._group?._getGlobalInteractive() || true);
-    if (this._runtimeInteractive !== runtimeInteractive) {
-      this._runtimeInteractive = runtimeInteractive;
-      this._updateState(true);
-    }
+    this._updateRuntimeInteractive(this._interactive);
   }
 }
packages/core/src/ui/UIUtils.ts (2)

1-16: Consider using a singleton pattern instead of static members.

While static analysis suggests converting to standalone functions, the shared state (_renderQueue, _virtualCamera, _viewport) suggests this class would benefit from a singleton pattern. This would provide better encapsulation and testability while maintaining the same usage pattern.

Example implementation:

export class UIUtils {
  private static instance: UIUtils;
  
  private _renderQueue: RenderQueue;
  private _virtualCamera: VirtualCamera;
  private _viewport: Vector4;

  private constructor() {
    // Initialize members
  }

  static getInstance(): UIUtils {
    if (!UIUtils.instance) {
      UIUtils.instance = new UIUtils();
    }
    return UIUtils.instance;
  }

  // Convert static methods to instance methods
}

128-152: Improve readability of matrix operations.

The direct matrix element assignments are hard to read and maintain. Consider using matrix utility methods or adding explanatory comments:

-    (projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0);
+    // Set orthographic projection matrix elements
+    projectE[0] = 2 / canvas.width;  // Scale X
+    projectE[5] = 2 / canvas.height; // Scale Y
+    projectE[10] = 0;               // Flatten Z

-    (viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
+    // Set view translation
+    viewE[12] = -transform.position.x; // Translate X
+    viewE[13] = -transform.position.y; // Translate Y

Consider creating helper methods for these matrix operations:

private static setOrthographicProjection(matrix: Matrix, width: number, height: number): void {
  const elements = matrix.elements;
  elements[0] = 2 / width;
  elements[5] = 2 / height;
  elements[10] = 0;
}

private static setViewTranslation(matrix: Matrix, position: Vector3): void {
  const elements = matrix.elements;
  elements[12] = -position.x;
  elements[13] = -position.y;
}
🧰 Tools
🪛 Biome

[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/UIRenderer.ts (2)

22-28: Consider implementing object pooling for temporary objects.

The static temporary objects could be managed through an object pool to reduce garbage collection pressure during frequent UI updates.

Consider implementing an object pool:

class UIRendererObjectPool {
  private static _vec3Pool: Vector3[] = [];
  private static _matrixPool: Matrix[] = [];
  
  static acquireVector3(): Vector3 {
    return this._vec3Pool.pop() || new Vector3();
  }
  
  static releaseVector3(vec: Vector3): void {
    vec.set(0, 0, 0);
    this._vec3Pool.push(vec);
  }
}

200-222: Optimize raycast implementation.

The raycast method could be optimized by:

  1. Caching the plane intersection result
  2. Using early returns for better readability
  3. Using more descriptive variable names

Consider this optimization:

 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
   const { transform } = this._entity;
   const normal = plane.normal.copyFrom(transform.worldForward);
   plane.distance = -Vector3.dot(normal, transform.worldPosition);
   
-  const curDistance = ray.intersectPlane(plane);
+  const intersectionDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

   const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
   const worldMatrixInv = UIRenderer._tempMat;
   Matrix.invert(transform.worldMatrix, worldMatrixInv);
   
   const localPosition = UIRenderer._tempVec31;
   Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
   
-  if (this._hitTest(localPosition)) {
-    out.distance = curDistance;
-    out.entity = this._entity;
-    out.component = this;
-    out.normal.copyFrom(normal);
-    out.point.copyFrom(hitPointWorld);
-    return true;
-  }
-  return false;
+  if (!this._hitTest(localPosition)) {
+    return false;
+  }
+
+  out.distance = intersectionDistance;
+  out.entity = this._entity;
+  out.component = this;
+  out.normal.copyFrom(normal);
+  out.point.copyFrom(hitPointWorld);
+  return true;
packages/core/src/2d/text/TextUtils.ts (2)

99-104: Add JSDoc documentation for the updated method signature.

Please add documentation for the new parameters to improve maintainability and developer experience.

+/**
+ * Measures text with word wrapping enabled.
+ * @param renderer - The text renderer or Text component
+ * @param rendererWidth - The maximum width for text wrapping
+ * @param rendererHeight - The height of the renderer
+ * @param lineSpacing - The spacing between lines
+ * @returns TextMetrics containing the measured text dimensions and line information
+ */
 static measureTextWithWrap(

276-280: Add JSDoc documentation for the updated method signature.

Please add documentation for the new parameters to improve maintainability.

+/**
+ * Measures text without word wrapping.
+ * @param renderer - The text renderer or Text component
+ * @param rendererHeight - The height of the renderer
+ * @param lineSpacing - The spacing between lines
+ * @returns TextMetrics containing the measured text dimensions and line information
+ */
 static measureTextWithoutWrap(
packages/core/src/ui/UICanvas.ts (1)

349-351: Consider using type predicates for safer type casting.

The current type casting could be made safer by using TypeScript type predicates:

function isUIGraphics(component: Component): component is IUIGraphics {
  return component._componentType === ComponentType.UIRenderer;
}

Then update the code:

-      if (component.enabled && component._componentType === ComponentType.UIRenderer) {
-        (component as unknown as IUIGraphics).depth = depth;
-        elements[depth] = component as unknown as IUIGraphics;
+      if (component.enabled && isUIGraphics(component)) {
+        component.depth = depth;
+        elements[depth] = component;
packages/core/src/2d/text/TextRenderer.ts (3)

Line range hint 66-74: Optimize color setter to avoid unnecessary updates.

The color setter should check individual components to avoid triggering unnecessary updates when the same color values are set.

set color(value: Color) {
-  if (this._color !== value) {
+  if (this._color.r !== value.r || 
+      this._color.g !== value.g || 
+      this._color.b !== value.b || 
+      this._color.a !== value.a) {
    this._color.copyFrom(value);
  }
}

Line range hint 473-616: Refactor _updateLocalData for better maintainability.

This method is quite complex and handles multiple responsibilities. Consider breaking it down into smaller, focused methods:

  • Text measurement
  • Layout calculation
  • Chunk management

Suggested structure:

private _updateLocalData(): void {
  if (!this._calculateTextMetrics()) {
    return;
  }
  
  this._calculateLayout();
  this._updateChunks();
  this._setDirtyFlagFalse(RendererUpdateFlags.LocalPositionAndBounds);
}

private _calculateTextMetrics(): boolean {
  // Text measurement logic
}

private _calculateLayout(): void {
  // Layout calculation logic
}

private _updateChunks(): void {
  // Chunk management logic
}

694-704: Consider explicit enum inheritance for clarity.

While the enum extends RendererUpdateFlags as per the comment, consider making this relationship more explicit in the code.

enum TextRendererUpdateFlags {
  // Base flags from RendererUpdateFlags
  LocalPosition = RendererUpdateFlags.LocalPosition,
  WorldPosition = RendererUpdateFlags.WorldPosition,
  LocalBounds = RendererUpdateFlags.LocalBounds,
  WorldBounds = RendererUpdateFlags.WorldBounds,
  
  // Text-specific flags
  SubFont = 0x10,
  Color = 0x20,

  FontAllPositionAndBounds = SubFont | LocalPosition | WorldPosition | LocalBounds | WorldBounds,
  All = FontAllPositionAndBounds | Color
}
packages/core/src/Entity.ts (3)

107-110: Consider adding transform setter validation.

While the getter is correctly implemented, consider adding a setter with validation to ensure the transform's integrity, especially since it's a critical component for entity positioning.

 get transform(): Transform {
   return this._transform;
 }
+set transform(value: Transform) {
+  if (!value) {
+    throw new Error("Transform cannot be null or undefined");
+  }
+  this._transform = value;
+}

790-797: Document EntityModifyFlags enum values.

The new enum lacks documentation for its values, which is important for maintainability.

 export enum EntityModifyFlags {
+  /** Flag indicating parent entity has changed */
   Parent = 0x1,
+  /** Flag indicating sibling index has changed */
   SiblingIndex = 0x2,
+  /** Flag indicating UI canvas was enabled in scene */
   UICanvasEnableInScene = 0x4,
+  /** Flag indicating UI canvas was disabled in scene */
   UICanvasDisableInScene = 0x8,
+  /** Flag indicating UI group was enabled in scene */
   UIGroupEnableInScene = 0x10,
+  /** Flag indicating UI group was disabled in scene */
   UIGroupDisableInScene = 0x20
 }

783-783: Consider caching matrix inversion.

Matrix inversion is computationally expensive. Consider caching the result if this method is called frequently.

 if (this._inverseWorldMatFlag.flag) {
+  // Cache the previous matrix for comparison
+  const prevMatrix = this._transform.worldMatrix.clone();
   Matrix.invert(this._transform.worldMatrix, this._invModelMatrix);
   this._inverseWorldMatFlag.flag = false;
+  // Store the matrix used for inversion to detect changes
+  this._lastInvertedMatrix = prevMatrix;
 }
packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (2)

168-190: Consider refactoring duplicated path comparison logic

The logic for comparing paths to find common ancestors is duplicated in multiple methods (_updateRaycast, _processUp, and _processLeave). Refactoring this code into a helper method would improve maintainability and reduce code duplication.

Do you want me to generate the refactored code or open a GitHub issue to track this task?


229-229: Define magic number as a constant for better maintainability

The magic number 1024 is used to prevent infinite loops by limiting the iteration count. Defining this number as a constant with a descriptive name enhances code readability and makes future adjustments easier.

Apply this diff to define the magic number as a constant:

-        for (; i < 1024 && !!entity && entity !== rootEntity; i++) {
+        const MAX_PATH_DEPTH = 1024;
+        for (; i < MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
packages/core/src/ui/Image.ts (1)

21-23: Consider moving static properties to the end of the class.

To improve readability and follow common conventions, consider moving the static properties _tempVec2 and _tempUnit8Array to the end of the class definition.

packages/core/src/ui/Text.ts (2)

225-225: Avoid using assignment expressions.

Using assignments within expressions can be confusing and is often considered a code smell. It's better to separate the assignment from the expression for clarity and maintainability.

Refactor the code to avoid assignment expressions. For example:

-    this._subFont && (this._subFont = null);
+    if (this._subFont) {
+        this._subFont = null;
+    }

Apply similar changes to the other instances of assignment expressions.

Also applies to: 466-467, 477-477, 479-479, 566-566, 568-568

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


612-616: Consider making the TextChunk class properties readonly.

Since the TextChunk class is an internal data structure used by the Text class, consider making its properties readonly to prevent accidental modifications from outside the class.

Apply the following changes:

 class TextChunk {
-  charRenderInfos = new Array<CharRenderInfo>();
+  readonly charRenderInfos = new Array<CharRenderInfo>();
-  subChunk: SubPrimitiveChunk;
+  readonly subChunk: SubPrimitiveChunk;
-  texture: Texture2D;
+  readonly texture: Texture2D;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2487d and 8094d8a.

📒 Files selected for processing (17)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/2d/text/TextUtils.ts (11 hunks)
  • packages/core/src/Entity.ts (10 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Image.ts (1 hunks)
  • packages/core/src/ui/Text.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interface/IUIElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGroupable.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts
  • packages/core/src/ui/interactive/transition/Transition.ts
  • packages/core/src/ui/interface/IUIGraphics.ts
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Text.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 357-357: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (49)
packages/core/src/ui/interface/IUIGroupable.ts (2)

1-2: LGTM! Imports are well-organized.

The imports are properly structured and use appropriate relative paths.


4-9: Verify the impact of interface changes across the codebase.

Since this is a new interface that will be implemented by UI components, we should verify its usage pattern across the codebase.

✅ Verification successful

Let me check the actual implementations and usages to better understand the impact.


Interface changes appear to be properly integrated across the codebase

The interface IUIGroupable is well-integrated into the UI component hierarchy:

  • Correctly implemented by UIInteractive class
  • Used as a base interface for IUIGraphics
  • Properly utilized in UIGroup for managing grouped elements
  • Integrated with UIUtils for group management functionality

The interface changes maintain consistency with its implementations and usages across the codebase. All required properties and methods are properly referenced.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential implementations and usages of IUIGroupable
echo "Searching for implementations of IUIGroupable..."
rg "implements.*IUIGroupable" -l

echo "\nSearching for imports of IUIGroupable..."
rg "import.*IUIGroupable" -l

echo "\nSearching for type references to IUIGroupable..."
rg ": IUIGroupable" -l

Length of output: 645


Script:

#!/bin/bash
# Check the implementation in UIInteractive
echo "Checking UIInteractive implementation..."
rg "implements.*IUIGroupable" -A 10 packages/core/src/ui/interactive/UIInteractive.ts

echo "\nChecking UIGroup usage of IUIGroupable..."
rg "IUIGroupable" -A 5 packages/core/src/ui/UIGroup.ts

echo "\nChecking UIUtils usage..."
rg "IUIGroupable" -A 5 packages/core/src/ui/UIUtils.ts

echo "\nChecking IUIGraphics interface..."
rg "IUIGroupable" -A 5 packages/core/src/ui/interface/IUIGraphics.ts

Length of output: 1924

packages/core/src/ui/interface/IUIElement.ts (1)

1-3: LGTM!

The imports are correct and necessary for the interface implementation.

packages/core/src/ui/index.ts (3)

1-4: LGTM! Well-organized core exports.

The grouping of core UI components and enums at the top of the file follows good organizational practices. The separation of enums into their own directory suggests good code organization.


1-15: Consider adding type exports for public interfaces.

While the component exports are well-organized, consider also exporting any public interfaces or types that consumers might need when working with these UI components.

Let's check for potentially missing type exports:

#!/bin/bash
# Description: Look for interfaces that might need to be exported

# Search for public interfaces in UI-related files
echo "Checking for public interfaces in UI files..."
rg "export interface" "./packages/core/src/ui/"

12-15: Verify transition system integration.

The transition system appears to be comprehensive, covering color, scale, and sprite transitions. However, we should verify that these transitions are properly integrated with the UI components.

Let's verify the integration:

✅ Verification successful

Transition system is properly integrated with UI components

The verification shows a well-structured transition system:

  • UIInteractive class serves as the integration point, managing transitions through _transitions array
  • Base Transition class is properly extended by ColorTransition, ScaleTransition, and SpriteTransition
  • Transitions are properly managed with methods like getTransition, addTransition, removeTransition
  • Transitions are automatically updated in the component lifecycle through onUpdate
  • State changes are properly propagated to all transitions via _setState

The implementation shows a robust and type-safe transition system that's tightly integrated with the UI component lifecycle.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if transitions are properly integrated with UI components

# Test 1: Check for transition usage in UI components
echo "Checking transition usage in UI components..."
rg -l "Transition" "./packages/core/src/ui/(Button|Image|Text).ts"

# Test 2: Check if Transition is the base class for other transitions
echo "Verifying transition hierarchy..."
ast-grep --pattern 'class $_ extends Transition'

Length of output: 666


Script:

#!/bin/bash
# Let's verify the transition system integration with more precise commands

# Check for transition imports and usage in UI components
echo "Checking transition usage in UI components..."
rg -l "import.*Transition" packages/core/src/ui/

# Check implementation of transition classes
echo "Checking transition implementations..."
rg -A 5 "class.*Transition" packages/core/src/ui/interactive/transition/

# Check if UI components have transition-related methods or properties
echo "Checking UI components for transition methods..."
rg -A 3 "(transition|animate)" packages/core/src/ui/

Length of output: 6692

packages/core/src/ui/interactive/UIInteractive.ts (6)

1-36: LGTM! Well-structured class definition with clear property organization.

The class structure follows good practices with appropriate use of decorators and clear property visibility modifiers.


52-78: LGTM! Well-implemented transition management with type safety.

The transition management methods make good use of TypeScript generics and provide a clean API for managing transitions.


80-102: LGTM! Clean event handling implementation.

The pointer event handlers maintain a clear separation between state tracking and updates.


107-123: LGTM! Proper lifecycle management with clean state handling.

The lifecycle methods properly manage registration/unregistration and state cleanup.


157-174: LGTM! Clean state management implementation.

The state management methods are well-organized with clear transition logic and state calculations.


130-140: ⚠️ Potential issue

Fix switch case fallthrough issue.

The cases for UICanvasEnableInScene and Parent are falling through to UIGroupEnableInScene unintentionally.

🧰 Tools
🪛 Biome

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts (5)

1-41: LGTM! Well-structured class definition with proper property management.

The class structure is well-organized with appropriate use of decorators and clear documentation of internal properties.


43-75: LGTM! Well-implemented property accessors.

The getters and setters are properly implemented with appropriate validation and update triggers.


117-144: LGTM! Thorough lifecycle management.

The lifecycle methods properly handle registration, cleanup, and garbage collection.


186-191: LGTM! Well-designed flags enum.

The GroupModifyFlags enum is properly implemented using bit flags, allowing efficient combination of multiple flags.


153-154: 🛠️ Refactor suggestion

Improve readability by avoiding assignment in expression.

The assignment within the logical AND expression makes the code harder to read and understand.

Apply this diff:

- replaced && (replaced._groupIndex = this._groupIndex);
+ if (replaced) {
+   replaced._groupIndex = this._groupIndex;
+ }

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts (1)

19-20: ⚠️ Potential issue

Add missing ISpriteRenderer interface.

The class should implement the ISpriteRenderer interface to maintain consistency with the sprite rendering system.

-export abstract class UIRenderer extends Renderer implements IUIGraphics {
+export abstract class UIRenderer extends Renderer implements IUIGraphics, ISpriteRenderer {

Likely invalid or redundant comment.

packages/core/src/2d/text/TextUtils.ts (2)

2-2: LGTM: Import addition aligns with new type requirements.

The addition of the Text import supports the new union type parameter (TextRenderer | Text) in the measure methods.


99-104: Verify interface compatibility between TextRenderer and Text.

Both measure methods accept either TextRenderer or Text type. Let's verify they implement the required interface members.

Also applies to: 276-280

✅ Verification successful

Let me gather more information about both TextRenderer and Text classes to verify their interface compatibility.


Let me search for more specific information about the text measurement methods and required interface members.


Let me search for the required properties and methods in both classes with a different pattern.


Based on the gathered information, I can now provide the final response:

Interface compatibility between TextRenderer and Text is verified.

Both TextRenderer and Text classes implement the required interface members for measureTextWithWrap:

  • Both have overflowMode property with identical behavior (defaults to OverflowMode.Overflow)
  • Both have width and height properties
  • Both use the same text measurement logic through TextUtils.measureTextWithWrap
  • Both handle text overflow and truncation in the same way

The shared interface is consistently implemented across both classes, making them safely interchangeable in the measureTextWithWrap method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the Text class definition to verify required members
echo "Searching for Text class definition..."
ast-grep --pattern 'class Text {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
}'

echo "Verifying overflowMode property..."
ast-grep --pattern 'class Text {
  $$$
  overflowMode: $_
  $$$
}'

Length of output: 314


Script:

#!/bin/bash
# Search for TextRenderer interface/class definition
echo "Searching for TextRenderer definition..."
rg "(?:interface|class|type)\s+TextRenderer" -A 10

# Search for Text class usage of measureTextWithWrap
echo "Searching for Text class measureText related methods..."
rg "class Text" -A 30

# Search for common method signatures between Text and TextRenderer
echo "Searching for common method signatures..."
ast-grep --pattern '(class Text|class TextRenderer) {
  $$$
  measureText($_) {
    $$$
  }
  $$$
}'

Length of output: 36998


Script:

#!/bin/bash
# Search for measureTextWithWrap implementation and usage
echo "Searching for measureTextWithWrap implementation..."
rg "measureTextWithWrap" -A 5

# Search for required properties and methods in both classes
echo "Searching for text measurement related properties..."
ast-grep --pattern '(class Text|class TextRenderer) {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
  overflowMode: $_
  $$$
}'

# Search for common interface or type definitions
echo "Searching for TextRenderer interface..."
rg "interface.*TextRenderer" -A 10

Length of output: 8882


Script:

#!/bin/bash
# Search for overflowMode property in both classes
echo "Searching for overflowMode property..."
rg "overflowMode.*=" -B 5 -A 1

# Search for _getSubFont method in both classes
echo "Searching for _getSubFont method..."
rg "_getSubFont.*{" -B 2 -A 10

# Search for width and height properties
echo "Searching for width and height properties..."
rg "(get width\(\)|get height\(\))" -B 2 -A 5

Length of output: 18444

packages/core/src/2d/text/TextRenderer.ts (1)

35-35: ⚠️ Potential issue

Fix array initialization syntax.

The array initialization syntax is incorrect. Using Array<TextChunk>() without the new keyword doesn't create a new array instance.

-private _textChunks = Array<TextChunk>();
+private _textChunks: TextChunk[] = [];

Likely invalid or redundant comment.

packages/core/src/Entity.ts (4)

99-105: LGTM: Property refactoring improves encapsulation.

The refactoring of transform to a private _transform property with a getter improves encapsulation. The addition of _updateFlagManager enables better state change management.


205-206: LGTM: Constructor initialization is correct.

The initialization of _transform and registration of world change flag is properly implemented.


527-530: LGTM: Proper cleanup in destroy method.

The cleanup of _updateFlagManager is correctly implemented with null checks and removal of all listeners.


404-404: Verify UI Transform inheritance.

The conditional addition of UITransform needs verification to ensure proper inheritance across the entity hierarchy.

✅ Verification successful

UI Transform inheritance is correctly implemented

The verification shows that the UI Transform inheritance is properly handled:

  1. UITransform extends the base Transform class and is automatically added to components that require it through the @dependentComponents decorator (seen in UIRenderer and UICanvas).
  2. The inheritance check in Entity.createChild() ensures that if a parent entity has a UITransform, its children will also get a UITransform component.
  3. The implementation maintains consistency in the UI component hierarchy by propagating the appropriate transform type from parent to child entities.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check UITransform usage patterns
# Expected: Consistent UITransform inheritance in parent-child relationships

# Check UITransform inheritance patterns
ast-grep --pattern 'class $_ extends $_ {
  $$$
  addComponent($_) {
    $$$
    UITransform
    $$$
  }
  $$$
}'

Length of output: 115


Script:

#!/bin/bash
# Let's try a different approach to analyze UITransform usage

# Search for UITransform related code patterns
rg "UITransform" -A 5 -B 5

# Also check for specific inheritance patterns
ast-grep --pattern 'this._transform instanceof UITransform'

# Look for component addition patterns
ast-grep --pattern 'addComponent($_)'

Length of output: 21016

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (3)

102-102: ****

🧰 Tools
🪛 Biome

[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


222-222: ****

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


167-168: ****

packages/core/src/ui/Image.ts (19)

1-19: LGTM!

The imports are well-organized and cover the necessary dependencies for the Image class implementation.


20-20: LGTM!

The class declaration is correct, extending UIRenderer and implementing the ISpriteRenderer interface.


24-35: LGTM!

The private properties are well-defined, with appropriate types and default values. The use of @ignoreClone and @assignmentClone decorators is consistent.


37-43: LGTM!

The getter and setter for alphaHitTestMinimumThreshold are implemented correctly, with proper clamping of the input value.


45-71: LGTM!

The getter and setter for drawMode are implemented correctly. The setter updates the _assembler based on the selected draw mode and resets the sprite data.


73-87: LGTM!

The getter and setter for tileMode are implemented correctly. The setter updates the dirty flag when the draw mode is set to Tiled.


89-104: LGTM!

The getter and setter for tiledAdaptiveThreshold are implemented correctly, with proper clamping of the input value and updating the dirty flag when the draw mode is set to Tiled.


106-130: LGTM!

The getter and setter for sprite are implemented correctly. The setter handles resource reference counting, event listener management, and updating the shader data.


132-143: LGTM!

The constructor initializes the necessary properties and binds event listeners correctly.


145-178: LGTM!

The _hitTest method is implemented correctly, handling the hit testing based on the sprite's bounds and alpha threshold.


180-191: LGTM!

The _updateLocalBounds method is implemented correctly, updating the local bounds based on the sprite's size and pivot.


193-248: LGTM!

The _render method is implemented correctly, handling the rendering process based on the sprite's properties and updating the necessary data when dirty flags are set.


250-255: LGTM!

The _canBatch method is implemented correctly, delegating the batching decision to BatchUtils.canBatchSprite.


257-262: LGTM!

The _batch method is implemented correctly, delegating the batching process to BatchUtils.batchFor2D.


264-283: LGTM!

The _onTransformChanged method is implemented correctly, updating the dirty flags based on the transform modification type.


285-298: LGTM!

The _onDestroy method is implemented correctly, handling resource cleanup and event listener removal.


300-341: LGTM!

The _onSpriteChange method is implemented correctly, updating the dirty flags based on the sprite modification type.


343-356: LGTM!

The _getUVByLocalPosition method is implemented correctly, delegating the UV calculation to the appropriate sprite assembler based on the draw mode.


359-371: LGTM!

The ImageUpdateFlags enum is defined correctly, extending the UIRendererUpdateFlags and providing additional flags specific to the Image class.

packages/core/src/ui/Text.ts (2)

25-609: The Text class implementation looks solid and comprehensive. Great work!

The class provides a robust set of properties and methods for managing text rendering in a UI context. The code is well-structured, follows best practices, and includes appropriate optimizations for efficient rendering.

Some key highlights:

  • Proper handling of resource management through reference counting
  • Efficient rendering using text chunks and sub-render elements
  • Comprehensive text layout and alignment options
  • Effective use of dirty flags for updating rendering state
  • Well-organized and readable code structure

Overall, the Text class seems to be a valuable addition to the UI rendering system.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1-628: Verify the integration of the Text class with other UI components.

The Text class seems to be well-integrated with the UI rendering system, particularly with the UIRenderer class. However, it's important to ensure that the Text class works seamlessly with other UI components introduced in this pull request, such as UIGroup and UICanvas.

To verify the integration, you can perform the following checks:

  1. Review the usage of the Text class in the context of UIGroup and UICanvas classes to ensure proper rendering order and hierarchy management.

  2. Analyze the interaction between the Text class and the enhanced ComponentsManager to confirm that text rendering is efficiently handled within the overall UI hierarchy.

  3. Test the rendering of Text instances in various scenarios, such as different canvas render modes, to validate the expected behavior.

  4. Verify that the Text class correctly utilizes the rendering optimizations provided by the UIRenderer class, such as rendering contexts and batching.

If any issues or inconsistencies are found during the verification process, please let me know, and I'll be happy to assist in resolving them.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 4 to 6
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider revising the interface design to hide implementation details.

The current design exposes internal implementation details through properties prefixed with underscores. This approach:

  1. Breaks encapsulation by exposing internal state
  2. Makes the interface tightly coupled to the implementation
  3. Deviates from TypeScript interface conventions

Consider this alternative design that hides implementation details:

-export interface IUIGroupable extends IUIElement {
-  _group: UIGroup;
-  _indexInGroup: number;
+export interface IUIGroupable extends IUIElement {
+  readonly group: UIGroup;
+  readonly indexInGroup: number;

This change:

  • Removes implementation-specific underscore prefixes
  • Makes properties readonly to prevent external modifications
  • Better adheres to interface design principles
📝 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
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
export interface IUIGroupable extends IUIElement {
readonly group: UIGroup;
readonly indexInGroup: number;

Comment on lines 47 to 82
static registerElementToCanvas(element: IUIElement, canvas: UICanvas): void {
const preCanvas = element._rootCanvas;
if (preCanvas !== canvas) {
element._rootCanvas = canvas;
if (preCanvas) {
const replaced = preCanvas._disorderedElements.deleteByIndex(element._indexInCanvas);
replaced && (replaced._indexInCanvas = element._indexInCanvas);
element._indexInCanvas = -1;
preCanvas._hierarchyDirty = true;
}
if (canvas) {
const disorderedElements = canvas._disorderedElements;
element._indexInCanvas = disorderedElements.length;
disorderedElements.add(element);
canvas._hierarchyDirty = true;
}
}
}

static registerElementToGroup(element: IUIGroupable, group: UIGroup): void {
const preGroup = element._group;
if (preGroup !== group) {
element._group = group;
if (preGroup) {
const replaced = preGroup._disorderedElements.deleteByIndex(element._indexInGroup);
replaced && (replaced._indexInGroup = element._indexInGroup);
element._indexInGroup = -1;
}
if (group) {
const disorderedElements = group._disorderedElements;
element._indexInGroup = disorderedElements.length;
disorderedElements.add(element);
}
element._onGroupModify(GroupModifyFlags.All);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication in registration methods.

The registerElementToCanvas and registerElementToGroup methods share similar logic. Consider extracting the common functionality into a private helper method:

private static registerElementToContainer<T extends IUIElement, C>(
  element: T,
  container: C,
  getPreContainer: (el: T) => C,
  setContainer: (el: T, c: C) => void,
  getDisorderedElements: (c: C) => DisorderedArray<T>,
  getIndex: (el: T) => number,
  setIndex: (el: T, i: number) => void,
  onModify?: (el: T) => void
): void {
  const preContainer = getPreContainer(element);
  if (preContainer !== container) {
    setContainer(element, container);
    if (preContainer) {
      const replaced = getDisorderedElements(preContainer).deleteByIndex(getIndex(element));
      if (replaced) {
        setIndex(replaced, getIndex(element));
      }
      setIndex(element, -1);
    }
    if (container) {
      const disorderedElements = getDisorderedElements(container);
      setIndex(element, disorderedElements.length);
      disorderedElements.add(element);
    }
    onModify?.(element);
  }
}
🧰 Tools
🪛 Biome

[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 84 to 126
static getRootCanvasInParent(entity: Entity): UICanvas {
while (entity) {
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (
component.enabled &&
component._componentType === ComponentType.UICanvas &&
(<UICanvas>component)._isRootCanvas
) {
return <UICanvas>component;
}
}
entity = entity.parent;
}
return null;
}

static getGroupInParents(entity: Entity): UIGroup {
let meetRootCanvas = false;
while (entity) {
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (component.enabled) {
switch (component._componentType) {
case ComponentType.UICanvas:
meetRootCanvas = (<UICanvas>component)._isRootCanvas;
break;
case ComponentType.UIGroup:
return <UIGroup>component;
default:
break;
}
}
}
if (meetRootCanvas) {
return null;
}
entity = entity.parent;
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety and performance in traversal methods.

  1. Replace type casting with type guards for better type safety.
  2. Consider caching results for frequently accessed entities.
private static isUICanvas(component: any): component is UICanvas {
  return component._componentType === ComponentType.UICanvas;
}

private static isUIGroup(component: any): component is UIGroup {
  return component._componentType === ComponentType.UIGroup;
}

static getRootCanvasInParent(entity: Entity): UICanvas {
  // Consider adding WeakMap cache for frequent lookups
  private static canvasCache = new WeakMap<Entity, UICanvas>();
  
  if (UIUtils.canvasCache.has(entity)) {
    return UIUtils.canvasCache.get(entity);
  }
  
  while (entity) {
    const components = entity._components;
    for (const component of components) {
      if (component.enabled && UIUtils.isUICanvas(component) && component._isRootCanvas) {
        UIUtils.canvasCache.set(entity, component);
        return component;
      }
    }
    entity = entity.parent;
  }
  return null;
}

Comment on lines +17 to +20
private static _renderQueue: RenderQueue;
private static _virtualCamera: VirtualCamera;
private static _viewport: Vector4;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Initialize static fields immediately to prevent race conditions.

Lazy initialization in static fields can lead to race conditions in concurrent scenarios. Consider initializing all fields during class definition:

-  private static _renderQueue: RenderQueue;
-  private static _virtualCamera: VirtualCamera;
-  private static _viewport: Vector4;
+  private static _renderQueue: RenderQueue = new RenderQueue(RenderQueueType.Transparent);
+  private static _virtualCamera: VirtualCamera = new VirtualCamera();
+  private static _viewport: Vector4 = new Vector4(0, 0, 1, 1);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 21 to 45
static registerEntityListener(element: IUIElement): void {
const parents = element._parents;
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}

static unRegisterEntityListener(element: IUIElement): void {
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and error handling.

The entity listener methods lack input validation and error handling. Consider adding checks for null/undefined parameters and handling edge cases:

 static registerEntityListener(element: IUIElement): void {
+  if (!element) {
+    throw new Error("Element cannot be null or undefined");
+  }
   const parents = element._parents;
+  if (!parents) {
+    throw new Error("Element parents array is not initialized");
+  }
   // ... rest of the method
 }

 static unRegisterEntityListener(element: IUIElement): void {
+  if (!element || !element._parents) {
+    return; // Silent fail on cleanup
+  }
   const { _parents: parents } = element;
   // ... rest of the method
 }
📝 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
static registerEntityListener(element: IUIElement): void {
const parents = element._parents;
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}
static unRegisterEntityListener(element: IUIElement): void {
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}
static registerEntityListener(element: IUIElement): void {
if (!element) {
throw new Error("Element cannot be null or undefined");
}
const parents = element._parents;
if (!parents) {
throw new Error("Element parents array is not initialized");
}
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}
static unRegisterEntityListener(element: IUIElement): void {
if (!element || !element._parents) {
return; // Silent fail on cleanup
}
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}

Comment on lines +611 to +613
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix assignment in expression.

The assignment within the expression can be confusing and should be split for better readability.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 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
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
_registerModifyListener(onChange: (EntityModifyFlags) => void): void {
if (!this._updateFlagManager) {
this._updateFlagManager = new UpdateFlagManager();
}
this._updateFlagManager.addListener(onChange);
}
🧰 Tools
🪛 Biome

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 22 to 86
override _processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
const { _tempRay: ray } = PointerEventEmitter;
const hitResult = this._hitResult;
const { position } = pointer;
const { x, y } = position;
for (let i = scenes.length - 1; i >= 0; i--) {
const scene = scenes[i];
if (!scene.isActive || scene.destroyed) continue;
const { _componentsManager: componentsManager } = scene;

/** Overlay Canvas */
let canvasElements = componentsManager._overlayCanvases;
ray.origin.set(position.x, position.y, 1);
ray.direction.set(0, 0, -1);
for (let j = canvasElements.length - 1; j >= 0; j--) {
if (canvasElements.get(j).raycast(ray, hitResult)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}

const cameras = componentsManager._activeCameras;
for (let j = cameras.length - 1; j >= 0; j--) {
const camera = cameras.get(j);
if (camera.renderTarget) continue;
const { pixelViewport } = camera;
if (
x < pixelViewport.x ||
y < pixelViewport.y ||
x > pixelViewport.x + pixelViewport.width ||
y > pixelViewport.y + pixelViewport.height
) {
continue;
}
camera.screenPointToRay(pointer.position, ray);

/** Other canvases */
const cameraPosition = camera.entity.transform.position;
/** Sort by rendering order */
canvasElements = componentsManager._canvases;
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._updateSortDistance(cameraPosition);
}
canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._canvasIndex = k;
}
const farClipPlane = camera.farClipPlane;
/** Post-rendering first detection */
for (let k = 0, n = canvasElements.length; k < n; k++) {
const canvas = canvasElements.get(k);
if (canvas.renderCamera !== camera) continue;
if (canvas.raycast(ray, hitResult, farClipPlane)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}
if (camera.clearFlags & CameraClearFlags.Color) {
this._updateRaycast(null);
return;
}
}
}
return null;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Method complexity exceeds recommended threshold

The _processRaycast method is complex with multiple nested loops and conditions. Consider breaking it down into smaller, focused methods:

  • processOverlayCanvas
  • processCameraCanvas
  • handleCameraRaycast

This would improve readability and maintainability.

Do you want me to generate the refactored code or open a GitHub issue to track this task?

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

Comment on lines 88 to 89
override _processDrag(pointer: Pointer): void {
if (this._draggedElement) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add parameter validation and error handling

The _processDrag method accepts a pointer parameter without validation. Consider adding null checks and appropriate error handling to prevent runtime exceptions.

Apply this diff to add validation:

override _processDrag(pointer: Pointer): void {
+  if (!pointer) {
+    console.warn('Invalid pointer provided to _processDrag');
+    return;
+  }
  if (this._draggedElement) {

Do you want me to open a GitHub issue to track this task?

📝 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
override _processDrag(pointer: Pointer): void {
if (this._draggedElement) {
override _processDrag(pointer: Pointer): void {
if (!pointer) {
console.warn('Invalid pointer provided to _processDrag');
return;
}
if (this._draggedElement) {

}
}
}
return null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unnecessary return null; in a void function

The method _processRaycast is declared with a return type of void, but it ends with return null;. Since the function is expected to return void, the return null; statement is unnecessary and should be removed to prevent confusion.

Apply this diff to fix the issue:

-    return null;
📝 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
return null;
🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

}

private _isTextNoVisible(): boolean {
debugger;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove the debugger statement.

The debugger statement should not be present in production code as it can pause execution and affect performance.

Remove the following line:

-    debugger;
📝 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
debugger;
🧰 Tools
🪛 Biome

[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

@singlecoder singlecoder changed the title Add GUI Infrastructure Add GUI Nov 20, 2024
Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (68)
packages/ui/src/component/UITransform.ts (2)

32-41: ⚠️ Potential issue

Avoid accessing private properties and suppressing TypeScript errors

The use of // @ts-ignore to access entity._transform and entity._inverseWorldMatFlag indicates that you're accessing private or protected properties. Suppressing TypeScript errors can hide potential issues and compromises type safety.

Consider the following actions:

  • Refactor the code to use public APIs or accessor methods: If possible, use public methods or properties provided by the Entity class to achieve the same functionality.
  • Modify the base class to expose necessary properties: If you have control over the Entity class, consider exposing _transform and _inverseWorldMatFlag through public or protected members.
  • Extend the Entity class appropriately: Create a subclass of Entity that exposes the needed properties in a type-safe manner.

46-47: ⚠️ Potential issue

Accessing private members _updateFlagManager may lead to maintenance issues

Directly accessing private members like this._updateFlagManager by suppressing TypeScript errors with // @ts-ignore can lead to maintenance challenges and violates encapsulation principles.

Consider the following solutions:

  • Expose a protected method or property in the base class: Modify the Transform class to provide protected access to _updateFlagManager.
  • Use public methods to dispatch updates: If available, use existing public methods to dispatch update flags without needing to access private members.

Also applies to: 52-53

packages/ui/src/component/interactive/transition/Transition.ts (4)

84-94: ⚠️ Potential issue

Prevent potential division by zero in set duration method

In the set duration(value: number) method, there is a potential division by zero when preDuration is zero.

At line 90:

this._countDown = value * (1 - this._countDown / preDuration);

If preDuration is zero, dividing by zero will result in Infinity or NaN, causing unexpected behavior. To prevent this, add a check to ensure preDuration is not zero before performing the division.

Apply this diff to fix the issue:

      set duration(value: number) {
        if (value < 0) value = 0;
        const preDuration = this._duration;
        if (preDuration !== value) {
          this._duration = value;
          if (this._countDown > 0) {
+           if (preDuration > 0) {
              this._countDown = value * (1 - this._countDown / preDuration);
+           } else {
+             this._countDown = value;
+           }
            this._updateValue();
          }
        }
      }

76-76: ⚠️ Potential issue

Ensure _currentValue is initialized before use

In lines 76 and 148, the method _applyValue is called with this._currentValue, which may be undefined if not properly initialized. This can lead to unexpected behavior or runtime errors.

Initialize this._currentValue before it's used. Consider setting a default value during declaration or ensuring it's assigned in all code paths before invocation:

  protected _currentValue: T;
+ 
+ constructor() {
+   // Initialize _currentValue with a default value, if appropriate
+   this._currentValue = this.normal; // or any suitable default
+ }

Alternatively, add checks before calling _applyValue:

        if (this._currentValue !== undefined) {
          value?.enabled && this._applyValue(this._currentValue);
        }

Ensuring _currentValue is properly initialized enhances code stability.

Also applies to: 148-148


135-138: 🛠️ Refactor suggestion

Eliminate the use of @ts-ignore directives

Using @ts-ignore in lines 135 and 137 suppresses TypeScript compiler errors, which can hide potential issues and reduce type safety.

Replace @ts-ignore with proper type checks or type assertions to maintain type safety. Here's how you can modify the code:

      protected _onStateValueDirty(state: InteractiveState, preValue: T, curValue: T): void {
-       // @ts-ignore
-       preValue instanceof ReferResource && preValue._addReferCount(-1);
+       if (preValue instanceof ReferResource) {
+         preValue._addReferCount(-1);
+       }
-       // @ts-ignore
-       curValue instanceof ReferResource && curValue._addReferCount(1);
+       if (curValue instanceof ReferResource) {
+         curValue._addReferCount(1);
+       }
        if (this._finalState === state) {
          this._finalValue = curValue;
          this._updateValue();
        }
      }

This approach ensures that preValue and curValue are instances of ReferResource before calling _addReferCount, improving code safety and readability.

📝 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.

    if (preValue instanceof ReferResource) {
      preValue._addReferCount(-1);
    }
    if (curValue instanceof ReferResource) {
      curValue._addReferCount(1);
    }

151-162: ⚠️ Potential issue

Add a default case to handle unexpected states in _getValueByState

The method _getValueByState lacks a default case in the switch statement. If an unexpected InteractiveState value is passed, the method may return undefined, leading to runtime errors.

Add a default case to ensure the method always returns a valid value:

      private _getValueByState(state: InteractiveState): T {
        switch (state) {
          case InteractiveState.Normal:
            return this.normal;
          case InteractiveState.Pressed:
            return this.pressed;
          case InteractiveState.Hover:
            return this.hover;
          case InteractiveState.Disable:
            return this.disabled;
+         default:
+           // Handle unexpected state, possibly by returning a default value or throwing an error
+           return this.normal; // or consider throwing an error
        }
      }

This change ensures that your code handles all possible cases, enhancing robustness.

📝 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.

  private _getValueByState(state: InteractiveState): T {
    switch (state) {
      case InteractiveState.Normal:
        return this.normal;
      case InteractiveState.Pressed:
        return this.pressed;
      case InteractiveState.Hover:
        return this.hover;
      case InteractiveState.Disable:
        return this.disabled;
      default:
        // Handle unexpected state, possibly by returning a default value or throwing an error
        return this.normal; // or consider throwing an error
    }
  }
packages/ui/src/Utils.ts (2)

79-79: 🛠️ Refactor suggestion

Avoid assignments within logical expressions for better clarity.

In lines 79, 100, 122, 133, and 135, assignments are performed within logical && expressions, which can be confusing and reduce readability.

For example, at line 79:

replaced && (replaced._indexInCanvas = element._indexInCanvas);

It's clearer to separate the assignment from the conditional check:

if (replaced) {
  replaced._indexInCanvas = element._indexInCanvas;
}

Apply similar changes to the other lines to enhance code clarity and maintainability.

Also applies to: 100-100, 122-122, 133-133, 135-135

🧰 Tools
🪛 Biome

[error] 79-79: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


18-18: 🛠️ Refactor suggestion

Minimize the use of // @ts-ignore to ensure type safety.

The code contains multiple // @ts-ignore comments (lines 18, 21, 43, 62, 87, 108), which suppress TypeScript compiler errors. Overusing // @ts-ignore can hide potential type issues and decrease code reliability. It's advisable to address the underlying type errors instead of suppressing them.

Also applies to: 21-21, 43-43, 62-62, 87-87, 108-108

packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts (1)

61-61: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

On line 61, the assignment combines multiple variables in a single expression. This can reduce code clarity and make it harder to debug.

Consider separating the assignments to enhance readability:

-const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity);
+this._pressedEntity = this._draggedEntity = this._enteredEntity;
+const entity = this._pressedEntity;
📝 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.

    this._pressedEntity = this._draggedEntity = this._enteredEntity;
    const entity = this._pressedEntity;
🧰 Tools
🪛 Biome

[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIGroup.ts (3)

148-149: ⚠️ Potential issue

Remove @ts-ignore and correctly override lifecycle methods

The use of @ts-ignore when overriding _onEnableInScene() and _onDisableInScene() suggests type mismatches. Ensure that these methods are properly declared in the base class and that your overrides match the expected signatures without suppressing type errors.

Consider updating your method names or checking the base class definitions to align with TypeScript's expectations.

Also applies to: 156-157


152-153: 🛠️ Refactor suggestion

Avoid accessing internal methods like _dispatchModify

Accessing this.entity._dispatchModify directly may break encapsulation if _dispatchModify is intended to be a private or internal method. Relying on internal APIs can lead to maintenance issues if the underlying implementation changes.

Consider using a public method or event system provided by the Entity class to dispatch modifications.


90-91: 🛠️ Refactor suggestion

Avoid using @ts-ignore; ensure _componentType assignment is type-safe

Using @ts-ignore suppresses TypeScript compiler errors and can mask underlying issues. Assigning to _componentType directly may not be safe if it's a protected or private member of the parent class. Consider providing a proper accessor or refactoring the class hierarchy to avoid bypassing TypeScript's type checking.

Apply this diff to address the issue:

-// @ts-ignore
 this._componentType = ComponentType.UIGroup;
+ (this as any)._componentType = ComponentType.UIGroup;

Or better, request an accessor from the base class if possible.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/interactive/UIInteractive.ts (3)

146-155: 🛠️ Refactor suggestion

Avoid using @ts-ignore; properly handle method overriding in _onEnableInScene.

Using @ts-ignore suppresses TypeScript errors and may conceal potential issues. The method _onEnableInScene is being overridden, but TypeScript likely flags an error due to access modifiers or method visibility in the base class Script.

Consider these options:

  • Option 1: Update the base class Script to declare _onEnableInScene as protected, allowing safe overriding.
  • Option 2: If modifying the base class is not feasible, create a new method in your subclass that does not conflict with the base class's private or internal methods.
  • Option 3: Use TypeScript's protected or public access modifiers to align with the intended visibility and override methods without suppressing errors.

Removing @ts-ignore ensures that TypeScript can properly check your code for errors.


157-167: 🛠️ Refactor suggestion

Avoid using @ts-ignore; ensure proper method overriding in _onDisableInScene.

Similar to the previous comment, suppressing TypeScript errors with @ts-ignore when overriding _onDisableInScene can hide potential issues. Properly handling method overriding is essential for code safety and maintainability.

Implement the same solutions as suggested for _onEnableInScene to address this issue.


105-109: ⚠️ Potential issue

Adjust generic type constraint in addTransition method to support constructors with parameters.

The current generic type constraint T extends new () => Transition may not accommodate Transition subclasses that require constructor arguments. This can lead to issues when adding transitions that need parameters during instantiation.

Apply this diff to adjust the generic type constraint and handle constructors with parameters:

- addTransition<T extends new () => Transition>(type: T): InstanceType<T> {
-   const transition = new type() as InstanceType<T>;
+ addTransition<T extends abstract new (...args: any[]) => Transition>(type: T, ...args: ConstructorParameters<T>): InstanceType<T> {
+   const transition = new type(...args) as InstanceType<T>;
    this._transitions.push(transition);
    transition._setState(this._state, true);
    return transition;
}
📝 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.

    addTransition<T extends abstract new (...args: any[]) => Transition>(type: T, ...args: ConstructorParameters<T>): InstanceType<T> {
      const transition = new type(...args) as InstanceType<T>;
      this._transitions.push(transition);
      transition._setState(this._state, true);
      return transition;
    }
packages/ui/src/input/UIPointerEventEmitter.ts (3)

222-222: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

Similar to line 103, the assignment on line 222 uses an assignment within an expression, which can reduce code clarity.

Refactor the code as follows:

-    let entity = (path[0] = element.entity);
+    path[0] = element.entity;
+    let entity = path[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.

    path[0] = element.entity;
    let entity = path[0];
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


89-89: ⚠️ Potential issue

Avoid returning a value from a function with a void return type

The processRaycast method is declared to return void, but it contains return null; on line 89. Returning a value in a void function can lead to confusion and potential type errors.

Apply this diff to correct the issue:

-    return null;
+    return;
📝 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.

    return;
🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


103-103: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

Assigning multiple variables within an expression can make the code harder to read and maintain. The assignment on line 103 combines several assignments in one expression.

Refactor the code as follows to improve clarity:

-    const element = (this._pressedElement = this._draggedElement = this._enteredElement);
+    this._pressedElement = this._draggedElement = this._enteredElement;
+    const element = this._pressedElement;
📝 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.

    this._pressedElement = this._draggedElement = this._enteredElement;
    const element = this._pressedElement;
🧰 Tools
🪛 Biome

[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIRenderer.ts (6)

134-135: 🛠️ Refactor suggestion

Eliminate the use of '@ts-ignore' by addressing type issues

The use of // @ts-ignore suppresses TypeScript's type checking, which may hide potential issues and reduce code maintainability. It's better to address the underlying type errors directly.

Consider modifying the base class or adjusting type declarations to eliminate the need for @ts-ignore. For example, if _componentType needs to be assigned, ensure it's properly declared in the base class or provide a setter method.


138-139: 🛠️ Refactor suggestion

Avoid accessing private properties directly

Accessing the _onValueChanged property directly may violate encapsulation principles, and using @ts-ignore to suppress the error is not ideal. Consider providing a public or protected method to handle the color change callback.

Refactor the Color class to expose a method or event for value changes, allowing you to subscribe without accessing private members.


179-182: 🛠️ Refactor suggestion

Replace '@ts-ignore' with proper type handling

The @ts-ignore directives here suppress errors related to _overrideUpdate and scene._componentsManager, possibly due to missing or incorrect type definitions.

Ensure that _overrideUpdate and scene._componentsManager are correctly defined and accessible. Update type declarations or class structures as necessary to eliminate the need for @ts-ignore.


187-190: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with '@ts-ignore'

Similar to previous comments, using @ts-ignore hides potential issues. Address the type errors directly for better code quality.

Ensure that methods like removeOnUpdateRenderers are properly defined in scene._componentsManager and that access levels permit their use.


215-215: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Using assignments within expressions can make the code less readable and potentially cause confusion. Consider refactoring the code for better clarity and maintainability.

Apply this diff to improve readability:

-          rootCanvas && (rootCanvas._hierarchyDirty = true);
+          if (rootCanvas) {
+            rootCanvas._hierarchyDirty = true;
+          }
📝 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.

      if (rootCanvas) {
            rootCanvas._hierarchyDirty = true;
          }
🧰 Tools
🪛 Biome

[error] 215-215: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


292-293: 🛠️ Refactor suggestion

Properly clean up event handlers without accessing private members

Setting _onValueChanged to null directly may lead to unexpected behavior, especially if _onValueChanged is a private property.

Provide a public method in the Color class to remove the event listener, ensuring encapsulation and proper cleanup.

-        //@ts-ignore
-        this._color._onValueChanged = null;
+        this._color.offValueChanged(this._onColorChange);

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/advanced/Image.ts (2)

123-124: 🛠️ Refactor suggestion

Avoid repeated use of @ts-ignore; properly handle TypeScript errors

There are several instances where @ts-ignore is used to suppress TypeScript errors (lines 123, 129, 147, 150, and 284). This practice can hide potential bugs and should be avoided. Please consider properly typing the properties or methods, or refactoring the code to handle the type errors safely.

Also applies to: 129-130, 147-147, 150-150, 284-285


27-27: ⚠️ Potential issue

Fix typo in variable name _tempUnit8Array

The variable name _tempUnit8Array seems to have a typo. It should be _tempUint8Array.

Apply this diff to correct the typo:

- private static _tempUnit8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
+ private static _tempUint8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
📝 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.

  private static _tempUint8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
packages/ui/src/component/advanced/Label.ts (5)

207-210: 🛠️ Refactor suggestion

Avoid using @ts-ignore; resolve TypeScript errors instead

The use of @ts-ignore suppresses TypeScript errors, which may hide underlying issues. It's better to address the root cause of the TypeScript errors to ensure type safety.

Consider updating the code to resolve the TypeScript errors without suppressing them.


456-456: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning maxY = Math.max(maxY, top) within a conditional expression can reduce code clarity.

Refactor the code as follows:

- i === firstLine && (maxY = Math.max(maxY, top));
+ if (i === firstLine) {
+   maxY = Math.max(maxY, top);
+ }
📝 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.

              if (i === firstLine) {
                maxY = Math.max(maxY, top);
              }
🧰 Tools
🪛 Biome

[error] 456-456: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


458-458: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning minX = Math.min(minX, left) within a conditional expression can be confusing.

Refactor the code as follows:

- j === firstRow && (minX = Math.min(minX, left));
+ if (j === firstRow) {
+   minX = Math.min(minX, left);
+ }
📝 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.

              if (j === firstRow) {
                minX = Math.min(minX, left);
              }
🧰 Tools
🪛 Biome

[error] 458-458: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


228-228: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Using an assignment within a conditional expression can be confusing and reduce code readability. It's clearer to separate the condition and the assignment.

Refactor the code as follows:

- this._subFont && (this._subFont = null);
+ if (this._subFont !== null) {
+   this._subFont = null;
+ }
📝 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.

    if (this._subFont !== null) {
      this._subFont = null;
    }
🧰 Tools
🪛 Biome

[error] 228-228: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


445-446: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning firstRow = j within a conditional expression can be misleading. It's better to use an explicit if statement for clarity.

Refactor the code as follows:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }
📝 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.

              if (firstRow < 0) {
                firstRow = j;
              }
              const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());
🧰 Tools
🪛 Biome

[error] 445-445: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UICanvas.ts (7)

406-408: ⚠️ Potential issue

Add missing break statement to prevent fallthrough in switch case

The case for ComponentType.UIRenderer is missing a break statement, causing it to unintentionally fall through to the next case ComponentType.UIInteractive. This can lead to unexpected behavior.

Apply this diff to fix the issue:

            switch (componentType) {
              case ComponentType.UIRenderer:
                elements[depth] = component as unknown as IGraphics;
                ++depth;
+               break;
              case ComponentType.UIInteractive:
                if ((component as unknown as IGroupAble)._isGroupDirty) {
                  tempGroupAbleList[groupAbleCount++] = component as unknown as IGroupAble;
                }
📝 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.

        case ComponentType.UIRenderer:
          elements[depth] = component as unknown as IGraphics;
          ++depth;
          break;
🧰 Tools
🪛 Biome

[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


222-222: 🛠️ Refactor suggestion

Avoid assignments within variable declarations

Assigning a value within a variable declaration can reduce readability and may introduce unintended side effects. It's better to separate the assignments.

Apply the following diff to refactor the code:

-        const renderElement = (this._renderElement = engine._renderElementPool.get());
+        this._renderElement = engine._renderElementPool.get();
+        const renderElement = this._renderElement;
📝 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.

        this._renderElement = engine._renderElementPool.get();
        const renderElement = this._renderElement;
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


587-588: ⚠️ Potential issue

Add missing break statement to prevent fallthrough in switch case

The case for CanvasRenderMode.ScreenSpaceOverlay is missing a break statement, causing unintended fallthrough to the next case. This can lead to unexpected behavior.

Apply this diff to fix the issue:

          switch (preRealMode) {
            case CanvasRenderMode.ScreenSpaceOverlay:
              this._removeCanvasListener();
+             break;
            case CanvasRenderMode.ScreenSpaceCamera:
            case CanvasRenderMode.WorldSpace:
              componentsManager.removeUICanvas(this, preRealMode === CanvasRenderMode.ScreenSpaceOverlay);
              break;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


437-437: 🛠️ Refactor suggestion

Avoid assignments within logical expressions

Assigning within a logical expression can reduce code clarity and introduce side effects. It's better to separate the assignment from the expression.

Apply this diff to refactor the code:

-          child.isActive && (depth = this._walk(child, elements, depth));
+          if (child.isActive) {
+            depth = this._walk(child, elements, depth);
+          }
📝 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.

      if (child.isActive) {
            depth = this._walk(child, elements, depth);
          }
🧰 Tools
🪛 Biome

[error] 437-437: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


597-601: ⚠️ Potential issue

Add missing break statements to prevent fallthrough in switch cases

The case clauses for CanvasRenderMode.ScreenSpaceOverlay and CanvasRenderMode.ScreenSpaceCamera are missing break statements, leading to unintended fallthrough. This can cause the code under subsequent cases to execute unintentionally.

Apply this diff to fix the issue:

          switch (curRealMode) {
            case CanvasRenderMode.ScreenSpaceOverlay:
              this._addCanvasListener();
+             break;
            case CanvasRenderMode.ScreenSpaceCamera:
              this._adapterPoseInScreenSpace();
              this._adapterSizeInScreenSpace();
+             break;
            case CanvasRenderMode.WorldSpace:
              componentsManager.addUICanvas(this, curRealMode === CanvasRenderMode.ScreenSpaceOverlay);
              break;
📝 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.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
          break;
        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
          break;
🧰 Tools
🪛 Biome

[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


159-159: 🛠️ Refactor suggestion

Avoid assignments within logical expressions

Assigning values within logical expressions can be confusing and may lead to unintended side effects. It's clearer to separate the assignment from the expression.

Apply the following diff to refactor the code:

-          this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
-            (this.scene._componentsManager._overlayCanvasesSortingFlag = true);
+          if (this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
+            this.scene._componentsManager._overlayCanvasesSortingFlag = true;
+          }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


236-238: ⚠️ Potential issue

Wrap variable declarations in switch cases with braces

Variables declared inside a case clause are accessible in subsequent cases due to JavaScript's scoping rules, which can lead to unexpected behavior. To prevent this, wrap the case block in braces to create a new scope.

Apply this diff to fix the issue:

            switch (mode) {
              case CanvasRenderMode.ScreenSpaceOverlay:
+            {
                const { min, max } = renderer.bounds;
                if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
                  continue;
                }
+            }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/design/src/ui/IUIElement.ts (1)

1-4: 🛠️ Refactor suggestion

Consider revising the API design for better encapsulation

The current design exposes implementation details through underscore-prefixed members in a public interface, which is unconventional. Consider separating the public and internal APIs.

Here's a suggested refactor:

-export interface IUIElement {
-  _onUIUpdateIndex: number;
-  _onUpdate(): void;
+/** Public interface for UI elements */
+export interface IUIElement {
+  /** Updates the UI element state */
+  update(): void;
+}
+
+/** Internal interface for UI system management */
+export interface IInternalUIElement extends IUIElement {
+  readonly updateIndex: number;
+}

This approach:

  1. Separates public and internal APIs
  2. Makes the update index readonly to prevent external modifications
  3. Provides a cleaner public interface for consumers
  4. Maintains type safety for internal system management
📝 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.

/** Public interface for UI elements */
export interface IUIElement {
  /** Updates the UI element state */
  update(): void;
}

/** Internal interface for UI system management */
export interface IInternalUIElement extends IUIElement {
  readonly updateIndex: number;
}
packages/design/src/ui/IUICanvas.ts (3)

1-7: 🛠️ Refactor suggestion

Improve type safety and interface design

The interface has several areas that could be improved:

  1. Replace any types with specific types to enhance type safety and IDE support
  2. Avoid mixing public and private members in interfaces
  3. Add JSDoc documentation

Consider refactoring the interface like this:

+ /**
+  * Represents a UI canvas that can be rendered in the scene.
+  */
 export interface IUICanvas {
-  entity: any;
-  sortOrder: number;
-  _canvasIndex: number;
-  _renderElement: any;
-  _prepareRender(renderContext: any): void;
+  /** The entity associated with this canvas */
+  entity: Entity;  // Replace with actual entity type
+  /** Determines the rendering order of the canvas */
+  sortOrder: number;
+  /** Prepares the canvas for rendering */
+  prepareRender(renderContext: RenderContext): void;  // Replace with actual context type
 }

+ /**
+  * Internal implementation details for UI canvas.
+  * Consider moving these to a separate implementation class.
+  */
+ interface IUICanvasImpl extends IUICanvas {
+  canvasIndex: number;
+  renderElement: RenderElement;  // Replace with actual element type
 }

Committable suggestion skipped: line range outside the PR's diff.


2-5: 💡 Codebase verification

Based on the search results, I can now generate the final response:

Use Entity and RenderElement types instead of any

The interface properties should be typed as follows:

  • entity should be typed as Entity since it's consistently used across the UI system
  • _renderElement should be typed as RenderElement as shown by its usage in UI canvas implementations

The types are available in the codebase and using them will provide better type safety and IDE support.

🔗 Analysis chain

Consider using strict typing for entity and renderElement

The use of any type reduces type safety. This could lead to runtime errors that TypeScript could otherwise catch during compilation.

Let's check how these types are used in the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for entity type definitions and usage
echo "Searching for Entity type definitions..."
ast-grep --pattern 'class $_ implements Entity'
ast-grep --pattern 'interface Entity'

echo "Searching for RenderElement type definitions..."
ast-grep --pattern 'class $_ implements RenderElement'
ast-grep --pattern 'interface RenderElement'

Length of output: 140


Script:

#!/bin/bash
# Let's try a broader search to understand the usage of these properties
echo "Searching for entity usage..."
rg "entity:" -A 2 -B 2

echo "\nSearching for _renderElement usage..."
rg "_renderElement:" -A 2 -B 2

echo "\nSearching for imports in the file..."
rg "import.*from" "packages/design/src/ui/IUICanvas.ts"

echo "\nChecking if this is an interface definition..."
rg "interface.*IUICanvas" "packages/design/src/ui/IUICanvas.ts"

Length of output: 36919


Script:

#!/bin/bash
# Let's search for the actual types used in implementations of IUICanvas
echo "Searching for UICanvas class implementations..."
rg "class.*implements.*IUICanvas"

echo "\nSearching for RenderElement type usage..."
rg "RenderElement" -A 2 -B 2

echo "\nSearching for Entity type usage..."
rg "Entity" "packages/design/src/ui/IUICanvas.ts"

Length of output: 33934


6-6: 💡 Codebase verification

Change any type to RenderContext from '@galacean/engine-core'

The renderContext parameter in _prepareRender should use the RenderContext type from the core package, as evidenced by:

  • The RenderContext class is defined in packages/core/src/RenderPipeline/RenderContext.ts
  • The method is used in UIUtils.ts with an instance of this RenderContext class
  • The interface is already importing other types from the core package
🔗 Analysis chain

Add type definition for renderContext parameter

The renderContext parameter should have a specific type rather than any to ensure type safety and provide better IDE support.

Let's check for existing render context types:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for RenderContext type definitions
echo "Searching for RenderContext type definitions..."
ast-grep --pattern 'interface RenderContext'
ast-grep --pattern 'class $_ implements RenderContext'

# Search for usage patterns
echo "Searching for renderContext parameter usage..."
rg -t ts "renderContext" -A 2 -B 2

Length of output: 208


Script:

#!/bin/bash
# Let's search more broadly for render context related types and usages
echo "Searching for render context related types..."
rg -t ts "type.*RenderContext|interface.*RenderContext|class.*RenderContext" -B 1 -A 3

echo "\nSearching for _prepareRender implementations..."
ast-grep --pattern 'class $_ {
  $$$
  _prepareRender($_) {
    $$$
  }
  $$$
}'

echo "\nSearching for files importing IUICanvas..."
rg -t ts "import.*IUICanvas"

Length of output: 1137


Script:

#!/bin/bash
# Let's examine the RenderContext class in detail
echo "Examining RenderContext class..."
cat packages/core/src/RenderPipeline/RenderContext.ts

# Let's also check how _prepareRender is used with RenderContext
echo "\nChecking _prepareRender usage with RenderContext..."
rg -t ts "_prepareRender.*renderContext" -B 2 -A 2

Length of output: 3941

packages/core/src/2d/text/ITextRenderer.ts (1)

7-7: 🛠️ Refactor suggestion

Reconsider the underscore prefix in method name

The method _getSubFont uses an underscore prefix, which typically indicates a private or internal member. However, in TypeScript interfaces, all members are inherently public. Consider removing the underscore prefix to better align with TypeScript conventions.

- _getSubFont(): SubFont;
+ getSubFont(): SubFont;
📝 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.

  getSubFont(): SubFont;
packages/ui/src/interface/IElement.ts (2)

4-6: 🛠️ Refactor suggestion

Add JSDoc documentation and consider architectural improvements.

  1. Add comprehensive JSDoc documentation to explain:

    • The purpose and responsibility of the IElement interface
    • The relationship between elements, entities, and canvas
    • Each property's purpose and usage
  2. Consider architectural improvements:

    • Could we reduce coupling with Entity by using a more abstract interface?
    • Should we add methods for lifecycle management and state updates?

Example documentation:

+/**
+ * Represents a UI element that can be rendered on a canvas.
+ * Provides core properties for managing the element's relationship
+ * with its underlying entity and canvas.
+ */
 export interface IElement {
+  /** The engine entity associated with this UI element */
   entity: Entity;
+  /** The canvas this element is rendered on */
   readonly canvas: UICanvas;
📝 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.

/**
 * Represents a UI element that can be rendered on a canvas.
 * Provides core properties for managing the element's relationship
 * with its underlying entity and canvas.
 */
export interface IElement {
  /** The engine entity associated with this UI element */
  entity: Entity;
  /** The canvas this element is rendered on */
  readonly canvas: UICanvas;

8-12: 💡 Codebase verification

⚠️ Potential issue

Based on the results, I can now generate the final response:

Consider using existing event patterns instead of exposing internal implementation details

The codebase already has established patterns for event handling, including:

  1. Proper event type enums (e.g., PointerEventType, XRInputEventType)
  2. Type-safe event parameters
  3. Clean event registration/unregistration mechanisms

Suggested refactor:

export enum CanvasEventType {
  Update = 1,
  Resize = 2,
  // Add other relevant types
}

export interface IElement {
  readonly entity: Entity;
  readonly canvas: UICanvas;
  
  // Public event API
  addEventListener(type: CanvasEventType, listener: (entity: Entity) => void): void;
  removeEventListener(type: CanvasEventType, listener: (entity: Entity) => void): void;
  dispose(): void;
}

This aligns with:

  • Existing event patterns in the codebase
  • Proper encapsulation of implementation details
  • Type-safe event handling
  • Clear cleanup through dispose pattern
🔗 Analysis chain

Critical: Refactor internal implementation details and improve type safety.

Several concerns with the current implementation:

  1. Internal implementation details shouldn't be exposed in the interface:

    • _indexInCanvas, _isCanvasDirty, _canvasListeningEntities should be implementation details
    • Consider moving these to an abstract class or internal interface
  2. Type safety issues:

    • Avoid using any type
    • The flag parameter should use an enum or const union type
  3. Potential memory management issues:

    • Storing entity references in _canvasListeningEntities could lead to memory leaks
    • Need clear cleanup mechanism for entity listeners

Here's a suggested refactor:

/** Defines valid canvas event types */
export enum CanvasEventType {
  Update = 1,
  Resize = 2,
  // ... add other event types
}

/** Type for canvas event parameters */
export type CanvasEventParam = {
  type: CanvasEventType;
  data: unknown;
};

export interface IElement {
  entity: Entity;
  readonly canvas: UICanvas;

  // Public methods instead of exposed internals
  addEventListener(type: CanvasEventType, listener: (event: CanvasEventParam) => void): void;
  removeEventListener(type: CanvasEventType, listener: (event: CanvasEventParam) => void): void;
  dispose(): void;
}

Let's verify the usage of internal properties across the codebase:


The initial results show extensive usage of these internal properties across multiple components. Let's verify:

  1. How the flag numbers are defined/used
  2. Whether proper cleanup mechanisms exist
  3. The implementation of the unregister mechanism

This will help determine if the suggested refactor is necessary or if the current implementation already has safeguards in place.


The previous results don't show clear event type definitions or proper cleanup patterns. Let's verify:

  1. If there are existing event type enums/constants
  2. The implementation of the registration mechanism
  3. Any type definitions for events

This will help determine if we need to introduce new event types or if there's an existing pattern we should follow.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for direct access to internal properties
rg "_indexInCanvas|_isCanvasDirty|_canvasListeningEntities|_canvasListener" --type ts

# Look for potential memory leak patterns with entity references
ast-grep --pattern 'class $_ implements IElement {
  $$$
  _canvasListeningEntities: Entity[] = [];
  $$$
}'

Length of output: 4697


Script:

#!/bin/bash
# Check for event type usage patterns
rg "flag.*=.*\d+" --type ts -A 2

# Check for cleanup/disposal patterns
ast-grep --pattern 'dispose() {
  $$$
  _unRegisterListener($$$)
  $$$
}'

# Check Utils._unRegisterListener implementation
ast-grep --pattern '_unRegisterListener($$$) {
  $$$
}'

Length of output: 2009


Script:

#!/bin/bash
# Check for event flag definitions and constants
rg "enum.*Event|const.*Event" --type ts -A 5

# Check Utils class implementation for event handling
ast-grep --pattern 'class Utils {
  $$$
  _registerListener($$$) {
    $$$
  }
  $$$
}'

# Look for event type definitions
fd -e ts -x grep -l "type.*Event"

Length of output: 29878

packages/ui/src/interface/IGroupAble.ts (1)

6-12: 🛠️ Refactor suggestion

Consider encapsulating internal state and improving type safety.

The interface mixes public and private members, which could lead to maintenance issues:

  1. Properties prefixed with underscore suggest internal implementation details but are still publicly accessible
  2. Optional properties (_globalAlpha, _globalInteractive) lack documentation explaining when they might be undefined

Consider this alternative structure:

 export interface IGroupAble extends IElement {
   readonly group: UIGroup;
-  _indexInGroup: number;
-  _groupListeningEntities: Entity[];
-  _isGroupDirty: boolean;
-  _onUIUpdateIndex?: number;
-  _globalAlpha?: number;
-  _globalInteractive?: boolean;
+  /** @internal */
+  private readonly _state: {
+    indexInGroup: number;
+    groupListeningEntities: Entity[];
+    isGroupDirty: boolean;
+    onUIUpdateIndex?: number;
+  };
+  
+  /**
+   * Global alpha value for the group.
+   * @description Controls the opacity of all elements in the group.
+   */
+  globalAlpha: number;
+  
+  /**
+   * Global interactive state for the group.
+   * @description Determines if the group and its children can receive input events.
+   */
+  globalInteractive: boolean;

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/interactive/transition/ScaleTransition.ts (1)

18-24: 🛠️ Refactor suggestion

Consider enhancing transition smoothness and adding validation.

A few suggestions for improvement:

  1. Consider using easing functions for smoother transitions
  2. Add validation for the weight parameter (should be between 0 and 1)
  3. Consider adding scale change event emission for animation tracking
   protected override _updateCurrentValue(srcValue: number, destValue: number, weight: number): void {
+    if (weight < 0 || weight > 1) {
+      throw new Error('Weight must be between 0 and 1');
+    }
-    this._currentValue = weight >= 1 ? destValue : (destValue - srcValue) * weight + srcValue;
+    // Apply easing function for smoother transition
+    const easedWeight = this._easeInOutQuad(weight);
+    this._currentValue = weight >= 1 ? destValue : (destValue - srcValue) * easedWeight + srcValue;
   }

+  private _easeInOutQuad(t: number): number {
+    return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
+  }

   protected override _applyValue(value: number): void {
     this._target.entity.transform.setScale(value, value, value);
+    this._target.entity.emit('scaleChange', value);
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/index.ts (1)

3-5: 💡 Codebase verification

Classes are not implementing the ISpriteAssembler interface

The sprite assembler classes (SimpleSpriteAssembler, SlicedSpriteAssembler, and TiledSpriteAssembler) are not implementing the ISpriteAssembler interface that exists in the codebase. This could lead to type safety issues and inconsistent implementations.

  • ./packages/core/src/2d/assembler/SimpleSpriteAssembler.ts: Class declared without interface implementation
  • ./packages/core/src/2d/assembler/SlicedSpriteAssembler.ts: Class declared without interface implementation
  • ./packages/core/src/2d/assembler/TiledSpriteAssembler.ts: Class declared without interface implementation
🔗 Analysis chain

Verify interface implementation in sprite assembler classes.

The new sprite assembler classes should implement the ISpriteAssembler interface.


Let me try a different approach to verify the interface implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that sprite assembler classes implement ISpriteAssembler interface

# Check implementation in each assembler class
for file in SimpleSpriteAssembler SlicedSpriteAssembler TiledSpriteAssembler; do
  echo "Checking $file..."
  ast-grep --pattern 'class $file implements $interface {
    $$$
  }' ./packages/core/src/2d/assembler/$file.ts
done

Length of output: 851


Script:

#!/bin/bash
# Search for the class declarations and their inheritance/implementation
echo "=== SimpleSpriteAssembler ==="
rg "class SimpleSpriteAssembler" ./packages/core/src/2d/assembler -A 2

echo -e "\n=== SlicedSpriteAssembler ==="
rg "class SlicedSpriteAssembler" ./packages/core/src/2d/assembler -A 2

echo -e "\n=== TiledSpriteAssembler ==="
rg "class TiledSpriteAssembler" ./packages/core/src/2d/assembler -A 2

# Also search for ISpriteAssembler interface definition
echo -e "\n=== ISpriteAssembler interface ==="
rg "interface ISpriteAssembler" ./packages/core/src/2d -A 2

Length of output: 1861

packages/ui/src/component/interactive/transition/SpriteTransition.ts (1)

9-32: 🛠️ Refactor suggestion

Improve type safety and reduce code duplication in _destroy method.

Several concerns in the cleanup logic:

  1. Multiple @ts-ignore comments without explanation
  2. Repetitive cleanup pattern for each state
  3. No error handling for reference counting

Consider refactoring to:

- override _destroy(): void {
-   if (this._normal) {
-     // @ts-ignore
-     this._normal._addReferCount(-1);
-     this._normal = null;
-   }
-   if (this._hover) {
-     // @ts-ignore
-     this._hover._addReferCount(-1);
-     this._hover = null;
-   }
-   if (this._pressed) {
-     // @ts-ignore
-     this._pressed._addReferCount(-1);
-     this._pressed = null;
-   }
-   if (this._disabled) {
-     // @ts-ignore
-     this._disabled._addReferCount(-1);
-     this._disabled = null;
-   }
-   this._initialValue = this._currentValue = this._finalValue = null;
-   this._target = null;
+ private _cleanupSprite(sprite: Sprite | null): void {
+   if (sprite) {
+     try {
+       (sprite as any)._addReferCount(-1);
+     } catch (e) {
+       console.warn('Failed to cleanup sprite reference:', e);
+     }
+   }
+ }
+
+ override _destroy(): void {
+   const states = ['_normal', '_hover', '_pressed', '_disabled'];
+   states.forEach(state => {
+     this._cleanupSprite(this[state]);
+     this[state] = null;
+   });
+   
+   this._initialValue = this._currentValue = this._finalValue = null;
+   this._target = null;
}

Also, please add a comment explaining why the type assertion is necessary for _addReferCount.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/advanced/Button.ts (2)

32-35: 🛠️ Refactor suggestion

Improve cleanup implementation in onDestroy.

Similar to the removeClicked method, the cleanup could be more explicit and effective.

Consider this implementation:

 override onDestroy(): void {
   super.onDestroy();
-  this._listeners.findAndRemove((value) => (value.destroyed = true));
+  // Clear all listeners
+  this._listeners.length = 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.

  override onDestroy(): void {
    super.onDestroy();
    // Clear all listeners
    this._listeners.length = 0;
  }
🧰 Tools
🪛 Biome

[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


11-21: 🛠️ Refactor suggestion

Improve listener management implementation.

The current implementation has two issues:

  1. Using assignment within the findAndRemove predicate reduces code clarity
  2. Marking listeners as destroyed without removing them could lead to memory leaks

Consider this cleaner implementation:

 removeClicked(listener: (event: PointerEventData) => void): void {
-  this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
+  this._listeners.findAndRemove((value) => {
+    if (value.fn === listener) {
+      value.destroyed = true;
+      return true;  // Remove the listener instead of just marking it
+    }
+    return false;
+  });
 }
📝 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.

  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.findAndRemove((value) => {
      if (value.fn === listener) {
        value.destroyed = true;
        return true;  // Remove the listener instead of just marking it
      }
      return false;
    });
  }
🧰 Tools
🪛 Biome

[error] 20-20: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts (1)

15-15: 🛠️ Refactor suggestion

Add input validation for method parameters

The render method lacks validation for its input parameters. This could lead to runtime errors if invalid inputs are provided.

 static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
+  if (!engine || !uiCanvases) {
+    throw new Error("Engine and uiCanvases parameters are required");
+  }
📝 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.

  static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
    if (!engine || !uiCanvases) {
      throw new Error("Engine and uiCanvases parameters are required");
    }
packages/ui/src/component/interactive/transition/ColorTransition.ts (2)

62-64: ⚠️ Potential issue

Add null check in _applyValue method.

The _applyValue method directly assigns to _target.color without checking if _target is defined.

   protected override _applyValue(value: Color): void {
-    this._target.color = value;
+    if (this._target) {
+      this._target.color = value;
+    }
   }
📝 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.

  protected override _applyValue(value: Color): void {
    if (this._target) {
      this._target.color = value;
    }
  }

22-29: 💡 Codebase verification

⚠️ Potential issue

Remove @ts-ignore comments by properly typing _onValueChanged property

The Color class already has the _onValueChanged property properly defined as () => void internally. The type safety issues can be resolved by:

  1. Removing the @ts-ignore comments
  2. Ensuring the callback methods (_onNormalValueChanged, etc.) match the () => void type signature

Example fix:

this._normal._onValueChanged = this._onNormalValueChanged;
this._hover._onValueChanged = this._onHoverValueChanged;
this._pressed._onValueChanged = this._onPressedValueChanged;
this._disabled._onValueChanged = this._onDisabledValueChanged;
🔗 Analysis chain

Address type safety issues instead of using @ts-ignore.

Multiple @ts-ignore comments suggest underlying type safety issues. Consider addressing these properly by:

  1. Adding proper type definitions for the _onValueChanged property
  2. Extending the Color class if needed

Let's verify if the Color class can be extended or modified:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Color class definition to understand its structure
ast-grep --pattern 'class Color {
  $$$
}'

Length of output: 44


Script:

#!/bin/bash
# Let's try to find the Color class definition with ripgrep
rg "class Color" -A 10

# Also search for any type definitions related to Color
rg "type.*Color" --type=typescript

# Search for Color-related files
fd -e ts -e tsx "Color"

# Search for _onValueChanged usage
rg "_onValueChanged" -A 2

Length of output: 38995


Script:

#!/bin/bash
# Let's examine the Color class implementation to understand its type structure
cat packages/math/src/Color.ts

Length of output: 9743

packages/core/src/ComponentsDependencies.ts (2)

14-14: 🛠️ Refactor suggestion

Fix type and consider adding documentation.

  1. Use boolean instead of Boolean for the map value type
  2. Consider adding JSDoc to document the purpose of this map
  3. Consider making the value type readonly since it's internal state
-  static _inheritedMap = new Map<ComponentConstructor, Boolean>();
+  /** Map tracking which components should be inherited by child entities */
+  private static _inheritedMap = new Map<ComponentConstructor, boolean>();
📝 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.

  /** Map tracking which components should be inherited by child entities */
  private static _inheritedMap = new Map<ComponentConstructor, boolean>();
🧰 Tools
🪛 Biome

[error] 14-14: Don't use 'Boolean' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'boolean' instead

(lint/complexity/noBannedTypes)


97-101: 🛠️ Refactor suggestion

Enhance type safety and add validation.

The decorator should validate its usage and prevent potential memory leaks.

 export function markAsInherited() {
   return function <T extends ComponentConstructor>(target: T): void {
+    if (!(target.prototype instanceof Component)) {
+      throw new Error("markAsInherited can only be used on Component classes");
+    }
+    if (ComponentsDependencies._inheritedMap.has(target)) {
+      console.warn(`Component ${target.name} is already marked as inherited`);
+      return;
+    }
     ComponentsDependencies._inheritedMap.set(target, true);
   };
 }

Additionally, consider adding a cleanup mechanism to prevent memory leaks when components are removed or unregistered.

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (2)

65-69: 🛠️ Refactor suggestion

Improve code readability by separating combined assignments

The combined assignment statements make the code harder to read and maintain. This was previously discussed but still needs to be addressed.

Apply this diff to improve readability:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

Also applies to: 74-78


169-173: 🛠️ Refactor suggestion

Maintain consistent code style in getUVByLocalPosition

The same combined assignment pattern is used here. For consistency with the earlier suggestions, these should also be separated.

Apply this diff:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;
📝 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.

      row[0] = expectWidth * left * widthScale;
      row[1] = fixedLeft * widthScale;
      row[2] = fixedLeft * widthScale;
      row[3] = width - expectWidth * (1 - right) * widthScale;
    } else {
      (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
      row[3] = width - expectWidth * (1 - right);
packages/core/src/input/pointer/PointerManager.ts (2)

151-157: ⚠️ Potential issue

Potential race condition in drag event processing

While the comment indicates that drag must be processed first, there's no synchronization mechanism to prevent race conditions when multiple emitters process events concurrently.

Consider adding a synchronization mechanism or using a queue to ensure proper event ordering:

- emitters.forEach((emitter) => {
-   emitter.processDrag(pointer);
- });
+ // Process drag events sequentially
+ for (const emitter of emitters) {
+   await emitter.processDrag(pointer);
+ }

Committable suggestion skipped: line range outside the PR's diff.


231-262: 🛠️ Refactor suggestion

Add error boundaries for event processing

The event handling logic should include error handling to prevent cascading failures.

Consider wrapping the event processing in try-catch blocks:

 switch (event.type) {
   case "pointerdown": {
+    try {
       const button = event.button;
       _downList.add(button);
       _downMap[button] = frameCount;
       pointer._downList.add(button);
       pointer._downMap[button] = frameCount;
       pointer._frameEvents |= PointerEventType.Down;
       pointer.phase = PointerPhase.Down;
+    } catch (error) {
+      console.error('Error processing pointer down event:', error);
+      pointer.phase = PointerPhase.Leave;  // Fail safe
+    }
     break;
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/ComponentsManager.ts (2)

185-188: ⚠️ Potential issue

Fix incorrect array reference in addOnUpdateUIElement

There's a critical bug where the method uses this._onUpdateRenderers.length instead of this._onUpdateUIElements.length for the index.

Apply this diff to fix the bug:

-    element._onUIUpdateIndex = this._onUpdateRenderers.length;
+    element._onUIUpdateIndex = this._onUpdateUIElements.length;
📝 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.

  addOnUpdateUIElement(element: IUIElement): void {
    element._onUIUpdateIndex = this._onUpdateUIElements.length;
    this._onUpdateUIElements.add(element);
  }

272-276: 🛠️ Refactor suggestion

Align UI update pattern with other component updates

The callUIOnUpdate method doesn't follow the consistent pattern of managing indices during iteration. This could lead to stale indices.

Apply this diff to align with other update methods:

   callUIOnUpdate(deltaTime: number): void {
-    this._onUpdateUIElements.forEach((element: IUIElement) => {
-      element._onUpdate();
-    });
+    this._onUpdateUIElements.forEach(
+      (element: IUIElement) => {
+        element._onUpdate();
+      },
+      (element: IUIElement, index: number) => {
+        element._onUIUpdateIndex = index;
+      }
+    );
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/text/TextUtils.ts (3)

98-103: ⚠️ Potential issue

Add parameter validation

The method should validate its numeric parameters to prevent potential rendering issues.

Add this validation at the start of the method:

 static measureTextWithWrap(
   renderer: ITextRenderer,
   rendererWidth: number,
   rendererHeight: number,
   lineSpacing: number
 ): TextMetrics {
+  if (rendererWidth <= 0 || rendererHeight <= 0 || lineSpacing < 0) {
+    throw new Error('Invalid dimensions: width and height must be positive, lineSpacing must be non-negative');
+  }
   const subFont = renderer._getSubFont();
📝 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.

  static measureTextWithWrap(
    renderer: ITextRenderer,
    rendererWidth: number,
    rendererHeight: number,
    lineSpacing: number
  ): TextMetrics {
    if (rendererWidth <= 0 || rendererHeight <= 0 || lineSpacing < 0) {
      throw new Error('Invalid dimensions: width and height must be positive, lineSpacing must be non-negative');
    }
    const subFont = renderer._getSubFont();

98-103: 💡 Codebase verification

Tests need to be updated with new method parameters

The test file is using outdated method signatures for both measureTextWithWrap and measureTextWithoutWrap. All production code has been properly updated, but the test file needs to be fixed. Here are the issues:

  • In measureTextWithWrap test cases, the method is called without required parameters: rendererWidth, rendererHeight, and lineSpacing
  • In measureTextWithoutWrap test cases, the method is called without required parameters: rendererHeight and lineSpacing

The test renderer objects already have these values set up:

  • width and height properties are set before each test
  • lineSpacing can be derived from the expected lineHeight assertions (27)
🔗 Analysis chain

Verify all callers are updated with new parameters

The signature changes for both measurement methods require verification that all calling code has been updated accordingly.

Also applies to: 275-275


Let me analyze the search results:

For measureTextWithWrap, there are calls in:

  1. packages/ui/src/component/advanced/Label.ts - Correctly provides all parameters: this, rendererWidth, rendererHeight, this._lineSpacing
  2. packages/core/src/2d/text/TextRenderer.ts - Correctly provides all parameters (using _pixelsPerUnit scaling)
  3. tests/src/core/2d/text/TextUtils.test.ts - Test calls appear to be using old signature

For measureTextWithoutWrap, there are calls in:

  1. packages/ui/src/component/advanced/Label.ts - Correctly provides all parameters: this, rendererHeight, this._lineSpacing
  2. packages/core/src/2d/text/TextRenderer.ts - Correctly provides all parameters (using _pixelsPerUnit scaling)
  3. tests/src/core/2d/text/TextUtils.test.ts - Test calls appear to be using old signature

Let me verify the test file to see if it's outdated:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all calls to these methods
echo "Searching for measureTextWithWrap calls:"
rg -A 2 "measureTextWithWrap\(" --type ts

echo -e "\nSearching for measureTextWithoutWrap calls:"
rg -A 2 "measureTextWithoutWrap\(" --type ts

Length of output: 7755


Script:

#!/bin/bash
# Check the test file content around the method calls
cat tests/src/core/2d/text/TextUtils.test.ts

Length of output: 14944


275-275: ⚠️ Potential issue

Add parameter validation

Similar to measureTextWithWrap, this method should validate its numeric parameters.

Add this validation at the start of the method:

 static measureTextWithoutWrap(
   renderer: ITextRenderer,
   rendererHeight: number,
   lineSpacing: number
 ): TextMetrics {
+  if (rendererHeight <= 0 || lineSpacing < 0) {
+    throw new Error('Invalid dimensions: height must be positive, lineSpacing must be non-negative');
+  }
   const subFont = renderer._getSubFont();

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (2)

81-86: ⚠️ Potential issue

Replace global isNaN with Number.isNaN

Using the global isNaN is unsafe as it performs type coercion. Use Number.isNaN instead for more predictable behavior.

-      if (isNaN(cUV.get(doubleJ)) || isNaN(cUV.get(doubleJ + 1))) {
+      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
         continue;
       }
       for (let i = 0; i < rowLength; i++) {
         const doubleI = i << 1;
-        if (isNaN(rUV.get(doubleI)) || isNaN(rUV.get(doubleI + 1))) {
+        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
📝 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.

      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
        continue;
      }
      for (let i = 0; i < rowLength; i++) {
        const doubleI = i << 1;
        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
🧰 Tools
🪛 Biome

[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


422-444: ⚠️ Potential issue

Add block scoping to switch cases

Variables declared in switch cases can leak into other cases. Wrap the declarations in blocks to prevent this.

     switch (rType) {
       case TiledType.Compressed: {
-        const scale = width / fixedLR;
-        rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
-        rPos.add(width - expectWidth * (1 - right) * scale);
-        rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
+        {
+          const scale = width / fixedLR;
+          rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
+          rPos.add(width - expectWidth * (1 - right) * scale);
+          rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
+        }
         break;
       }
       case TiledType.WithTiled: {
-        const uv1 = spriteUV1.x;
-        const uv2 = spriteUV2.x;
-        const repeatWidth = (width - fixedLR) / rTiledCount;
+        {
+          const uv1 = spriteUV1.x;
+          const uv2 = spriteUV2.x;
+          const repeatWidth = (width - fixedLR) / rTiledCount;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 422-422: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 434-434: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 435-435: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 436-436: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/Entity.ts (1)

613-622: 🛠️ Refactor suggestion

Fix assignment in expression

The modification listener management is good, but the assignment in expression should be split for better readability.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 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.

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }
🧰 Tools
🪛 Biome

[error] 614-614: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (76)
packages/ui/src/component/UITransform.ts (1)

32-41: ⚠️ Potential issue

Avoid using @ts-ignore to suppress TypeScript errors

The use of @ts-ignore comments in the constructor to access private or protected properties can lead to maintenance issues and hides potential type safety problems. Specifically, accessing:

  • entity._transform.destroy();
  • Setting entity._transform = this;
  • Assigning to entity._inverseWorldMatFlag
  • Binding _onValueChanged callbacks for _size and _pivot

This practice violates encapsulation principles and may cause unexpected behavior if internal implementations of these classes change.

Consider the following actions:

  • Expose necessary functionality through public APIs: Modify the Entity, Transform, and Vector2 classes to provide public methods or properties that allow the required interactions.
  • Refactor the design: Reevaluate the class interactions to ensure that they adhere to object-oriented design principles without needing to access internal properties.

This approach maintains type safety and improves code maintainability.

packages/ui/src/component/interactive/transition/Transition.ts (4)

25-67: 🛠️ Refactor suggestion

Avoid comparing object references in setters; use appropriate equality checks instead

In the setters for normal, pressed, hover, and disabled (lines 25-67), comparing objects using !== may not accurately detect changes for object types like Sprite or Color, as it checks reference equality rather than value equality. This could lead to missed updates when the object's properties change but the reference remains the same. Consider implementing proper equality checks to ensure changes are detected correctly.

You can modify the comparison to use a deep equality check or implement an equality method for TransitionValueType. For example:

-if (preNormal !== value) {
+if (!this._areValuesEqual(preNormal, value)) {

And add a helper method _areValuesEqual:

private _areValuesEqual(a: T, b: T): boolean {
  if (typeof a === 'number' && typeof b === 'number') {
    return a === b;
  } else if (a instanceof Color && b instanceof Color) {
    return a.equals(b);
  } else if (a instanceof Sprite && b instanceof Sprite) {
    return a === b; // Update this logic if necessary to compare Sprites
  }
  return false;
}

151-162: ⚠️ Potential issue

Add a default case in '_getValueByState' to prevent returning undefined

In the _getValueByState method (lines 151-162), there is no default case in the switch statement. If an unexpected InteractiveState value is passed, the method may return undefined, leading to potential runtime errors when the value is used elsewhere. To enhance robustness, add a default case to handle unexpected states.

Apply the following modification:

private _getValueByState(state: InteractiveState): T {
  switch (state) {
    case InteractiveState.Normal:
      return this.normal;
    case InteractiveState.Pressed:
      return this.pressed;
    case InteractiveState.Hover:
      return this.hover;
    case InteractiveState.Disable:
      return this.disabled;
+   default:
+     return this.normal; // Or handle appropriately based on your application's logic
  }
}

Alternatively, you could throw an error for unrecognized states to catch issues during development:

default:
  throw new Error(`Unhandled InteractiveState: ${state}`);

134-143: 🛠️ Refactor suggestion

Avoid using '// @ts-ignore'; properly handle TypeScript type checks

In the _onStateValueDirty method (lines 134-143), suppressing TypeScript errors with // @ts-ignore is discouraged, as it can conceal underlying issues. Instead, use type assertions or type guards to handle cases where preValue and curValue might be instances of ReferResource.

Modify the code to include type checks without suppressing TypeScript errors:

-// @ts-ignore
-preValue instanceof ReferResource && preValue._addReferCount(-1);
-// @ts-ignore
-curValue instanceof ReferResource && curValue._addReferCount(1);
+if (preValue instanceof ReferResource) {
+  preValue._addReferCount(-1);
+}
+if (curValue instanceof ReferResource) {
+  curValue._addReferCount(1);
+}

Ensure that TransitionValueType is correctly defined to include types that may extend ReferResource, or adjust the type casting as needed.

📝 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.

  protected _onStateValueDirty(state: InteractiveState, preValue: T, curValue: T): void {
    if (preValue instanceof ReferResource) {
      preValue._addReferCount(-1);
    }
    if (curValue instanceof ReferResource) {
      curValue._addReferCount(1);
    }
    if (this._finalState === state) {
      this._finalValue = curValue;
      this._updateValue();
    }
  }

85-94: ⚠️ Potential issue

Fix potential division by zero in duration setter

In the set duration(value: number) method (lines 85-94), if preDuration is zero and _countDown > 0, the calculation this._countDown / preDuration will result in a division by zero error. This can cause runtime exceptions and unexpected behavior. Ensure that preDuration is not zero before performing the division to prevent this issue.

Apply the following fix:

const preDuration = this._duration;
if (preDuration !== value) {
  this._duration = value;
  if (this._countDown > 0) {
+   if (preDuration !== 0) {
      this._countDown = value * (1 - this._countDown / preDuration);
+   } else {
+     this._countDown = value;
+   }
    this._updateValue();
  }
}
📝 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.

    if (value < 0) value = 0;
    const preDuration = this._duration;
    if (preDuration !== value) {
      this._duration = value;
      if (this._countDown > 0) {
        if (preDuration !== 0) {
          this._countDown = value * (1 - this._countDown / preDuration);
        } else {
          this._countDown = value;
        }
        this._updateValue();
      }
    }
  }
packages/ui/src/Utils.ts (5)

133-133: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

For improved readability, refactor the assignment to be explicit.

Apply this diff:

-            replaced && (replaced._indexInCanvas = element._indexInCanvas);
+            if (replaced) {
+              replaced._indexInCanvas = element._indexInCanvas;
+            }
📝 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.

      if (replaced) {
        replaced._indexInCanvas = element._indexInCanvas;
      }
🧰 Tools
🪛 Biome

[error] 133-133: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


100-100: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Using assignments within expressions can reduce code readability. Refactor the assignment to make it explicit.

Apply this diff:

-            replaced && (replaced._indexInGroup = element._indexInGroup);
+            if (replaced) {
+              replaced._indexInGroup = element._indexInGroup;
+            }
📝 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.

        if (replaced) {
          replaced._indexInGroup = element._indexInGroup;
        }
🧰 Tools
🪛 Biome

[error] 100-100: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


122-122: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Assignments within expressions can be confusing. It's better to make the assignment explicit.

Apply this diff:

-            replaced && (replaced._indexInGroup = element._indexInGroup);
+            if (replaced) {
+              replaced._indexInGroup = element._indexInGroup;
+            }
📝 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.

      if (replaced) {
        replaced._indexInGroup = element._indexInGroup;
      }
🧰 Tools
🪛 Biome

[error] 122-122: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


135-135: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Making assignments explicit enhances code clarity.

Apply this diff:

-            isGraphics && (preCanvas._hierarchyDirty = true);
+            if (isGraphics) {
+              preCanvas._hierarchyDirty = true;
+            }
📝 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.

            if (isGraphics) {
              preCanvas._hierarchyDirty = true;
            }
🧰 Tools
🪛 Biome

[error] 135-135: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


79-79: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Using assignments within expressions can make the code harder to read and may lead to confusion. It's recommended to refactor the assignment to make it more explicit.

Apply this diff to improve clarity:

-            replaced && (replaced._indexInCanvas = element._indexInCanvas);
+            if (replaced) {
+              replaced._indexInCanvas = element._indexInCanvas;
+            }
📝 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.

        if (replaced) {
          replaced._indexInCanvas = element._indexInCanvas;
        }
🧰 Tools
🪛 Biome

[error] 79-79: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts (1)

61-61: 🛠️ Refactor suggestion

Avoid assignments within expressions to improve readability

Assigning variables within an expression can reduce code readability and may introduce unintended side effects. It's better to separate assignments into individual statements.

Apply this diff to improve clarity:

-    const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity);
+    this._pressedEntity = this._draggedEntity = this._enteredEntity;
+    const entity = this._pressedEntity;
📝 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.

    this._pressedEntity = this._draggedEntity = this._enteredEntity;
    const entity = this._pressedEntity;
🧰 Tools
🪛 Biome

[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIGroup.ts (4)

90-94: ⚠️ Potential issue

Avoid using @ts-ignore; ensure TypeScript compatibility instead.

Using @ts-ignore suppresses TypeScript errors, potentially hiding real issues. The override modifier and direct assignment to _componentType might not be recognized in your current TypeScript configuration. Ensure that your project uses a compatible TypeScript version (4.3 or higher) and adjust access modifiers or class structures to avoid the need for @ts-ignore.


148-149: ⚠️ Potential issue

Remove @ts-ignore and verify method overriding correctly.

The use of @ts-ignore before the override keyword suppresses potential TypeScript errors. Ensure that the base class Component has the method _onEnableInScene and that you're correctly overriding it. Updating your TypeScript version or compiler options might resolve this without ignoring errors.


184-190: ⚠️ Potential issue

Use bitwise operations when checking flags in _canvasListener.

Similar to the previous issue, the flag check should use bitwise operations to handle multiple flags correctly.

Apply this diff:

- if (flag === EntityModifyFlags.Parent) {
+ if ((flag & EntityModifyFlags.Parent) !== 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.

  _canvasListener(flag: number): void {
    if (this._isCanvasDirty) return;
    if ((flag & EntityModifyFlags.Parent) !== 0) {
      Utils._onCanvasDirty(this, this._canvas);
      Utils._onGroupDirty(this, this._group);
    }
  }

174-178: ⚠️ Potential issue

Use bitwise operations when checking flags in _groupListener.

The current flag check uses strict equality, which may fail if multiple flags are set simultaneously. Modify the condition to use bitwise operations to correctly detect flag combinations.

Apply this diff to fix the issue:

- if (flag === EntityModifyFlags.Parent || flag === EntityUIModifyFlags.UIGroupEnableInScene) {
+ if ((flag & (EntityModifyFlags.Parent | EntityUIModifyFlags.UIGroupEnableInScene)) !== 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.

    if (this._isGroupDirty) return;
    if ((flag & (EntityModifyFlags.Parent | EntityUIModifyFlags.UIGroupEnableInScene)) !== 0) {
      Utils._onGroupDirty(this, this._group);
    }
  }
packages/ui/src/component/interactive/UIInteractive.ts (4)

151-152: ⚠️ Potential issue

Avoid accessing private members of other classes

Accessing _componentsManager from this.scene in lines 151 and 162 is likely accessing a private member of the Scene class. Accessing private properties breaks encapsulation and can lead to maintenance issues.

Consider using public methods or properties provided by Scene to register and unregister UI elements. If such methods do not exist, it may be necessary to update the Scene class to expose the required functionality.

Also applies to: 162-163


176-183: 🛠️ Refactor suggestion

Ensure _onUpdate properly updates interaction state

Currently, _onUpdate only calls _updateGlobalInteractive(). To ensure that interaction states are consistently updated, consider also calling _updateState(false) within this method.

Apply this change:

  _onUpdate(): void {
    this._updateGlobalInteractive();
+   this._updateState(false);
  }

This guarantees that the state reflects any changes in interactivity each frame.

📝 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.

  /**
   * @internal
   */
  _onUpdate(): void {
    this._updateGlobalInteractive();
    this._updateState(false);
  }

  /**

146-149: ⚠️ Potential issue

Address the use of multiple @ts-ignore comments

The repeated use of // @ts-ignore suggests that TypeScript errors are being suppressed. This can hide potential issues and reduce type safety. Investigate and resolve the underlying TypeScript errors instead of suppressing them.

For example, if the _onEnableInScene method is not defined in the parent class or has a different access modifier, consider updating the method access or properly extending the class.

- // @ts-ignore
  override _onEnableInScene(): void {
-   // @ts-ignore
    super._onEnableInScene();
    // ...
  }

Ensure that _onEnableInScene is correctly declared in the parent class Script and that it can be overridden.

Committable suggestion skipped: line range outside the PR's diff.


104-109: 🛠️ Refactor suggestion

Refine generic type constraints in addTransition method

In the addTransition method, the generic type T is constrained as new () => Transition, but this may not correctly capture subclasses of Transition with their own constructor signatures.

Modify the generic constraint to accept any constructor of a Transition subclass:

-addTransition<T extends new () => Transition>(type: T): InstanceType<T> {
+addTransition<T extends Transition>(type: { new (...args: any[]): T }): T {
  const transition = new type();
  // ...
}

Similarly, update the return type to T for clarity.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/input/UIPointerEventEmitter.ts (8)

103-103: ⚠️ Potential issue

Avoid assignment within an expression for clarity

At line 103, there is an assignment within an expression:

const element = (this._pressedElement = this._draggedElement = this._enteredElement);

This can make the code harder to read and understand. It's better to separate the assignments for improved readability.

Apply this diff to refactor the code:

- const element = (this._pressedElement = this._draggedElement = this._enteredElement);
+ this._pressedElement = this._draggedElement = this._enteredElement;
+ const element = this._pressedElement;
🧰 Tools
🪛 Biome

[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


222-222: ⚠️ Potential issue

Avoid assignment within an expression for clarity

At line 222, there's an assignment within an expression:

let entity = (path[0] = element.entity);

Assigning within an expression can reduce code readability. Separating the assignments makes the code clearer.

Apply this diff to refactor the code:

- let entity = (path[0] = element.entity);
+ path[0] = element.entity;
+ let entity = path[0];
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


35-35: 🛠️ Refactor suggestion

Accessing private properties using @ts-ignore is discouraged

At line 35:

// @ts-ignore
const componentsManager = scene._componentsManager;

Using @ts-ignore to access private members bypasses TypeScript's type checking, which can lead to maintenance issues and potential runtime errors.

Consider exposing componentsManager through a public getter or method in the Scene class. This way, you can access it without ignoring TypeScript's checks.


205-212: 🛠️ Refactor suggestion

Avoid using @ts-ignore to access private members

At lines 205-212:

// @ts-ignore
entity._scripts.forEach(
  (script: Script) => {
    script[methods]?.(eventData);
  },
  (script: Script, index: number) => {
    // @ts-ignore
    script._entityScriptsIndex = index;
  }
);

Using @ts-ignore to access _scripts and _entityScriptsIndex can lead to fragile code. It bypasses the safety offered by TypeScript's access modifiers.

Consider adding public methods or accessors to interact with the scripts. For example, the Entity class could provide a method to get its scripts, and Script could manage its index internally.


225-229: 🛠️ Refactor suggestion

Ensure proper access to component properties

At lines 225-229, you are accessing _componentType and _isRootCanvas:

// @ts-ignore
if (element._componentType === ComponentType.UICanvas && element._isRootCanvas) {
  path.length = 1;
  return path;
} else {

Accessing private or protected properties with @ts-ignore should be avoided as it circumvents TypeScript's type safety.

Propose exposing necessary properties or methods in the Component or UICanvas classes to access this information without using @ts-ignore.


217-235: ⚠️ Potential issue

Potential infinite loop in _composedPath method

In the _composedPath method, you have a loop that depends on entity.parent:

for (; i < UIPointerEventEmitter._MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
  entity = path[i] = entity.parent;
}

There is a risk of entering an infinite loop if there is a cycle in the entity's parent hierarchy or if rootEntity is not correctly identified.

Consider adding safeguards to detect cycles or ensure that rootEntity is correctly set.

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


89-89: 💡 Codebase verification

Return statement is inconsistent with void return type

The processRaycast method is declared with a void return type but includes a return null statement. This is unnecessary since:

  1. The method already has multiple return statements without values
  2. Void functions should not return values in TypeScript

Remove the return null statement at line 89 as it's both unnecessary and inconsistent with the method's signature.

🔗 Analysis chain

Verify: Consistency of the return type with method signature

As per the static analysis hint, the function processRaycast is expected to return void, but it includes a return null; statement at line 89.

Run the following script to check for any other instances where void functions return a value:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find methods declared as void that return a value
ast-grep 'function $_(...) : void { $$$; return $_; $$$ }' --lang typescript

Length of output: 223


Script:

#!/bin/bash
# Let's first check the method signature and implementation
rg -U "processRaycast.*void.*\{[\s\S]*?return null" packages/ui/src/input/UIPointerEventEmitter.ts

# Also check the entire file content for better context
cat packages/ui/src/input/UIPointerEventEmitter.ts

Length of output: 10852

🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


26-89: ⚠️ Potential issue

Remove the return statement in a void function

The processRaycast method is declared with a return type of void, but it includes a return null; statement at line 89. Returning a value from a void function can cause confusion and may lead to unexpected behavior.

Apply this diff to correct the issue:

89 -        return null;
+        return;
📝 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.

  override processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
    const { _tempRay: ray } = PointerEventEmitter;
    const hitResult = this._hitResult;
    const { position } = pointer;
    const { x, y } = position;
    for (let i = scenes.length - 1; i >= 0; i--) {
      const scene = scenes[i];
      if (!scene.isActive || scene.destroyed) continue;
      // @ts-ignore
      const componentsManager = scene._componentsManager;
      /** Overlay Canvas */
      let canvasElements = componentsManager._overlayCanvases;
      ray.origin.set(position.x, position.y, 1);
      ray.direction.set(0, 0, -1);
      for (let j = canvasElements.length - 1; j >= 0; j--) {
        if (canvasElements.get(j).raycast(ray, hitResult)) {
          this._updateRaycast(<Component>hitResult.component, pointer);
          return;
        }
      }

      const cameras = componentsManager._activeCameras;
      for (let j = cameras.length - 1; j >= 0; j--) {
        const camera = cameras.get(j);
        if (camera.renderTarget) continue;
        const { pixelViewport } = camera;
        if (
          x < pixelViewport.x ||
          y < pixelViewport.y ||
          x > pixelViewport.x + pixelViewport.width ||
          y > pixelViewport.y + pixelViewport.height
        ) {
          continue;
        }
        camera.screenPointToRay(pointer.position, ray);

        /** Other canvases */
        const cameraPosition = camera.entity.transform.position;
        /** Sort by rendering order */
        canvasElements = componentsManager._canvases;
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          canvasElements.get(k)._updateSortDistance(cameraPosition);
        }
        canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          canvasElements.get(k)._canvasIndex = k;
        }
        const farClipPlane = camera.farClipPlane;
        /** Post-rendering first detection */
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          const canvas = canvasElements.get(k);
          if (canvas.renderCamera !== camera) continue;
          if (canvas.raycast(ray, hitResult, farClipPlane)) {
            this._updateRaycast(<Component>hitResult.component, pointer);
            return;
          }
        }
        if (camera.clearFlags & CameraClearFlags.Color) {
          this._updateRaycast(null);
          return;
        }
      }
    }
    return;
🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

packages/ui/src/component/UIRenderer.ts (5)

134-135: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with // @ts-ignore

The use of // @ts-ignore suppresses TypeScript compiler errors, which can mask underlying issues in the code. It's important to address the root cause of the TypeScript error to ensure type safety and maintainability.

Consider adding a proper type declaration for _componentType:

// Before the constructor, add the property declaration
protected _componentType: ComponentType;

This will eliminate the need for // @ts-ignore and make the code more robust.


139-139: 🛠️ Refactor suggestion

Avoid direct access to internal properties

Directly accessing internal properties like _onValueChanged can break encapsulation and lead to maintenance challenges. It's better to use public methods or properties provided by the class.

Consider adding a method to Color class to handle the value change subscription:

// In Color class
public set onValueChanged(callback: () => void) {
  this._onValueChanged = callback;
}

And then update your code:

-    //@ts-ignore
-    this._color._onValueChanged = this._onColorChange;
+    this._color.onValueChanged = this._onColorChange;

292-292: 🛠️ Refactor suggestion

Avoid direct assignment to internal properties

Setting internal properties like _onValueChanged to null directly can cause unexpected behavior. Provide a proper method to unsubscribe or dispose of event handlers.

Modify the Color class to include an unsubscribe method:

// In Color class
public clearOnValueChanged(): void {
  this._onValueChanged = null;
}

And update your code accordingly:

-    //@ts-ignore
-    this._color._onValueChanged = null;
+    this._color.clearOnValueChanged();

173-175: 🛠️ Refactor suggestion

Resolve TypeScript issues instead of using // @ts-ignore

Using // @ts-ignore bypasses TypeScript's type checking, which can lead to runtime errors. It's crucial to address the type incompatibilities to maintain type safety.

Investigate the types of this.shaderData._macroCollection and this._globalShaderMacro to ensure they match the expected parameters of ShaderMacroCollection.unionCollection. Update the type definitions if necessary to align with the method's requirements.


214-215: ⚠️ Potential issue

Refactor assignment in expression for clarity

Using an assignment within a logical expression can be confusing and may lead to unintended side effects. At line 215, the assignment _hierarchyDirty = true is made within a logical AND operation. This can be refactored for better readability and to follow best practices.

Apply this diff to improve the code clarity:

       const rootCanvas = this._canvas;
-      rootCanvas && (rootCanvas._hierarchyDirty = true);
+      if (rootCanvas) {
+        rootCanvas._hierarchyDirty = true;
+      }
📝 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.

      const rootCanvas = this._canvas;
      if (rootCanvas) {
        rootCanvas._hierarchyDirty = true;
      }
🧰 Tools
🪛 Biome

[error] 215-215: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/advanced/Image.ts (2)

150-150: ⚠️ Potential issue

Undefined method _onColorChange

The method _onColorChange is referenced but not defined in the Image class. This will cause a runtime error when _onColorChange is called.

Consider adding the _onColorChange method to handle updates when the color changes. Here's a possible implementation:

private _onColorChange(): void {
  this._dirtyUpdateFlag |= UIRendererUpdateFlags.Color;
}

146-147: 🛠️ Refactor suggestion

Avoid using @ts-ignore by addressing TypeScript type issues

Using @ts-ignore suppresses TypeScript errors, potentially hiding bugs and undermining type safety. It's better to resolve the underlying type issues to ensure code reliability.

Review the following instances where @ts-ignore is used:

  • Lines 123-125 and 129-131: Accessing _updateFlagManager on Sprite.
  • Lines 146-147 and 218-219: Accessing _engine._basicResources.uiDefaultMaterial.
  • Lines 149-150: Assigning to this._color._onValueChanged.

Consider the following actions:

  • Expose necessary properties or methods: If properties like _updateFlagManager and _basicResources are needed externally, provide public getters or methods in their respective classes.
  • Adjust TypeScript definitions: Ensure all accessed properties and methods are correctly typed and accessible according to their visibility (public, protected, private).
  • Refactor code: Modify the design to eliminate the need for accessing internal properties or using @ts-ignore.

Also applies to: 149-150, 123-125, 129-131, 218-219

packages/ui/src/component/advanced/Label.ts (6)

228-228: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Using assignments within expressions can make the code harder to read and maintain. Consider refactoring the assignment for better readability.

-        this._subFont && (this._subFont = null);
+        if (this._subFont) {
+          this._subFont = null;
+        }
📝 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.

    if (this._subFont) {
      this._subFont = null;
    }
🧰 Tools
🪛 Biome

[error] 228-228: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


458-458: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

For better code readability, avoid assigning values within expressions. Refactor to use an if statement.

-              j === firstRow && (minX = Math.min(minX, left));
+              if (j === firstRow) {
+                minX = Math.min(minX, left);
+              }
📝 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.

              if (j === firstRow) {
                minX = Math.min(minX, left);
              }
🧰 Tools
🪛 Biome

[error] 458-458: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


456-456: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Assignments within expressions can reduce code clarity. Use an explicit conditional statement instead.

-              i === firstLine && (maxY = Math.max(maxY, top));
+              if (i === firstLine) {
+                maxY = Math.max(maxY, top);
+              }
📝 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.

              if (i === firstLine) {
                maxY = Math.max(maxY, top);
              }
🧰 Tools
🪛 Biome

[error] 456-456: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


445-446: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

The assignment inside the logical expression can be confusing. Refactor to an explicit if statement for improved readability.

              if (charInfo.h > 0) {
-                firstRow < 0 && (firstRow = j);
+                if (firstRow < 0) {
+                  firstRow = j;
+                }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 445-445: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


231-234: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with @ts-ignore

Suppressing TypeScript checks can mask potential issues. Refactor the code to address the type errors without using @ts-ignore.

-      // @ts-ignore
       override _cloneTo(target: Label, srcRoot: Entity, targetRoot: Entity): void {
-        // @ts-ignore
         super._cloneTo(target, srcRoot, targetRoot);
         target.font = this._font;
       }

Ensure that method overrides correctly match the base class signatures and that types are properly defined.

Committable suggestion skipped: line range outside the PR's diff.


207-210: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with @ts-ignore

Using @ts-ignore may hide underlying issues and lead to maintenance challenges. Consider resolving the TypeScript errors directly or adjusting the access modifiers.

-        // @ts-ignore
         this.font = engine._textDefaultFont;
-        // @ts-ignore
         this.setMaterial(engine._basicResources.textDefaultMaterial);

If accessing private or protected members, consider exposing them through public getters or adjusting the class design.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/UICanvas.ts (6)

587-588: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

In the switch statement at line 586, the case CanvasRenderMode.ScreenSpaceOverlay lacks a break, causing it to fall through to the next case unintentionally.

Apply this diff to add the missing break:

            case CanvasRenderMode.ScreenSpaceOverlay:
              this._removeCanvasListener();
+             break;
📝 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.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._removeCanvasListener();
          break;
🧰 Tools
🪛 Biome

[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


159-159: 🛠️ Refactor suggestion

Avoid assignment within a logical expression for better readability

On line 159, assigning this.scene._componentsManager._overlayCanvasesSortingFlag = true within a logical expression can be confusing and reduce code clarity.

Apply this diff to improve readability:

         this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
           // @ts-ignore
-          (this.scene._componentsManager._overlayCanvasesSortingFlag = true);
+          this.scene._componentsManager._overlayCanvasesSortingFlag = true;
📝 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.

        this.scene._componentsManager._overlayCanvasesSortingFlag = true;
🧰 Tools
🪛 Biome

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


597-598: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

At line 597, the case CanvasRenderMode.ScreenSpaceOverlay is missing a break statement, leading to unintended execution of subsequent cases.

Apply this diff to correct the switch case:

            case CanvasRenderMode.ScreenSpaceOverlay:
              this._addCanvasListener();
+             break;
📝 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.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
          break;
🧰 Tools
🪛 Biome

[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


599-601: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

The case CanvasRenderMode.ScreenSpaceCamera at line 599 lacks a break, causing unintended fall-through to the next case.

Apply this diff to add the missing break:

            case CanvasRenderMode.ScreenSpaceCamera:
              this._adapterPoseInScreenSpace();
              this._adapterSizeInScreenSpace();
+             break;
📝 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.

        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
          break;
🧰 Tools
🪛 Biome

[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


406-408: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

The case ComponentType.UIRenderer lacks a break statement, causing it to fall through to ComponentType.UIInteractive, which may not be the intended behavior.

Apply this diff to add the missing break:

            case ComponentType.UIRenderer:
              elements[depth] = component as unknown as IGraphics;
              ++depth;
+             break;
📝 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.

        case ComponentType.UIRenderer:
          elements[depth] = component as unknown as IGraphics;
          ++depth;
          break;
🧰 Tools
🪛 Biome

[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


237-237: ⚠️ Potential issue

Wrap variable declarations in switch cases with blocks to prevent scope issues

Declaring variables directly within a switch case can cause them to be accessible in other cases due to JavaScript's function-level scoping.

Apply this diff to encapsulate the declaration:

              case CanvasRenderMode.ScreenSpaceOverlay:
+               {
                  const { min, max } = renderer.bounds;
                  if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
                    continue;
                  }
                  break;
+               }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/design/src/ui/IUIElement.ts (1)

1-4: 🛠️ Refactor suggestion

Consider revising member naming conventions and visibility.

The interface members use underscore prefix suggesting they are private/internal, which is unconventional for interface contracts. Interface members typically define the public API contract.

Consider this alternative approach:

 export interface IUIElement {
-  _onUIUpdateIndex: number;
-  _onUpdate(): void;
+  updateIndex: number;
+  update(): void;
 }

Additionally, please add JSDoc comments to document:

  • The purpose of this interface
  • The role of updateIndex in controlling update order/priority
  • When and how the update method is called

Committable suggestion skipped: line range outside the PR's diff.

packages/design/src/ui/IUICanvas.ts (2)

2-2: 🛠️ Refactor suggestion

⚠️ Potential issue

Replace any types with specific types for better type safety.

Using any type bypasses TypeScript's type checking and can lead to runtime errors. Consider defining proper types for these members.

Consider defining specific types:

import { Entity } from '../core/Entity';
import { RenderContext } from '../renderer/RenderContext';
import { RenderElement } from '../renderer/RenderElement';

export interface UICanvas {
  entity: Entity;
  // ... other members
  _renderElement: RenderElement;
  _prepareRender(renderContext: RenderContext): void;
}

Also applies to: 5-5, 6-6


4-5: 🛠️ Refactor suggestion

Remove private members from the interface definition.

The interface includes members prefixed with underscore, suggesting they are private implementation details. Interfaces should define the public contract, not implementation details.

Consider moving private members to the implementing class:

export interface UICanvas {
  entity: Entity;
  sortOrder: number;
  prepareRender(renderContext: RenderContext): void;
}

// In the implementing class:
class Canvas implements UICanvas {
  private _canvasIndex: number;
  private _renderElement: RenderElement;
  // ... implementation
}
packages/core/src/2d/text/ITextRenderer.ts (1)

4-8: 🛠️ Refactor suggestion

Consider removing underscore prefix from public interface method

The method _getSubFont() uses an underscore prefix, which typically indicates a private or internal member. However, this is part of a public interface and TypeScript has proper visibility modifiers that should be used instead.

Consider this alternative:

 export interface ITextRenderer {
   text: string;
   overflowMode: OverflowMode;
-  _getSubFont(): SubFont;
+  getSubFont(): SubFont;
 }
📝 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.

export interface ITextRenderer {
  text: string;
  overflowMode: OverflowMode;
  getSubFont(): SubFont;
}
packages/ui/src/interface/IGraphics.ts (1)

8-8: 🛠️ Refactor suggestion

Consider a more TypeScript-idiomatic approach for the raycast method.

Several concerns with the current method signature:

  1. The underscore prefix suggests an internal implementation, but TypeScript has built-in visibility modifiers that could be more appropriate.
  2. Using an out parameter (HitResult) is not a typical TypeScript pattern. Consider returning a HitResult | null instead.
  3. The purpose of the distance parameter isn't clear from the signature.

Consider this alternative approach:

-  _raycast(ray: Ray, out: HitResult, distance: number): boolean;
+  /** Performs raycasting against this graphics element
+   * @param ray The ray to test against
+   * @param maxDistance The maximum distance to test for intersections
+   * @returns The hit result if an intersection occurred, null otherwise
+   */
+  protected raycast(ray: Ray, maxDistance: number): HitResult | null;
📝 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.

  /** Performs raycasting against this graphics element
   * @param ray The ray to test against
   * @param maxDistance The maximum distance to test for intersections
   * @returns The hit result if an intersection occurred, null otherwise
   */
  protected raycast(ray: Ray, maxDistance: number): HitResult | null;
packages/ui/src/interface/IElement.ts (2)

8-10: 🛠️ Refactor suggestion

Consider encapsulating internal state.

Internal state properties prefixed with _ should not be exposed in the public interface. Consider:

  1. Moving these to a separate internal interface
  2. Using private fields in the implementing class
// Example approach using a separate internal interface
interface IElementInternal {
  _indexInCanvas: number;
  _isCanvasDirty: boolean;
}

export interface IElement {
  entity: Entity;
  readonly canvas: UICanvas;
}

11-12: 🛠️ Refactor suggestion

Improve type safety and encapsulation of event handling.

The current event handling implementation has several issues:

  1. The flag parameter uses a number type instead of an enum
  2. The param parameter is loosely typed as any
  3. The listener array is exposed as an internal property

Consider this improved approach:

/** Event types for canvas interactions */
export enum CanvasEventType {
  // Define your event types here
}

/** Event data structure */
export interface CanvasEventData {
  // Define strongly typed event parameters
}

export interface IElement {
  // ... other members ...
  
  /** Add a canvas event listener */
  addEventListener(entity: Entity, callback: (event: CanvasEventType, data?: CanvasEventData) => void): void;
  
  /** Remove a canvas event listener */
  removeEventListener(entity: Entity): void;
}
packages/core/src/enums/ComponentType.ts (1)

22-22: ⚠️ Potential issue

Fix incorrect bitwise combination value

The value 0x680 for UIElement appears to include an extra bit that doesn't correspond to any of its component flags. If it's meant to be a combination of UICanvas (0x80), UIGroup (0x100), and UIRenderer (0x200), the value should be 0x380.

- UIElement = 0x680
+ UIElement = 0x380  // UICanvas | UIGroup | UIRenderer
📝 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.

  UIElement = 0x380  // UICanvas | UIGroup | UIRenderer
packages/ui/src/interface/IGroupAble.ts (2)

14-16: 🛠️ Refactor suggestion

Review method visibility and callback type safety

A few concerns about the method declarations:

  1. _onUpdate and _onGroupModify are internal methods but part of the public interface
  2. _groupListener uses a generic number type for the flag parameter, while _onGroupModify uses the more specific GroupModifyFlags

Consider these improvements:

export interface IGroupAble extends IElement {
  readonly group: UIGroup;
}

interface IGroupableInternal extends IGroupAble {
  _onUpdate?(): void;
  _onGroupModify(flag: GroupModifyFlags): void;
  _groupListener: (flag: GroupModifyFlags) => void; // Use specific enum type
}

1-17: 💡 Codebase verification

Based on the search results, I can now generate the final response:

Optional properties in IGroupAble are implemented differently across classes

The verification shows that these properties are not truly optional in implementations:

  • _onUIUpdateIndex is initialized to 0 in both UIInteractive and UIRenderer
  • _globalInteractive is initialized to true in UIGroup and false in UIInteractive
  • _globalAlpha is initialized to 1 in UIGroup
  • _onUpdate is implemented in UIInteractive but not in UIGroup

The interface should reflect the actual implementation requirements by removing the optional markers (?) since these properties are always present in implementing classes.

🔗 Analysis chain

Verify usage of optional properties

The interface has several optional properties (_onUIUpdateIndex, _globalAlpha, _globalInteractive, _onUpdate). We should verify if these are truly optional or if they're always set during initialization.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for implementations to verify optional property usage
ast-grep --pattern 'class $_ implements IGroupAble {
  $$$
}'

# Search for property assignments
rg -A 5 "_onUIUpdateIndex|_globalAlpha|_globalInteractive" --type typescript

Length of output: 177


Script:

#!/bin/bash
# Search for implementations using different file extensions
ast-grep --pattern 'class $_ implements IGroupAble {
  $$$
}'

# Search for property assignments with .ts and .tsx extensions
rg -t ts -t tsx -A 5 "_onUIUpdateIndex|_globalAlpha|_globalInteractive"

# Search for property initialization patterns
rg -t ts -t tsx "constructor.*IGroupAble" -A 20

# Search for files that might implement IGroupAble
fd -e ts -e tsx | xargs grep -l "implements IGroupAble"

Length of output: 393


Script:

#!/bin/bash
# Let's examine the content of the implementing files
cat packages/ui/src/component/UIGroup.ts packages/ui/src/component/interactive/UIInteractive.ts

# Search for property assignments without file type flags
rg "_onUIUpdateIndex|_globalAlpha|_globalInteractive" 

# Search for constructor initialization
rg "constructor.*{" -A 10 packages/ui/src/component/UIGroup.ts packages/ui/src/component/interactive/UIInteractive.ts

Length of output: 15476

packages/ui/src/component/interactive/transition/ScaleTransition.ts (1)

22-24: ⚠️ Potential issue

Add null check for target entity.

The _applyValue method should include a null check before accessing the target entity.

 protected override _applyValue(value: number): void {
+  if (!this._target?.entity) {
+    return;
+  }
   this._target.entity.transform.setScale(value, value, value);
 }
📝 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.

  protected override _applyValue(value: number): void {
    if (!this._target?.entity) {
      return;
    }
    this._target.entity.transform.setScale(value, value, value);
  }
packages/core/src/2d/index.ts (1)

3-5: 💡 Codebase verification

Documentation needed for sprite assembler system

The sprite documentation (sprite.md) lacks information about the different sprite assembler types and their use cases. While it covers sprite properties and general usage, it doesn't explain:

  • The purpose of Simple, Sliced, and Tiled sprite assemblers
  • When to use each assembler type
  • How these assemblers interact with the sprite rendering system

The implementation files also lack comprehensive documentation comments explaining their architectural roles.

Suggested documentation points:

  • Add a new section in sprite.md about sprite assembly modes
  • Include code-level documentation in assembler classes
  • Consider creating a dedicated guide for sprite rendering architecture
🔗 Analysis chain

Consider adding architectural documentation for the new assembler system.

The introduction of three distinct sprite assemblers (Simple, Sliced, Tiled) suggests a significant architectural enhancement. This warrants documentation explaining:

  • The purpose and use case for each assembler type
  • How to choose between different assemblers
  • The relationship between assemblers and renderers

Let's check if documentation exists:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Look for documentation about the assembler system

# Search for documentation files
echo "Searching for documentation files:"
fd -e md -e txt . | grep -i "assembler\|sprite\|gui"

# Search for documentation comments in assembler files
echo "Searching for documentation comments in assembler files:"
rg "\/\*\*|\*\/" "./assembler/"

Length of output: 708


Script:

#!/bin/bash
# Let's check the content of relevant documentation and assembler files

# Check English documentation content
echo "=== Sprite Documentation Content ==="
cat docs/en/graphics/2D/sprite.md

# Check assembler files for documentation
echo -e "\n=== Assembler Implementation Documentation ==="
rg -A 5 "class.*Assembler" "packages/core/src/2d/assembler/"

Length of output: 6836

packages/ui/src/component/interactive/transition/SpriteTransition.ts (1)

34-44: ⚠️ Potential issue

Add null checks and error handling for robustness.

The value management methods need additional safety checks:

  1. _getTargetValueCopy might return undefined when _target is null
  2. _updateCurrentValue lacks validation for null/undefined values
  3. _applyValue might throw if this._target is null

Consider adding these safety checks:

- protected _getTargetValueCopy(): Sprite {
-   return this._target?.sprite;
+ protected _getTargetValueCopy(): Sprite | null {
+   return this._target?.sprite ?? null;
  }

  protected override _updateCurrentValue(srcValue: Sprite, destValue: Sprite, weight: number): void {
+   if (!srcValue && !destValue) {
+     this._currentValue = null;
+     return;
+   }
    this._currentValue = weight >= 1 ? destValue : srcValue;
  }

  protected override _applyValue(value: Sprite): void {
+   if (!this._target) {
+     return;
+   }
    this._target.sprite = value || this._normal;
  }
📝 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.

  protected _getTargetValueCopy(): Sprite | null {
    return this._target?.sprite ?? null;
  }

  protected override _updateCurrentValue(srcValue: Sprite, destValue: Sprite, weight: number): void {
    if (!srcValue && !destValue) {
      this._currentValue = null;
      return;
    }
    this._currentValue = weight >= 1 ? destValue : srcValue;
  }

  protected override _applyValue(value: Sprite): void {
    if (!this._target) {
      return;
    }
    this._target.sprite = value || this._normal;
  }
packages/ui/src/component/advanced/Button.ts (2)

32-35: 🛠️ Refactor suggestion

Improve cleanup implementation and readability.

  1. The current cleanup only marks listeners as destroyed but doesn't clear the array.
  2. The assignment in expression makes the code less maintainable.

Consider this improved implementation:

  override onDestroy(): void {
    super.onDestroy();
-   this._listeners.findAndRemove((value) => (value.destroyed = true));
+   this._listeners.forEach(listener => listener.destroyed = true);
+   this._listeners.clear();
  }
📝 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.

  override onDestroy(): void {
    super.onDestroy();
    this._listeners.forEach(listener => listener.destroyed = true);
    this._listeners.clear();
  }
🧰 Tools
🪛 Biome

[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


11-21: 🛠️ Refactor suggestion

Improve code readability and documentation.

  1. The assignment within the expression makes the code harder to read and maintain.
  2. The method documentation could be more descriptive about the event parameter.

Consider these improvements:

  /**
   * Add a listening function for click.
   * @param listener - The listening function
+  * @param event - The pointer event data containing click information
   */
  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
+  * @returns boolean - True if the listener was found and removed
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
-   this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
+   this._listeners.findAndRemove((value) => {
+     if (value.fn === listener) {
+       value.destroyed = true;
+       return true;
+     }
+     return false;
+   });
  }
📝 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.

  /**
   * Add a listening function for click.
   * @param listener - The listening function
   * @param event - The pointer event data containing click information
   */
  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
   * @returns boolean - True if the listener was found and removed
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.findAndRemove((value) => {
      if (value.fn === listener) {
        value.destroyed = true;
        return true;
      }
      return false;
    });
  }
🧰 Tools
🪛 Biome

[error] 20-20: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts (2)

10-14: 🛠️ Refactor suggestion

Consider refactoring to a module pattern with initialized fields

The current implementation as a static-only class could be improved by converting it to a module with standalone functions and properly initialized fields. This would align with TypeScript best practices and prevent potential race conditions.

-export class UIUtils {
-  private static _renderQueue: RenderQueue;
-  private static _virtualCamera: VirtualCamera;
-  private static _viewport: Vector4;
+const _renderQueue = new RenderQueue(RenderQueueType.Transparent);
+const _virtualCamera = new VirtualCamera();
+const _viewport = new Vector4(0, 0, 1, 1);
+
+export function render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
   // ... implementation
-}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 10-39: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


15-19: 🛠️ Refactor suggestion

Improve initialization pattern and add input validation

The current initialization pattern uses assignments within expressions and lacks input validation. This can be improved for better readability and robustness.

 static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
+  if (!engine || !uiCanvases) {
+    throw new Error("Engine and uiCanvases parameters are required");
+  }
+
-  const uiRenderQueue = (this._renderQueue ||= new RenderQueue(RenderQueueType.Transparent));
-  const virtualCamera = (this._virtualCamera ||= new VirtualCamera());
-  const viewport = (this._viewport ||= new Vector4(0, 0, 1, 1));
+  const uiRenderQueue = UIUtils._renderQueue;
+  const virtualCamera = UIUtils._virtualCamera;
+  const viewport = UIUtils._viewport;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 16-16: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 16-16: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 17-17: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 17-17: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 18-18: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 18-18: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/ui/src/component/interactive/transition/ColorTransition.ts (2)

22-29: ⚠️ Potential issue

Address type safety concerns with @ts-ignore.

Multiple @ts-ignore comments suggest potential type safety issues. Consider:

  1. Properly typing the Color class to include _onValueChanged
  2. Using a proper event emitter pattern instead of direct property assignment
// Example type definition to consider
interface TransitionColor extends Color {
  _onValueChanged?: () => void;
}

48-64: ⚠️ Potential issue

Add null safety checks in protected methods.

Several methods could benefit from additional null safety checks:

  1. _getTargetValueCopy: The optional chaining on _target?.color is good, but reusing this._color for multiple operations could lead to side effects
  2. _applyValue: Missing null check for this._target
 protected _getTargetValueCopy(): Color {
-  const color = this._color;
+  const color = new Color();
   color.copyFrom(this._target?.color || this._normal);
   return color;
 }

 protected override _applyValue(value: Color): void {
+  if (!this._target) {
+    return;
+  }
   this._target.color = value;
 }
📝 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.

  protected _getTargetValueCopy(): Color {
    const color = new Color();
    color.copyFrom(this._target?.color || this._normal);
    return color;
  }

  protected override _updateCurrentValue(srcValue: Color, destValue: Color, weight: number): void {
    if (weight >= 1) {
      this._currentValue.copyFrom(destValue);
    } else {
      Color.lerp(srcValue, destValue, weight, this._currentValue);
    }
  }

  protected override _applyValue(value: Color): void {
    if (!this._target) {
      return;
    }
    this._target.color = value;
  }
packages/core/src/ComponentsDependencies.ts (2)

97-101: 🛠️ Refactor suggestion

Add JSDoc documentation and type validation

The decorator lacks documentation and type validation. Consider:

  1. Adding JSDoc documentation to explain its purpose and usage
  2. Adding validation to ensure the target is a valid component constructor
+/**
+ * Decorator that marks a component to be automatically inherited by child entities.
+ * Used primarily for GUI components that should propagate through the entity hierarchy.
+ * @example
+ * ```ts
+ * @markAsInherited()
+ * class UIComponent extends Component {
+ *   // ...
+ * }
+ * ```
+ */
 export function markAsInherited() {
   return function <T extends ComponentConstructor>(target: T): void {
+    if (!(target.prototype instanceof Component)) {
+      throw new Error(`${target.name} must be a Component class`);
+    }
     ComponentsDependencies._inheritedMap.set(target, true);
   };
 }

14-14: 🛠️ Refactor suggestion

Use lowercase boolean type instead of Boolean

For consistency with TypeScript best practices, use the primitive type boolean instead of the object wrapper type Boolean.

- static _inheritedMap = new Map<ComponentConstructor, Boolean>();
+ static _inheritedMap = new Map<ComponentConstructor, boolean>();
📝 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.

  static _inheritedMap = new Map<ComponentConstructor, boolean>();
🧰 Tools
🪛 Biome

[error] 14-14: Don't use 'Boolean' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'boolean' instead

(lint/complexity/noBannedTypes)

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (2)

65-69: 🛠️ Refactor suggestion

Improve readability by separating combined assignments

The combined assignment statements make the code harder to read and maintain. Consider breaking them down into separate lines.

Apply this diff to improve readability:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
- row[3] = width - expectWidth * (1 - right);
+ row[0] = expectWidth * left;
+ row[1] = fixedLeft;
+ row[2] = width - fixedRight;
+ row[3] = width - expectWidth * (1 - right);

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

- (column[0] = expectHeight * bottom), (column[1] = fixedBottom), (column[2] = height - fixedTop);
- column[3] = height - expectHeight * (1 - top);
+ column[0] = expectHeight * bottom;
+ column[1] = fixedBottom;
+ column[2] = height - fixedTop;
+ column[3] = height - expectHeight * (1 - top);

Also applies to: 74-78


169-173: 🛠️ Refactor suggestion

Improve readability of UV calculations

Similar to earlier feedback, the combined assignment statements here should be separated for better readability.

Apply this diff:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
- row[3] = width - expectWidth * (1 - right);
+ row[0] = expectWidth * left;
+ row[1] = fixedLeft;
+ row[2] = width - fixedRight;
+ row[3] = width - expectWidth * (1 - right);

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

- (column[0] = expectHeight * bottom), (column[1] = fixedBottom), (column[2] = height - fixedTop);
- column[3] = height - expectHeight * (1 - top);
+ column[0] = expectHeight * bottom;
+ column[1] = fixedBottom;
+ column[2] = height - fixedTop;
+ column[3] = height - expectHeight * (1 - top);

Also applies to: 178-182

packages/core/src/ComponentsManager.ts (2)

272-276: 🛠️ Refactor suggestion

Align callUIOnUpdate with other update methods

The implementation is inconsistent with other update methods in the class. Other methods maintain indices during iteration.

Apply this change to maintain consistency:

 callUIOnUpdate(deltaTime: number): void {
-  this._onUpdateUIElements.forEach((element: IUIElement) => {
-    element._onUpdate();
-  });
+  this._onUpdateUIElements.forEach(
+    (element: IUIElement) => {
+      element._onUpdate();
+    },
+    (element: IUIElement, index: number) => {
+      element._onUIUpdateIndex = index;
+    }
+  );
 }

Committable suggestion skipped: line range outside the PR's diff.


185-188: ⚠️ Potential issue

Fix incorrect array reference in addOnUpdateUIElement

There's a critical bug in the implementation. The method is using this._onUpdateRenderers.length instead of this._onUpdateUIElements.length for the index calculation.

Apply this fix:

 addOnUpdateUIElement(element: IUIElement): void {
-  element._onUIUpdateIndex = this._onUpdateRenderers.length;
+  element._onUIUpdateIndex = this._onUpdateUIElements.length;
   this._onUpdateUIElements.add(element);
 }
📝 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.

  addOnUpdateUIElement(element: IUIElement): void {
    element._onUIUpdateIndex = this._onUpdateUIElements.length;
    this._onUpdateUIElements.add(element);
  }
packages/core/src/2d/sprite/SpriteRenderer.ts (1)

421-451: 💡 Codebase verification

Based on the provided code snippet and the enum values found (texture, size, atlasRotate, atlasRegion, atlasRegionOffset, border), I can now generate the final response.

Missing handlers for several sprite modification flags

The current switch statement only handles size and border modifications, but the SpriteModifyFlags enum defines additional flags that should be handled:

  • texture (0x1)
  • atlasRotate (0x4)
  • atlasRegion (0x8)
  • atlasRegionOffset (0x10)
🔗 Analysis chain

Verify the completeness of sprite modification handling

While the switch statement handles various modification types, we should verify if all possible sprite modifications are covered.


Let me check the complete sprite modification handling by looking at the switch statement in the file.


Let me try a different approach to find the sprite modification handling code.


Let me try one more search with a broader pattern to find the sprite modification handling.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all possible sprite modification flags
rg -A 5 "enum SpriteModifyFlags" 

Length of output: 455


Script:

#!/bin/bash
# Search for the switch statement handling sprite modifications
rg -B 5 -A 30 "switch.*modifyFlags" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 83


Script:

#!/bin/bash
# Search for the sprite modification handling in SpriteRenderer
rg -B 5 -A 30 "_onSprite.*Modified" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 83


Script:

#!/bin/bash
# Search for any method handling sprite modifications in SpriteRenderer
rg -B 5 -A 30 "modifyFlags" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 73

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (2)

81-86: ⚠️ Potential issue

Replace global isNaN with Number.isNaN

The global isNaN function performs type coercion which can lead to unexpected results. Use Number.isNaN instead for more precise NaN checking.

-      if (isNaN(cUV.get(doubleJ)) || isNaN(cUV.get(doubleJ + 1))) {
+      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
-        if (isNaN(rUV.get(doubleI)) || isNaN(rUV.get(doubleI + 1))) {
+        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
📝 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.

      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
        continue;
      }
      for (let i = 0; i < rowLength; i++) {
        const doubleI = i << 1;
        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
🧰 Tools
🪛 Biome

[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


422-444: ⚠️ Potential issue

Add block scoping to switch cases

Variables declared in switch cases can leak into other cases. Wrap case blocks in curly braces to prevent variable scope leakage.

     switch (rType) {
       case TiledType.Compressed: {
         const scale = width / fixedLR;
         rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
         rPos.add(width - expectWidth * (1 - right) * scale);
         rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
         break;
+      }
       case TiledType.WithoutTiled: {
         // ... existing code ...
         break;
+      }
       case TiledType.WithTiled: {
         // ... existing code ...
         break;
+      }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 422-422: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 434-434: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 435-435: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 436-436: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/Entity.ts (1)

613-628: 🛠️ Refactor suggestion

Refactor assignment in expression for better readability.

The modification listener system is well-designed, but the assignment in expression should be refactored for clarity.

Apply this diff to improve readability:

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
-   (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+   if (!this._updateFlagManager) {
+     this._updateFlagManager = new UpdateFlagManager();
+   }
+   this._updateFlagManager.addListener(onChange);
  }
📝 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.

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }

  /**
   * @internal
   */
  _dispatchModify(flag: EntityModifyFlags, param?: any): void {
    this._updateFlagManager?.dispatch(flag, param);
🧰 Tools
🪛 Biome

[error] 614-614: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/Camera.ts (1)

738-747: 🛠️ Refactor suggestion

Refactor the assignment in expression

The assignment within the expression can make the code harder to understand and maintain.

Consider refactoring to improve readability:

-  (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+  if (!this._updateFlagManager) {
+    this._updateFlagManager = new UpdateFlagManager();
+  }
+  this._updateFlagManager.addListener(onChange);
📝 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.

  _registerModifyListener(onChange: (flag: CameraModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: CameraModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }
🧰 Tools
🪛 Biome

[error] 739-739: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

fix lint error
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (7)
packages/ui/package.json (1)

26-30: Review completeness of included files

The files array should include all necessary files while excluding development files.

Consider adding the following to ensure proper packaging:

  • Add README.md to the files array for npm registry documentation
  • Add LICENSE file reference
  • Consider adding package.json explicitly
  "files": [
    "dist/**/*",
    "libs/**/*",
    "types/**/*",
+   "README.md",
+   "LICENSE",
+   "package.json"
  ],
packages/core/src/animation/Keyframe.ts (2)

13-22: Fix indentation according to project style guidelines.

The conditional type expressions need consistent indentation to match the project's formatting rules.

Apply this formatting fix:

    ? Vector2
-    : V extends Vector3
-    ? Vector3
-    : V extends Vector4 | Color | Quaternion | Rect
-    ? Vector4
-    : V extends number[] | Float32Array
-    ? number[]
-    : V extends ReferResource
-    ? ReferResource
-    : never
+  : V extends Vector3
+  ? Vector3
+  : V extends Vector4 | Color | Quaternion | Rect
+    ? Vector4
+  : V extends number[] | Float32Array
+    ? number[]
+  : V extends ReferResource
+    ? ReferResource
+  : never
🧰 Tools
🪛 eslint

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········


Line range hint 1-22: LGTM! Well-structured type definition.

The generic type parameter T effectively maps input types to their appropriate value types, maintaining type safety throughout the animation system. The conditional type expressions handle all possible KeyframeValueType variants correctly.

Consider documenting the type mapping logic in the class JSDoc to help other developers understand the relationship between V and T type parameters.

🧰 Tools
🪛 eslint

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········

packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts (2)

95-96: Consider refactoring nested ternary expressions for better readability.

While the logic is correct, the nested ternary expressions make the code harder to maintain. Consider extracting this logic into a separate method or using if-else statements.

Fix the indentation and refactor for better readability:

-        ? this.cureType._getZeroValue(this.baseEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.baseEvaluateData.value)
+          : this.defaultValue;

Alternative implementation using a method:

private _getFallbackValue(evaluateData: IEvaluateData<V>, additive: boolean): V {
  return additive 
    ? this.cureType._getZeroValue(evaluateData.value)
    : this.defaultValue;
}
🧰 Tools
🪛 eslint

[error] 95-95: Insert ··

(prettier/prettier)


[error] 96-96: Insert ··

(prettier/prettier)


104-105: Apply consistent formatting and refactoring.

For consistency with the previous changes, apply the same formatting and consider using the suggested _getFallbackValue method here as well.

-        ? this.cureType._getZeroValue(this.crossEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.crossEvaluateData.value)
+          : this.defaultValue;
🧰 Tools
🪛 eslint

[error] 104-104: Insert ··

(prettier/prettier)


[error] 105-105: Insert ··

(prettier/prettier)

packages/core/src/RenderPipeline/BasicRenderPipeline.ts (1)

346-362: Consider caching bounds check results

The bounds intersection check against the frustum is performed for every renderer on each frame. For static objects that don't move, this check could be cached until the object or camera moves.

Example optimization:

 if (enableFrustumCulling) {
-  if (!frustum.intersectsBox(renderer.bounds)) {
+  if (!renderer._isMoving && renderer._cachedFrustumResult !== undefined) {
+    if (!renderer._cachedFrustumResult) continue;
+  } else {
+    renderer._cachedFrustumResult = frustum.intersectsBox(renderer.bounds);
+    if (!renderer._cachedFrustumResult) continue;
+  }
   continue;
 }
packages/core/src/Engine.ts (1)

518-522: Consider performance optimization for UI rendering

While the UI rendering implementation is correct, consider these performance optimizations:

  1. Cache the length check result of uiCanvas to avoid multiple array access
  2. Consider adding a dirty flag system to skip UI sorting and rendering when no changes occurred

Here's a suggested optimization:

-      const uiCanvas = componentsManager._overlayCanvases;
-      if (uiCanvas.length > 0) {
-        componentsManager.sortUICanvases();
-        UIUtils.render(this, uiCanvas);
-      }
+      const uiCanvases = componentsManager._overlayCanvases;
+      const uiCanvasCount = uiCanvases.length;
+      if (uiCanvasCount > 0) {
+        if (componentsManager._isUIDirty) {
+          componentsManager.sortUICanvases();
+          componentsManager._isUIDirty = false;
+        }
+        UIUtils.render(this, uiCanvases);
+      }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3a29609 and c4ace76.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/core/src/BasicResources.ts (3 hunks)
  • packages/core/src/Engine.ts (4 hunks)
  • packages/core/src/RenderPipeline/BasicRenderPipeline.ts (2 hunks)
  • packages/core/src/animation/Keyframe.ts (1 hunks)
  • packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts (2 hunks)
  • packages/core/src/particle/ParticleRenderer.ts (3 hunks)
  • packages/ui/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/BasicResources.ts
  • packages/core/src/particle/ParticleRenderer.ts
🧰 Additional context used
🪛 eslint
packages/core/src/animation/Keyframe.ts

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts

[error] 95-95: Insert ··

(prettier/prettier)


[error] 96-96: Insert ··

(prettier/prettier)


[error] 104-104: Insert ··

(prettier/prettier)


[error] 105-105: Insert ··

(prettier/prettier)


[error] 129-129: Insert ··

(prettier/prettier)


[error] 130-130: Insert ··

(prettier/prettier)

🪛 GitHub Check: lint
packages/core/src/animation/Keyframe.ts

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········

🪛 Biome
packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (9)
packages/ui/package.json (3)

2-3: Verify version alignment with core package

The package version is set to 1.3.20. Since this is a new UI package that depends on @galacean/engine, we should verify version alignment.

✅ Verification successful

Version alignment verified across all Galacean packages

All Galacean packages, including the core engine and UI package, are consistently versioned at 1.3.20. This alignment is correct and follows proper monorepo versioning practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check version alignment between UI package and core engine
echo "Checking core engine version..."
jq -r '.version' packages/core/package.json || echo "Core package.json not found"

# List all related packages and their versions for consistency
echo -e "\nChecking all related package versions..."
fd -t f package.json -x jq -r '. | select(.name | startswith("@galacean")) | .name + ": " + .version'

Length of output: 899


31-36: Verify workspace dependency configuration

The package uses workspace dependencies for @galacean/engine in both dev and peer dependencies. This is correct for monorepo setup, but we should verify the workspace configuration.

✅ Verification successful

The workspace configuration and package manager settings look correct. However, let me verify one more thing about the dependency versions to ensure complete validation.


Workspace dependency configuration is correctly set up

The workspace configuration is properly set up with:

  • Valid pnpm workspace configuration in pnpm-workspace.yaml
  • Correct package manager specification ([email protected])
  • Consistent usage of workspace:* for @galacean/engine across multiple packages in the monorepo
  • Matching pattern of having both devDependencies and peerDependencies for the engine package
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for workspace configuration
echo "Checking workspace configuration..."
cat pnpm-workspace.yaml 2>/dev/null || cat package.json | jq '.workspaces'

# Verify package manager
echo -e "\nChecking package manager configuration..."
cat package.json | jq -r '.packageManager' 2>/dev/null || echo "No package manager specified"

Length of output: 399


Script:

#!/bin/bash
# Check the engine package's version in the workspace
echo "Checking @galacean/engine package version..."
cat packages/engine/package.json | jq '.version'

# Verify if other packages in the workspace also use the same dependency pattern
echo -e "\nChecking other packages using @galacean/engine..."
fd -e json -x cat {} \; -p packages/ | jq 'select(.dependencies."@galacean/engine" or .devDependencies."@galacean/engine" or .peerDependencies."@galacean/engine")'

Length of output: 4434


12-16: Verify the existence of entry point files

The package defines multiple entry points and type definitions. Let's ensure these paths are valid and the build system is configured to generate them.

✅ Verification successful

Let me gather more information about the build configuration and entry points.


Let me check the rollup configuration since we found a rollup.config.js file.


Based on the rollup configuration and file structure, I can now provide a final response:

Entry points configuration is properly set up

The rollup configuration correctly handles all the specified entry points in package.json:

  • dist/main.js (CommonJS) and dist/module.js (ES Module) are generated via the module() config
  • dist/browser.js is generated via the umd() config
  • TypeScript declaration files in types/ are handled by the TypeScript configuration with "declaration": true and "declarationDir": "types"
  • Source entry point src/index.ts exists at packages/ui/src/index.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for build configuration that generates these files
echo "Checking build configuration..."
fd -t f "rollup|webpack|tsconfig" -x cat {} \; | grep -A 5 "dist/"

# Verify TypeScript configuration for types generation
echo -e "\nChecking TypeScript configuration..."
fd tsconfig.json -x cat {} \; | grep -A 5 "\"declaration\""

Length of output: 3364


Script:

#!/bin/bash
# Check for build configuration files
echo "Checking for build configuration files..."
fd -t f "rollup.config|webpack.config|vite.config"

# Check source entry point
echo -e "\nChecking source entry point..."
fd "index.ts" -p "src"

# Check if dist directory exists and its contents
echo -e "\nChecking dist directory contents..."
fd . -t f -p "dist"

# Check if types directory exists and its contents
echo -e "\nChecking types directory contents..."
fd . -t f -p "types"

Length of output: 753


Script:

#!/bin/bash
# Check rollup configuration
echo "Checking rollup configuration..."
cat rollup.config.js

# Check if the source entry point exists
echo -e "\nChecking for source files..."
fd -t f "index.ts" --full-path

# List all package.json files to understand project structure
echo -e "\nListing package.json files..."
fd -t f "package.json" --full-path

Length of output: 9114

packages/core/src/RenderPipeline/BasicRenderPipeline.ts (3)

343-343: LGTM! Good use of destructuring

The destructuring of camera properties improves both readability and performance by avoiding repeated property access.


Line range hint 376-382: LGTM! Well-structured bitwise flags enum

The RenderQueueFlags enum is well-designed:

  • Uses power-of-two values for efficient bitwise operations
  • Includes a comprehensive All flag
  • Values align with the RenderQueueType usage in pushRenderElementByType

364-373: Verify: Intentional omission of frustum culling for canvases

The code applies culling mask checks but not frustum culling for canvas elements. While this makes sense for UI elements that should always be visible, please verify this is intentional.

✅ Verification successful

Let me gather more information about canvas rendering by checking the implementation details.


Based on the search results, I can now provide a definitive response about the intentional omission of frustum culling for canvases.

Frustum culling behavior for canvases is correctly implemented

The code correctly handles frustum culling for canvases based on their render mode:

  • For ScreenSpaceOverlay mode: Frustum culling only checks if elements are within the screen bounds
  • For ScreenSpaceCamera and WorldSpace modes: Full frustum culling is applied using frustum.intersectsBox()

This is the expected behavior since overlay UI elements should only be culled based on screen bounds while world-space UI should respect the camera's view frustum.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any existing frustum culling patterns with canvases
rg -A 5 "canvas.*frustum|frustum.*canvas" 

# Check if this is consistent with other UI/canvas related code
ast-grep --pattern 'class $_ implements Canvas {
  $$$
  _prepareRender($_) {
    $$$
  }
  $$$
}'

Length of output: 1621


Script:

#!/bin/bash
# Check Canvas interface definition and implementations
ast-grep --pattern 'interface Canvas {
  $$$
}'

# Look for canvas-related comments or documentation
rg -g '!*.{md,json}' -B 2 -A 2 'Canvas' --type ts

# Check if there are any frustum-related methods in canvas implementations
rg -g '!*.{md,json}' 'frustum' --type ts -B 2 -A 2

Length of output: 115384

packages/core/src/Engine.ts (3)

40-40: LGTM: Import addition aligns with new UI functionality

The addition of the UIUtils import is consistent with the new UI rendering capabilities introduced in this PR.


483-485: LGTM: UI update integration follows logical order

The UI update is correctly placed before the renderer update, ensuring UI state is consistent within the frame.


341-341: Verify removal of physics initialization check

The removal of the physicsInitialized check before firing pointer scripts could potentially cause issues if any pointer event handlers depend on physics being initialized. Please verify that this is intentional and that there are no pointer scripts that require physics initialization.

Let's check for any pointer scripts that might depend on physics:

Comment on lines +17 to +19
"scripts": {
"b:types": "tsc"
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding essential npm scripts

The scripts section only includes type generation. Consider adding standard lifecycle scripts.

Add these common npm scripts:

  "scripts": {
-   "b:types": "tsc"
+   "b:types": "tsc",
+   "build": "your-build-command",
+   "test": "your-test-command",
+   "clean": "rimraf dist types",
+   "prepare": "npm run build"
  },

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 129 to 130
? this.cureType._getZeroValue(this.crossEvaluateData.value)
: this.defaultValue;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Eliminate code duplication by extracting common logic.

This is the third occurrence of the same fallback value logic. Following the DRY principle, this is a strong indicator that we should extract this into a reusable method.

  1. Fix the indentation:
-        ? this.cureType._getZeroValue(this.crossEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.crossEvaluateData.value)
+          : this.defaultValue;
  1. Extract the common logic into a helper method as suggested earlier and use it in all three locations:
private _getFallbackValue(evaluateData: IEvaluateData<V>, additive: boolean): V {
  return additive 
    ? this.cureType._getZeroValue(evaluateData.value)
    : this.defaultValue;
}

This will improve maintainability and reduce the likelihood of inconsistencies when making future changes.

🧰 Tools
🪛 eslint

[error] 129-129: Insert ··

(prettier/prettier)


[error] 130-130: Insert ··

(prettier/prettier)

fix lint error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GUI 基建
7 participants