Skip to main content

Message Format

The Moitribe SDK uses ArrayBuffer as the standard message format for real-time multiplayer communication. This binary format provides efficient data transmission and optimal performance for game data.

ArrayBuffer Overview

ArrayBuffer is a generic, fixed-length raw binary data buffer that provides:

  • Efficient memory usage - Direct binary representation
  • Fast serialization - No JSON parsing overhead
  • Cross-platform compatibility - Works in browsers and Node.js
  • Type safety - Strong typing with typed arrays

Message Structure

All messages in the SDK follow this structure:

interface RealTimeMessage {
messageData: ArrayBuffer; // Binary message data
senderParticipantID: string; // Who sent the message
isReliable: boolean; // Delivery reliability type
}

Working with ArrayBuffer

Creating Messages

JavaScript Example

// Create a simple text message
function createTextMessage(text) {
const encoder = new TextEncoder();
return encoder.encode(text).buffer;
}

// Create a binary message for game data
function createPositionMessage(x, y, z) {
const buffer = new ArrayBuffer(12); // 3 floats * 4 bytes each
const view = new DataView(buffer);

view.setFloat32(0, x, true); // Little-endian
view.setFloat32(4, y, true);
view.setFloat32(8, z, true);

return buffer;
}

// Send the message
const positionBuffer = createPositionMessage(100.5, 50.2, 0);
MoitribeSDK('my-game', 'sendmsg', {
message: positionBuffer,
isReliable: false
}, (result) => {
console.log('Position message sent:', result.success);
});

TypeScript Example

import MoitribeSDK from '@veniso/moitribe-js';

interface GameMessage {
type: 'position' | 'action' | 'state';
timestamp: number;
data: any;
}

class MessageEncoder {
static encode(message: GameMessage): ArrayBuffer {
const jsonString = JSON.stringify(message);
const encoder = new TextEncoder();
return encoder.encode(jsonString).buffer;
}

static decode(buffer: ArrayBuffer): GameMessage {
const decoder = new TextDecoder();
const jsonString = decoder.decode(buffer);
return JSON.parse(jsonString);
}
}

// Usage
const gameMessage: GameMessage = {
type: 'position',
timestamp: Date.now(),
data: { x: 100, y: 50, z: 0 }
};

const messageBuffer = MessageEncoder.encode(gameMessage);

MoitribeSDK('my-game', 'sendmsg', {
message: messageBuffer,
isReliable: false
}, (result: { success: boolean }) => {
console.log('Message encoded and sent:', result.success);
});

Receiving and Parsing Messages

Basic Message Handling

function handleMessage(messageData, senderParticipantID, isReliable) {
console.log(`Message from ${senderParticipantID} (${isReliable ? 'reliable' : 'unreliable'})`);

// Try to decode as JSON first
try {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

processGameMessage(message);
} catch (error) {
// Handle as binary data
processBinaryMessage(messageData);
}
}

function processGameMessage(message) {
switch (message.type) {
case 'position':
updatePlayerPosition(message.data);
break;
case 'action':
handlePlayerAction(message.data);
break;
case 'state':
updateGameState(message.data);
break;
}
}

Binary Data Processing

function processBinaryMessage(buffer) {
const view = new DataView(buffer);

if (buffer.byteLength === 12) {
// Assume position data (3 floats)
const x = view.getFloat32(0, true);
const y = view.getFloat32(4, true);
const z = view.getFloat32(8, true);

updatePlayerPosition({ x, y, z });
} else if (buffer.byteLength === 8) {
// Assume 2D position (2 floats)
const x = view.getFloat32(0, true);
const y = view.getFloat32(4, true);

updatePlayerPosition({ x, y });
}
}

Advanced Message Formats

Custom Binary Protocol

class GameProtocol {
static POSITION = 0x01;
static ACTION = 0x02;
static STATE = 0x03;

static encodePosition(x, y, z) {
const buffer = new ArrayBuffer(13); // 1 byte type + 12 bytes data
const view = new DataView(buffer);

view.setUint8(0, this.POSITION);
view.setFloat32(1, x, true);
view.setFloat32(5, y, true);
view.setFloat32(9, z, true);

return buffer;
}

static encodeAction(action, targetId) {
const buffer = new ArrayBuffer(6); // 1 byte type + 1 byte action + 4 bytes target
const view = new DataView(buffer);

view.setUint8(0, this.ACTION);
view.setUint8(1, this.getActionCode(action));
view.setUint32(2, targetId, true);

return buffer;
}

static decode(buffer) {
const view = new DataView(buffer);
const messageType = view.getUint8(0);

switch (messageType) {
case this.POSITION:
return {
type: 'position',
data: {
x: view.getFloat32(1, true),
y: view.getFloat32(5, true),
z: view.getFloat32(9, true)
}
};

case this.ACTION:
return {
type: 'action',
data: {
action: this.getActionName(view.getUint8(1)),
targetId: view.getUint32(2, true)
}
};

default:
throw new Error(`Unknown message type: ${messageType}`);
}
}

static getActionCode(action) {
const actions = { 'move': 1, 'attack': 2, 'defend': 3 };
return actions[action] || 0;
}

static getActionName(code) {
const actions = { 1: 'move', 2: 'attack', 3: 'defend' };
return actions[code] || 'unknown';
}
}

