@cryptovoxels/messages
Advanced tools
Comparing version 2.0.0-0 to 2.0.0-1
@@ -142,3 +142,4 @@ export { Emotes } from './constant'; | ||
export declare const DestroyAvatarEncoder: (msg: DestroyAvatarMessage) => Uint8Array; | ||
export declare function wrap(values: number[], max: number, min?: number): number[]; | ||
export declare type Message = PingMessage | PongMessage | AnonMessage | LoginMessage | LoginCompleteMessage | TrafficMessage | ChatMessage | AvatarEmoteMessage | MemeCubeMessage | NewCostumeMessage | TypingMessage | VoiceStateMessage | WompMessage | CreateAvatarMessage | UpdateAvatarMessage | JoinMessage | WorldStateMessage | DestroyAvatarMessage | AvatarChangedMessage; | ||
export declare const GenericEncoder: (msg: Message) => Uint8Array; |
@@ -22,3 +22,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.GenericEncoder = exports.DestroyAvatarEncoder = exports.WorldStateEncoder = exports.JoinEncoder = exports.UpdateAvatarEncoder = exports.WompMessageEncoder = exports.VoiceStateMessageEncoder = exports.AvatarVoiceState = exports.TypingMessageEncoder = exports.NewCostumeEncoder = exports.MemeCubeEncoder = exports.EmoteEncoder = exports.AvatarChangedEncoder = exports.CreateAvatarEncoder = exports.ChatEncoder = exports.TrafficEncoder = exports.LoginCompleteEncoder = exports.LoginEncoder = exports.AnonEncoder = exports.PongEncoder = exports.PingEncoder = exports.decode = exports.MessageType = exports.Emotes = void 0; | ||
exports.GenericEncoder = exports.wrap = exports.DestroyAvatarEncoder = exports.WorldStateEncoder = exports.JoinEncoder = exports.UpdateAvatarEncoder = exports.WompMessageEncoder = exports.VoiceStateMessageEncoder = exports.AvatarVoiceState = exports.TypingMessageEncoder = exports.NewCostumeEncoder = exports.MemeCubeEncoder = exports.EmoteEncoder = exports.AvatarChangedEncoder = exports.CreateAvatarEncoder = exports.ChatEncoder = exports.TrafficEncoder = exports.LoginCompleteEncoder = exports.LoginEncoder = exports.AnonEncoder = exports.PongEncoder = exports.PingEncoder = exports.decode = exports.MessageType = exports.Emotes = void 0; | ||
const msgpack_1 = require("@msgpack/msgpack"); | ||
@@ -137,3 +137,3 @@ const uuidParse = __importStar(require("uuid-parse")); | ||
} | ||
return msgpack_1.encode([uuidParse.parse(input.uuid), Float32Array.from(input.position), Float32Array.from(input.rotation), input.animation]); | ||
return msgpack_1.encode([uuidParse.parse(input.uuid), Float32Array.from(input.position), rotationEncode(input.rotation), input.animation]); | ||
}, | ||
@@ -146,3 +146,3 @@ decode: (data) => { | ||
position: uint8ToFloat32(res[1]), | ||
rotation: uint8ToFloat32(res[2]), | ||
rotation: rotationDecode(res[2]), | ||
animation: res[3], | ||
@@ -216,4 +216,43 @@ }; | ||
} | ||
// @todo optimise this malarky | ||
function wrap(values, max, min = 0) { | ||
return values.map((value) => { | ||
if (isNaN(value)) | ||
return min; | ||
value -= min; | ||
max -= min; | ||
if (max == 0) | ||
return min; | ||
value = value % max; | ||
value += min; | ||
while (value < min) { | ||
value += max; | ||
} | ||
return value; | ||
}); | ||
} | ||
exports.wrap = wrap; | ||
// we dont need a full 3 * 32float precision of rotation, it's enough if have a precision 360 / 1024 = 0.35 degrees | ||
// @todo see if there is a way to only use 10 * 3 bits = 30 bits / 8 = 3.75 bytes 4 bytes for all values | ||
// @todo optimise this malarky | ||
function rotationEncode(input) { | ||
return Uint16Array.from( | ||
// first normalise the rotation between -3.14 and 3.14 (radians) (a full circle) | ||
wrap(input, Math.PI, -Math.PI) | ||
// then convert radians to a value between 0 and 1 | ||
.map((v) => (v / Math.PI + 1) / 2) | ||
// convert to between 0 and 1024 | ||
.map((v) => Math.round(v * 1024))); | ||
} | ||
function rotationDecode(data) { | ||
const a = Uint8Array.from(data); | ||
const x = new Uint16Array(a.buffer, a.byteOffset, 3); | ||
return ([x[0], x[1], x[2]] | ||
// first get into a range between 0 and 1 | ||
.map((v) => (isNaN(v) ? 0 : v / 1024)) | ||
// then convert into a range of -3.14 and 3.14 (a full circle) | ||
.map((v) => (v * 2 - 1) * Math.PI)); | ||
} | ||
// all encoders can in practice encode all types of messages, but we typehint the message argument with encoderCreator | ||
exports.GenericEncoder = encoderCreator(); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@cryptovoxels/messages", | ||
"version": "2.0.0-0", | ||
"version": "2.0.0-1", | ||
"description": "common protocol for multiplayer messages", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -230,7 +230,7 @@ import { Decoder, Encoder, decode as decodeAlias, encode, ExtensionCodec } from '@msgpack/msgpack' | ||
} | ||
return encode([uuidParse.parse(input.uuid), Float32Array.from(input.position), Float32Array.from(input.rotation), input.animation]) | ||
return encode([uuidParse.parse(input.uuid), Float32Array.from(input.position), rotationEncode(input.rotation), input.animation]) | ||
}, | ||
decode: (data): UpdateAvatarMessage => { | ||
const res = decodeAlias(data) as any[] | ||
return { | ||
@@ -240,3 +240,3 @@ type: MessageType.updateAvatar, | ||
position: uint8ToFloat32(res[1]), | ||
rotation: uint8ToFloat32(res[2]), | ||
rotation: rotationDecode(res[2]), | ||
animation: res[3], | ||
@@ -333,2 +333,43 @@ } | ||
// @todo optimise this malarky | ||
export function wrap(values: number[], max: number, min: number = 0) { | ||
return values.map((value) => { | ||
if (isNaN(value)) return min | ||
value -= min | ||
max -= min | ||
if (max == 0) return min | ||
value = value % max | ||
value += min | ||
while (value < min) { | ||
value += max | ||
} | ||
return value | ||
}) | ||
} | ||
// we dont need a full 3 * 32float precision of rotation, it's enough if have a precision 360 / 1024 = 0.35 degrees | ||
// @todo see if there is a way to only use 10 * 3 bits = 30 bits / 8 = 3.75 bytes 4 bytes for all values | ||
// @todo optimise this malarky | ||
function rotationEncode(input: number[]): Uint16Array { | ||
return Uint16Array.from( | ||
// first normalise the rotation between -3.14 and 3.14 (radians) (a full circle) | ||
wrap(input, Math.PI, -Math.PI) | ||
// then convert radians to a value between 0 and 1 | ||
.map((v) => (v / Math.PI + 1) / 2) | ||
// convert to between 0 and 1024 | ||
.map((v) => Math.round(v * 1024)) | ||
) | ||
} | ||
function rotationDecode(data: Iterable<number>): number[] { | ||
const a = Uint8Array.from(data) | ||
const x = new Uint16Array(a.buffer, a.byteOffset, 3) | ||
return ( | ||
[x[0], x[1], x[2]] | ||
// first get into a range between 0 and 1 | ||
.map((v) => (isNaN(v) ? 0 : v / 1024)) | ||
// then convert into a range of -3.14 and 3.14 (a full circle) | ||
.map((v) => (v * 2 - 1) * Math.PI) | ||
) | ||
} | ||
export type Message = | ||
@@ -335,0 +376,0 @@ | PingMessage |
import test, { Test } from 'tape' | ||
import * as msg from '../src/index' | ||
import { JoinEncoder, JoinMessage, WorldStateEncoder, WorldStateMessage } from '../src/index' | ||
import { JoinEncoder, JoinMessage, WorldStateEncoder, WorldStateMessage, wrap } from '../src/index' | ||
@@ -55,3 +55,3 @@ const token = ''.padEnd(200, 'x') | ||
const tests: { encoder: (msg: msg.UpdateAvatarMessage) => Uint8Array; size: number; msg: msg.UpdateAvatarMessage }[] = [ | ||
{ encoder: msg.UpdateAvatarEncoder, size: 62, msg: { type: msg.MessageType.updateAvatar, uuid: uuid, position: [1.23, 4.56, 7.89], rotation: [1.23, 4.56, 7.89], animation: 2 } }, | ||
{ encoder: msg.UpdateAvatarEncoder, size: 56, msg: { type: msg.MessageType.updateAvatar, uuid: uuid, position: [1.23, 4.56, 7.89], rotation: [1.23, 4.56, 7.89], animation: 2 } }, | ||
] | ||
@@ -64,3 +64,3 @@ tests.forEach((tc) => { | ||
equalF32Array(t, decoded.position, tc.msg.position, 'position') | ||
equalF32Array(t, decoded.rotation, tc.msg.rotation, 'rotation') | ||
equalRotation(t, decoded.rotation, tc.msg.rotation, 'rotation') | ||
t.equal(encoded.length, tc.size, `encoded size is ${tc.size}`) | ||
@@ -79,5 +79,5 @@ }) | ||
const encoded = WorldStateEncoder({ type: msg.MessageType.worldState, avatars: avatars }) | ||
t.equal(encoded.length, 182, 'encoded message size is correct') | ||
t.equal(encoded.length, 164, 'encoded message size is correct') | ||
const decoded: WorldStateMessage = msg.decode(encoded) | ||
t.equal(decoded.avatars.length, 3, 'we have three avatar update messages') | ||
t.equal(decoded.avatars.length, avatars.length, 'we have three avatar update messages') | ||
@@ -88,3 +88,3 @@ decoded.avatars.forEach((v, i: number) => { | ||
equalF32Array(t, v.position, avatars[i].position, `avatar[${i}] position`) | ||
equalF32Array(t, v.rotation, avatars[i].rotation, `avatar[${i}] rotation`) | ||
equalRotation(t, v.rotation, avatars[i].rotation, `avatar[${i}] rotation`) | ||
}) | ||
@@ -114,3 +114,3 @@ t.end() | ||
const encoded = JoinEncoder(joinMsg) | ||
t.equal(encoded.length, 361, 'encoded message size is correct') | ||
t.equal(encoded.length, 343, 'encoded message size is correct') | ||
const decoded: JoinMessage = msg.decode(encoded) | ||
@@ -122,3 +122,3 @@ t.equal(decoded.type, msg.MessageType.join) | ||
equalF32Array(t, v.position, avatars[i].position, `avatar[${i}] position`) | ||
equalF32Array(t, v.rotation, avatars[i].rotation, `avatar[${i}] rotation`) | ||
equalRotation(t, v.rotation, avatars[i].rotation, `avatar[${i}] rotation`) | ||
}) | ||
@@ -200,1 +200,15 @@ decoded.createAvatars.forEach((v, i: number) => { | ||
} | ||
// compare two rotations and decides if they are equal enough counting floating point difference and precision | ||
function equalRotation(t: Test, a: number[], b: number[], testCaseId: string): boolean { | ||
t.equal(a.length, b.length, `${testCaseId} arrays should be the same size`) | ||
const EPSILON = 0.003 // allowed difference (precision loss) | ||
const wrappedB = wrap(b, Math.PI, -Math.PI) | ||
for (let i = 0; i < a.length; i++) { | ||
const c = a[i] | ||
const d = wrappedB[i] | ||
const diff = Math.abs(c - d) | ||
t.ok(diff <= EPSILON, `${testCaseId}[${i}] difference shouldn't be too large`) | ||
} | ||
return true | ||
} |
Sorry, the diff of this file is not supported yet
59412
1343