201 lines
4.4 KiB
JavaScript
201 lines
4.4 KiB
JavaScript
import { SocketClient } from './SocketClient.js';
|
|
|
|
const INPUT_RATE = 30;
|
|
|
|
export class NetworkClient {
|
|
constructor(options = {}) {
|
|
this.socket = new SocketClient(options);
|
|
this.room = null;
|
|
this.playerId = null;
|
|
this.playerName = null;
|
|
this.joined = false;
|
|
this.creator = false;
|
|
this.prev = null;
|
|
this.prevAt = 0;
|
|
this.curr = null;
|
|
this.currAt = 0;
|
|
this.onStateChange = null;
|
|
this.onRoomError = null;
|
|
this._input = null;
|
|
this._inputTimer = null;
|
|
|
|
this.socket.onConnectionChange = (connected) => {
|
|
if (connected && this.joined && this.room) {
|
|
if (this.creator) {
|
|
this._sendCreate();
|
|
} else {
|
|
this._sendJoin();
|
|
}
|
|
}
|
|
this.onStateChange?.({ type: 'connection', connected });
|
|
};
|
|
}
|
|
|
|
connect() {
|
|
this.socket.connect();
|
|
}
|
|
|
|
get connected() {
|
|
return this.socket.connected;
|
|
}
|
|
|
|
join(room, { id, name }) {
|
|
this._enterRoom(room, { id, name }, false);
|
|
}
|
|
|
|
create(room, { id, name }) {
|
|
this._enterRoom(room, { id, name }, true);
|
|
}
|
|
|
|
_enterRoom(room, { id, name }, isCreator) {
|
|
const changingRoom = this.room && this.room !== room;
|
|
if (changingRoom) {
|
|
this.socket.unsubscribe(this._snapshotChannel());
|
|
this.socket.unsubscribe(this._inputChannel());
|
|
this._sendControl('leave');
|
|
}
|
|
this.room = room;
|
|
this.playerId = id;
|
|
this.playerName = name;
|
|
this.joined = true;
|
|
this.creator = isCreator;
|
|
|
|
this.prev = null;
|
|
this.prevAt = 0;
|
|
this.curr = null;
|
|
this.currAt = 0;
|
|
|
|
this.socket.subscribe(this._snapshotChannel(), {
|
|
onEvent: (event, payload, rawBytes) => {
|
|
if (event === 'snapshot') {
|
|
this._ingest(payload, rawBytes);
|
|
} else if (event === 'room-error') {
|
|
this.onRoomError?.(payload);
|
|
}
|
|
},
|
|
});
|
|
this.socket.subscribe(this._inputChannel());
|
|
if (isCreator) {
|
|
this._sendCreate();
|
|
} else {
|
|
this._sendJoin();
|
|
}
|
|
|
|
if (!this._inputTimer) {
|
|
this._inputTimer = setInterval(() => this._flushInput(), 1000 / INPUT_RATE);
|
|
}
|
|
}
|
|
|
|
leave() {
|
|
if (!this.joined) return;
|
|
this._sendControl('leave');
|
|
this.joined = false;
|
|
this.creator = false;
|
|
this.room = null;
|
|
this.prev = null;
|
|
this.prevAt = 0;
|
|
this.curr = null;
|
|
this.currAt = 0;
|
|
|
|
if (this._inputTimer) {
|
|
clearInterval(this._inputTimer);
|
|
this._inputTimer = null;
|
|
}
|
|
}
|
|
|
|
abandonRoom() {
|
|
if (this.room) {
|
|
this.socket.unsubscribe(this._snapshotChannel());
|
|
this.socket.unsubscribe(this._inputChannel());
|
|
}
|
|
this.joined = false;
|
|
this.creator = false;
|
|
this.room = null;
|
|
this.prev = null;
|
|
this.prevAt = 0;
|
|
this.curr = null;
|
|
this.currAt = 0;
|
|
|
|
if (this._inputTimer) {
|
|
clearInterval(this._inputTimer);
|
|
this._inputTimer = null;
|
|
}
|
|
}
|
|
|
|
start() {
|
|
this._sendControl('start');
|
|
}
|
|
|
|
reset() {
|
|
this._sendControl('reset');
|
|
}
|
|
|
|
sendInput(frame) {
|
|
this._input = frame;
|
|
}
|
|
|
|
stopSendingInput() {
|
|
this._input = null;
|
|
}
|
|
|
|
_flushInput() {
|
|
if (!this._input || !this._connected()) return;
|
|
const { id, keys, aim, fire } = this._input;
|
|
this.socket.trigger('client-input', this._inputChannel(), {
|
|
id,
|
|
keys,
|
|
aim,
|
|
fire,
|
|
});
|
|
}
|
|
|
|
_connected() {
|
|
return this.joined && this.room && this.socket.connected;
|
|
}
|
|
|
|
_snapshotChannel() {
|
|
return `game.${this.room}`;
|
|
}
|
|
|
|
_inputChannel() {
|
|
return `game.${this.room}.input`;
|
|
}
|
|
|
|
_sendControl(type) {
|
|
if (!this._connected()) return;
|
|
this.socket.trigger(`client-${type}`, this._inputChannel(), {
|
|
type,
|
|
id: this.playerId,
|
|
name: this.playerName,
|
|
});
|
|
}
|
|
|
|
_sendJoin() {
|
|
if (!this.room || !this.playerId) return;
|
|
this.socket.trigger('client-join', this._inputChannel(), {
|
|
type: 'join',
|
|
id: this.playerId,
|
|
name: this.playerName,
|
|
});
|
|
}
|
|
|
|
_sendCreate() {
|
|
if (!this.room || !this.playerId) return;
|
|
this.socket.trigger('client-create', this._inputChannel(), {
|
|
type: 'create',
|
|
id: this.playerId,
|
|
name: this.playerName,
|
|
});
|
|
}
|
|
|
|
_ingest(payload, rawBytes = 0) {
|
|
if (this.curr && payload.seq !== undefined && this.curr.seq !== undefined && payload.seq <= this.curr.seq) {
|
|
return;
|
|
}
|
|
this.prev = this.curr;
|
|
this.prevAt = this.currAt;
|
|
this.curr = payload;
|
|
this.curr.bytes = rawBytes;
|
|
this.currAt = performance.now();
|
|
}
|
|
} |