import { InputFrame, PointerFrame, MouseFrame, KeyboardFrame } from '../Input' enum ReportTypes { None = 0, Metadata = 1, Gamepad = 2, Pointer = 4, ClientMetadata = 8, ServerMetadata = 16, Mouse = 32, Keyboard = 64, Vibration = 128, Sensor = 256, } enum GamepadInputPhysicality { None = 0x00000000, DPadUp = 0x00000001, DPadDown = 0x00000002, DPadLeft = 0x00000004, DPadRight = 0x00000008, Menu = 0x00000010, View = 0x00000020, LeftThumb = 0x00000040, RightThumb = 0x00000080, LeftShoulder = 0x00000100, RightShoulder = 0x00000200, Nexus = 0x00000400, Misc = 0x00000800, A = 0x00001000, B = 0x00002000, X = 0x00004000, Y = 0x00008000, LeftTrigger = 0x00010000, RightTrigger = 0x00020000, LeftThumbXAxis = 0x00040000, LeftThumbYAxis = 0x00080000, RightThumbXAxis = 0x00100000, RightThumbYAxis = 0x00200000, } export default class InputPacket { _reportType = ReportTypes.None _totalSize = -1 _sequence = -1 _metadataFrames:Array = [] _gamepadFrames:Array = [] _pointerFrames:Array = [] _mouseFrames:Array = [] _keyboardFrames:Array = [] _maxTouchpoints = 0 constructor(sequence){ this._sequence = sequence } setMetadata(maxTouchpoints = 0){ this._reportType = ReportTypes.ClientMetadata this._totalSize = 15 this._maxTouchpoints = maxTouchpoints } setData(metadataQueue:Array, gamepadQueue:Array, pointerQueue:Array, mouseQueue:Array, keyboardQueue:Array){ let size = 14 if(metadataQueue.length > 0){ this._reportType |= ReportTypes.Metadata size = size+this._calculateMetadataSize(metadataQueue) this._metadataFrames = metadataQueue } if(gamepadQueue.length > 0){ this._reportType |= ReportTypes.Gamepad size = size+this._calculateGamepadSize(gamepadQueue) this._gamepadFrames = gamepadQueue } if(pointerQueue.length > 0){ this._reportType |= ReportTypes.Pointer size = size+this._calculatePointerSize(pointerQueue) this._pointerFrames = pointerQueue } if(mouseQueue.length > 0){ this._reportType |= ReportTypes.Mouse size = size+this._calculateMouseSize(mouseQueue) this._mouseFrames = mouseQueue } if(keyboardQueue.length > 0){ this._reportType |= ReportTypes.Keyboard size = size+this._calculateKeyboardSize(keyboardQueue) this._keyboardFrames = keyboardQueue } this._totalSize = size } _calculateMetadataSize(frames){ return 1 + ((7 * 4) * frames.length) } _calculateGamepadSize(frames:Array){ return 1 + (23 * frames.length) } _calculatePointerSize(frames:Array){ let pointerSize = 1 for(const frame in frames){ pointerSize = pointerSize + 1 + (frames[frame].events.length * 20) } return pointerSize } _calculateMouseSize(frames:Array){ return 1 + (18 * frames.length) } _calculateKeyboardSize(frames:Array){ return 1 + (3 * frames.length) } _writeMetadataData(packet:DataView, offset:number, frames:Array){ packet.setUint8(offset, frames.length) offset++ if(frames.length >= 30){ console.warn('metadataQueue is bigger then 30. This might impact reliability!') } for (; frames.length > 0;) { // this._metadataFps.count() const frame = frames.shift() const firstFramePacketArrivalTimeMs = frame.firstFramePacketArrivalTimeMs const frameSubmittedTimeMs = frame.frameSubmittedTimeMs const frameDecodedTimeMs = frame.frameDecodedTimeMs const frameRenderedTimeMs = frame.frameRenderedTimeMs const framePacketTime = performance.now() const frameDateNow = performance.now() packet.setUint32(offset, frame.serverDataKey, true) packet.setUint32(offset+4, firstFramePacketArrivalTimeMs, true) packet.setUint32(offset+8, frameSubmittedTimeMs, true) packet.setUint32(offset+12, frameDecodedTimeMs, true) packet.setUint32(offset+16, frameRenderedTimeMs, true) packet.setUint32(offset+20, framePacketTime, true) packet.setUint32(offset+24, frameDateNow, true) offset += 28 } return offset } _writeGamepadData(packet:DataView, offset:number, frames:Array){ packet.setUint8(offset, frames.length) offset++ if(frames.length >= 30){ console.warn('gamepadQueue is bigger then 30. This might impact reliability!') } for (; frames.length > 0;) { // this._inputFps.count() const shift = frames.shift() if(shift !== undefined){ const input:InputFrame = shift packet.setUint8(offset, input.GamepadIndex) offset++ let buttonMask = 0 if(input.Nexus > 0){ buttonMask |= 2 } if(input.Menu > 0){ buttonMask |= 4 } if(input.View > 0){ buttonMask |= 8 } if(input.A > 0){ buttonMask |= 16 } if(input.B > 0){ buttonMask |= 32 } if(input.X > 0){ buttonMask |= 64 } if(input.Y > 0){ buttonMask |= 128 } if(input.DPadUp > 0){ buttonMask |= 256 } if(input.DPadDown > 0){ buttonMask |= 512 } if(input.DPadLeft > 0){ buttonMask |= 1024 } if(input.DPadRight > 0){ buttonMask |= 2048 } if(input.LeftShoulder > 0){ buttonMask |= 4096 } if(input.RightShoulder > 0){ buttonMask |= 8192 } if(input.LeftThumb > 0){ buttonMask |= 16384 } if(input.RightThumb > 0){ buttonMask |= 32768 } packet.setUint16(offset, buttonMask, true) packet.setInt16(offset+2, this._normalizeAxisValue(input.LeftThumbXAxis), true) // LeftThumbXAxis packet.setInt16(offset+4, this._normalizeAxisValue(-input.LeftThumbYAxis), true) // LeftThumbYAxis packet.setInt16(offset+6, this._normalizeAxisValue(input.RightThumbXAxis), true) // RightThumbXAxis packet.setInt16(offset+8, this._normalizeAxisValue(-input.RightThumbYAxis), true) // RightThumbYAxis packet.setUint16(offset+10, this._normalizeTriggerValue(input.LeftTrigger), true) // LeftTrigger packet.setUint16(offset+12, this._normalizeTriggerValue(input.RightTrigger), true) // RightTrigger const inputWithPhysicality = input as InputFrame & { PhysicalPhysicality?: number; VirtualPhysicality?: number; } const physicalPhysicality = Number.isFinite(inputWithPhysicality.PhysicalPhysicality) ? (inputWithPhysicality.PhysicalPhysicality as number) : this._calculateGamepadPhysicality(input) const virtualPhysicality = Number.isFinite(inputWithPhysicality.VirtualPhysicality) ? (inputWithPhysicality.VirtualPhysicality as number) : 0 packet.setUint32(offset + 14, physicalPhysicality, true) // PhysicalPhysicality packet.setUint32(offset + 18, virtualPhysicality, true) // VirtualPhysicality offset += 22 } } return offset } _writePointerData(packet:DataView, offset:number, frames:Array){ packet.setUint8(offset, frames.length) offset++ if(frames.length >= 30){ console.warn('pointerQueue is bigger then 30. This might impact reliability!') } const targetWidth = 1920 const targetHeight = 1080 for (; frames.length > 0;) { const shift = frames.shift() if(shift === undefined){ continue } packet.setUint8(offset, shift.events.length) offset++ for (const event in shift.events) { const pointerEvent = shift.events[event] const clientWidth = Math.max(1, pointerEvent.clientWidth || targetWidth) const clientHeight = Math.max(1, pointerEvent.clientHeight || targetHeight) const width = Math.max(1, pointerEvent.width || 1) const height = Math.max(1, pointerEvent.height || 1) const pressure = Math.max(0, Math.min(1, pointerEvent.pressure || 0)) const twist = Math.max(0, pointerEvent.twist || 0) const normalizedHeight = Math.max(0, Math.min(65535, Math.round((height * targetHeight) / clientHeight))) const normalizedWidth = Math.max(0, Math.min(65535, Math.round((width * targetWidth) / clientWidth))) const normalizedX = Math.max(0, Math.min(targetWidth, Math.round((pointerEvent.x * targetWidth) / clientWidth))) const normalizedY = Math.max(0, Math.min(targetHeight, Math.round((pointerEvent.y * targetHeight) / clientHeight))) packet.setUint16(offset, normalizedHeight, true) packet.setUint16(offset + 2, normalizedWidth, true) packet.setUint8(offset + 4, Math.round(255 * pressure)) packet.setUint16(offset + 5, twist, true) packet.setUint32(offset + 7, pointerEvent.pointerId || 0, true) packet.setUint32(offset + 11, normalizedX, true) packet.setUint32(offset + 15, normalizedY, true) packet.setUint8( offset + 19, pointerEvent.type === 'pointerdown' ? 1 : (pointerEvent.type === 'pointerup' || pointerEvent.type === 'pointercancel') ? 2 : pointerEvent.type === 'pointermove' ? 3 : 0, ) offset = offset + 20 } } return offset } _writeMouseData(packet:DataView, offset:number, frames:Array){ packet.setUint8(offset, frames.length) offset++ if(frames.length >= 30){ console.warn('mouseQueue is bigger then 30. This might impact reliability!') } for (; frames.length > 0;) { // this._inputFps.count() const shift = frames.shift() if(shift !== undefined){ const input:MouseFrame = shift packet.setUint32(offset, input.X, true) packet.setUint32(offset+4, input.Y, true) packet.setUint32(offset+8, input.WheelX, true) packet.setUint32(offset+12, input.WheelY, true) packet.setUint8(offset+16, input.Buttons) packet.setUint8(offset+17, input.Relative) offset += 18 } } return offset } _writeKeyboardData(packet:DataView, offset:number, frames:Array){ packet.setUint8(offset, frames.length) offset++ if(frames.length >= 30){ console.warn('keyboardQueue is bigger then 30. This might impact reliability!') } for (; frames.length > 0;) { // this._inputFps.count() const shift = frames.shift() if(shift !== undefined){ const input:KeyboardFrame = shift packet.setUint8(offset, 2) // 1 = Known, 2 = VKey, 3 = AppCommand, 0 = Unknown packet.setUint8(offset+1, input.pressed ? 1 : 0) packet.setUint8(offset+2, input.keyCode) // packet.setUint8(offset+3, 1) // packet.setUint8(offset+4, input.keyCode) offset += 3 } } return offset } toBuffer(){ const metadataAlloc = new Uint8Array(this._totalSize) const packet = new DataView(metadataAlloc.buffer) packet.setUint16(0, this._reportType, true) packet.setUint32(2, this._sequence, true) packet.setFloat64(6, performance.now(), true) let offset = 14 if(this._metadataFrames.length > 0) {offset = this._writeMetadataData(packet, offset, this._metadataFrames)} if(this._gamepadFrames.length > 0) {offset = this._writeGamepadData(packet, offset, this._gamepadFrames)} if(this._pointerFrames.length > 0) {offset = this._writePointerData(packet, offset, this._pointerFrames)} if(this._mouseFrames.length > 0) {offset = this._writeMouseData(packet, offset, this._mouseFrames)} if(this._keyboardFrames.length > 0) {offset = this._writeKeyboardData(packet, offset, this._keyboardFrames)} if(this._reportType === ReportTypes.ClientMetadata){ packet.setUint8(offset, this._maxTouchpoints) offset++ } if(offset !== this._totalSize){ throw new Error('Packet length mismatch. Something is wrong!') } return packet } _normalizeTriggerValue(e) { if (e < 0) { return this._convertToUInt16(0) } const t = 65535 * e, a = t > 65535 ? 65535 : t return this._convertToUInt16(a) } _normalizeAxisValue(e) { const t = this._convertToInt16(32767), a = this._convertToInt16(-32767), n = e * t return n > t ? t : n < a ? a : this._convertToInt16(n) } _convertToInt16(e) { const int = new Int16Array(1) return int[0] = e, int[0] } _convertToUInt16(e) { const int = new Uint16Array(1) return int[0] = e, int[0] } _calculateGamepadPhysicality(input: InputFrame, thumbstickDeadzone = 0) { let physicality = GamepadInputPhysicality.None if (input.DPadUp) { physicality |= GamepadInputPhysicality.DPadUp } if (input.DPadDown) { physicality |= GamepadInputPhysicality.DPadDown } if (input.DPadLeft) { physicality |= GamepadInputPhysicality.DPadLeft } if (input.DPadRight) { physicality |= GamepadInputPhysicality.DPadRight } if (input.Menu) { physicality |= GamepadInputPhysicality.Menu } if (input.View) { physicality |= GamepadInputPhysicality.View } if (input.LeftThumb) { physicality |= GamepadInputPhysicality.LeftThumb } if (input.RightThumb) { physicality |= GamepadInputPhysicality.RightThumb } if (input.LeftShoulder) { physicality |= GamepadInputPhysicality.LeftShoulder } if (input.RightShoulder) { physicality |= GamepadInputPhysicality.RightShoulder } if (input.Nexus) { physicality |= GamepadInputPhysicality.Nexus } if (input.A) { physicality |= GamepadInputPhysicality.A } if (input.B) { physicality |= GamepadInputPhysicality.B } if (input.X) { physicality |= GamepadInputPhysicality.X } if (input.Y) { physicality |= GamepadInputPhysicality.Y } if (input.LeftTrigger) { physicality |= GamepadInputPhysicality.LeftTrigger } if (input.RightTrigger) { physicality |= GamepadInputPhysicality.RightTrigger } let distanceToOrigin = Math.hypot( input.LeftThumbXAxis || 0, input.LeftThumbYAxis || 0, ) let isThumbstickIdle = distanceToOrigin <= thumbstickDeadzone if (!isThumbstickIdle) { physicality |= GamepadInputPhysicality.LeftThumbXAxis physicality |= GamepadInputPhysicality.LeftThumbYAxis } distanceToOrigin = Math.hypot( input.RightThumbXAxis || 0, input.RightThumbYAxis || 0, ) isThumbstickIdle = distanceToOrigin <= thumbstickDeadzone if (!isThumbstickIdle) { physicality |= GamepadInputPhysicality.RightThumbXAxis physicality |= GamepadInputPhysicality.RightThumbYAxis } return physicality } }