Push multiple updates in each update event

Refactor HostUpdatesEvent, InstanceDetailsUpdatesEvent, ModUpdatesEvent,
ModPackUpdatesEvent and UserUpdatesEvent to have multiple updates of the
entities they support in each event. This is intended to make the events
capable of syncing the entire list that they represent.
This commit is contained in:
Hornwitser committed 2023-12-08 22:01:46 +01:00
1 parent f12a9926eb
commit 20330bc2c3
18 files changed
+215 -190

No files matched your search

+11 -11
View File
@@ -229,29 +229,29 @@ export default class BaseControllerPlugin {
async onPrepareHostDisconnect(connection: HostConnection) { }
/**
* Called when a mod pack is updated
* Called when one or more mod packs are updated
*
* Invoked when a mod pack has been added, updated or deleted in the
* cluster.
* Invoked when one or more mod pack have been added, updated or deleted
* in the cluster.
*
* If the mod pack has been deleted its `.isDeleted` property will be
* If a mod pack has been deleted its `.isDeleted` property will be
* true.
*
* @param modPack - Mod pack that updated.
* @param modPacks - Mod packs that updated.
*/
async onModPackUpdated(modPack: ModPack) { }
async onModPacksUpdated(modPacks: ModPack[]) { }
/**
* Called when a mod stored on the controller is updated
* Called when one or more mod stored on the controller are updated
*
* Invoked when a mod has been added, updated or deleted from the pool
* of shared mods stored on the cluster.
* Invoked when one or more mods have been added, updated or deleted
* from the pool of shared mods stored on the cluster.
*
* If a mod has been deleted its `.isDeleted` property will be true.
*
* @param mod - Mod that updated.
* @param mods - Mods that updated.
*/
async onModUpdated(mod: ModInfo) { }
async onModsUpdated(mods: ModInfo[]) { }
/**
* Called when a player joins or leaves an instance
+11 -11
View File
@@ -195,7 +195,7 @@ export default class ControlConnection extends BaseConnection {
hostConnection.connector.terminate();
}
this._controller.hostUpdated(host as HostInfo);
this._controller.hostsUpdated([host]);
}
async handleHostListRequest(): Promise<lib.HostDetails[]> {
@@ -434,7 +434,7 @@ export default class ControlConnection extends BaseConnection {
throw new lib.RequestError(`Mod pack with ID ${modPack.id} already exist`);
}
this._controller.modPacks.set(modPack.id, modPack);
this._controller.modPackUpdated(modPack);
this._controller.modPacksUpdated([modPack]);
}
async handleModPackUpdateRequest(request: lib.ModPackUpdateRequest) {
@@ -443,7 +443,7 @@ export default class ControlConnection extends BaseConnection {
throw new lib.RequestError(`Mod pack with ID ${modPack.id} does not exist`);
}
this._controller.modPacks.set(modPack.id, modPack);
this._controller.modPackUpdated(modPack);
this._controller.modPacksUpdated([modPack]);
}
async handleModPackDeleteRequest(request: lib.ModPackDeleteRequest) {
@@ -454,7 +454,7 @@ export default class ControlConnection extends BaseConnection {
}
modPack.isDeleted = true;
this._controller.modPacks.delete(id);
this._controller.modPackUpdated(modPack);
this._controller.modPacksUpdated([modPack]);
}
async handleModGetRequest(request: lib.ModGetRequest) {
@@ -680,7 +680,7 @@ export default class ControlConnection extends BaseConnection {
async handleUserCreateRequest(request: lib.UserCreateRequest) {
let user = this._controller.userManager.createUser(request.name);
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
}
async handleUserRevokeTokenRequest(request: lib.UserRevokeTokenRequest) {
@@ -698,7 +698,7 @@ export default class ControlConnection extends BaseConnection {
controlConnection.connector.terminate();
}
}
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
}
async handleUserUpdateRolesRequest(request: lib.UserUpdateRolesRequest) {
@@ -715,7 +715,7 @@ export default class ControlConnection extends BaseConnection {
user.roleIds = new Set(request.roles);
this._controller.userPermissionsUpdated(user);
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
}
async handleUserSetAdminRequest(request: lib.UserSetAdminRequest) {
@@ -731,7 +731,7 @@ export default class ControlConnection extends BaseConnection {
}
user.isAdmin = admin;
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
this._controller.sendTo("allInstances", new lib.InstanceAdminlistUpdateEvent(name, admin));
}
@@ -749,7 +749,7 @@ export default class ControlConnection extends BaseConnection {
user.isBanned = banned;
user.banReason = reason;
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
this._controller.sendTo("allInstances", new lib.InstanceBanlistUpdateEvent(name, banned, reason));
}
@@ -766,7 +766,7 @@ export default class ControlConnection extends BaseConnection {
}
user.isWhitelisted = whitelisted;
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
this._controller.sendTo("allInstances", new lib.InstanceWhitelistUpdateEvent(name, whitelisted));
}
@@ -779,7 +779,7 @@ export default class ControlConnection extends BaseConnection {
user.isDeleted = true;
this._controller.userManager.users.delete(name);
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
if (user.isAdmin) {
this._controller.sendTo("allInstances", new lib.InstanceAdminlistUpdateEvent(name, false));
+48 -38
View File
@@ -143,16 +143,16 @@ export default class Controller {
this.subscriptions = new lib.SubscriptionController(this);
this.modStore.on("change", mod => {
this.modUpdated(mod);
this.modsUpdated([mod]);
});
// Handle subscriptions for all internal properties
this.subscriptions.handle(lib.HostUpdateEvent);
this.subscriptions.handle(lib.InstanceDetailsUpdateEvent);
this.subscriptions.handle(lib.HostUpdatesEvent);
this.subscriptions.handle(lib.InstanceDetailsUpdatesEvent);
this.subscriptions.handle(lib.InstanceSaveListUpdateEvent);
this.subscriptions.handle(lib.ModPackUpdateEvent);
this.subscriptions.handle(lib.ModUpdateEvent);
this.subscriptions.handle(lib.UserUpdateEvent);
this.subscriptions.handle(lib.ModPackUpdatesEvent);
this.subscriptions.handle(lib.ModUpdatesEvent);
this.subscriptions.handle(lib.UserUpdatesEvent);
}
async start(args: ControllerArgs) {
@@ -607,8 +607,8 @@ export default class Controller {
);
}
hostUpdated(host: HostInfo) {
let update = new lib.HostDetails(
hostsUpdated(hosts: HostInfo[]) {
let updates = hosts.map(host => new lib.HostDetails(
host.agent,
host.version,
host.name,
@@ -616,9 +616,9 @@ export default class Controller {
this.wsServer.hostConnections.has(host.id),
host.public_address,
host.token_valid_after,
);
));
this.subscriptions.broadcast(new lib.HostUpdateEvent(update));
this.subscriptions.broadcast(new lib.HostUpdatesEvent(updates));
}
/**
@@ -690,7 +690,7 @@ export default class Controller {
this.instancesDirty = true;
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance);
this.addInstanceHooks(instance);
this.instanceDetailsUpdated(instance);
this.instanceDetailsUpdated([instance]);
return instance;
}
@@ -742,7 +742,7 @@ export default class Controller {
);
} else {
instance.status = "unassigned";
this.instanceDetailsUpdated(instance);
this.instanceDetailsUpdated([instance]);
}
}
@@ -765,7 +765,7 @@ export default class Controller {
let prev = instance.status;
instance.status = "deleted";
this.instanceDetailsUpdated(instance);
this.instanceDetailsUpdated([instance]);
await lib.invokeHook(this.plugins, "onInstanceStatusChanged", instance, prev);
}
@@ -793,7 +793,7 @@ export default class Controller {
addInstanceHooks(instance: InstanceInfo) {
instance.config.on("fieldChanged", (group: lib.ConfigGroup, field: string, prev: any) => {
if (group.name === "instance" && field === "name") {
this.instanceDetailsUpdated(instance);
this.instanceDetailsUpdated([instance]);
}
this.instancesDirty = true;
@@ -801,50 +801,60 @@ export default class Controller {
});
}
instanceDetailsUpdated(instance: InstanceInfo) {
let assigned_host: number|null|undefined = instance.config.get("instance.assigned_host");
if (assigned_host === null) {
assigned_host = undefined;
}
instanceDetailsUpdated(instances: InstanceInfo[]) {
const now = Date.now();
const updates: lib.InstanceDetails[] = [];
for (const instance of instances) {
let assigned_host: number|null|undefined = instance.config.get("instance.assigned_host");
if (assigned_host === null) {
assigned_host = undefined;
}
let game_port: number|null|undefined = instance.game_port;
if (game_port === null) {
game_port = undefined;
}
let game_port: number|null|undefined = instance.game_port;
if (game_port === null) {
game_port = undefined;
}
this.subscriptions.broadcast(new lib.InstanceDetailsUpdateEvent(
new lib.InstanceDetails(
updates.push(new lib.InstanceDetails(
instance.config.get("instance.name"),
instance.id,
assigned_host,
game_port,
instance.status,
Date.now(),
)
));
now,
));
}
this.subscriptions.broadcast(new lib.InstanceDetailsUpdatesEvent(updates));
}
saveListUpdate(instanceId: number, saves: lib.SaveDetails[]) {
this.subscriptions.broadcast(new lib.InstanceSaveListUpdateEvent(instanceId, saves));
}
modPackUpdated(modPack: lib.ModPack) {
modPack.updatedAt = Date.now();
modPacksUpdated(modPacks: lib.ModPack[]) {
const now = Date.now();
for (const modPack of modPacks) {
modPack.updatedAt = now;
}
this.modPacksDirty = true;
this.subscriptions.broadcast(new lib.ModPackUpdateEvent(modPack));
lib.invokeHook(this.plugins, "onModPackUpdated", modPack);
this.subscriptions.broadcast(new lib.ModPackUpdatesEvent(modPacks));
lib.invokeHook(this.plugins, "onModPacksUpdated", modPacks);
}
modUpdated(mod: lib.ModInfo) {
modsUpdated(mods: lib.ModInfo[]) {
// ModStore sets updatedAt for mods
this.subscriptions.broadcast(new lib.ModUpdateEvent(mod));
lib.invokeHook(this.plugins, "onModUpdated", mod);
this.subscriptions.broadcast(new lib.ModUpdatesEvent(mods));
lib.invokeHook(this.plugins, "onModsUpdated", mods);
}
userUpdated(user: ControllerUser) {
user.updatedAt = Date.now();
usersUpdated(users: ControllerUser[]) {
const now = Date.now();
for (const user of users) {
user.updatedAt = now;
}
this.userManager.dirty = true;
this.subscriptions.broadcast(new lib.UserUpdateEvent(user));
this.subscriptions.broadcast(new lib.UserUpdatesEvent(users));
}
/**
+12 -5
View File
@@ -69,16 +69,18 @@ export default class HostConnection extends BaseConnection {
this.connector.on("close", () => {
// Update status to unknown for instances on this host.
const instances: InstanceInfo[] = [];
for (let instance of this._controller.instances.values()) {
if (instance.config.get("instance.assigned_host") !== this._id) {
continue;
}
instances.push(instance);
let prev = instance.status;
instance.status = "unknown";
this._controller.instanceDetailsUpdated(instance);
lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
}
this._controller.instanceDetailsUpdated(instances);
});
this.handle(lib.InstanceStatusChangedEvent, this.handleInstanceStatusChangedEvent.bind(this));
@@ -180,7 +182,7 @@ export default class HostConnection extends BaseConnection {
instance.status = request.status as lib.InstanceStatus;
instance.game_port = request.gamePort || null;
logger.verbose(`Instance ${instance.config.get("instance.name")} State: ${instance.status}`);
this._controller.instanceDetailsUpdated(instance);
this._controller.instanceDetailsUpdated([instance]);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", instance, prev);
}
@@ -195,6 +197,7 @@ export default class HostConnection extends BaseConnection {
}
// Assign instances the host has but controller does not
const instanceUpdates: InstanceInfo[] = [];
for (let instanceData of request.instances) {
let instanceConfig = new lib.InstanceConfig("controller");
await instanceConfig.load(instanceData.config as lib.SerializedConfig, "host");
@@ -214,7 +217,7 @@ export default class HostConnection extends BaseConnection {
let prev = controllerInstance.status;
controllerInstance.status = instanceData.status as InstanceStatus;
logger.verbose(`Instance ${instanceConfig.get("instance.name")} State: ${instanceData.status}`);
this._controller.instanceDetailsUpdated(controllerInstance);
instanceUpdates.push(controllerInstance);
await lib.invokeHook(
this._controller.plugins, "onInstanceStatusChanged", controllerInstance, prev
);
@@ -234,10 +237,14 @@ export default class HostConnection extends BaseConnection {
instanceConfig.get("instance.id"), instanceConfig.serialize("host")
)
);
this._controller.instanceDetailsUpdated(newInstance);
instanceUpdates.push(newInstance);
await lib.invokeHook(this._controller.plugins, "onInstanceStatusChanged", newInstance);
}
if (instanceUpdates.length) {
this._controller.instanceDetailsUpdated(instanceUpdates);
}
// Push lists to make sure they are in sync.
let adminlist: Set<string> = new Set();
let banlist: Map<string, string> = new Map();
@@ -286,7 +293,7 @@ export default class HostConnection extends BaseConnection {
user.instanceStats.set(instanceId, event.stats);
user.recalculatePlayerStats();
this._controller.userUpdated(user);
this._controller.usersUpdated([user]);
let instance = this._controller.instances.get(instanceId)!;
await lib.invokeHook(this._controller.plugins, "onPlayerEvent", instance, {
+2 -2
View File
@@ -274,13 +274,13 @@ ${err.stack}`
connector.on("close", () => {
if (this.hostConnections.get(data.id) === connection) {
this.hostConnections.delete(data.id);
this.controller.hostUpdated(this.controller.hosts.get(data.id) as HostInfo);
this.controller.hostsUpdated([this.controller.hosts.get(data.id)!]);
} else {
logger.warn("Unlisted HostConnection closed");
}
});
this.hostConnections.set(data.id, connection);
this.controller.hostUpdated(this.controller.hosts.get(data.id) as HostInfo);
this.controller.hostsUpdated([this.controller.hosts.get(data.id)!]);
let src = new lib.Address(lib.Address.host, data.id);
connector.ready(socket, src, sessionToken, undefined);
}
+1 -1
View File
@@ -256,7 +256,7 @@ async function uploadExport(req: Request, res: Response) {
modPack.exportManifest = new lib.ExportManifest(assets);
modPack.fillDefaultSettings(settingPrototypes, logger);
res.app.locals.controller.modPackUpdated(modPack);
res.app.locals.controller.modPacksUpdated([modPack]);
res.sendStatus(200);
}