返回提交历史
Modified
config-vanilla.json
+7
-1
Modified
src/controllers/arbiter/hubController.ts
+25
-7
Added
src/controllers/arbiter/statsController.ts
+43
-0
Added
src/controllers/custom/hubDroppedController.ts
+10
-0
Modified
src/routes/arbiter.ts
+2
-0
Modified
src/routes/custom.ts
+2
-0
Added
src/services/arbiterService.ts
+40
-0
Modified
src/services/configService.ts
+8
-0
Modified
src/services/configWatcherService.ts
+16
-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
代码差异
9 个文件
+153
-8
@@ -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,
@@ -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
};
@@ -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
}
@@ -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
};
@@ -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 };
@@ -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);
@@ -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
};
@@ -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;
@@ -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];