Skip to content

Commit

Permalink
Session 41: M U L T I P L A Y E R
Browse files Browse the repository at this point in the history
  • Loading branch information
dominic committed Oct 2, 2020
1 parent ba6071b commit 31f8806
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 200 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"scripts": {
"start": "parcel src/index.html",
"build": "parcel build src/index.html --public-url ./",
"dev": "npx ts-node src/build/dev.ts",
"devServer": "npx ts-node out/server/server.js",
"dev": "npx ts-node --transpile-only src/build/dev.ts",
"devServer": "npx ts-node --transpile-only out/server/server.js",
"typecheck": "tsc -p . --noEmit",
"lint": "eslint 'src/**/*.{js,ts,tsx}' --quiet --fix",
"test": "jest",
Expand Down Expand Up @@ -63,4 +63,4 @@
"ts-jest": "^26.1.0",
"typescript": "^3.8.3"
}
}
}
151 changes: 86 additions & 65 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,13 @@ export class Client {
}
break
case ServerMessageType.SPEED_UP:
this.ticksPerUpdate = 3
this.ticksPerUpdate = 2
this.updatePeriod = (1000 * SIMULATION_PERIOD_S) / 2
break
case ServerMessageType.SLOW_DOWN:
this.ticksPerUpdate = 1
this.updatePeriod = 1000 * SIMULATION_PERIOD_S
break
}
}

