log only 5 min after game end + some type hinting :P

This commit is contained in:
Zutatensuppe 2021-05-31 20:05:41 +02:00
parent c2da0759b9
commit 870f827e49
9 changed files with 108 additions and 56 deletions

View file

@ -1186,6 +1186,17 @@ const PUBLIC_DIR = `${BASE_DIR}/build/public/`;
const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`; const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`;
const DB_FILE = `${BASE_DIR}/data/db.sqlite`; const DB_FILE = `${BASE_DIR}/data/db.sqlite`;
const POST_GAME_LOG_DURATION = 5 * Time.MIN;
const shouldLog = (finishTs, currentTs) => {
// when not finished yet, always log
if (!finishTs) {
return true;
}
// in finished games, log max POST_GAME_LOG_DURATION after
// the game finished, to record winning dance moves etc :P
const timeSinceGameEnd = currentTs - finishTs;
return timeSinceGameEnd <= POST_GAME_LOG_DURATION;
};
const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`;
const create = (gameId) => { const create = (gameId) => {
const file = filename(gameId); const file = filename(gameId);
@ -1237,6 +1248,7 @@ const get = async (gameId, offset = 0, size = 10000) => {
}); });
}; };
var GameLog = { var GameLog = {
shouldLog,
create, create,
exists, exists,
log: _log, log: _log,
@ -1689,21 +1701,25 @@ async function createGame(gameId, targetTiles, image, ts, scoreMode) {
GameStorage.setDirty(gameId); GameStorage.setDirty(gameId);
} }
function addPlayer(gameId, playerId, ts) { function addPlayer(gameId, playerId, ts) {
const idx = GameCommon.getPlayerIndexById(gameId, playerId); if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) {
const diff = ts - GameCommon.getStartTs(gameId); const idx = GameCommon.getPlayerIndexById(gameId, playerId);
if (idx === -1) { const diff = ts - GameCommon.getStartTs(gameId);
GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); if (idx === -1) {
} GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff);
else { }
GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); else {
GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff);
}
} }
GameCommon.addPlayer(gameId, playerId, ts); GameCommon.addPlayer(gameId, playerId, ts);
GameStorage.setDirty(gameId); GameStorage.setDirty(gameId);
} }
function handleInput(gameId, playerId, input, ts) { function handleInput(gameId, playerId, input, ts) {
const idx = GameCommon.getPlayerIndexById(gameId, playerId); if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) {
const diff = ts - GameCommon.getStartTs(gameId); const idx = GameCommon.getPlayerIndexById(gameId, playerId);
GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); const diff = ts - GameCommon.getStartTs(gameId);
GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff);
}
const ret = GameCommon.handleInput(gameId, playerId, input, ts); const ret = GameCommon.handleInput(gameId, playerId, input, ts);
GameStorage.setDirty(gameId); GameStorage.setDirty(gameId);
return ret; return ret;
@ -2069,6 +2085,8 @@ wss.on('message', async ({ socket, data }) => {
const proto = socket.protocol.split('|'); const proto = socket.protocol.split('|');
const clientId = proto[0]; const clientId = proto[0];
const gameId = proto[1]; const gameId = proto[1];
// TODO: maybe handle different types of data
// (but atm only string comes through)
const msg = JSON.parse(data); const msg = JSON.parse(data);
const msgType = msg[0]; const msgType = msg[0];
switch (msgType) { switch (msgType) {
@ -2152,12 +2170,12 @@ const gracefulShutdown = (signal) => {
process.exit(); process.exit();
}; };
// used by nodemon // used by nodemon
process.once('SIGUSR2', function () { process.once('SIGUSR2', () => {
gracefulShutdown('SIGUSR2'); gracefulShutdown('SIGUSR2');
}); });
process.once('SIGINT', function () { process.once('SIGINT', () => {
gracefulShutdown('SIGINT'); gracefulShutdown('SIGINT');
}); });
process.once('SIGTERM', function () { process.once('SIGTERM', () => {
gracefulShutdown('SIGTERM'); gracefulShutdown('SIGTERM');
}); });

View file

@ -15,6 +15,7 @@ export type Change = Array<any>
export type GameEvent = Array<any> export type GameEvent = Array<any>
export type ServerEvent = Array<any>
export type ClientEvent = Array<any> export type ClientEvent = Array<any>
export type EncodedPlayer = FixedLengthArray<[ export type EncodedPlayer = FixedLengthArray<[

View file

@ -29,8 +29,11 @@ const pad = (x: number, pad: string): string => {
return pad.substr(0, pad.length - str.length) + str return pad.substr(0, pad.length - str.length) + str
} }
export const logger = (...pre: Array<any>) => { type LogArgs = Array<any>
const log = (m: 'log'|'info'|'error') => (...args: Array<any>) => { type LogFn = (...args: LogArgs) => void
export const logger = (...pre: string[]): { log: LogFn, error: LogFn, info: LogFn } => {
const log = (m: 'log'|'info'|'error') => (...args: LogArgs): void => {
const d = new Date() const d = new Date()
const hh = pad(d.getHours(), '00') const hh = pad(d.getHours(), '00')
const mm = pad(d.getMinutes(), '00') const mm = pad(d.getMinutes(), '00')

View file

@ -1,6 +1,6 @@
"use strict" "use strict"
import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types' import { ClientEvent, EncodedGame, GameEvent, ReplayData, ServerEvent } from '../common/Types'
import Util, { logger } from '../common/Util' import Util, { logger } from '../common/Util'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
@ -17,8 +17,8 @@ const CONN_STATE_CLOSED = 4 // not connected (closed on purpose)
let ws: WebSocket let ws: WebSocket
let missedMessages: Array<any> = [] let missedMessages: ServerEvent[] = []
let changesCallback = (msg: Array<any>) => { let changesCallback = (msg: ServerEvent) => {
missedMessages.push(msg) missedMessages.push(msg)
} }
@ -27,7 +27,7 @@ let connectionStateChangeCallback = (state: number) => {
missedStateChanges.push(state) missedStateChanges.push(state)
} }
function onServerChange(callback: (msg: Array<any>) => void): void { function onServerChange(callback: (msg: ServerEvent) => void): void {
changesCallback = callback changesCallback = callback
for (const missedMessage of missedMessages) { for (const missedMessage of missedMessages) {
changesCallback(missedMessage) changesCallback(missedMessage)
@ -77,8 +77,8 @@ function connect(
setConnectionState(CONN_STATE_CONNECTED) setConnectionState(CONN_STATE_CONNECTED)
send([Protocol.EV_CLIENT_INIT]) send([Protocol.EV_CLIENT_INIT])
} }
ws.onmessage = (e) => { ws.onmessage = (e: MessageEvent) => {
const msg = JSON.parse(e.data) const msg: ServerEvent = JSON.parse(e.data)
const msgType = msg[0] const msgType = msg[0]
if (msgType === Protocol.EV_SERVER_INIT) { if (msgType === Protocol.EV_SERVER_INIT) {
const game = msg[1] const game = msg[1]
@ -102,7 +102,7 @@ function connect(
throw `[ 2021-05-15 onerror ]` throw `[ 2021-05-15 onerror ]`
} }
ws.onclose = (e) => { ws.onclose = (e: CloseEvent) => {
if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) {
setConnectionState(CONN_STATE_CLOSED) setConnectionState(CONN_STATE_CLOSED)
} else { } else {

View file

@ -21,6 +21,7 @@ import {
ReplayData, ReplayData,
Timestamp, Timestamp,
GameEvent, GameEvent,
ServerEvent,
} from '../common/Types' } from '../common/Types'
declare global { declare global {
interface Window { interface Window {
@ -485,7 +486,7 @@ export async function main(
} }
if (MODE === MODE_PLAY) { if (MODE === MODE_PLAY) {
Communication.onServerChange((msg) => { Communication.onServerChange((msg: ServerEvent) => {
const msgType = msg[0] const msgType = msg[0]
const evClientId = msg[1] const evClientId = msg[1]
const evClientSeq = msg[2] const evClientSeq = msg[2]

View file

@ -1,12 +1,14 @@
import GameCommon from './../common/GameCommon' import GameCommon from './../common/GameCommon'
import { Change, Game, Input, ScoreMode, Timestamp } from './../common/Types' import { Change, Game, Input, ScoreMode, Timestamp } from './../common/Types'
import Util from './../common/Util' import Util, { logger } from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import GameLog from './GameLog' import GameLog from './GameLog'
import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
const log = logger('Game.ts')
async function createGameObject( async function createGameObject(
gameId: string, gameId: string,
targetTiles: number, targetTiles: number,
@ -49,12 +51,14 @@ async function createGame(
} }
function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { function addPlayer(gameId: string, playerId: string, ts: Timestamp): void {
const idx = GameCommon.getPlayerIndexById(gameId, playerId) if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) {
const diff = ts - GameCommon.getStartTs(gameId) const idx = GameCommon.getPlayerIndexById(gameId, playerId)
if (idx === -1) { const diff = ts - GameCommon.getStartTs(gameId)
GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff) if (idx === -1) {
} else { GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff)
GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) } else {
GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff)
}
} }
GameCommon.addPlayer(gameId, playerId, ts) GameCommon.addPlayer(gameId, playerId, ts)
@ -67,9 +71,11 @@ function handleInput(
input: Input, input: Input,
ts: Timestamp ts: Timestamp
): Array<Change> { ): Array<Change> {
const idx = GameCommon.getPlayerIndexById(gameId, playerId) if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) {
const diff = ts - GameCommon.getStartTs(gameId) const idx = GameCommon.getPlayerIndexById(gameId, playerId)
GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff) const diff = ts - GameCommon.getStartTs(gameId)
GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff)
}
const ret = GameCommon.handleInput(gameId, playerId, input, ts) const ret = GameCommon.handleInput(gameId, playerId, input, ts)
GameStorage.setDirty(gameId) GameStorage.setDirty(gameId)

View file

@ -1,11 +1,27 @@
import fs from 'fs' import fs from 'fs'
import readline from 'readline' import readline from 'readline'
import stream from 'stream' import stream from 'stream'
import Time from '../common/Time'
import { Timestamp } from '../common/Types'
import { logger } from './../common/Util' import { logger } from './../common/Util'
import { DATA_DIR } from './../server/Dirs' import { DATA_DIR } from './../server/Dirs'
const log = logger('GameLog.js') const log = logger('GameLog.js')
const POST_GAME_LOG_DURATION = 5 * Time.MIN
const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => {
// when not finished yet, always log
if (!finishTs) {
return true
}
// in finished games, log max POST_GAME_LOG_DURATION after
// the game finished, to record winning dance moves etc :P
const timeSinceGameEnd = currentTs - finishTs
return timeSinceGameEnd <= POST_GAME_LOG_DURATION
}
const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log`
const create = (gameId: string): void => { const create = (gameId: string): void => {
@ -66,6 +82,7 @@ const get = async (
} }
export default { export default {
shouldLog,
create, create,
exists, exists,
log: _log, log: _log,

View file

@ -56,7 +56,7 @@ class WebSocketServer {
socket.close() socket.close()
return return
} }
socket.on('message', (data: any) => { socket.on('message', (data: WebSocket.Data) => {
log.log(`ws`, socket.protocol, data) log.log(`ws`, socket.protocol, data)
this.evt.dispatch('message', {socket, data}) this.evt.dispatch('message', {socket, data})
}) })

View file

@ -19,7 +19,7 @@ import {
UPLOAD_DIR, UPLOAD_DIR,
} from './Dirs' } from './Dirs'
import GameCommon from '../common/GameCommon' import GameCommon from '../common/GameCommon'
import { Game as GameType, GameSettings, ScoreMode } from '../common/Types' import { ServerEvent, Game as GameType, GameSettings, ScoreMode } from '../common/Types'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
import Db from './Db' import Db from './Db'
@ -57,14 +57,14 @@ const storage = multer.diskStorage({
}) })
const upload = multer({storage}).single('file'); const upload = multer({storage}).single('file');
app.get('/api/conf', (req, res) => { app.get('/api/conf', (req, res): void => {
res.send({ res.send({
WS_ADDRESS: config.ws.connectstring, WS_ADDRESS: config.ws.connectstring,
}) })
}) })
app.get('/api/replay-data', async (req, res) => { app.get('/api/replay-data', async (req, res): Promise<void> => {
const q = req.query as any const q: Record<string, any> = req.query
const offset = parseInt(q.offset, 10) || 0 const offset = parseInt(q.offset, 10) || 0
if (offset < 0) { if (offset < 0) {
res.status(400).send({ reason: 'bad offset' }) res.status(400).send({ reason: 'bad offset' })
@ -95,8 +95,8 @@ app.get('/api/replay-data', async (req, res) => {
res.send({ log, game: game ? Util.encodeGame(game) : null }) res.send({ log, game: game ? Util.encodeGame(game) : null })
}) })
app.get('/api/newgame-data', (req, res) => { app.get('/api/newgame-data', (req, res): void => {
const q = req.query as any const q: Record<string, any> = req.query
const tagSlugs: string[] = q.tags ? q.tags.split(',') : [] const tagSlugs: string[] = q.tags ? q.tags.split(',') : []
res.send({ res.send({
images: Images.allImagesFromDb(db, tagSlugs, q.sort), images: Images.allImagesFromDb(db, tagSlugs, q.sort),
@ -104,10 +104,10 @@ app.get('/api/newgame-data', (req, res) => {
}) })
}) })
app.get('/api/index-data', (req, res) => { app.get('/api/index-data', (req, res): void => {
const ts = Time.timestamp() const ts = Time.timestamp()
const games = [ const games = [
...GameCommon.getAllGames().map((game: any) => ({ ...GameCommon.getAllGames().map((game: GameType) => ({
id: game.id, id: game.id,
hasReplay: GameLog.exists(game.id), hasReplay: GameLog.exists(game.id),
started: GameCommon.getStartTs(game.id), started: GameCommon.getStartTs(game.id),
@ -131,7 +131,7 @@ interface SaveImageRequestData {
tags: string[] tags: string[]
} }
const setImageTags = (db: Db, imageId: number, tags: string[]) => { const setImageTags = (db: Db, imageId: number, tags: string[]): void => {
tags.forEach((tag: string) => { tags.forEach((tag: string) => {
const slug = Util.slug(tag) const slug = Util.slug(tag)
const id = db.upsert('categories', { slug, title: tag }, { slug }, 'id') const id = db.upsert('categories', { slug, title: tag }, { slug }, 'id')
@ -144,7 +144,7 @@ const setImageTags = (db: Db, imageId: number, tags: string[]) => {
}) })
} }
app.post('/api/save-image', express.json(), (req, res) => { app.post('/api/save-image', express.json(), (req, res): void => {
const data = req.body as SaveImageRequestData const data = req.body as SaveImageRequestData
db.update('images', { db.update('images', {
title: data.title, title: data.title,
@ -160,8 +160,8 @@ app.post('/api/save-image', express.json(), (req, res) => {
res.send({ ok: true }) res.send({ ok: true })
}) })
app.post('/api/upload', (req, res) => { app.post('/api/upload', (req, res): void => {
upload(req, res, async (err: any) => { upload(req, res, async (err: any): Promise<void> => {
if (err) { if (err) {
log.log(err) log.log(err)
res.status(400).send("Something went wrong!"); res.status(400).send("Something went wrong!");
@ -189,7 +189,7 @@ app.post('/api/upload', (req, res) => {
}) })
}) })
app.post('/api/newgame', express.json(), async (req, res) => { app.post('/api/newgame', express.json(), async (req, res): Promise<void> => {
const gameSettings = req.body as GameSettings const gameSettings = req.body as GameSettings
log.log(gameSettings) log.log(gameSettings)
const gameId = Util.uniqId() const gameId = Util.uniqId()
@ -211,13 +211,15 @@ app.use('/', express.static(PUBLIC_DIR))
const wss = new WebSocketServer(config.ws); const wss = new WebSocketServer(config.ws);
const notify = (data: any, sockets: Array<WebSocket>) => { const notify = (data: ServerEvent, sockets: Array<WebSocket>): void => {
for (const socket of sockets) { for (const socket of sockets) {
wss.notifyOne(data, socket) wss.notifyOne(data, socket)
} }
} }
wss.on('close', async ({socket} : {socket: WebSocket}) => { wss.on('close', async (
{socket} : { socket: WebSocket }
): Promise<void> => {
try { try {
const proto = socket.protocol.split('|') const proto = socket.protocol.split('|')
// const clientId = proto[0] // const clientId = proto[0]
@ -228,12 +230,16 @@ wss.on('close', async ({socket} : {socket: WebSocket}) => {
} }
}) })
wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { wss.on('message', async (
{socket, data} : { socket: WebSocket, data: WebSocket.Data }
): Promise<void> => {
try { try {
const proto = socket.protocol.split('|') const proto = socket.protocol.split('|')
const clientId = proto[0] const clientId = proto[0]
const gameId = proto[1] const gameId = proto[1]
const msg = JSON.parse(data) // TODO: maybe handle different types of data
// (but atm only string comes through)
const msg = JSON.parse(data as string)
const msgType = msg[0] const msgType = msg[0]
switch (msgType) { switch (msgType) {
case Protocol.EV_CLIENT_INIT: { case Protocol.EV_CLIENT_INIT: {
@ -303,7 +309,7 @@ const server = app.listen(
wss.listen() wss.listen()
const memoryUsageHuman = () => { const memoryUsageHuman = (): void => {
const totalHeapSize = v8.getHeapStatistics().total_available_size const totalHeapSize = v8.getHeapStatistics().total_available_size
const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2) const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2)
@ -322,7 +328,7 @@ const persistInterval = setInterval(() => {
memoryUsageHuman() memoryUsageHuman()
}, config.persistence.interval) }, config.persistence.interval)
const gracefulShutdown = (signal: any) => { const gracefulShutdown = (signal: string): void => {
log.log(`${signal} received...`) log.log(`${signal} received...`)
log.log('clearing persist interval...') log.log('clearing persist interval...')
@ -342,14 +348,14 @@ const gracefulShutdown = (signal: any) => {
} }
// used by nodemon // used by nodemon
process.once('SIGUSR2', function () { process.once('SIGUSR2', (): void => {
gracefulShutdown('SIGUSR2') gracefulShutdown('SIGUSR2')
}) })
process.once('SIGINT', function () { process.once('SIGINT', (): void => {
gracefulShutdown('SIGINT') gracefulShutdown('SIGINT')
}) })
process.once('SIGTERM', function () { process.once('SIGTERM', (): void => {
gracefulShutdown('SIGTERM') gracefulShutdown('SIGTERM')
}) })