Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ n.on('ready', () => {
lobbies.forEach(lobby => {
const li = document.createElement('li')
li.id = lobby.code
li.innerHTML = `<a href="javascript:void(0)" class="code">${lobby.code}</a> - <span class="map_name">${lobby.customData?.map as string ?? 'unknown map'}</span> - <span class="players">${lobby.playerCount}</span> players`
li.innerHTML = `<a href="javascript:void(0)" class="code">${lobby.code}</a> - <span class="map_name">${lobby.customData?.map as string ?? 'unknown map'}</span> - <span class="players">${lobby.playerCount}</span> players (${lobby.latency ?? '<unknown>'}ms)`
el.appendChild(li)
if (n.currentLobby === undefined) {
li.querySelector('a.code')?.addEventListener('click', () => {
Expand Down
174 changes: 174 additions & 0 deletions features/latency.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
Feature: Latency

Background:
Given the "signaling" backend is running
And the "testproxy" backend is running


Scenario: Lobby listings include the latency to the peer
Given the next peer's latency vector is set to:
"""
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
"""
And "green" is connected as "1u8fw4aph5ypt" and ready for game "b6f7fc97-8545-4ffd-b714-7cf339048556"
And "green" creates a lobby with these settings:
"""json
{
"public": true
}
"""
And "green" receives the network event "lobby" with the argument "h5yzwyizlwao"
And the next peer's latency vector is set to:
"""
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
"""
And "blue" is connected as "19yrzmetd2bn7" and ready for game "b6f7fc97-8545-4ffd-b714-7cf339048556"

When "blue" requests lobbies with:
"""json
{}
"""

Then "blue" should have received only these lobbies:
| code | latency |
| h5yzwyizlwao | 24 |


Scenario: Lobby with multiple peers
Given the next peer's latency vector is set to:
"""
99, 99, 99, 99, 10, 10, 10, 10, 10, 10, 10
"""
And "blue" is connected as "1u8fw4aph5ypt" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And the next peer's latency vector is set to:
"""
10, 10, 10, 99, 99, 99, 99, 10, 10, 10, 10
"""
And "yellow" is connected as "h5yzwyizlwao" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue,yellow" are joined in a public lobby
And the next peer's latency vector is set to:
"""
10, 10, 10, 10, 10, 10, 99, 99, 99, 99, 10
"""
And "green" is connected as "3t3cfgcqup9e" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"

When "green" requests lobbies with:
"""json
{}
"""

Then "green" should have received only these lobbies:
| code | latency |
| 19yrzmetd2bn7 | 89 |


Scenario: Sort lobbies by latency
Given the next peer's latency vector is set to:
"""
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30
"""
And "blue" is connected as "1u8fw4aph5ypt" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "blue" creates a lobby with these settings:
"""json
{
"public": true,
"customData": {
"map": "de_nuke"
}
}
"""
And the next peer's latency vector is set to:
"""
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99
"""
And "yellow" is connected as "19yrzmetd2bn7" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"
And "yellow" creates a lobby with these settings:
"""json
{
"public": true,
"customData": {
"map": "de_dust"
}
}
"""
And the next peer's latency vector is set to:
"""
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
"""
And "green" is connected as "prb67ouj837u" and ready for game "4307bd86-e1df-41b8-b9df-e22afcf084bd"

When "green" requests lobbies with:
| filter | {} |
| sort | { "latency": 1 } |
| limit | 1 |

Then "green" should have received only these lobbies:
| code | latency | customData |
| h5yzwyizlwao | 34 | {"map":"de_nuke"} |


Scenario: Latency to your own lobby
Given the next peer's latency vector is set to:
"""
325, 523, 64, 21, 76, 23, 54, 235, 76, 23, 142
"""
And "green" is connected as "1u8fw4aph5ypt" and ready for game "b6f7fc97-8545-4ffd-b714-7cf339048556"
And "green" creates a lobby with these settings:
"""json
{
"public": true
}
"""
And "green" receives the network event "lobby" with the argument "h5yzwyizlwao"

When "green" requests lobbies with:
"""json
{}
"""

Then "green" should have received only these lobbies:
| code | latency |
| h5yzwyizlwao | 0 |


Scenario: Peers without latency vectors are not included in the estimate
Given "green" is connected as "1u8fw4aph5ypt" and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884"
And these lobbies exist:
| code | game | peers | public |
| 1u8fw4aph5ypt | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | {"peer1"} | true |
| 0qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | {"peer2", "peer3"} | true |
And these peers exist:
| peer | game | latency_vector |
| peer1 | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | null |
| peer2 | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | null |
| peer3 | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | {10,10,10,10,10,10,10,10,10,10,10} |

When "green" requests lobbies with:
| filter | {} |
| sort | { "latency": 1 } |
Then "green" should have received only these lobbies:
| code | latency |
| 0qva9vyurwbbl | 10 |
| 1u8fw4aph5ypt | undefined |


Scenario: Client without latency vectors gives null latency estimates
Given the next peer's latency vector is set to:
"""
null
"""
Given "green" is connected as "1u8fw4aph5ypt" and ready for game "f666036d-d9e1-4d70-b0c3-4a68b24a9884"
And these lobbies exist:
| code | game | peers | public |
| 0qva9vyurwbbl | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | {"peer1", "peer2"} | true |
And these peers exist:
| peer | game | latency_vector |
| peer1 | f666036d-d9e1-4d70-b0c3-4a68b24a9884 | {10,10,10,10,10,10,10,10,10,10,10} |

When "green" requests lobbies with:
"""json
{}
"""
Then "green" should have received only these lobbies:
| code | latency |
| 0qva9vyurwbbl | undefined |
76 changes: 69 additions & 7 deletions features/support/steps/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Given('{string} is connected as {string} and ready for game {string}', async fun
}
})

async function areJoinedInALobby (this: World, playerNamesRaw: string): Promise<void> {
async function areJoinedInALobby (this: World, playerNamesRaw: string, publc: boolean): Promise<void> {
const playerNames = playerNamesRaw.split(',').map(s => s.trim())
if (playerNames.length < 2) {
throw new Error('need at least 2 players to join a lobby')
Expand All @@ -29,7 +29,9 @@ async function areJoinedInALobby (this: World, playerNamesRaw: string): Promise<
throw new Error(`player ${playerNames[0]} not found`)
}

void first.network.create()
void first.network.create({
public: publc
})
const lobbyEvent = await first.waitForEvent('lobby')
const lobbyCode = lobbyEvent.eventPayload[0] as string

Expand All @@ -55,7 +57,13 @@ async function areJoinedInALobby (this: World, playerNamesRaw: string): Promise<
}
}

Given('{string} are joined in a lobby', areJoinedInALobby)
Given('{string} are joined in a lobby', async function (this: World, playerNamesRaw: string) {
await areJoinedInALobby.call(this, playerNamesRaw, false)
})

Given('{string} are joined in a public lobby', async function (this: World, playerNamesRaw: string) {
await areJoinedInALobby.call(this, playerNamesRaw, true)
})

Given('{string} are joined in a lobby for game {string}', async function (this: World, playerNamesRaw: string, gameID: string) {
const playerNames = playerNamesRaw.split(',').map(s => s.trim())
Expand All @@ -72,7 +80,7 @@ Given('{string} are joined in a lobby for game {string}', async function (this:
}
}

await areJoinedInALobby.call(this, playerNamesRaw)
await areJoinedInALobby.call(this, playerNamesRaw, false)
})

Given('these lobbies exist:', async function (this: World, lobbies: DataTable) {
Expand Down Expand Up @@ -123,6 +131,41 @@ Given('these lobbies exist:', async function (this: World, lobbies: DataTable) {
})
})

Given('these peers exist:', async function (this: World, peers: DataTable) {
if (this.testproxyURL === undefined) {
throw new Error('testproxy not active')
}

const columns: string[] = []
const values: string[] = []

peers.hashes().forEach(row => {
const v: string[] = []

Object.keys(row).forEach(key => {
const value = row[key]
if (!columns.includes(key)) {
columns.push(key)
}

if (value === 'null') {
v.push('NULL')
} else if (key === 'latency_vector') {
v.push(`ARRAY[${value.substring(1, value.length - 1)}]::vector(11)`)
} else {
v.push(`'${value}'`)
}
})

values.push(`(${v.join(', ')})`)
})

await fetch(`${this.testproxyURL}/sql`, {
method: 'POST',
body: 'INSERT INTO peers (' + columns.join(', ') + ') VALUES ' + values.join(', ')
})
})

When('{string} creates a network for game {string}', async function (this: World, playerName: string, gameID: string) {
await this.createPlayer(playerName, gameID)
})
Expand Down Expand Up @@ -307,7 +350,7 @@ Then('{string} should have received only these lobbies:', function (this: World,
expectedLobbies.hashes().forEach(row => {
const correctCodeLobby = player.lastReceivedLobbies.filter(lobby => lobby.code === row.code)
if (correctCodeLobby.length !== 1) {
throw new Error(`expected to find one lobby with code ${row.code} but found ${correctCodeLobby.length}`)
throw new Error(`expected to find one lobby with code ${row.code} but found ${correctCodeLobby.length} in [${player.lastReceivedLobbies.map(l => l.code).join(', ')}]`)
}
const lobby = correctCodeLobby[0] as any
Object.keys(lobby).forEach(key => {
Expand All @@ -318,8 +361,14 @@ Then('{string} should have received only these lobbies:', function (this: World,
})
const want = row as any
Object.keys(row).forEach(key => {
if (`${lobby[key] as string}` !== `${want[key] as string}`) {
throw new Error(`expected ${key} to be ${want[key] as string} but got ${lobby[key] as string}`)
if (typeof lobby[key] === 'object') {
if (JSON.stringify(lobby[key]) !== `${want[key] as string}`) {
throw new Error(`expected ${key} to be ${want[key] as string} but got ${JSON.stringify(lobby[key])}`)
}
} else {
if (`${lobby[key] as string}` !== `${want[key] as string}`) {
throw new Error(`expected ${key} to be ${want[key] as string} but got ${lobby[key] as string}`)
}
}
})
})
Expand Down Expand Up @@ -435,3 +484,16 @@ Then('{string} failed to join the lobby', function (playerName: string) {
throw new Error(`player is in lobby ${player.network.currentLobby as string}`)
}
})

When('the next peer\'s latency vector is set to:', function (latencies: string) {
if (latencies === 'null') {
this.latencyVector = null
return
}

const lv = latencies.split(',').map(s => parseInt(s.trim(), 10))
if (lv.length !== 11) {
throw new Error('latency vector must have 11 elements')
}
this.latencyVector = lv
})
16 changes: 0 additions & 16 deletions features/support/steps/util.ts

This file was deleted.

11 changes: 10 additions & 1 deletion features/support/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ws from 'ws'
import wrtc from '@roamhq/wrtc'

import { Player } from './types'
import { PeerConfiguration } from '../../lib/types'

import { Network } from '../../lib'

Expand Down Expand Up @@ -35,6 +36,7 @@ export class World extends CucumberWorld {
public testproxyURL?: string
public useTestProxy: boolean = false
public databaseURL?: string
public latencyVector: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

public players: Map<string, Player> = new Map<string, Player>()
public lastError: Map<string, Error> = new Map<string, Error>()
Expand All @@ -49,7 +51,13 @@ export class World extends CucumberWorld {

public async createPlayer (playerName: string, gameID: string): Promise<Player> {
return await new Promise((resolve) => {
const config = this.useTestProxy ? { testproxyURL: this.testproxyURL } : undefined
const config: PeerConfiguration = {}
if (this.useTestProxy) {
config.testproxyURL = this.testproxyURL
}
config.testLatency = {
vector: this.latencyVector
}
const network = new Network(gameID, config, this.signalingURL)
const player = new Player(playerName, network)
this.players.set(playerName, player)
Expand Down Expand Up @@ -100,6 +108,7 @@ AfterAll(function () {

Before(function (this: World) {
this.scenarioRunning = true
this.latencyVector = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
})
After(function (this: World, { result }) {
this.scenarioRunning = false
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/jackc/pgx/v5 v5.7.6
github.com/koenbollen/logging v0.0.0-20230520102501-e01d64214504
github.com/ory/dockertest/v3 v3.12.0
github.com/pgvector/pgvector-go v0.3.0
github.com/poki/mongodb-filter-to-postgres v1.0.7
github.com/rs/cors v1.11.1
github.com/rs/xid v1.6.0
Expand Down Expand Up @@ -44,6 +45,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
Expand Down
Loading
Loading