Expand Down Expand Up @@ -308,7 +312,13 @@ export class Client {

this.renderer.setTransform(mat2d.identity(mat2d.create()))

// systems.playerHealthBar(this)
systems.playerHealthBar(
{
entityManager: this.entityManager,
playerNumber: this.playerNumber,
},
this.renderer,
)
// systems.inventoryDisplay(this, this.entityManager.getPlayer())

if (this.state === GameState.YouDied) {
Expand Down Expand Up @@ -364,66 +374,78 @@ export class Client {
})

this.debugDraw(
() => [
{
primitive: Primitive.TEXT,
text: `Player ${this.playerNumber}`,
pos: vec2.fromValues(10, 10),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Render FPS: ${(
1 / this.renderFrameDurations.average()
).toFixed(2)}`,
pos: vec2.fromValues(10, 30),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Tick FPS: ${(1 / this.tickDurations.average()).toFixed(2)}`,
pos: vec2.fromValues(10, 50),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Update FPS: ${(1 / this.updateDurations.average()).toFixed(
2,
)}`,
pos: vec2.fromValues(10, 70),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `FAOS: ${this.framesAheadOfServer.average().toFixed(2)}`,
pos: vec2.fromValues(10, 90),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `SIPF: ${this.serverInputsPerFrame.average().toFixed(2)}`,
pos: vec2.fromValues(10, 110),
hAlign: TextAlign.Min,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
],
() => {
const rightEdge = this.camera.viewportDimensions[0]
return [
{
primitive: Primitive.TEXT,
text: `Player ${this.playerNumber}`,
pos: vec2.fromValues(rightEdge - 10, 10),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Render FPS: ${(
1 / this.renderFrameDurations.average()
).toFixed(2)}`,
pos: vec2.fromValues(rightEdge - 10, 30),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Update FPS: ${(1 / this.updateDurations.average()).toFixed(
2,
)}`,
pos: vec2.fromValues(rightEdge - 10, 50),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `Tick FPS: ${(1 / this.tickDurations.average()).toFixed(2)}`,
pos: vec2.fromValues(rightEdge - 10, 70),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `FAOS: ${this.framesAheadOfServer.average().toFixed(2)}`,
pos: vec2.fromValues(rightEdge - 10, 90),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: `SIPF: ${this.serverInputsPerFrame.average().toFixed(2)}`,
pos: vec2.fromValues(rightEdge - 10, 110),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '16px monospace',
style: 'cyan',
},
{
primitive: Primitive.TEXT,
text: this.ticksPerUpdate > 1 ? `OVERDRIVE` : '',
pos: vec2.fromValues(rightEdge - 10, 150),
hAlign: TextAlign.Max,
vAlign: TextAlign.Center,
font: '36px monospace',
style: 'red',
},
]
},
{ viewspace: true },
)

Expand All @@ -438,11 +460,10 @@ export class Client {

registerParticleEmitter(params: {
emitter: ParticleEmitter
frame: number
entity: string
}): void {
if (!this.emitterHistory.has(`${params.frame}:${params.entity}`)) {
this.emitterHistory.add(`${params.frame}:${params.entity}`)
if (!this.emitterHistory.has(`${this.simulationFrame}:${params.entity}`)) {
this.emitterHistory.add(`${this.simulationFrame}:${params.entity}`)
this.emitters.push(params.emitter)
}
}
Expand Down
13 changes: 5 additions & 8 deletions src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,13 @@ export class Server {
)

const framesBehindLeader = this.maxReceivedClientFrame - client.frame

if (framesBehindLeader > this.minFramesBehindClient) {
// TODO: remember to also send SLOW_DOWN
client.conn.send({ type: ServerMessageType.SPEED_UP })
} else if (
this.minFramesBehindClient / 2 >= framesBehindLeader ||
client.frame === this.maxReceivedClientFrame
) {
client.conn.send({ type: ServerMessageType.SLOW_DOWN })
}
}

Expand Down Expand Up @@ -181,12 +184,6 @@ export class Server {

// Remove this frame's client messages from the history, then process.
const frameMessages = this.clientMessagesByFrame.shift() || []
if (frameMessages.length > 0) {
console.log(
`server: processing frame ${this.simulationFrame}, ${frameMessages.length} client messages`,
)
}

simulate(
{
entityManager: this.entityManager,
Expand Down
11 changes: 1 addition & 10 deletions src/build/buildServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,7 @@ console.log('Creating bundle...')
const buildkey = getMtimeMs(gameSrcPath).toString()
console.log(`buildkey: ${buildkey}`)

const buildkeySrc = fs.readFileSync(buildkeyPath).toString('utf8')
fs.writeFileSync(
buildkeyPath,
Buffer.from(
buildkeySrc.replace(
/buildkey\s+=\s+["'][^"']+["']/,
`buildkey = '${buildkey}'`,
),
),
)
fs.writeFileSync(buildkeyPath, `export const buildkey = '${buildkey}'`)

const bundler = new Bundler(path.join(gameSrcPath, 'serverMain.ts'), {
target: 'node',
Expand Down
2 changes: 2 additions & 0 deletions src/build/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const rebuild = async () => {
const clientBuild = new Promise((resolve) => {
const build = spawn('npx', [
'ts-node',
'--transpile-only',
path.join(gameSrcPath, 'build', 'buildClient.ts'),
])
build.on('close', resolve)
Expand All @@ -46,6 +47,7 @@ const rebuild = async () => {
const serverBuild = new Promise((resolve) => {
const build = spawn('npx', [
'ts-node',
'--transpile-only',
path.join(gameSrcPath, 'build', 'buildServer.ts'),
])
build.on('close', resolve)
Expand Down
19 changes: 10 additions & 9 deletions src/entities/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ export class EntityManager {
entities: { [key: string]: Entity } // array of structures -> structure of arrays

toDelete: string[]
checkpoint: { [key: string]: Entity }
checkpointedEntities: { [key: string]: Entity }
uncommitted: Set<string>

constructor() {
this.nextEntityId = 0
this.entities = {}
this.toDelete = []
this.checkpoint = {}
this.checkpointedEntities = {}
this.uncommitted = new Set()
}

Expand All @@ -24,28 +24,28 @@ export class EntityManager {
this.toDelete = []
}

checkpointEntity(id: string): void {
if (this.checkpoint[id]) {
checkpoint(id: string): void {
if (this.checkpointedEntities[id]) {
return
}
this.checkpoint[id] = _.cloneDeep(this.entities[id])
this.checkpointedEntities[id] = _.cloneDeep(this.entities[id])
}

restoreCheckpoints(): void {
for (const id of Object.keys(this.checkpoint)) {
this.entities[id] = this.checkpoint[id]
for (const id of Object.keys(this.checkpointedEntities)) {
this.entities[id] = this.checkpointedEntities[id]
}

this.nextEntityId -= this.uncommitted.size
this.uncommitted.forEach((id) => delete this.entities[id])

this.uncommitted = new Set()
this.checkpoint = {}
this.checkpointedEntities = {}
}

clearCheckpoint(): void {
this.uncommitted = new Set()
this.checkpoint = {}
this.checkpointedEntities = {}
}

getRenderables(): Renderable[] {
Expand Down Expand Up @@ -76,6 +76,7 @@ export class EntityManager {
}

markForDeletion(id: string): void {
this.checkpoint(id)
this.toDelete.push(id)
}
}
3 changes: 2 additions & 1 deletion src/serverMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import { Server as GameServer } from '~/Server'

// TODO: read from envvar
const playerCount = 2
const clientBufferSize = 10
const clientBufferSize = 15

const gameServer = new GameServer({
playerCount,
minFramesBehindClient: clientBufferSize,
})

setInterval(
() => gameServer.update(SIMULATION_PERIOD_S),
(1000 * SIMULATION_PERIOD_S) / 2,
Expand Down
5 changes: 2 additions & 3 deletions src/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type SimState = {
terrainLayer: terrain.Layer
registerParticleEmitter?: (params: {
emitter: ParticleEmitter
frame: number
entity: string
}) => void
}
Expand All @@ -36,10 +35,10 @@ export const simulate = (
systems.bullet(simState, dt)
// systems.pickups(this, this.entityManager)
systems.wallCollider(simState)
// systems.attack(this, this.entityManager)
systems.attack(simState)
systems.playfieldClamping(simState)

// systems.damageable(this, this.entityManager)
systems.damageable(simState)

// TODO: need mechanism to sync state with client
// if (this.state === GameState.YouDied) {
Expand Down
Loading

0 comments on commit 31f8806

Please sign in to comment.