Skip to content
1 change: 1 addition & 0 deletions packages/core/src/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ export class Engine extends EventDispatcher {
this._renderElementPool.garbageCollection();
this._textRenderElementPool.garbageCollection();
this._renderContext.garbageCollection();
this.inputManager._gc();
const scenes = this._sceneManager._scenes.getLoopArray();
for (let i = 0, n = scenes.length; i < n; i++) {
scenes[i]?.physics?._gc();
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/input/InputManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ export class InputManager {
this._initialized && this._pointerManager._firePointerScript(scenes);
}

/**
* @internal
*/
_gc(): void {
this._initialized && this._pointerManager._gc();
}

/**
* @internal
*/
Expand Down
14 changes: 2 additions & 12 deletions packages/core/src/input/pointer/Pointer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Vector2 } from "@galacean/engine-math";
import { ClearableObjectPool } from "../../utils/ClearableObjectPool";
import { DisorderedArray } from "../../utils/DisorderedArray";
import { PointerButton } from "../enums/PointerButton";
import { PointerPhase } from "../enums/PointerPhase";
import { PointerEventData } from "./PointerEventData";
import { PointerEventEmitter } from "./emitter/PointerEventEmitter";

/**
Expand All @@ -23,6 +21,8 @@ export class Pointer {
pressedButtons: PointerButton;
/** The position of the pointer in screen space pixel coordinates. */
position: Vector2 = new Vector2();
/** The position of the pointer when it was last pressed down (in screen space pixel coordinates). */
pressedPosition: Vector2 = new Vector2();
/** The change of the pointer. */
deltaPosition: Vector2 = new Vector2();
/** @internal */
Expand All @@ -49,16 +49,6 @@ export class Pointer {
this.id = id;
}

/**
* @internal
*/
_addEmitters<T extends new (pool: ClearableObjectPool<PointerEventData>) => PointerEventEmitter>(
type: T,
pool: ClearableObjectPool<PointerEventData>
) {
this._emitters.push(new type(pool));
}

/**
* @internal
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/input/pointer/PointerEventData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Vector3 } from "@galacean/engine-math";
import { Entity } from "../../Entity";
import { IPoolElement } from "../../utils/ObjectPool";
import { Pointer } from "./Pointer";

Expand All @@ -10,11 +11,17 @@ export class PointerEventData implements IPoolElement {
pointer: Pointer;
/** The position of the event trigger (in world space). */
worldPosition: Vector3 = new Vector3();
/** The hit-tested target entity (deepest entity on the bubble path). */
target: Entity = null;
/** The entity currently handling the event (same as target on non-bubbling fire, varies on bubble). */
currentTarget: Entity = null;

/**
* @internal
*/
dispose() {
this.pointer = null;
this.target = null;
this.currentTarget = null;
}
}
26 changes: 22 additions & 4 deletions packages/core/src/input/pointer/PointerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ export class PointerManager implements IInput {
pointer = pointerPool[j];
if (!pointer) {
pointer = pointerPool[j] = new Pointer(j);
engine._physicsInitialized && pointer._addEmitters(PhysicsPointerEventEmitter, eventPool);
PointerManager._pointerEventEmitters.forEach((emitter) => {
pointer._addEmitters(emitter, eventPool);
});
this._addEmitters(pointer);
}
pointer._uniqueID = pointerId;
pointer._events.push(evt);
Expand Down Expand Up @@ -193,6 +190,13 @@ export class PointerManager implements IInput {
}
}

/**
* @internal
*/
_gc(): void {
this._eventPool.garbageCollection();
}

/**
* @internal
*/
Expand All @@ -201,6 +205,16 @@ export class PointerManager implements IInput {
this._pointerPool.length = 0;
}

private _addEmitters(pointer: Pointer): void {
const { _eventPool: eventPool, _engine: engine } = this;
const emitters = pointer._emitters;
engine._physicsInitialized && emitters.push(new PhysicsPointerEventEmitter(eventPool));
const emitterTypes = PointerManager._pointerEventEmitters;
for (let i = 0, n = emitterTypes.length; i < n; i++) {
emitters.push(new emitterTypes[i](eventPool));
}
}