Mixed Format Messages

function createMixedMessage() {
// Create a message with both binary and JSON data
const jsonData = {
playerId: 'player123',
timestamp: Date.now(),
metadata: { level: 5, score: 1000 }
};

const jsonString = JSON.stringify(jsonData);
const encoder = new TextEncoder();
const jsonBytes = encoder.encode(jsonString);

// Create buffer: 4 bytes length + JSON data + binary data
const buffer = new ArrayBuffer(4 + jsonBytes.length + 8);
const view = new DataView(buffer);

// Write JSON length
view.setUint32(0, jsonBytes.length, true);

// Write JSON data
const jsonView = new Uint8Array(buffer, 4, jsonBytes.length);
jsonView.set(jsonBytes);

// Write binary data (e.g., position)
view.setFloat32(4 + jsonBytes.length, 100.5, true);
view.setFloat32(8 + jsonBytes.length, 50.2, true);

return buffer;
}

function parseMixedMessage(buffer) {
const view = new DataView(buffer);

// Read JSON length
const jsonLength = view.getUint32(0, true);

// Read JSON data
const jsonBytes = new Uint8Array(buffer, 4, jsonLength);
const decoder = new TextDecoder();
const jsonString = decoder.decode(jsonBytes);
const jsonData = JSON.parse(jsonString);

// Read binary data
const x = view.getFloat32(4 + jsonLength, true);
const y = view.getFloat32(8 + jsonLength, true);

return {
...jsonData,
position: { x, y }
};
}

Performance Optimization

Message Size Considerations

Performance Tip

Keep messages under 1KB for optimal performance. Larger messages may cause fragmentation and increased latency.

function optimizeMessageSize(data) {
// Use smaller data types when possible
const buffer = new ArrayBuffer(8); // Instead of 12 bytes
const view = new DataView(buffer);

// Use uint16 for coordinates that don't need float precision
view.setUint16(0, Math.round(data.x), true);
view.setUint16(2, Math.round(data.y), true);
view.setUint16(4, data.health, true); // Health as 0-65535
view.setUint8(6, data.flags); // Flags as single byte

return buffer;
}

Batch Messages

function createBatchMessage(messages) {
// Calculate total size needed
let totalSize = 2; // Message count

messages.forEach(msg => {
totalSize += 1 + msg.data.byteLength; // Type + data
});

const buffer = new ArrayBuffer(totalSize);
const view = new DataView(buffer);

// Write message count
view.setUint16(0, messages.length, true);

let offset = 2;
messages.forEach(msg => {
view.setUint8(offset, msg.type);
offset += 1;

const dataView = new Uint8Array(buffer, offset, msg.data.byteLength);
dataView.set(new Uint8Array(msg.data));
offset += msg.data.byteLength;
});

return buffer;
}

Debugging ArrayBuffer Messages

function debugArrayBuffer(buffer) {
console.log('ArrayBuffer Info:');
console.log(` Byte Length: ${buffer.byteLength}`);

const view = new DataView(buffer);
const bytes = new Uint8Array(buffer);

console.log(' Hex Dump:');
let hexString = '';
for (let i = 0; i < Math.min(bytes.length, 32); i++) {
hexString += bytes[i].toString(16).padStart(2, '0') + ' ';
if ((i + 1) % 8 === 0) {
console.log(' ' + hexString);
hexString = '';
}
}

if (hexString) {
console.log(' ' + hexString);
}

// Try to interpret as different types
if (buffer.byteLength >= 4) {
console.log(` As Uint32: ${view.getUint32(0, true)}`);
console.log(` As Float32: ${view.getFloat32(0, true)}`);
}

if (buffer.byteLength >= 8) {
console.log(` As Uint64: ${view.getBigUint64(0, true)}`);
console.log(` As Float64: ${view.getFloat64(0, true)}`);
}
}

Error Handling

function safeMessageDecode(buffer) {
try {
// Validate buffer
if (!buffer || buffer.byteLength === 0) {
throw new Error('Empty buffer');
}

// Try JSON decode first
const decoder = new TextDecoder();
const jsonString = decoder.decode(buffer);

if (jsonString.trim().startsWith('{') || jsonString.trim().startsWith('[')) {
return JSON.parse(jsonString);
}

// Fallback to binary processing
return processBinaryData(buffer);

} catch (error) {
console.error('Message decode failed:', error);
return null;
}
}

Next Steps