XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: support multiple hub servers (#3305)

This will need a Bootstrapper update so the hub response can initate UDP proxying. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3305 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

2a703911
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

9 个文件 +153 -8
Modified config-vanilla.json +7 -1
@@ -13,7 +13,13 @@
13 13 "ircExecutable": null,
14 14 "ircAddress": null,
15 15 "hubExecutable": null,
16 "hubAddress": null,
16 "hubServers": [
17 {
18 "address": "%THIS_MACHINE%:6952",
19 "regions": ["ASIA", "OCEANIA", "EUROPE", "RUSSIA", "NORTH_AMERICA", "SOUTH_AMERICA"],
20 "dtlsUnsupported": true
21 }
22 ],
17 23 "noHubDiscrimination": false,
18 24 "nrsAddress": null,
19 25 "dtls": null,
Modified src/controllers/arbiter/hubController.ts +25 -7
@@ -1,13 +1,31 @@
1 1 import type { RequestHandler } from "express";
2 import { config, getReflexiveAddress } from "../../services/configService.ts";
2 import { config, getReflexiveAddress, type IHubServer } from "../../services/configService.ts";
3 import { hubInstances, pickHubServer, type TRegionId } from "../../services/arbiterService.ts";
3 4
4 5 export const hubController: RequestHandler = (req, res) => {
5 if (config.noHubDiscrimination) {
6 const arr = (req.query.level as string).split("_");
7 const instanceId = arr.pop();
8 const level = arr[0];
9 res.json(`hub ${config.hubAddress ?? getReflexiveAddress(req).myAddress}:6952 ${instanceId} ${level}`);
6 const arr = (req.query.level as string).split("_");
7 const instanceId = arr.pop();
8 const level = config.noHubDiscrimination ? arr[0] : arr.join("_");
9
10 let hubServer: IHubServer | undefined;
11 if (`${level}_${instanceId}` in hubInstances) {
12 hubInstances[`${level}_${instanceId}`].Players += 1;
13 const addr = hubInstances[`${level}_${instanceId}`].Hub;
14 hubServer = config.hubServers?.find(hub => hub.address == addr);
15 }
16 if (!hubServer) {
17 hubServer = pickHubServer(req.query.regionId as TRegionId);
18 hubInstances[`${level}_${instanceId}`] = {
19 Players: 1,
20 Hub: hubServer.address
21 };
22 }
23 const needToUseUdpProxy = (config.dtls ?? 0) & 1 && hubServer.dtlsUnsupported;
24 const addr = hubServer.address.split("%THIS_MACHINE%").join(getReflexiveAddress(req).myAddress);
25
26 if (`${level}_${instanceId}` == req.query.level) {
27 res.json(`${needToUseUdpProxy ? "udp_proxy_upstream" : "hub"} ${addr}`);
10 28 } else {
11 res.json(`hub ${config.hubAddress ?? getReflexiveAddress(req).myAddress}:6952`);
29 res.json(`${needToUseUdpProxy ? "udp_proxy_upstream" : "hub"} ${addr} ${instanceId} ${level}`);
12 30 }
13 31 };
Added src/controllers/arbiter/statsController.ts +43 -0
@@ -0,0 +1,43 @@
1 import type { RequestHandler } from "express";
2 import {
3 getAllHubServerStats,
4 hubInstances,
5 type IHubServerStats,
6 type TRegionId
7 } from "../../services/arbiterService.ts";
8 import { config } from "../../services/configService.ts";
9
10 export const statsController: RequestHandler = (_req, res) => {
11 const response: IArbiterStatsResponse = {
12 Hubs: {},
13 Levels: hubInstances
14 };
15 const allStats = getAllHubServerStats();
16 for (const server of config.hubServers!) {
17 const stats = allStats[server.address] as IHubServerStats | undefined;
18 response.Hubs[server.address] = {
19 Players: stats?.Players ?? 0,
20 Instances: stats?.Instances ?? 0,
21 RegionIds: server.regions!
22 };
23 }
24 res.json(response);
25 };
26
27 interface IArbiterStatsResponse {
28 Hubs: Record<
29 string,
30 {
31 Players: number;
32 Instances: number;
33 RegionIds: TRegionId[];
34 }
35 >;
36 Levels: Record<
37 string,
38 {
39 Players: number;
40 Hub: string;
41 }
42 >;
43 }
Added src/controllers/custom/hubDroppedController.ts +10 -0
@@ -0,0 +1,10 @@
1 import type { RequestHandler } from "express";
2 import { hubInstances, type IHubInstance } from "../../services/arbiterService.ts";
3
4 export const hubDroppedController: RequestHandler = (req, res) => {
5 const level = hubInstances[req.query.level as string] as IHubInstance | undefined;
6 if (level) {
7 level.Players = Math.max(0, level.Players - 1);
8 }
9 res.end();
10 };
Modified src/routes/arbiter.ts +2 -0
@@ -2,6 +2,7 @@ import express from "express";
2 2 import { dojoController, setDojoURLController } from "../controllers/arbiter/dojoController.ts";
3 3 import { hubController } from "../controllers/arbiter/hubController.ts";
4 4 import { hubInstancesController } from "../controllers/arbiter/hubInstancesController.ts";
5 import { statsController } from "../controllers/arbiter/statsController.ts";
5 6
6 7 const arbiterRouter = express.Router();
7 8
@@ -9,5 +10,6 @@ arbiterRouter.get("/dojo", dojoController);
9 10 arbiterRouter.get("/hub", hubController);
10 11 arbiterRouter.get("/hubInstances", hubInstancesController);
11 12 arbiterRouter.get("/setDojoURL", setDojoURLController);
13 arbiterRouter.get("/stats", statsController);
12 14
13 15 export { arbiterRouter };
Modified src/routes/custom.ts +2 -0
@@ -12,6 +12,7 @@ import { getGuildController } from "../controllers/custom/getGuildController.ts"
12 12 import { getAllianceController } from "../controllers/custom/getAllianceController.ts";
13 13 import { renameAccountController } from "../controllers/custom/renameAccountController.ts";
14 14 import { ircDroppedController } from "../controllers/custom/ircDroppedController.ts";
15 import { hubDroppedController } from "../controllers/custom/hubDroppedController.ts";
15 16 import { unlockAllIntrinsicsController } from "../controllers/custom/unlockAllIntrinsicsController.ts";
16 17 import { addMissingMaxRankModsController } from "../controllers/custom/addMissingMaxRankModsController.ts";
17 18 import { webuiFileChangeDetectedController } from "../controllers/custom/webuiFileChangeDetectedController.ts";
@@ -67,6 +68,7 @@ customRouter.get("/getGuild", getGuildController);
67 68 customRouter.get("/getAlliance", getAllianceController);
68 69 customRouter.get("/renameAccount", renameAccountController);
69 70 customRouter.get("/ircDropped", ircDroppedController);
71 customRouter.get("/hubDropped", hubDroppedController);
70 72 customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
71 73 customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
72 74 customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
Added src/services/arbiterService.ts +40 -0
@@ -0,0 +1,40 @@
1 import { config, type IHubServer } from "./configService.ts";
2
3 export type TRegionId = "ASIA" | "OCEANIA" | "EUROPE" | "RUSSIA" | "NORTH_AMERICA" | "SOUTH_AMERICA";
4
5 export interface IHubInstance {
6 Players: number;
7 Hub: string;
8 }
9
10 export const hubInstances: Record<string, IHubInstance> = {};
11
12 export interface IHubServerStats {
13 Players: number;
14 Instances: number;
15 }
16
17 export const getAllHubServerStats = (): Record<string, IHubServerStats> => {
18 const stats: Record<string, IHubServerStats> = {};
19 for (const level of Object.values(hubInstances)) {
20 stats[level.Hub] ??= { Players: 0, Instances: 0 };
21 stats[level.Hub].Players += level.Players;
22 stats[level.Hub].Instances += 1;
23 }
24 return stats;
25 };
26
27 export const pickHubServer = (regionId: TRegionId): IHubServer => {
28 let bestServer = { address: "%THIS_MACHINE%:6952" };
29 let bestServerScore = Number.MAX_SAFE_INTEGER;
30 const allStats = getAllHubServerStats();
31 for (const server of config.hubServers!) {
32 const stats = allStats[server.address] as IHubServerStats | undefined;
33 const score = (stats?.Players ?? 0) + (stats?.Instances ?? 0) + (server.regions?.includes(regionId) ? 0 : 1000);
34 if (score < bestServerScore) {
35 bestServer = server;
36 bestServerScore = score;
37 }
38 }
39 return bestServer;
40 };
Modified src/services/configService.ts +8 -0
@@ -5,6 +5,13 @@ import { args } from "../helpers/commandLineArguments.ts";
5 5 import { Inbox } from "../models/inboxModel.ts";
6 6 import type { Request } from "express";
7 7 import { Account } from "../models/loginModel.ts";
8 import type { TRegionId } from "./arbiterService.ts";
9
10 export interface IHubServer {
11 address: string;
12 regions?: TRegionId[];
13 dtlsUnsupported?: boolean;
14 }
8 15
9 16 export interface IConfig {
10 17 mongodbUrl: string;
@@ -22,6 +29,7 @@ export interface IConfig {
22 29 ircAddress?: string;
23 30 hubExecutable?: string;
24 31 hubAddress?: string;
32 hubServers?: IHubServer[];
25 33 noHubDiscrimination?: boolean;
26 34 nrsAddress?: string;
27 35 dtls?: number;
Modified src/services/configWatcherService.ts +16 -0
@@ -73,6 +73,22 @@ export const validateConfig = (): void => {
73 73 modified = true;
74 74 }
75 75 }
76 if (!config.hubServers) {
77 config.hubServers = [
78 {
79 address:
80 (config.hubAddress == "127.0.0.1:6951" ? config.tunables?.udpProxyUpstream : config.hubAddress) ??
81 "%THIS_MACHINE%:6952",
82 regions: ["ASIA", "OCEANIA", "EUROPE", "RUSSIA", "NORTH_AMERICA", "SOUTH_AMERICA"],
83 dtlsUnsupported: true
84 }
85 ];
86 delete config.hubAddress;
87 if (config.tunables) {
88 delete config.tunables.udpProxyUpstream;
89 }
90 modified = true;
91 }
76 92 if (config.administratorNames) {
77 93 if (!Array.isArray(config.administratorNames)) {
78 94 config.administratorNames = [config.administratorNames];