diff --git a/@types/pal/input.d.ts b/@types/pal/input.d.ts index 502622ee940..cc96d0c94b7 100644 --- a/@types/pal/input.d.ts +++ b/@types/pal/input.d.ts @@ -14,6 +14,7 @@ declare module 'pal/input' { * Register the touch event callback. */ public on (eventType: import('cocos/input/types/event-enum').InputEventType, callback: TouchCallback, target?: any); + public dispatchEventsInCache (): void; } type MouseCallback = (res: import('cocos/input/types').EventMouse) => void; @@ -30,6 +31,8 @@ declare module 'pal/input' { public dispatchMouseMoveEvent? (nativeMouseEvent: any); public dispatchMouseUpEvent? (nativeMouseEvent: any); public dispatchScrollEvent? (nativeMouseEvent: any); + + public dispatchEventsInCache (): void } type KeyboardCallback = (res: import('cocos/input/types').EventKeyboard) => void; diff --git a/cocos/input/input.ts b/cocos/input/input.ts index b2bb8f57e1b..b9435be729b 100644 --- a/cocos/input/input.ts +++ b/cocos/input/input.ts @@ -142,8 +142,6 @@ export class Input { private _hmdInput$ = new HMDInputDevice(); private _handheldInput$ = new HandheldInputDevice(); - private _eventTouchList$: EventTouch[] = []; - private _eventMouseList$: EventMouse[] = []; private _eventKeyboardList$: EventKeyboard[] = []; private _eventAccelerationList$: EventAcceleration[] = []; private _eventGamepadList$: EventGamepad[] = []; @@ -324,7 +322,7 @@ export class Input { if (eventType === InputEventType.TOUCH_END) { touchManager.releaseTouch(touchID); } - this._dispatchOrPushEventTouch$(eventTouch, this._eventTouchList$); + this._dispatchEventTouch$(eventTouch); } /** @@ -353,47 +351,45 @@ export class Input { private _registerEvent$ (): void { if (sys.hasFeature(sys.Feature.INPUT_TOUCH)) { - const eventTouchList = this._eventTouchList$; this._touchInput$.on(InputEventType.TOUCH_START, (event): void => { - this._dispatchOrPushEventTouch$(event, eventTouchList); + this._dispatchEventTouch$(event); }); this._touchInput$.on(InputEventType.TOUCH_MOVE, (event): void => { - this._dispatchOrPushEventTouch$(event, eventTouchList); + this._dispatchEventTouch$(event); }); this._touchInput$.on(InputEventType.TOUCH_END, (event): void => { - this._dispatchOrPushEventTouch$(event, eventTouchList); + this._dispatchEventTouch$(event); }); this._touchInput$.on(InputEventType.TOUCH_CANCEL, (event): void => { - this._dispatchOrPushEventTouch$(event, eventTouchList); + this._dispatchEventTouch$(event); }); } if (sys.hasFeature(sys.Feature.EVENT_MOUSE)) { - const eventMouseList = this._eventMouseList$; this._mouseInput$.on(InputEventType.MOUSE_DOWN, (event): void => { this._needSimulateTouchMoveEvent$ = true; this._simulateEventTouch$(event); - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); this._mouseInput$.on(InputEventType.MOUSE_MOVE, (event): void => { if (this._needSimulateTouchMoveEvent$) { this._simulateEventTouch$(event); } - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); this._mouseInput$.on(InputEventType.MOUSE_UP, (event): void => { this._needSimulateTouchMoveEvent$ = false; this._simulateEventTouch$(event); - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); this._mouseInput$.on(InputEventType.MOUSE_WHEEL, (event): void => { - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); this._mouseInput$.on(InputEventType.MOUSE_LEAVE, (event): void => { - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); this._mouseInput$.on(InputEventType.MOUSE_ENTER, (event): void => { - this._dispatchOrPushEvent$(event, eventMouseList); + this._dispatchEventMouse$(event); }); } @@ -459,8 +455,6 @@ export class Input { * @engineInternal */ public _clearEvents (): void { - this._eventMouseList$.length = 0; - this._eventTouchList$.length = 0; this._eventKeyboardList$.length = 0; this._eventAccelerationList$.length = 0; this._eventGamepadList$.length = 0; @@ -476,17 +470,17 @@ export class Input { } } - private _dispatchOrPushEventTouch$ (eventTouch: EventTouch, touchEventList: EventTouch[]): void { - if (dispatchImmediately) { - const touches = eventTouch.getTouches(); - const touchesLength = touches.length; - for (let i = 0; i < touchesLength; ++i) { - eventTouch.touch = touches[i]; - eventTouch.propagationStopped = eventTouch.propagationImmediateStopped = false; - this._emitEvent$(eventTouch); - } - } else { - touchEventList.push(eventTouch); + private _dispatchEventMouse$ (event: Event): void { + this._emitEvent$(event); + } + + private _dispatchEventTouch$ (eventTouch: EventTouch): void { + const touches = eventTouch.getTouches(); + const touchesLength = touches.length; + for (let i = 0; i < touchesLength; ++i) { + eventTouch.touch = touches[i]; + eventTouch.propagationStopped = eventTouch.propagationImmediateStopped = false; + this._emitEvent$(eventTouch); } } @@ -509,25 +503,8 @@ export class Input { this._emitEvent$(eventHandheld); } - const eventMouseList = this._eventMouseList$; - // TODO: culling event queue - for (let i = 0, length = eventMouseList.length; i < length; ++i) { - const eventMouse = eventMouseList[i]; - this._emitEvent$(eventMouse); - } - - const eventTouchList = this._eventTouchList$; - // TODO: culling event queue - for (let i = 0, length = eventTouchList.length; i < length; ++i) { - const eventTouch = eventTouchList[i]; - const touches = eventTouch.getTouches(); - const touchesLength = touches.length; - for (let j = 0; j < touchesLength; ++j) { - eventTouch.touch = touches[j]; - eventTouch.propagationStopped = eventTouch.propagationImmediateStopped = false; - this._emitEvent$(eventTouch); - } - } + this._mouseInput$.dispatchEventsInCache(); + this._touchInput$.dispatchEventsInCache(); const eventKeyboardList = this._eventKeyboardList$; // TODO: culling event queue diff --git a/pal/input/minigame/mouse-input.ts b/pal/input/minigame/mouse-input.ts index 7cffe0fe027..497a2787e3f 100644 --- a/pal/input/minigame/mouse-input.ts +++ b/pal/input/minigame/mouse-input.ts @@ -110,4 +110,8 @@ export class MouseInputSource { public on (eventType: InputEventType, callback: MouseCallback, target?: any): void { this._eventTarget$.on(eventType, callback, target); } + + public dispatchEventsInCache (): void { + // Do nothing + } } diff --git a/pal/input/minigame/touch-input.ts b/pal/input/minigame/touch-input.ts index cd5ce773b0b..db9ffa94153 100644 --- a/pal/input/minigame/touch-input.ts +++ b/pal/input/minigame/touch-input.ts @@ -92,4 +92,8 @@ export class TouchInputSource { public on (eventType: InputEventType, callback: TouchCallback, target?: any): void { this._eventTarget$.on(eventType, callback, target); } + + public dispatchEventsInCache (): void { + // Do nothing + } } diff --git a/pal/input/native/mouse-input.ts b/pal/input/native/mouse-input.ts index 1a68b86ce4b..046758e219e 100644 --- a/pal/input/native/mouse-input.ts +++ b/pal/input/native/mouse-input.ts @@ -32,31 +32,84 @@ export type MouseCallback = (res: EventMouse) => void; declare const jsb: any; +class MouseEventElement { + type: InputEventType | null = null; + mouseEvent = { + x: 0, + y: 0, + xDelta: 0, + yDelta: 0, + button: 0, + windowId: 0, + wheelDeltaX: 0, + wheelDeltaY: 0, + }; +} + +class MouseEventCache { + private _events: MouseEventElement[] = []; + private _length = 0; + + push (eventType: InputEventType, mouseEvent?: any): void { + const events = this._events; + const index = this._length; + if (index >= events.length) { + events.push(new MouseEventElement()); + } + const e = events[index]; + e.type = eventType; + const cachedEvent = e.mouseEvent; + if (mouseEvent) { + Object.assign(cachedEvent, mouseEvent); + } else { + cachedEvent.x = cachedEvent.y = cachedEvent.xDelta = cachedEvent.yDelta = 0; + cachedEvent.button = cachedEvent.windowId = cachedEvent.wheelDeltaX = cachedEvent.wheelDeltaY = 0; + } + + ++this._length; + } + + clear (): void { + this._length = 0; + } + + forEach (cb: ((e: MouseEventElement) => void)): void { + for (let i = 0, len = this._length; i < len; ++i) { + cb(this._events[i]); + } + } +} + export class MouseInputSource { private _eventTarget: EventTarget = new EventTarget(); private _preMousePos: Vec2 = new Vec2(); private _isPressed = false; private _windowManager: any; private _pointLocked = false; + private _cache = new MouseEventCache(); private _handleMouseDown: (mouseEvent: jsb.MouseEvent) => void; private _handleMouseMove: (mouseEvent: jsb.MouseEvent) => void; private _handleMouseUp: (mouseEvent: jsb.MouseEvent) => void; - private _boundedHandleMouseWheel: (mouseEvent: jsb.MouseWheelEvent) => void; + private _handleWindowLeave: () => void; + private _handleWindowEnter: () => void; + private _handleMouseWheel: (mouseEvent: jsb.MouseWheelEvent) => void; constructor () { - this._handleMouseDown = this._createCallback(InputEventType.MOUSE_DOWN); - this._handleMouseMove = this._createCallback(InputEventType.MOUSE_MOVE); - this._handleMouseUp = this._createCallback(InputEventType.MOUSE_UP); - this._boundedHandleMouseWheel = this._handleMouseWheel.bind(this); - this._registerEvent(); + this._handleMouseDown = this._createEventCacheCallback$(InputEventType.MOUSE_DOWN); + this._handleMouseMove = this._createEventCacheCallback$(InputEventType.MOUSE_MOVE); + this._handleMouseUp = this._createEventCacheCallback$(InputEventType.MOUSE_UP); + this._handleWindowLeave = this._createEventCacheCallback$(InputEventType.MOUSE_LEAVE); + this._handleWindowEnter = this._createEventCacheCallback$(InputEventType.MOUSE_ENTER); + this._handleMouseWheel = this._createEventCacheCallback$(InputEventType.MOUSE_WHEEL); + this._registerEvent$(); this._windowManager = jsb.ISystemWindowManager.getInstance(); } public dispatchMouseDownEvent (nativeMouseEvent: any): void { this._handleMouseDown(nativeMouseEvent as jsb.MouseEvent); } public dispatchMouseMoveEvent (nativeMouseEvent: any): void { this._handleMouseMove(nativeMouseEvent as jsb.MouseEvent); } public dispatchMouseUpEvent (nativeMouseEvent: any): void { this._handleMouseUp(nativeMouseEvent as jsb.MouseEvent); } - public dispatchScrollEvent (nativeMouseEvent: any): void { this._boundedHandleMouseWheel(nativeMouseEvent as jsb.MouseWheelEvent); } + public dispatchScrollEvent (nativeMouseEvent: any): void { this._handleMouseWheel(nativeMouseEvent as jsb.MouseWheelEvent); } private _getLocation (event: jsb.MouseEvent): Vec2 { const window = this._windowManager.getWindow(event.windowId); @@ -67,53 +120,80 @@ export class MouseInputSource { return new Vec2(x, y); } - private _registerEvent (): void { + private _registerEvent$ (): void { jsb.onMouseDown = this._handleMouseDown; jsb.onMouseMove = this._handleMouseMove; jsb.onMouseUp = this._handleMouseUp; - jsb.onMouseWheel = this._boundedHandleMouseWheel; + jsb.onMouseWheel = this._handleMouseWheel; jsb.onPointerlockChange = (value: boolean): void => { this._pointLocked = value; }; // Treat window leave/enter events as mouse events as web. - jsb.onWindowLeave = this._handleWindowLeave.bind(this); - jsb.onWindowEnter = this._handleWindowEnter.bind(this); + jsb.onWindowLeave = this._handleWindowLeave; + jsb.onWindowEnter = this._handleWindowEnter; + } + + private _createEventCacheCallback$ (eventType: InputEventType) { + return (mouseEvent?: jsb.MouseEvent | jsb.MouseWheelEvent): void => { + this._cache.push(eventType, mouseEvent); + }; } - private _createCallback (eventType: InputEventType) { - return (mouseEvent: jsb.MouseEvent): void => { - const location = this._getLocation(mouseEvent); - let button = mouseEvent.button; - switch (eventType) { - case InputEventType.MOUSE_DOWN: - this._isPressed = true; + public dispatchEventsInCache (): void { + const cache = this._cache; + + cache.forEach((e: MouseEventElement) => { + switch (e.type) { + case InputEventType.MOUSE_LEAVE: + this._dispatchWindowLeave$(); break; - case InputEventType.MOUSE_UP: - this._isPressed = false; + case InputEventType.MOUSE_ENTER: + this._dispatchWindowEnter$(); break; - case InputEventType.MOUSE_MOVE: - if (!this._isPressed) { - button = EventMouse.BUTTON_MISSING; - } + case InputEventType.MOUSE_WHEEL: + this._dispatchMouseWheel$(e.mouseEvent as jsb.MouseWheelEvent); break; default: + this._dispatchEvent$(e.type!, e.mouseEvent); break; } + }); - const eventMouse = new EventMouse(eventType, false, this._preMousePos, mouseEvent.windowId); - eventMouse.setLocation(location.x, location.y); - eventMouse.setButton(button); - const dpr = screenAdapter.devicePixelRatio; - eventMouse.movementX = typeof mouseEvent.xDelta === 'undefined' ? 0 : mouseEvent.xDelta * dpr; - eventMouse.movementY = typeof mouseEvent.yDelta === 'undefined' ? 0 : mouseEvent.yDelta * dpr; - // update previous mouse position. - this._preMousePos.set(location.x, location.y); - this._eventTarget.emit(eventType, eventMouse); - }; + cache.clear(); + } + + private _dispatchEvent$ (eventType: InputEventType, mouseEvent: jsb.MouseEvent): void { + const location = this._getLocation(mouseEvent); + let button = mouseEvent.button; + switch (eventType) { + case InputEventType.MOUSE_DOWN: + this._isPressed = true; + break; + case InputEventType.MOUSE_UP: + this._isPressed = false; + break; + case InputEventType.MOUSE_MOVE: + if (!this._isPressed) { + button = EventMouse.BUTTON_MISSING; + } + break; + default: + break; + } + + const eventMouse = new EventMouse(eventType, false, this._preMousePos, mouseEvent.windowId); + eventMouse.setLocation(location.x, location.y); + eventMouse.setButton(button); + const dpr = screenAdapter.devicePixelRatio; + eventMouse.movementX = typeof mouseEvent.xDelta === 'undefined' ? 0 : mouseEvent.xDelta * dpr; + eventMouse.movementY = typeof mouseEvent.yDelta === 'undefined' ? 0 : mouseEvent.yDelta * dpr; + // update previous mouse position. + this._preMousePos.set(location.x, location.y); + this._eventTarget.emit(eventType, eventMouse); } - private _handleMouseWheel (mouseEvent: jsb.MouseWheelEvent): void { + private _dispatchMouseWheel$ (mouseEvent: jsb.MouseWheelEvent): void { const eventType = InputEventType.MOUSE_WHEEL; const location = this._getLocation(mouseEvent); const button = mouseEvent.button; @@ -136,13 +216,13 @@ export class MouseInputSource { } // Should include window id if supporting multiple windows. - private _handleWindowLeave (): void { + private _dispatchWindowLeave$ (): void { const eventType = InputEventType.MOUSE_LEAVE; const eventMouse = new EventMouse(eventType, false); this._eventTarget.emit(eventType, eventMouse); } - private _handleWindowEnter (): void { + private _dispatchWindowEnter$ (): void { const eventType = InputEventType.MOUSE_ENTER; const eventMouse = new EventMouse(eventType, false); this._eventTarget.emit(eventType, eventMouse); diff --git a/pal/input/native/touch-input.ts b/pal/input/native/touch-input.ts index 58bf2655964..8a2a751f6bb 100644 --- a/pal/input/native/touch-input.ts +++ b/pal/input/native/touch-input.ts @@ -25,63 +25,122 @@ import { screenAdapter } from 'pal/screen-adapter'; import { Size, Vec2 } from '../../../cocos/core/math'; import { EventTarget } from '../../../cocos/core/event'; -import { EventTouch, Touch } from '../../../cocos/input/types'; +import { EventTouch, Touch as CCTouch } from '../../../cocos/input/types'; import { touchManager } from '../touch-manager'; -import { macro } from '../../../cocos/core/platform/macro'; import { InputEventType } from '../../../cocos/input/types/event-enum'; export type TouchCallback = (res: EventTouch) => void; declare const jsb: any; +class TouchEventElement { + type: InputEventType | null = null; + changedTouches: Touch[] = []; + windowId: number = 0; +} + +class TouchEventCache { + private _events: TouchEventElement[] = []; + private _length = 0; + + push (eventType: InputEventType, changedTouches: Touch[], windowId: number): void { + const events = this._events; + const index = this._length; + if (index >= events.length) { + events.push(new TouchEventElement()); + } + const e = events[index]; + const cachedTouches = e.changedTouches; + e.type = eventType; + cachedTouches.length = changedTouches.length; + e.windowId = windowId; + + for (let i = 0, len = changedTouches.length; i < len; ++i) { + const src = changedTouches[i]; + let dst = cachedTouches[i] as any; + if (!dst) { + (cachedTouches[i] as any) = dst = {}; + } + Object.assign(dst, src); + } + + ++this._length; + } + + clear (): void { + this._length = 0; + } + + forEach (cb: ((e: TouchEventElement) => void)): void { + for (let i = 0, len = this._length; i < len; ++i) { + cb(this._events[i]); + } + } +} + export class TouchInputSource { private _eventTarget: EventTarget = new EventTarget(); private _windowManager: any; + private _cache = new TouchEventCache(); + constructor () { this._registerEvent(); this._windowManager = jsb.ISystemWindowManager.getInstance(); } private _registerEvent (): void { - jsb.onTouchStart = this._createCallback(InputEventType.TOUCH_START); - jsb.onTouchMove = this._createCallback(InputEventType.TOUCH_MOVE); - jsb.onTouchEnd = this._createCallback(InputEventType.TOUCH_END); - jsb.onTouchCancel = this._createCallback(InputEventType.TOUCH_CANCEL); + jsb.onTouchStart = this._createEventCacheCallback(InputEventType.TOUCH_START); + jsb.onTouchMove = this._createEventCacheCallback(InputEventType.TOUCH_MOVE); + jsb.onTouchEnd = this._createEventCacheCallback(InputEventType.TOUCH_END); + jsb.onTouchCancel = this._createEventCacheCallback(InputEventType.TOUCH_CANCEL); + } + + private _createEventCacheCallback (eventType: InputEventType) { + return (changedTouches: Touch[], windowId: number): void => { + this._cache.push(eventType, changedTouches, windowId); + }; } - private _createCallback (eventType: InputEventType) { - return (changedTouches: TouchList, windowId: number): void => { - const handleTouches: Touch[] = []; - const length = changedTouches.length; - const windowSize = this._windowManager.getWindow(windowId).getViewSize() as Size; - for (let i = 0; i < length; ++i) { - const changedTouch = changedTouches[i]; - const touchID = changedTouch.identifier; - if (touchID === null) { - continue; - } - const location = this._getLocation(changedTouch, windowSize); - const touch = touchManager.getOrCreateTouch(touchID, location.x, location.y); - if (!touch) { - continue; - } - if (eventType === InputEventType.TOUCH_END || eventType === InputEventType.TOUCH_CANCEL) { - touchManager.releaseTouch(touchID); - } - handleTouches.push(touch); + public dispatchEventsInCache (): void { + const cache = this._cache; + cache.forEach((e: TouchEventElement) => { + this._dispatchEvent(e.type!, e.changedTouches, e.windowId); + }); + + cache.clear(); + } + + private _dispatchEvent (eventType: InputEventType, changedTouches: Touch[], windowId: number): void { + const handleTouches: CCTouch[] = []; + const length = changedTouches.length; + const windowSize = this._windowManager.getWindow(windowId).getViewSize() as Size; + for (let i = 0; i < length; ++i) { + const changedTouch = changedTouches[i]; + const touchID = changedTouch.identifier; + if (touchID === null) { + continue; } - if (handleTouches.length > 0) { - const eventTouch = new EventTouch( - handleTouches, - false, - eventType, - touchManager.getAllTouches(), - ); - eventTouch.windowId = windowId; - this._eventTarget.emit(eventType, eventTouch); + const location = this._getLocation(changedTouch, windowSize); + const touch = touchManager.getOrCreateTouch(touchID, location.x, location.y); + if (!touch) { + continue; } - }; + if (eventType === InputEventType.TOUCH_END || eventType === InputEventType.TOUCH_CANCEL) { + touchManager.releaseTouch(touchID); + } + handleTouches.push(touch); + } + if (handleTouches.length > 0) { + const eventTouch = new EventTouch( + handleTouches, + false, + eventType, + touchManager.getAllTouches(), + ); + eventTouch.windowId = windowId; + this._eventTarget.emit(eventType, eventTouch); + } } private _getLocation (touch: globalThis.Touch, windowSize: Size): Vec2 { diff --git a/pal/input/web/mouse-input.ts b/pal/input/web/mouse-input.ts index e7b20fa077b..d204c99fc25 100644 --- a/pal/input/web/mouse-input.ts +++ b/pal/input/web/mouse-input.ts @@ -31,6 +31,7 @@ import { EventTarget } from '../../../cocos/core/event'; import { Rect, Vec2 } from '../../../cocos/core/math'; import { InputEventType } from '../../../cocos/input/types/event-enum'; import { Feature } from '../../system-info/enum-type'; +import { warn } from '../../../cocos/core/platform/debug'; export class MouseInputSource { private _canvas?: HTMLCanvasElement; @@ -47,7 +48,7 @@ export class MouseInputSource { if (systemInfo.hasFeature(Feature.EVENT_MOUSE)) { this._canvas = document.getElementById('GameCanvas') as HTMLCanvasElement; if (!this._canvas && !TEST && !EDITOR) { - console.warn('failed to access canvas'); + warn('failed to access canvas'); } this._handleMouseDown = this._createCallback(InputEventType.MOUSE_DOWN); @@ -60,9 +61,9 @@ export class MouseInputSource { } } - public dispatchMouseDownEvent (nativeMouseEvent: any): void { this._handleMouseDown(nativeMouseEvent); } - public dispatchMouseMoveEvent (nativeMouseEvent: any): void { this._handleMouseMove(nativeMouseEvent); } - public dispatchMouseUpEvent (nativeMouseEvent: any): void { this._handleMouseUp(nativeMouseEvent); } + public dispatchMouseDownEvent (nativeMouseEvent: any): void { this._handleMouseDown(nativeMouseEvent as MouseEvent); } + public dispatchMouseMoveEvent (nativeMouseEvent: any): void { this._handleMouseMove(nativeMouseEvent as MouseEvent); } + public dispatchMouseUpEvent (nativeMouseEvent: any): void { this._handleMouseUp(nativeMouseEvent as MouseEvent); } public dispatchScrollEvent (nativeMouseEvent: WheelEvent): void { this._handleMouseWheel(nativeMouseEvent); } public on (eventType: InputEventType, callback: MouseCallback, target?: any): void { @@ -208,4 +209,8 @@ export class MouseInputSource { const eventMouse = new EventMouse(eventType, false); this._eventTarget.emit(eventType, eventMouse); } + + public dispatchEventsInCache (): void { + // Do nothing + } } diff --git a/pal/input/web/touch-input.ts b/pal/input/web/touch-input.ts index 48ccb8dffe8..ff95abc940a 100644 --- a/pal/input/web/touch-input.ts +++ b/pal/input/web/touch-input.ts @@ -131,4 +131,8 @@ export class TouchInputSource { public on (eventType: InputEventType, callback: TouchCallback, target?: any): void { this._eventTarget.on(eventType, callback, target); } + + public dispatchEventsInCache (): void { + // Do nothing + } }