puzzle/server/index.js

277 lines
7 KiB
JavaScript
Raw Normal View History

2020-11-07 11:35:29 +01:00
import WebSocketServer from './WebSocketServer.js'
2020-11-25 22:03:35 +01:00
import fs from 'fs'
2020-11-07 11:35:29 +01:00
import express from 'express'
2020-11-25 22:03:35 +01:00
import multer from 'multer'
2020-12-04 23:11:30 +01:00
import config from './../config.js'
2020-11-17 21:46:56 +01:00
import Protocol from './../common/Protocol.js'
2020-11-12 19:25:42 +01:00
import Util from './../common/Util.js'
2020-11-12 19:19:02 +01:00
import Game from './Game.js'
2020-11-25 22:03:35 +01:00
import twing from 'twing'
import bodyParser from 'body-parser'
2020-12-13 15:38:44 +01:00
import v8 from 'v8'
2020-12-21 18:34:57 +01:00
import { Rng } from '../common/Rng.js'
2020-12-22 22:35:09 +01:00
import GameLog from './GameLog.js'
2020-11-12 19:19:02 +01:00
2020-11-25 22:03:35 +01:00
const allImages = () => [
...fs.readdirSync('./../data/uploads/').map(f => ({
file: `./../data/uploads/${f}`,
url: `/uploads/${f}`,
})),
...fs.readdirSync('./../game/example-images/').map(f => ({
file: `./../game/example-images/${f}`,
url: `/example-images/${f}`,
})),
2020-11-08 14:13:43 +01:00
]
2020-11-07 12:21:38 +01:00
const port = config.http.port
const hostname = config.http.hostname
2020-11-07 11:35:29 +01:00
const app = express()
2020-11-25 22:03:35 +01:00
const uploadDir = './../data/uploads'
const storage = multer.diskStorage({
destination: uploadDir,
filename: function (req, file, cb) {
cb(null , file.originalname);
}
})
const upload = multer({storage}).single('file');
2020-11-07 12:21:38 +01:00
const statics = express.static('./../game/')
2020-11-25 22:03:35 +01:00
const render = async (template, data) => {
const loader = new twing.TwingLoaderFilesystem('./../game/templates')
const env = new twing.TwingEnvironment(loader)
return env.render(template, data)
}
app.use('/g/:gid', async (req, res, next) => {
res.send(await render('game.html.twig', {
GAME_ID: req.params.gid,
WS_ADDRESS: config.ws.connectstring,
}))
})
2020-12-22 22:35:09 +01:00
app.use('/replay/:gid', async (req, res, next) => {
res.send(await render('replay.html.twig', {
GAME_ID: req.params.gid,
WS_ADDRESS: config.ws.connectstring,
}))
})
2020-11-25 22:03:35 +01:00
app.post('/upload', (req, res) => {
upload(req, res, (err) => {
if (err) {
console.log(err)
res.status(400).send("Something went wrong!");
}
res.send({
image: {
file: './../data/uploads/' + req.file.filename,
url: '/uploads/' + req.file.filename,
},
})
})
})
app.post('/newgame', bodyParser.json(), async (req, res) => {
console.log(req.body.tiles, req.body.image)
const gameId = Util.uniqId()
if (!Game.exists(gameId)) {
2020-12-22 22:35:09 +01:00
const ts = Util.timestamp()
await Game.createGame(gameId, req.body.tiles, req.body.image, ts)
2020-11-25 22:03:35 +01:00
}
res.send({ url: `/g/${gameId}` })
2020-11-08 14:49:34 +01:00
})
2020-11-12 19:19:02 +01:00
app.use('/common/', express.static('./../common/'))
2020-11-25 22:03:35 +01:00
app.use('/uploads/', express.static('./../data/uploads/'))
app.use('/', async (req, res, next) => {
2020-11-07 12:21:38 +01:00
if (req.path === '/') {
2020-12-22 22:35:09 +01:00
const ts = Util.timestamp()
2020-11-25 22:03:35 +01:00
const games = [
2020-12-05 19:45:34 +01:00
...Game.getAllGames().map(game => ({
id: game.id,
2020-12-22 22:54:31 +01:00
hasReplay: GameLog.exists(game.id),
2020-12-07 02:38:07 +01:00
started: Game.getStartTs(game.id),
finished: Game.getFinishTs(game.id),
2020-12-05 19:45:34 +01:00
tilesFinished: Game.getFinishedTileCount(game.id),
tilesTotal: Game.getTileCount(game.id),
2020-12-22 22:35:09 +01:00
players: Game.getActivePlayers(game.id, ts).length,
2020-12-05 19:45:34 +01:00
imageUrl: Game.getImageUrl(game.id),
2020-11-25 22:03:35 +01:00
})),
]
res.send(await render('index.html.twig', {
games,
images: allImages(),
}))
2020-11-07 12:21:38 +01:00
} else {
statics(req, res, next)
}
})
2020-11-07 11:35:29 +01:00
2020-11-07 12:21:38 +01:00
const wss = new WebSocketServer(config.ws);
2020-11-07 11:35:29 +01:00
2020-11-08 17:11:32 +01:00
const notify = (data, sockets) => {
2020-11-17 21:46:56 +01:00
// TODO: throttle?
2020-11-08 17:11:32 +01:00
for (let socket of sockets) {
wss.notifyOne(data, socket)
}
2020-11-07 11:35:29 +01:00
}
2020-11-25 22:03:35 +01:00
wss.on('close', async ({socket}) => {
const proto = socket.protocol.split('|')
const clientId = proto[0]
const gameId = proto[1]
if (Game.exists(gameId)) {
Game.removeSocket(gameId, socket)
}
})
2020-11-08 14:13:43 +01:00
wss.on('message', async ({socket, data}) => {
2020-11-07 11:35:29 +01:00
try {
const proto = socket.protocol.split('|')
2020-11-17 21:46:56 +01:00
const clientId = proto[0]
2020-11-12 19:19:02 +01:00
const gameId = proto[1]
2020-11-17 21:46:56 +01:00
const msg = JSON.parse(data)
const msgType = msg[0]
switch (msgType) {
2020-12-22 22:35:09 +01:00
case Protocol.EV_CLIENT_INIT_REPLAY: {
const log = GameLog.get(gameId)
let game = await Game.createGameObject(
gameId,
log[0][1],
log[0][2],
log[0][3]
)
notify(
[Protocol.EV_SERVER_INIT_REPLAY, {
id: game.id,
rng: {
type: game.rng.type,
obj: Rng.serialize(game.rng.obj),
},
puzzle: game.puzzle,
players: game.players,
sockets: [],
evtInfos: game.evtInfos,
}, log],
[socket]
)
} break;
2020-11-17 21:46:56 +01:00
case Protocol.EV_CLIENT_INIT: {
2020-11-12 19:19:02 +01:00
if (!Game.exists(gameId)) {
2020-11-25 22:03:35 +01:00
throw `[game ${gameId} does not exist... ]`
2020-11-08 17:11:32 +01:00
}
2020-12-22 22:35:09 +01:00
const ts = Util.timestamp()
Game.addPlayer(gameId, clientId, ts)
Game.addSocket(gameId, socket)
const game = Game.get(gameId)
notify(
[Protocol.EV_SERVER_INIT, {
2020-12-05 19:45:34 +01:00
id: game.id,
2020-12-21 18:34:57 +01:00
rng: {
type: game.rng.type,
obj: Rng.serialize(game.rng.obj),
},
puzzle: game.puzzle,
players: game.players,
2020-12-05 19:45:34 +01:00
sockets: [],
evtInfos: game.evtInfos,
}],
[socket]
)
2020-11-07 11:35:29 +01:00
} break;
2020-11-17 21:46:56 +01:00
case Protocol.EV_CLIENT_EVENT: {
const clientSeq = msg[1]
const clientEvtData = msg[2]
2020-12-22 22:35:09 +01:00
const ts = Util.timestamp()
Game.addPlayer(gameId, clientId, ts)
Game.addSocket(gameId, socket)
const game = Game.get(gameId)
notify(
[Protocol.EV_SERVER_INIT, {
2020-12-05 19:45:34 +01:00
id: game.id,
puzzle: game.puzzle,
players: game.players,
2020-12-05 19:45:34 +01:00
sockets: [],
evtInfos: game.evtInfos,
}],
[socket]
)
2020-12-22 22:35:09 +01:00
const changes = Game.handleInput(gameId, clientId, clientEvtData, ts)
notify(
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
Game.getSockets(gameId)
)
2020-11-07 11:35:29 +01:00
} break;
}
} catch (e) {
console.error(e)
}
})
2020-11-08 16:42:59 +01:00
2020-11-25 22:03:35 +01:00
Game.loadAllGames()
const server = app.listen(
port,
hostname,
() => console.log(`server running on http://${hostname}:${port}`)
)
2020-11-07 11:35:29 +01:00
wss.listen()
2020-12-13 14:46:11 +01:00
const memoryUsageHuman = () => {
2020-12-13 15:38:44 +01:00
const totalHeapSize = v8.getHeapStatistics().total_available_size
let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2)
console.log(`Total heap size (bytes) ${totalHeapSize}, (GB ~${totalHeapSizeInGB})`)
2020-12-13 14:46:11 +01:00
const used = process.memoryUsage().heapUsed / 1024 / 1024
2020-12-13 15:38:44 +01:00
console.log(`Mem: ${Math.round(used * 100) / 100}M`)
2020-12-13 14:46:11 +01:00
}
2020-12-13 15:38:44 +01:00
memoryUsageHuman()
2020-12-13 14:46:11 +01:00
// persist games in fixed interval
2020-12-04 21:52:51 +01:00
const persistInterval = setInterval(() => {
console.log('Persisting games...');
2020-12-13 00:11:42 +01:00
Game.persistChangedGames()
2020-12-13 14:46:11 +01:00
2020-12-13 15:38:44 +01:00
memoryUsageHuman()
}, config.persistence.interval)
const gracefulShutdown = (signal) => {
console.log(`${signal} received...`)
2020-12-04 21:52:51 +01:00
console.log('clearing persist interval...')
clearInterval(persistInterval)
console.log('persisting games...')
2020-12-13 00:11:42 +01:00
Game.persistChangedGames()
2020-12-04 21:52:51 +01:00
console.log('shutting down webserver...')
server.close()
2020-12-04 21:52:51 +01:00
console.log('shutting down websocketserver...')
wss.close()
2020-12-04 21:52:51 +01:00
console.log('shutting down...')
2020-12-04 21:19:45 +01:00
process.exit()
}
// used by nodemon
process.once('SIGUSR2', function () {
gracefulShutdown('SIGUSR2')
});
process.once('SIGINT', function (code) {
gracefulShutdown('SIGINT')
});
process.once('SIGTERM', function (code) {
gracefulShutdown('SIGTERM')
});