diff --git a/modules/lobbies/scripts/find_or_create.ts b/modules/lobbies/scripts/find_or_create.ts index c60673b7..20dd53f9 100644 --- a/modules/lobbies/scripts/find_or_create.ts +++ b/modules/lobbies/scripts/find_or_create.ts @@ -19,7 +19,7 @@ export interface Request { noWait?: boolean; createConfig: { - region: string; + region: string; tags?: Record; maxPlayers: number; maxPlayersDirect: number; @@ -56,19 +56,19 @@ export async function run( { query: { version: req.version, - regions: req.regions, + regions: req.regions, tags: req.tags, }, lobby: { lobbyId, version: req.version, - region: req.createConfig.region, + region: req.createConfig.region, tags: req.createConfig.tags, maxPlayers: req.createConfig.maxPlayers, maxPlayersDirect: req.createConfig.maxPlayersDirect, }, players: req.players, - noWait: req.noWait ?? false, + noWait: req.noWait ?? false, } ); diff --git a/modules/lobbies/scripts/list_regions.ts b/modules/lobbies/scripts/list_regions.ts index 447b21c6..b8551f68 100644 --- a/modules/lobbies/scripts/list_regions.ts +++ b/modules/lobbies/scripts/list_regions.ts @@ -1,6 +1,7 @@ import { RuntimeError, ScriptContext } from "../module.gen.ts"; import { getLobbyConfig } from "../utils/lobby_config.ts"; -import { Region, regionsForBackend } from "../utils/region.ts"; +import { getSortedRegionsByProximity, Region, regionsForBackend } from "../utils/region.ts"; +import { getRequestGeoCoords } from "../utils/rivet/geo_coord.ts"; export interface Request { tags?: Record, @@ -16,7 +17,10 @@ export async function run( ): Promise { const lobbyConfig = getLobbyConfig(ctx.config, req.tags ?? {}); - const regions = regionsForBackend(lobbyConfig.backend) + const regions = getSortedRegionsByProximity( + regionsForBackend(lobbyConfig.backend), + getRequestGeoCoords(ctx), + ); return { regions }; } diff --git a/modules/lobbies/utils/region.ts b/modules/lobbies/utils/region.ts index eda10bef..45826dd4 100644 --- a/modules/lobbies/utils/region.ts +++ b/modules/lobbies/utils/region.ts @@ -8,7 +8,13 @@ import { REGIONS as LOCAL_DEVELOPMENT_REGIONS } from "./lobby/backend/local_deve import { UnreachableError } from "../module.gen.ts"; import { LobbyBackend } from "../config.ts"; -export interface Region { +export interface RegionGeoCoords { + latitude: number; + longitude: number; +} + + +export interface Region extends RegionGeoCoords { id: string; name: string; latitude: number; @@ -22,3 +28,40 @@ export function regionsForBackend(backend: LobbyBackend): Region[] { else throw new UnreachableError(backend); } +export const EMPTY_GEO_COORDS: RegionGeoCoords = Object.freeze({ + latitude: 0, + longitude: 0 +}); + +const getDistSq = (a: RegionGeoCoords, b: RegionGeoCoords) => { + const dlat = a.latitude - b.latitude; + const dlong = a.longitude - b.longitude; + return dlat * dlat + dlong * dlong; +} + +export function getClosestRegion( + region: Region[], + coords: RegionGeoCoords +) { + if (coords === EMPTY_GEO_COORDS) return region[0]; + let closestRegion: Region | null = null; + let closestDistance = Infinity; + for (const r of region) { + const distSq = getDistSq(r, coords); + if (distSq < closestDistance) { + closestRegion = r; + closestDistance = distSq; + } + } + return closestRegion; +} + +export function getSortedRegionsByProximity( + regions: Region[], + coords: RegionGeoCoords +) { + if (coords === EMPTY_GEO_COORDS) return [...regions]; + return [...regions].sort((a, b) => { + return getDistSq(a, coords) - getDistSq(b, coords); + }); +} \ No newline at end of file diff --git a/modules/lobbies/utils/rivet/geo_coord.ts b/modules/lobbies/utils/rivet/geo_coord.ts new file mode 100644 index 00000000..b4dc5e9b --- /dev/null +++ b/modules/lobbies/utils/rivet/geo_coord.ts @@ -0,0 +1,41 @@ +import { ScriptContext } from "../../module.gen.ts"; +import { RegionGeoCoords, EMPTY_GEO_COORDS } from "../region.ts"; + +type TraceEntry = ScriptContext["trace"]["entries"][0]; +type TraceEntryTypeHttpRequest = Extract["httpRequest"]; +export const getRequestGeoCoords = ( + ctx: ScriptContext +): RegionGeoCoords => { + // If they aren't real servers, we're probably not in managed + // So we just return + if (!("server" in ctx.config.lobbies.backend)) return EMPTY_GEO_COORDS; + + // Check if env flag is set for managed + // TODO(Nathan): Set this flag to 1 for managed + if (ctx.environment.get("MANAGED") !== "1") return EMPTY_GEO_COORDS; + + // TODO: Only check the first/last entry + let httpReq: TraceEntryTypeHttpRequest | null = null; + for (const entry of ctx.trace.entries) { + if ("httpRequest" in entry.type) { + httpReq = entry.type.httpRequest; + break; + } + } + + if (!httpReq) return EMPTY_GEO_COORDS; + + // TODO(Nathan): + // Add header to worker + if (!httpReq.headers["x-backend-client-coords"]) return EMPTY_GEO_COORDS; + + // TODO: Optimize this + // Even add some checks for nan and infinities to be extra safe + const coords = httpReq.headers["x-backend-client-coords"].split(",").map(e => parseFloat(e.trim())); + if (coords.length !== 2) return EMPTY_GEO_COORDS; + + return { + latitude: coords[0]!, + longitude: coords[1]! + } +} \ No newline at end of file