Skip to content

Conversation

@abstrakt8
Copy link
Contributor

@abstrakt8 abstrakt8 commented Dec 23, 2025

What has changed?

@abstrakt8 abstrakt8 self-assigned this Dec 23, 2025
@changeset-bot
Copy link

changeset-bot bot commented Dec 23, 2025

⚠️ No Changeset found

Latest commit: d9d47ec

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 23, 2025

Open in StackBlitz

npm i https://pkg.pr.new/@playcanvas/blocks@298
npm i https://pkg.pr.new/@playcanvas/react@298

commit: d9d47ec

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces an optimization to prevent unnecessary prop applications that trigger setter side effects, specifically addressing issue #297 where the GSplat component's unified flag couldn't be changed after the component was enabled.

  • Adds a valuesEqual utility function that handles equality checking for primitives and PlayCanvas objects (Vec2, Vec3, Color, etc.) with support for approximate floating-point comparisons
  • Modifies applyProps to skip property application when values haven't changed, avoiding unwanted side effects from setters
  • Refactors shallowEquals to use the new valuesEqual function for more robust property comparisons

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/lib/src/utils/compare.tsx Introduces the new valuesEqual function with support for PlayCanvas object equality checking via equalsApprox and equals methods; updates shallowEquals to leverage this new function
packages/lib/src/utils/validation.ts Integrates valuesEqual into applyProps to check if property values have changed before applying them, preventing unnecessary setter invocations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +232 to +237
const currentValue = (instance as Record<string, unknown>)[key];

// Skip if value hasn't changed (avoids side effects from setters)
if (valuesEqual(currentValue, value)) {
return;
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The equality check compares the current instance property value with the new prop value, but this comparison may be incorrect when a custom apply function exists. The apply function often transforms the input (e.g., an array [1, 2, 3] gets transformed into a Vec3 object). This means you're comparing a Vec3 object with an array, which will always return false and the optimization won't work.

Consider checking equality after the transformation, or store the last applied raw value to compare against the new raw value before transformation.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that is a good suggestion, will try to rework this

Comment on lines +39 to +59
export function valuesEqual(a: unknown, b: unknown): boolean {
if (a === b) return true;

// Early exit if either is null/undefined (using type coercion)
if (a == null || b == null) return false;

if (typeof a === 'object') {
// Priority 1: Floating point approximation (handles precision drift)
if ('equalsApprox' in a && typeof (a as Approximate).equalsApprox === 'function') {
return (a as Approximate).equalsApprox(b);
}

// Priority 2: Strict structural equality
if ('equals' in a && typeof (a as Equatable).equals === 'function') {
return (a as Equatable).equals(b);
}
}

// For other objects, return false to trigger re-apply (conservative)
return false;
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The new valuesEqual function lacks test coverage. Given the critical nature of this function (it's used to prevent unnecessary prop applications and avoid side effects), it should have comprehensive tests covering:

  • Primitive comparisons (numbers, strings, booleans, null, undefined)
  • Object comparisons using equalsApprox (Vec2, Vec3, Vec4, Color, Quat)
  • Object comparisons using equals
  • Edge cases like comparing objects with arrays

Consider adding a test file similar to picker.test.tsx to ensure this function behaves correctly in all scenarios.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion

equalsApprox(other: unknown): boolean;
}

interface Equatable {
Copy link
Member

Choose a reason for hiding this comment

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

Don't think Equatable is quite right... Exact maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants