enable replays

This commit is contained in:
Zutatensuppe 2021-05-29 11:44:55 +02:00
parent eabe338971
commit 7a7d6580fc
7 changed files with 129 additions and 36 deletions

View file

@ -170,8 +170,11 @@ function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null {
return null return null
} }
function getPlayer(gameId: string, playerId: string): Player { function getPlayer(gameId: string, playerId: string): Player|null {
const idx = getPlayerIndexById(gameId, playerId) const idx = getPlayerIndexById(gameId, playerId)
if (idx === -1) {
return null
}
return Util.decodePlayer(GAMES[gameId].players[idx]) return Util.decodePlayer(GAMES[gameId].players[idx])
} }
@ -299,6 +302,10 @@ function changePlayer(
change: any change: any
): void { ): void {
const player = getPlayer(gameId, playerId) const player = getPlayer(gameId, playerId)
if (player === null) {
return
}
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
// @ts-ignore // @ts-ignore
player[k] = change[k] player[k] = change[k]
@ -647,9 +654,13 @@ function handleInput(
} }
const _playerChange = (): void => { const _playerChange = (): void => {
const player = getPlayer(gameId, playerId)
if (!player) {
return
}
changes.push([ changes.push([
Protocol.CHANGE_PLAYER, Protocol.CHANGE_PLAYER,
Util.encodePlayer(getPlayer(gameId, playerId)), Util.encodePlayer(player),
]) ])
} }

View file

@ -40,10 +40,10 @@ EV_SERVER_INIT: event sent to one client after that client
*/ */
const EV_SERVER_EVENT = 1 const EV_SERVER_EVENT = 1
const EV_SERVER_INIT = 4 const EV_SERVER_INIT = 4
const EV_SERVER_INIT_REPLAY = 5 const EV_SERVER_REPLAY_DATA = 5
const EV_CLIENT_EVENT = 2 const EV_CLIENT_EVENT = 2
const EV_CLIENT_INIT = 3 const EV_CLIENT_INIT = 3
const EV_CLIENT_INIT_REPLAY = 6 const EV_CLIENT_REPLAY_DATA = 6
const LOG_HEADER = 1 const LOG_HEADER = 1
const LOG_ADD_PLAYER = 2 const LOG_ADD_PLAYER = 2
@ -68,10 +68,10 @@ const CHANGE_PLAYER = 3
export default { export default {
EV_SERVER_EVENT, EV_SERVER_EVENT,
EV_SERVER_INIT, EV_SERVER_INIT,
EV_SERVER_INIT_REPLAY, EV_SERVER_REPLAY_DATA,
EV_CLIENT_EVENT, EV_CLIENT_EVENT,
EV_CLIENT_INIT, EV_CLIENT_INIT,
EV_CLIENT_INIT_REPLAY, EV_CLIENT_REPLAY_DATA,
LOG_HEADER, LOG_HEADER,
LOG_ADD_PLAYER, LOG_ADD_PLAYER,

View file

@ -96,6 +96,13 @@ function connect(
}) })
} }
function requestReplayData(
offset: number,
size: number
): void {
send([Protocol.EV_CLIENT_REPLAY_DATA, offset, size])
}
// TOOD: change replay stuff // TOOD: change replay stuff
function connectReplay( function connectReplay(
address: string, address: string,
@ -109,16 +116,25 @@ function connectReplay(
ws = new WebSocket(address, clientId + '|' + gameId) ws = new WebSocket(address, clientId + '|' + gameId)
ws.onopen = (e) => { ws.onopen = (e) => {
setConnectionState(CONN_STATE_CONNECTED) setConnectionState(CONN_STATE_CONNECTED)
send([Protocol.EV_CLIENT_INIT_REPLAY]) requestReplayData(0, 10000)
} }
ws.onmessage = (e) => { ws.onmessage = (e) => {
const msg = JSON.parse(e.data) const msg = JSON.parse(e.data)
const msgType = msg[0] const msgType = msg[0]
if (msgType === Protocol.EV_SERVER_INIT_REPLAY) { if (msgType === Protocol.EV_SERVER_REPLAY_DATA) {
const game = msg[1] const log: any[] = msg[1]
const log = msg[2] const game = msg[2] // can be null or encoded game
const replay: { game: any, log: Array<any> } = { game, log } if (game !== null) {
// this is the first/initial message
const replay: {
game: any,
log: any[]
} = { game, log }
resolve(replay) resolve(replay)
} else {
// this is just the next batch of log entries
changesCallback(msg)
}
} else { } else {
throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]` throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]`
} }
@ -158,6 +174,7 @@ function sendClientEvent(evt: any): void {
export default { export default {
connect, connect,
connectReplay, connectReplay,
requestReplayData,
disconnect, disconnect,
sendClientEvent, sendClientEvent,
onServerChange, onServerChange,

View file

@ -7,7 +7,7 @@
{{time(game.started, game.finished)}}<br /> {{time(game.started, game.finished)}}<br />
</span> </span>
</router-link> </router-link>
<router-link v-if="false && game.hasReplay" class="game-replay" :to="{ name: 'replay', params: { id: game.id } }"> <router-link v-if="game.hasReplay" class="game-replay" :to="{ name: 'replay', params: { id: game.id } }">
Watch replay Watch replay
</router-link> </router-link>
</div> </div>

View file

@ -48,7 +48,10 @@ interface Hud {
setReplayPaused?: (v: boolean) => void setReplayPaused?: (v: boolean) => void
} }
interface Replay { interface Replay {
final: boolean
requesting: boolean
log: Array<any> log: Array<any>
logPointer: number,
logIdx: number logIdx: number
speeds: Array<number> speeds: Array<number>
speedIdx: number speedIdx: number
@ -260,7 +263,10 @@ export async function main(
// stuff only available in replay mode... // stuff only available in replay mode...
// TODO: refactor // TODO: refactor
const REPLAY: Replay = { const REPLAY: Replay = {
final: false,
requesting: true,
log: [], log: [],
logPointer: 0,
logIdx: 0, logIdx: 0,
speeds: [0.5, 1, 2, 5, 10, 20, 50], speeds: [0.5, 1, 2, 5, 10, 20, 50],
speedIdx: 1, speedIdx: 1,
@ -283,12 +289,26 @@ export async function main(
TIME = () => Time.timestamp() TIME = () => Time.timestamp()
} else if (MODE === MODE_REPLAY) { } else if (MODE === MODE_REPLAY) {
// TODO: change how replay connect is done... // TODO: change how replay connect is done...
Communication.onServerChange((msg) => {
const log = msg[1]
// cut log that was already handled
REPLAY.log = REPLAY.log.slice(REPLAY.logPointer)
REPLAY.logPointer = 0
REPLAY.log.push(...msg[1])
if (log.length < 10000) {
REPLAY.final = true
}
REPLAY.requesting = false
})
const replay: {game: any, log: Array<any>} = await Communication.connectReplay(wsAddress, gameId, clientId) const replay: {game: any, log: Array<any>} = await Communication.connectReplay(wsAddress, gameId, clientId)
const gameObject = Util.decodeGame(replay.game) const gameObject = Util.decodeGame(replay.game)
Game.setGame(gameObject.id, gameObject) Game.setGame(gameObject.id, gameObject)
REPLAY.requesting = false
REPLAY.log = replay.log REPLAY.log = replay.log
REPLAY.lastRealTs = Time.timestamp() REPLAY.lastRealTs = Time.timestamp()
REPLAY.gameStartTs = parseInt(REPLAY.log[0][REPLAY.log[0].length - 2], 10) REPLAY.gameStartTs = parseInt(REPLAY.log[0][4], 10)
REPLAY.lastGameTs = REPLAY.gameStartTs REPLAY.lastGameTs = REPLAY.gameStartTs
TIME = () => REPLAY.lastGameTs TIME = () => REPLAY.lastGameTs
} else { } else {
@ -426,6 +446,7 @@ export async function main(
} }
if (MODE === MODE_PLAY) { if (MODE === MODE_PLAY) {
// TODO: register onServerChange function before connecting to server
Communication.onServerChange((msg) => { Communication.onServerChange((msg) => {
const msgType = msg[0] const msgType = msg[0]
const evClientId = msg[1] const evClientId = msg[1]
@ -458,6 +479,18 @@ export async function main(
// only the REPLAY.log is relevant // only the REPLAY.log is relevant
let inter = setInterval(() => { let inter = setInterval(() => {
const realTs = Time.timestamp() const realTs = Time.timestamp()
if (REPLAY.requesting) {
REPLAY.lastRealTs = realTs
return
}
if (REPLAY.logPointer + 1 >= REPLAY.log.length) {
REPLAY.lastRealTs = realTs
REPLAY.requesting = true
Communication.requestReplayData(REPLAY.logIdx, 10000)
return
}
if (REPLAY.paused) { if (REPLAY.paused) {
REPLAY.lastRealTs = realTs REPLAY.lastRealTs = realTs
return return
@ -469,9 +502,11 @@ export async function main(
if (REPLAY.paused) { if (REPLAY.paused) {
break break
} }
const nextIdx = REPLAY.logIdx + 1 const nextIdx = REPLAY.logPointer + 1
if (nextIdx >= REPLAY.log.length) { if (nextIdx >= REPLAY.log.length) {
if (REPLAY.final) {
clearInterval(inter) clearInterval(inter)
}
break break
} }
@ -502,7 +537,8 @@ export async function main(
Game.handleInput(gameId, playerId, input, nextTs) Game.handleInput(gameId, playerId, input, nextTs)
RERENDER = true RERENDER = true
} }
REPLAY.logIdx = nextIdx REPLAY.logPointer = nextIdx
REPLAY.logIdx++
} while (true) } while (true)
REPLAY.lastRealTs = realTs REPLAY.lastRealTs = realTs
REPLAY.lastGameTs = maxGameTs REPLAY.lastGameTs = maxGameTs

View file

@ -1,4 +1,6 @@
import fs from 'fs' import fs from 'fs'
import readline from 'readline'
import stream from 'stream'
import { logger } from './../common/Util' import { logger } from './../common/Util'
import { DATA_DIR } from './../server/Dirs' import { DATA_DIR } from './../server/Dirs'
@ -27,19 +29,39 @@ const _log = (gameId: string, ...args: Array<any>) => {
fs.appendFileSync(file, str + "\n") fs.appendFileSync(file, str + "\n")
} }
const get = (gameId: string) => { const get = async (
gameId: string,
offset: number = 0,
size: number = 10000
): Promise<any[]> => {
const file = filename(gameId) const file = filename(gameId)
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
return [] return []
} }
const lines = fs.readFileSync(file, 'utf-8').split("\n") return new Promise((resolve) => {
return lines.filter((line: string) => !!line).map((line: string) => { const instream = fs.createReadStream(file)
try { const outstream = new stream.Writable()
return JSON.parse(line) const rl = readline.createInterface(instream, outstream)
} catch (e) { const lines: any[] = []
log.log(line) let i = -1
log.log(e) rl.on('line', (line) => {
if (!line) {
// skip empty
return
} }
i++
if (offset > i) {
return
}
if (offset + size <= i) {
rl.close()
return
}
lines.push(JSON.parse(line))
})
rl.on('close', () => {
resolve(lines)
})
}) })
} }

View file

@ -203,20 +203,27 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
const msg = JSON.parse(data) const msg = JSON.parse(data)
const msgType = msg[0] const msgType = msg[0]
switch (msgType) { switch (msgType) {
case Protocol.EV_CLIENT_INIT_REPLAY: { case Protocol.EV_CLIENT_REPLAY_DATA: {
if (!GameLog.exists(gameId)) { if (!GameLog.exists(gameId)) {
throw `[gamelog ${gameId} does not exist... ]` throw `[gamelog ${gameId} does not exist... ]`
} }
const log = GameLog.get(gameId) const offset = msg[1]
const game = await Game.createGameObject( const size = msg[2]
const log = await GameLog.get(gameId, offset, size)
let game = null
if (offset === 0) {
// also need the game
game = await Game.createGameObject(
gameId, gameId,
log[0][2], log[0][2],
log[0][3], log[0][3],
log[0][4], log[0][4],
log[0][5] || ScoreMode.FINAL log[0][5] || ScoreMode.FINAL
) )
}
notify( notify(
[Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null],
[socket] [socket]
) )
} break } break