mudb
Version:
Real-time database for multiplayer games
273 lines (243 loc) • 7.79 kB
text/typescript
import {
MuSocketServer,
MuSocketServerSpec,
MuSocket,
MuSocketState,
MuSocketSpec,
MuSessionId,
MuData,
} from '../socket';
import {
MuBuffer,
allocBuffer,
freeBuffer,
} from '../../stream';
import { MuLogger, MuDefaultLogger } from '../../logger';
class MuBufferWrapper {
private _buffer:MuBuffer;
public bytes:Uint8Array;
constructor (bytes:Uint8Array) {
this._buffer = allocBuffer(bytes.length);
this.bytes = this._buffer.uint8.subarray(0, bytes.length);
this.bytes.set(bytes);
}
public free () {
freeBuffer(this._buffer);
}
}
function calcDelay (latency:number, jitter:number) {
return latency + Math.floor(Math.random() * jitter);
}
function drain (
pendingMessages:(string|MuBufferWrapper)[],
handler:(data:MuData) => void,
) {
const message = pendingMessages.shift();
if (typeof message === 'string') {
handler(message);
} else if (message) {
handler(message.bytes);
message.free();
}
}
export class MuDebugSocket implements MuSocket {
public socket:MuSocket;
public readonly sessionId:MuSessionId;
public state() : MuSocketState {
return this.socket.state();
}
public inLatency = 0;
public inJitter = 0;
public inPacketLoss = 0;
public outLatency = 0;
public outJitter = 0;
public outPacketLoss = 0;
public logger:MuLogger;
constructor (spec:{
socket:MuSocket,
inLatency?:number,
inJitter?:number,
inPacketLoss?:number,
outLatency?:number,
outJitter?:number,
outPacketLoss?:number,
logger?:MuLogger,
}) {
this.socket = spec.socket;
this.sessionId = this.socket.sessionId;
if (typeof spec.inLatency === 'number') {
this.inLatency = Math.max(0, spec.inLatency);
}
if (typeof spec.inJitter === 'number') {
this.inJitter = Math.max(0, spec.inJitter);
}
if (typeof spec.inPacketLoss === 'number') {
this.inPacketLoss = Math.min(100, Math.max(0, spec.inPacketLoss));
}
if (typeof spec.outLatency === 'number') {
this.outLatency = Math.max(0, spec.outLatency);
}
if (typeof spec.outJitter === 'number') {
this.outJitter = Math.max(0, spec.outJitter);
}
if (typeof spec.outPacketLoss === 'number') {
this.outPacketLoss = Math.min(100, Math.max(0, spec.outPacketLoss));
}
this.logger = spec.logger || MuDefaultLogger;
}
private _inbox:(string|MuBufferWrapper)[] = [];
public open (spec:MuSocketSpec) {
this.socket.open({
ready: () => {
try {
spec.ready();
} catch (e) {
this.logger.exception(e);
}
},
message: (data, unreliable) => {
if (unreliable) {
if (Math.random() * 100 < this.inPacketLoss) {
return;
}
setTimeout(
() => {
try {
spec.message(data, true);
} catch (e) {
this.logger.exception(e);
}
},
calcDelay(this.inLatency, this.inJitter),
);
} else {
const message = typeof data === 'string' ? data : new MuBufferWrapper(data);
this._inbox.push(message);
setTimeout(
() => {
try {
drain(
this._inbox,
(data_) => spec.message(data_, false));
} catch (e) {
this.logger.exception(e);
}
},
calcDelay(this.inLatency, this.inJitter),
);
}
},
close: () => {
try {
spec.close();
} catch (e) {
this.logger.exception(e);
}
},
});
}
private _outbox:(string|MuBufferWrapper)[] = [];
public send (data:MuData, unreliable?:boolean) {
const message = typeof data === 'string' ? data : new MuBufferWrapper(data);
this._outbox.push(message);
const unreliable_ = !!unreliable;
if (unreliable_) {
if (Math.random() * 100 < this.outPacketLoss) {
return;
}
}
setTimeout(
() => drain(
this._outbox,
(data_) => {
try {
this.socket.send(data_, unreliable_);
} catch (e) {
this.logger.exception(e);
}
},
),
calcDelay(this.outLatency, this.outJitter),
);
}
public close () {
this.socket.close();
}
public reliableBufferedAmount () {
return this.socket.reliableBufferedAmount();
}
public unreliableBufferedAmount () {
return this.socket.unreliableBufferedAmount();
}
}
export class MuDebugServer implements MuSocketServer {
public socketServer:MuSocketServer;
public state () {
return this.socketServer.state();
}
public clients:MuDebugSocket[] = [];
public inLatency:number;
public inJitter:number;
public inPacketLoss:number;
public outLatency:number;
public outJitter:number;
public outPacketLoss:number;
public logger:MuLogger;
constructor (spec:{
socketServer:MuSocketServer,
inLatency?:number,
inJitter?:number,
inPacketLoss?:number,
outLatency?:number,
outJitter?:number,
outPacketLoss?:number,
logger?:MuLogger,
}) {
this.socketServer = spec.socketServer;
this.inLatency = spec.inLatency || 0;
this.inJitter = spec.inJitter || 0;
this.inPacketLoss = spec.inPacketLoss || 0;
this.outLatency = spec.outLatency || 0;
this.outJitter = spec.outJitter || 0;
this.outPacketLoss = spec.outPacketLoss || 0;
this.logger = spec.logger || MuDefaultLogger;
}
public start (spec:MuSocketServerSpec) {
this.socketServer.start({
ready: () => {
try {
spec.ready();
} catch (e) {
this.logger.exception(e);
}
},
connection: (socket) => {
const client = new MuDebugSocket({
socket,
inLatency: this.inLatency,
inJitter: this.inJitter,
inPacketLoss: this.inPacketLoss,
outLatency: this.outLatency,
outJitter: this.outJitter,
outPacketLoss: this.outPacketLoss,
});
this.clients.push(client);
try {
spec.connection(client);
} catch (e) {
this.logger.exception(e);
}
},
close: () => {
try {
spec.close();
} catch (e) {
this.logger.exception(e);
}
},
});
}
public close () {
this.socketServer.close();
}
}