Skip to content

Commit

Permalink
"simplify" anchorPoint definition using a new ObservablePoint class…
Browse files Browse the repository at this point in the history
… (lighter than the previous ObservableVector2d class, and with Point class being more than enough for an anchorPoint)
  • Loading branch information
obiot committed Aug 3, 2024
1 parent 9eedc76 commit c174ca6
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 24 deletions.
1 change: 1 addition & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Chore: replaced mocha and puppeteer with vitest (@hornta)
- Chore: deprecated classes and methods from version 15 and lower have been removed (see https://github.com/melonjs/melonJS/wiki/Upgrade-Guide)
- Math: namespace `Math` is now deprecated and renamed to `math` for better consistency with the rest of the API.
- Renderable: the `anchorPoint` property now used the lighter `ObservablePoint` class instead of `ObservableVector2d`.

## [17.4.0] (melonJS 2) - _2024-06-22_

Expand Down
136 changes: 136 additions & 0 deletions packages/melonjs/src/geometries/observablePoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Point } from "./point.ts";

/**
* Represents a point in a 2D coordinate system that can be observed for changes.
*/
/**
* Represents an observable point in 2D space.
*/
export class ObservablePoint {
private _callback: () => void;
private _point: Point;
private _revoke: () => void;

private callBackEnabled: boolean = true;

type: "ObservablePoint";

/**
* Creates a new ObservablePoint instance.
* @param x - The x-coordinate of the point. Default is 0.
* @param y - The y-coordinate of the point. Default is 0.
* @param callback - The callback function to be called when the point changes. Default is undefined.
*/
constructor(x: number = 0, y: number = 0, callback?: () => void) {
const { proxy, revoke } = Proxy.revocable(new Point(x, y), {
set: (target, property, value) => {
if (property === "x" || property === "y") {
Reflect.set(target, property, value);
if (this.callBackEnabled) {
this._callback?.();
}
return true;
}
return false;
},
});

this._revoke = revoke;
this._point = proxy;

if (callback) {
this.setCallback(callback);
}
}

/**
* Sets the x and y coordinates of the point.
* @param x - The new x-coordinate value.
* @param y - The new y-coordinate value.
* @returns Reference to this object for method chaining.
*/
set(x = 0, y = 0) {
this._point.x = x;
this._point.y = y;
return this;
}

/**
* Sets the x and y coordinates of the point without triggering the callback.
* @param x - The new x-coordinate value.
* @param y - The new y-coordinate value.
* @returns Reference to this object for method chaining.
*/
setMuted(x = 0, y = 0) {
this.callBackEnabled = false;
this._point.x = x;
this._point.y = y;
this.callBackEnabled = true;
return this;
}

/**
* Gets the x-coordinate of the point.
*/
get x() {
return this._point.x;
}

/**
* Sets the x-coordinate of the point.
* @param value - The new x-coordinate value.
*/
set x(value: number) {
this._point.x = value;
}

/**
* Gets the y-coordinate of the point.
*/
get y() {
return this._point.y;
}

/**
* Sets the y-coordinate of the point.
* @param value - The new y-coordinate value.
*/
set y(value: number) {
this._point.y = value;
}

/**
* Sets the callback function to be called when the point changes.
* @param callback - The callback function.
*/
setCallback(callback: () => void) {
this._callback = callback;
}

/**
* Checks if the point is equal to the given coordinates or another ObservablePoint.
* @param x - The x-coordinate or the ObservablePoint to compare.
* @param y - The y-coordinate. Required if the first parameter is a number.
* @returns True if the point is equal to the given coordinates or another ObservablePoint, false otherwise.
*/
equals(x: number, y: number): boolean;
equals(point: ObservablePoint): boolean;
equals(xOrPoint: number | ObservablePoint, y?: number | undefined) {
return this._point.equals(xOrPoint as any, y as any);
}

/**
* Creates a clone of the ObservablePoint.
* @returns A new ObservablePoint instance with the same coordinates and callback function.
*/
clone() {
return new ObservablePoint(this._point.x, this._point.y, this._callback);
}

/**
* Revokes the proxy object, preventing further access to the ObservablePoint instance.
*/
revoke() {
this._revoke();
}
}
2 changes: 1 addition & 1 deletion packages/melonjs/src/geometries/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class Point {
equals(x: number, y: number): boolean;
equals(point: Point): boolean;
equals(xOrPoint: number | Point, y?: number | undefined) {
if (xOrPoint instanceof Point) {
if (typeof xOrPoint === "object") {
return this.x === xOrPoint.x && this.y === xOrPoint.y;
}
return this.x === xOrPoint && this.y === y;
Expand Down
1 change: 1 addition & 0 deletions packages/melonjs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export { Matrix2d } from "./math/matrix2d.ts";
export { Matrix3d } from "./math/matrix3d.ts";
export { Color } from "./math/color.ts";
export { Point } from "./geometries/point.ts";
export { ObservablePoint } from "./geometries/observablePoint.ts";
export { Bounds } from "./physics/bounds.ts";
export { Polygon } from "./geometries/polygon.ts";
export { Line } from "./geometries/line.ts";
Expand Down
2 changes: 1 addition & 1 deletion packages/melonjs/src/renderable/imagelayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default class ImageLayer extends Sprite {
if (typeof settings.anchorPoint === "number") {
this.anchorPoint.set(settings.anchorPoint, settings.anchorPoint);
} /* vector */ else {
this.anchorPoint.setV(settings.anchorPoint);
this.anchorPoint.set(settings.anchorPoint.x, settings.anchorPoint.y);
}
}

Expand Down
29 changes: 8 additions & 21 deletions packages/melonjs/src/renderable/renderable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { Bounds, boundsPool } from "./../physics/bounds.ts";
import GLShader from "./../video/webgl/glshader.js";
import { Color, colorPool } from "./../math/color.ts";
import { createObservableVector3d } from "../math/observableVector3d.ts";
import { Vector2d, vector2dPool } from "../math/vector2d.ts";
import { vector2dPool } from "../math/vector2d.ts";
import { matrix2dPool } from "../math/matrix2d.ts";
import { Vector3d } from "../math/vector3d.ts";
import { createObservableVector2d } from "../math/observableVector2d.ts";
import { ObservablePoint } from "../geometries/observablePoint.ts";

/**
* additional import for TypeScript
Expand Down Expand Up @@ -59,12 +59,12 @@ export default class Renderable extends Rect {
* <br>
* <i><b>Note:</b> Object created through Tiled will have their anchorPoint set to (0, 0) to match Tiled Level editor implementation.
* To specify a value through Tiled, use a json expression like `json:{"x":0.5,"y":0.5}`. </i>
* @type {ObservableVector2d}
* @type {ObservablePoint}
* @default <0.5,0.5>
*/
this.anchorPoint = createObservableVector2d({
target: new Vector2d(0.5, 0.5),
updateFn: this.onAnchorUpdate.bind(this),
this.anchorPoint = new ObservablePoint(0.5, 0.5, () => {
this.updateBounds();
this.isDirty = true;
});

if (typeof this.currentTransform === "undefined") {
Expand Down Expand Up @@ -668,21 +668,6 @@ export default class Renderable extends Rect {
return this._absPos;
}

/**
* called when the anchor point value is changed
* @private
* @param {number} x - the new X value to be set for the anchor
* @param {number} y - the new Y value to be set for the anchor
*/
onAnchorUpdate({ newX: x, newY: y }) {
// since the callback is called before setting the new value
// manually update the anchor point (required for updateBoundsPos)
this.anchorPoint.setMuted(x, y);
// then call updateBounds
this.updateBounds();
this.isDirty = true;
}

/**
* Prepare the rendering context before drawing (automatically called by melonJS).
* This will apply any defined transforms, anchor point, tint or blend mode and translate the context accordingly to this renderable position.
Expand Down Expand Up @@ -819,7 +804,9 @@ export default class Renderable extends Rect {
matrix2dPool.release(this.currentTransform);
this.currentTransform = undefined;

this.anchorPoint.revoke();
this.anchorPoint = undefined;

this.pos = undefined;

if (typeof this._absPos !== "undefined") {
Expand Down
2 changes: 1 addition & 1 deletion packages/melonjs/src/renderable/text/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export default class Text extends Renderable {

// anchor point
if (typeof settings.anchorPoint !== "undefined") {
this.anchorPoint.setV(settings.anchorPoint);
this.anchorPoint.set(settings.anchorPoint.x, settings.anchorPoint.y);
} else {
this.anchorPoint.set(0, 0);
}
Expand Down
83 changes: 83 additions & 0 deletions packages/melonjs/tests/observablePoint.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, it } from "vitest";
import { ObservablePoint, Point } from "../src/index.js";

describe("ObservablePoint : constructor", () => {
it("creates a new ObservablePoint instance with default values", () => {
const observablePoint = new ObservablePoint();
expect(observablePoint.x).toEqual(0);
expect(observablePoint.y).toEqual(0);
});

it("creates a new ObservablePoint instance with specified values", () => {
const observablePoint = new ObservablePoint(10, 20);
expect(observablePoint.x).toEqual(10);
expect(observablePoint.y).toEqual(20);
});

it("values can be compared with a Point object", () => {
const observablePoint = new ObservablePoint(10, 30);
const point = new Point(10, 30);
expect(observablePoint.equals(point)).toEqual(true);
});

it("triggers the callback function when setting values", () => {
let callbackCalled = false;
const callback = () => {
callbackCalled = true;
};
const observablePoint = new ObservablePoint(0, 0, callback);
expect(observablePoint.x).toEqual(0);
expect(observablePoint.y).toEqual(0);
expect(callbackCalled).toEqual(false);

observablePoint.x = 10;
expect(observablePoint.x).toEqual(10);
expect(callbackCalled).toEqual(true);
});

it("triggers the callback function when calling set()", () => {
let callbackCalled = false;
const callback = () => {
callbackCalled = true;
};
const observablePoint = new ObservablePoint(0, 0, callback);
expect(observablePoint.x).toEqual(0);
expect(observablePoint.y).toEqual(0);
expect(callbackCalled).toEqual(false);

observablePoint.set(10, 20);
expect(observablePoint.equals(10, 20)).toEqual(true);
expect(callbackCalled).toEqual(true);
});

it("does not trigger the callback function when using setMuted", () => {
let callbackCalled = false;
const callback = () => {
callbackCalled = true;
};
const observablePoint = new ObservablePoint(0, 0, callback);
expect(observablePoint.x).toEqual(0);
expect(observablePoint.y).toEqual(0);
expect(callbackCalled).toEqual(false);

observablePoint.setMuted(10, 20);
expect(observablePoint.equals(10, 20)).toEqual(true);
expect(callbackCalled).toEqual(false);
});

it("does not trigger the callback function when revoked", () => {
let callbackCalled = false;
const callback = () => {
callbackCalled = true;
};
const observablePoint = new ObservablePoint(0, 0, callback);
expect(observablePoint.x).toEqual(0);
expect(observablePoint.y).toEqual(0);
expect(callbackCalled).toEqual(false);

observablePoint.revoke();
expect(() => {
observablePoint.set(20, 10);
}).toThrow();
});
});

0 comments on commit c174ca6

Please sign in to comment.