private _onPointerEvent(evt: PointerEvent) {
this._nativeEvents.push(evt);
}
Expand Down Expand Up @@ -245,6 +259,10 @@ export class PointerManager implements IInput {
pointer._downMap[button] = frameCount;
pointer._frameEvents |= PointerEventType.Down;
pointer.phase = PointerPhase.Down;
pointer.pressedPosition.set(
(event.clientX - left) * widthPixelRatio,
(event.clientY - top) * heightPixelRatio
);
break;
}
case "pointerup": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter {

override processDrag(pointer: Pointer): void {
const entity = this._draggedEntity;
entity && this._fireDrag(entity, this._createEventData(pointer));
entity && this._fireDrag(entity, this._createEventData(pointer, entity));
}

override processDown(pointer: Pointer): void {
const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity);
if (entity) {
const eventData = this._createEventData(pointer);
const eventData = this._createEventData(pointer, entity);
this._fireDown(entity, eventData);
this._fireBeginDrag(entity, eventData);
}
Expand All @@ -69,28 +69,28 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter {
const { _enteredEntity: enteredEntity, _draggedEntity: draggedEntity } = this;
if (enteredEntity) {
const sameTarget = this._pressedEntity === enteredEntity;
const eventData = this._createEventData(pointer);
const eventData = this._createEventData(pointer, enteredEntity);
this._fireUp(enteredEntity, eventData);
sameTarget && this._fireClick(enteredEntity, eventData);
this._fireDrop(enteredEntity, eventData);
}
this._pressedEntity = null;
if (draggedEntity) {
this._fireEndDrag(draggedEntity, this._createEventData(pointer));
this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity));
this._draggedEntity = null;
}
}

override processLeave(pointer: Pointer): void {
const enteredEntity = this._enteredEntity;
if (enteredEntity) {
this._fireExit(enteredEntity, this._createEventData(pointer));
this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity));
this._enteredEntity = null;
}

const draggedEntity = this._draggedEntity;
if (draggedEntity) {
this._fireEndDrag(draggedEntity, this._createEventData(pointer));
this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity));
this._draggedEntity = null;
}
this._pressedEntity = null;
Expand All @@ -108,10 +108,10 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter {
const enteredEntity = this._enteredEntity;
if (entity !== enteredEntity) {
if (enteredEntity) {
this._fireExit(enteredEntity, this._createEventData(pointer));
this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity));
}
if (entity) {
this._fireEnter(entity, this._createEventData(pointer));
this._fireEnter(entity, this._createEventData(pointer, entity));
}
this._enteredEntity = entity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ export abstract class PointerEventEmitter {

protected abstract _init(): void;

protected _createEventData(pointer: Pointer): PointerEventData {
protected _createEventData(pointer: Pointer, target: Entity, currentTarget: Entity = target): PointerEventData {
const data = this._pool.get();
data.pointer = pointer;
data.worldPosition.copyFrom(this._hitResult.point);
data.target = target;
data.currentTarget = currentTarget;
return data;
}

Expand Down
21 changes: 9 additions & 12 deletions packages/ui/src/input/UIPointerEventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ export class UIPointerEventEmitter extends PointerEventEmitter {
}
}
if (camera.clearFlags & CameraClearFlags.Color) {
this._updateRaycast(null);
this._updateRaycast(null, pointer);
return;
}
}
this._updateRaycast(null);
this._updateRaycast(null, pointer);
}
}

