Kopie van https://gitlab.com/studieverenigingvia/ict/centurion met een paar aanpassingen
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
centurion/frontend/src/lib/Connection.ts

321 lines
8.4 KiB

import io from "socket.io-client";
import {useEffect, useState} from "react";
import {parse as parseQueryString, stringify as stringifyQueryString} from 'query-string';
import {Room, Tick, Timeline} from "../types/types";
import {Sub, useSub} from "../util/sub";
class Connection {
url = '/';
socket: SocketIOClient.Socket;
room = new Sub<Room | null>();
tick = new Sub<Tick | null>();
timeline = new Sub<Timeline | null>();
timeSyncIntervals = [500, 1000, 3000, 5000, 10000, 30000];
timeSyncs: { [requestId: number]: TimeSyncRequest } = {};
timeSyncTimeoutIds: number[] = [];
timeSyncTooOld = 120000;
roomTime = new Sub<number>();
calls: { [id: number]: Call } = {};
constructor() {
this.socket = io(this.url, {
autoConnect: false
});
this.setupSocketListeners();
this.connect();
this.roomTime.set(0);
}
connect() {
this.socket.connect();
}
setupSocketListeners() {
this.socket.on('connect', () => {
this.onConnect();
})
this.socket.on('disconnect', () => {
this.onDisconnect();
})
this.socket.on('time_sync', (data: any) => {
this.timeSyncResponse(data.requestId, data.clientDiff, data.serverTime);
})
this.socket.on('room', (data: any) => {
console.log('ROOM', data.room);
if (data.room) {
this.setQueryLobbyId(data.room.id);
}
this.room.set(data.room);
});
this.socket.on('tick_event', (data: any) => {
this.tick.set(data.tick);
});
this.socket.on('timeline', (data: any) => {
if (data.timeline) {
this.timeline.set(new Timeline(data.timeline));
} else {
this.timeline.set(null);
}
});
this.socket.on('call_response', (data: any) => {
let call = this.calls[data.id];
if (!call) return;
if (data.error) {
call.callback(data.error, null);
} else {
call.callback(null, data.response);
}
});
}
onConnect() {
this.startTimeSync();
let lobbyId = this.getQueryLobbyId();
if (lobbyId) {
this.requestJoin(lobbyId).then(v => {
if (!v) {
this.setQueryLobbyId(null);
}
})
} else {
this.requestJoinRandom();
}
}
onDisconnect() {
this.stopTimeSync();
}
private getQueryLobbyId(): number | null {
let query = parseQueryString(window.location.search);
if (query.lobby) {
let lobbyId = Number.parseInt(query.lobby.toString());
if (Number.isSafeInteger(lobbyId) && lobbyId > 0) {
return lobbyId
}
}
return null;
}
private setQueryLobbyId(lobbyId: number | null) {
let query = parseQueryString(window.location.search);
if (lobbyId) {
query.lobby = lobbyId.toString();
} else {
delete query.lobby;
}
console.log('QUERY', query);
let newUrl = window.location.protocol + "//" + window.location.host +
window.location.pathname + (Object.keys(query).length ? ('?' + stringifyQueryString(query)) : '');
window.history.pushState({}, '', newUrl);
}
async call(name: string, params: any) {
return new Promise<any>((resolve, reject) => {
let callback = (err: any, res: any) => {
if (err) {
return reject(err);
}
resolve(res);
};
let call = new Call(name, params, callback);
this.calls[call.id] = call;
this.socket.emit('call', call.id, name, params);
});
}
requestStart() {
this.socket.emit('request_start');
}
async requestJoin(roomId: number): Promise<boolean> {
return this.call('room_exists', {roomId: roomId}).then(v => {
if (v) {
this.socket.emit('request_join', roomId);
return true;
} else {
return false;
}
})
}
requestJoinRandom() {
this.socket.emit('request_join_random');
}
startTimeSync() {
for (let i = 0; i < this.timeSyncIntervals.length; i++) {
let timeoutId = setTimeout(() => {
this.sendTimeSync(i === this.timeSyncIntervals.length - 1);
}, this.timeSyncIntervals[i]);
// @ts-ignore
this.timeSyncTimeoutIds.push(timeoutId);
}
}
stopTimeSync() {
for (let i = 0; i < this.timeSyncTimeoutIds.length; i++) {
clearTimeout(this.timeSyncTimeoutIds[i]);
}
}
sendTimeSync(alsoSchedule: boolean) {
let sync = new TimeSyncRequest();
this.socket.emit('time_sync', sync.requestId, Date.now());
this.timeSyncs[sync.requestId] = sync;
if (alsoSchedule) {
setTimeout(() => {
this.sendTimeSync(true);
}, this.timeSyncIntervals[this.timeSyncIntervals.length - 1]);
}
}
timeSyncResponse(requestId: number, clientDiff: number, serverTime: number) {
let syncReq = this.timeSyncs[requestId];
if (!syncReq) return;
syncReq.response(clientDiff, serverTime);
for (let i in this.timeSyncs) {
if (this.timeSyncs[i].start < Date.now() - this.timeSyncTooOld) {
delete this.timeSyncs[i];
break;
}
}
// console.log(this.timeSyncs);
// console.log('SERVER TIME', this.serverTimeOffset());
this.roomTime.set(roomTime());
}
serverTime(): number {
return Date.now() + this.serverTimeOffset();
}
serverTimeOffset(): number {
let num = 0;
let sum = 0;
for (let i in this.timeSyncs) {
let sync = this.timeSyncs[i];
if (!sync.ready) continue;
sum += sync.offset;
num += 1;
}
if (num === 0) {
return 0;
}
return Math.round(sum / num);
}
}
let _callId = 0;
class Call {
id: number;
name: string;
params: any;
callback: (err: any, res: any) => any;
constructor(name: string, params: any, callback: (err: any, res: any) => void) {
this.name = name;
this.params = params;
this.id = _callId++;
this.callback = callback;
}
}
let _timeSyncId = 0;
class TimeSyncRequest {
requestId: number;
start: number;
offset: number = 0;
ready = false;
constructor() {
this.requestId = _timeSyncId++;
this.start = Date.now();
}
response(clientDiff: number, serverTime: number) {
this.ready = true;
let now = Date.now();
let lag = now - this.start;
this.offset = serverTime - now + lag / 2;
// console.log('TIME SYNC', 'cdiff:', clientDiff, 'lag:',
// lag, 'diff:', serverTime - now, 'offset:', this.offset);
}
}
let connection: Connection = new Connection();
// @ts-ignore
window['connection'] = connection;
export default connection;
export function useRoom(): Room | null {
return useSub(connection.room);
}
export function useRoomRunningChanged(): Room | null {
return useSub(connection.room, (v) => [v && v.running]);
}
export function useTimeline(): Timeline | null {
return useSub(connection.timeline);
}
export function useTick(): Tick | null {
return useSub(connection.tick);
}
export function useRoomTime(): number {
return useSub(connection.roomTime);
}
export function roomTime(): number {
let room = connection.room.get();
if (!room) return 0;
return connection.serverTime() - room.startTime;
}
export function useIsConnected() {
const [isConnected, setIsConnected] = useState(connection.socket.connected);
useEffect(() => {
let connectListener = () => setIsConnected(true);
let disconnectListener = () => setIsConnected(false);
connection.socket.on('connect', connectListener);
connection.socket.on('disconnect', disconnectListener);
return () => {
connection.socket.off('connect', connectListener);
connection.socket.off('disconnect', disconnectListener);
}
}, [isConnected]);
return isConnected;
}