Expand Down Expand Up @@ -128,10 +128,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter {
if (pressedPath.length > 0) {
const common = UIPointerEventEmitter._tempArray0;
if (this._findCommonInPath(enteredPath, pressedPath, common)) {
const eventData = this._createEventData(pointer);
for (let i = 0, n = common.length; i < n; i++) {
this._fireClick(common[i], eventData);
}
this._bubble(common, pointer, this._fireClick);
common.length = 0;
}
}
Expand Down Expand Up @@ -170,18 +167,17 @@ export class UIPointerEventEmitter extends PointerEventEmitter {
this._enteredPath.length = this._pressedPath.length = this._draggedPath.length = 0;
}

private _updateRaycast(element: UIRenderer, pointer: Pointer = null): void {
private _updateRaycast(element: UIRenderer | null, pointer: Pointer): void {
const enteredPath = this._enteredPath;
const curPath = this._composedPath(element, UIPointerEventEmitter._path);
const add = UIPointerEventEmitter._tempArray0;
const del = UIPointerEventEmitter._tempArray1;
if (this._findDiffInPath(enteredPath, curPath, add, del)) {
const eventData = this._createEventData(pointer);
for (let i = 0, n = add.length; i < n; i++) {
this._fireEnter(add[i], eventData);
this._fireEnter(add[i], this._createEventData(pointer, add[i]));
}
for (let i = 0, n = del.length; i < n; i++) {
this._fireExit(del[i], eventData);
this._fireExit(del[i], this._createEventData(pointer, del[i]));
}

const length = (enteredPath.length = curPath.length);
Expand All @@ -193,7 +189,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter {
curPath.length = 0;
}

private _composedPath(element: UIRenderer, path: Entity[]): Entity[] {
private _composedPath(element: UIRenderer | null, path: Entity[]): Entity[] {
if (!element) {
path.length = 0;
return path;
Expand Down Expand Up @@ -256,8 +252,9 @@ export class UIPointerEventEmitter extends PointerEventEmitter {
private _bubble(path: Entity[], pointer: Pointer, fireEvent: FireEvent): void {
const length = path.length;
if (length <= 0) return;
const eventData = this._createEventData(pointer);
const eventData = this._createEventData(pointer, path[0]);
for (let i = 0; i < length; i++) {
eventData.currentTarget = path[i];
fireEvent(path[i], eventData);
}
}
Expand Down
60 changes: 60 additions & 0 deletions tests/src/core/input/InputManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,41 @@ describe("InputManager", async () => {
engine.update();
});

it("pointer pressedPosition", () => {
// @ts-ignore
const { _pointerManager: pointerManager } = inputManager;
const { _target: target } = pointerManager;
const { left, top } = target.getBoundingClientRect();

// Frame 1: a pointerdown and a move to a different position are batched into one frame.
target.dispatchEvent(generatePointerEvent("pointerdown", 5, left + 1, top + 1));
target.dispatchEvent(generatePointerEvent("pointermove", 5, left + 3, top + 3));
engine.update();
const pointer = inputManager.pointers[0];
// @ts-ignore
const uniqueID = pointer._uniqueID;
const position1 = pointer.position.clone();
const pressedPosition1 = pointer.pressedPosition.clone();

// Frame 2: keep dragging to yet another position.
target.dispatchEvent(generatePointerEvent("pointermove", 5, left + 6, top + 6));
engine.update();
const position2 = pointer.position.clone();
const pressedPosition2 = pointer.pressedPosition.clone();

// Release the pointer BEFORE asserting, so a failing expectation cannot leak it into later tests.
target.dispatchEvent(generatePointerEvent("pointerleave", 5, left + 6, top + 6, -1, 0));
engine.update();

expect(uniqueID).to.eq(5);
// `position` always tracks the frame's latest event...
expect(position1).to.deep.eq(new Vector2(3 * 2, 3 * 2));
expect(position2).to.deep.eq(new Vector2(6 * 2, 6 * 2));
// ...while `pressedPosition` stays at the pointerdown location regardless of later moves.
expect(pressedPosition1).to.deep.eq(new Vector2(1 * 2, 1 * 2));
expect(pressedPosition2).to.deep.eq(new Vector2(1 * 2, 1 * 2));
});

it("keyboard", () => {
// @ts-ignore
const { _keyboardManager: keyboardManager } = inputManager;
Expand Down Expand Up @@ -243,6 +278,31 @@ describe("InputManager", async () => {
expect(inputManager.wheelDelta).to.deep.eq(new Vector3(1, 2, 3));
});

it("gc reclaims the pointer event data pool", () => {
// @ts-ignore
const { _pointerManager: pointerManager } = inputManager;
const { _target: target } = pointerManager;
// @ts-ignore
const elements = pointerManager._eventPool._elements;
const { left, top } = target.getBoundingClientRect();

// Pressing on the box allocates event data into the pool.
target.dispatchEvent(generatePointerEvent("pointerdown", 6, left + 2, top + 2));
engine.update();
expect(elements.length).to.be.greaterThan(0);

// Engine gc reclaims the pool, releasing the entity references it held.
// @ts-ignore
engine._pendingGC();
expect(elements.length).to.eq(0);

// The pool keeps working after gc.
target.dispatchEvent(generatePointerEvent("pointerup", 6, left + 2, top + 2, 0, 0));
target.dispatchEvent(generatePointerEvent("pointerleave", 6, left + 2, top + 2, -1, 0));
engine.update();
expect(elements.length).to.be.greaterThan(0);
});

it("destroy", () => {
engine.destroy();
// @ts-ignore
Expand Down
Loading
Loading