Browse Source

refactor: Separates model classes

Owen Diffey 1 month ago
parent
commit
fd8caed83a

+ 15 - 0
common/DeferredPromise.ts

@@ -0,0 +1,15 @@
+export class DeferredPromise<T = any> {
+	promise: Promise<T>;
+
+	reject;
+
+	resolve;
+
+	// eslint-disable-next-line require-jsdoc
+	constructor() {
+		this.promise = new Promise<T>((resolve, reject) => {
+			this.reject = reject;
+			this.resolve = resolve;
+		});
+	}
+}

+ 1 - 109
frontend/src/Model.ts

@@ -3,115 +3,7 @@
 import { forEachIn } from "@common/utils/forEachIn";
 import { useModelStore } from "./stores/model";
 import { useWebsocketStore } from "./stores/websocket";
-
-class DeferredPromise<T = any> {
-	promise: Promise<T>;
-
-	reject;
-
-	resolve;
-
-	// eslint-disable-next-line require-jsdoc
-	constructor() {
-		this.promise = new Promise<T>((resolve, reject) => {
-			this.reject = reject;
-			this.resolve = resolve;
-		});
-	}
-}
-
-interface ModelPermissionFetcherRequest {
-	promise: DeferredPromise;
-	payload: {
-		modelName: string;
-		modelId: string;
-	};
-}
-
-/**
- * Class used for fetching model permissions in bulk, every 25ms max
- * So if there's 200 models loaded, it would do only 1 request to fetch model permissions, not 200 separate ones
- */
-class ModelPermissionFetcher {
-	private static requestsQueued: ModelPermissionFetcherRequest[] = [];
-
-	private static timeoutActive = false;
-
-	private static fetch() {
-		// If there is no other timeout running, indicate we will run one. Otherwise, return, as a timeout is already running
-		if (!this.timeoutActive) this.timeoutActive = true;
-		else return;
-
-		setTimeout(() => {
-			// Reset timeout active, so another one can run
-			this.timeoutActive = false;
-			// Make a copy of all requests currently queued, and then take those requests out of the queue so we can request them
-			const requests = this.requestsQueued;
-			this.requestsQueued = [];
-
-			// Splits the requests per model
-			const requestsPerModel = {};
-			requests.forEach(request => {
-				const { modelName } = request.payload;
-				if (!Array.isArray(requestsPerModel[modelName]))
-					requestsPerModel[modelName] = [];
-				requestsPerModel[modelName].push(request);
-			});
-
-			const modelNames = Object.keys(requestsPerModel);
-
-			const { runJob } = useWebsocketStore();
-
-			// Runs the requests per model
-			forEachIn(modelNames, async modelName => {
-				// Gets a unique list of all model ids for the current model that we want to request permissions for
-				const modelIds = Array.from(
-					new Set(
-						requestsPerModel[modelName].map(
-							request => request.payload.modelId
-						)
-					)
-				);
-
-				const result = await runJob("data.users.getModelPermissions", {
-					modelName,
-					modelIds
-				});
-
-				const requests = requestsPerModel[modelName];
-				// For all requests, resolve the deferred promise with the returned permissions for the model that request requested
-				requests.forEach(request => {
-					const { payload, promise } = request;
-					const { modelId } = payload;
-					promise.resolve(result[modelId]);
-				});
-			});
-		}, 25);
-	}
-
-	public static fetchModelPermissions(modelName, modelId) {
-		return new Promise(resolve => {
-			const promise = new DeferredPromise();
-
-			// Listens for the deferred promise response, before we actually push and fetch
-			promise.promise.then(result => {
-				resolve(result);
-			});
-
-			// Pushes the request to the queue
-			this.requestsQueued.push({
-				payload: {
-					modelName,
-					modelId
-				},
-				promise
-			});
-
-			// Calls the fetch function, which will start a timeout if one isn't already running, which will actually request the permissions
-			this.fetch();
-		});
-	}
-}
+import { ModelPermissionFetcher } from "./ModelPermissionFetcher";
 
 export default class Model {
 	private _permissions?: object;

+ 153 - 0
frontend/src/ModelFetcher.ts

@@ -0,0 +1,153 @@
+import { DeferredPromise } from "@common/DeferredPromise";
+import { forEachIn } from "@common/utils/forEachIn";
+import { useWebsocketStore } from "@/stores/websocket";
+
+export interface ModelFetcherRequest {
+	promise: DeferredPromise;
+	payload: {
+		modelName: string;
+		modelIds: string[];
+	};
+}
+
+// TODO combine the ModelFetcher and the ModelPermissionFetcher
+/**
+ * Class used for fetching models in bulk, every 25ms max, per model type
+ * So if we tried to fetch 100 different minifiedUser models separately, it would do only 1 request to fetch the models, not 100 separate ones
+ */
+export class ModelFetcher {
+	private static requestsQueued: ModelFetcherRequest[] = [];
+
+	private static timeoutActive = false;
+
+	private static responseCache = {};
+
+	private static fetch() {
+		// If there is no other timeout running, indicate we will run one. Otherwise, return, as a timeout is already running
+		if (!this.timeoutActive) this.timeoutActive = true;
+		else return;
+
+		setTimeout(() => {
+			// Reset timeout active, so another one can run
+			this.timeoutActive = false;
+			// Make a copy of all requests currently queued, and then take those requests out of the queue so we can request them
+			const requests = this.requestsQueued;
+			this.requestsQueued = [];
+
+			// Splits the requests per model
+			const requestsPerModel = {};
+			requests.forEach(request => {
+				const { modelName } = request.payload;
+				if (!Array.isArray(requestsPerModel[modelName]))
+					requestsPerModel[modelName] = [];
+				requestsPerModel[modelName].push(request);
+			});
+
+			const modelNames = Object.keys(requestsPerModel);
+
+			const { runJob } = useWebsocketStore();
+
+			// TODO somehow make the following forEachIn run at the same time for all modelNames
+			// Runs the requests per model
+			forEachIn(modelNames, async modelName => {
+				// All already cached model ids
+				let cachedModelIds = Object.keys(this.responseCache[modelName]);
+
+				// Gets a unique list of all model ids for the current model that we want to request permissions for, that are not already cached
+				const modelIds = Array.from(
+					new Set(
+						requestsPerModel[modelName].flatMap(
+							request => request.payload.modelIds
+						)
+					)
+				).filter(
+					(modelId: string) => !cachedModelIds.includes(modelId)
+				);
+
+				// Only do a request if more than one model isn't already cached
+				if (modelIds.length > 0) {
+					console.log(`Requesting model ids`, modelName, modelIds);
+					const result = (await runJob(
+						`data.${modelName}.findManyById`,
+						{
+							_ids: modelIds
+						}
+					)) as any[];
+
+					// Cache the responses for the requested model ids
+					modelIds.forEach(modelId => {
+						const model = result.find(
+							model => model._id === modelId
+						);
+						console.log(`Caching ${modelName}.${modelId}`, model);
+						this.responseCache[modelName][modelId] = model;
+					});
+				}
+
+				const requests = requestsPerModel[modelName];
+				// For all requests, resolve the deferred promise with the returned model(s) that was requested
+				requests.forEach(request => {
+					const { payload, promise } = request;
+					const { modelIds } = payload;
+					const models = modelIds
+						.map(modelId => this.responseCache[modelName][modelId])
+						.filter(model => model);
+					promise.resolve(models);
+				});
+
+				// A unique list of model ids that are will be requested in the next batch for the current model type
+				const queuedModelIds = Array.from(
+					new Set(
+						this.requestsQueued
+							.filter(request => request.payload.modelName)
+							.flatMap(request => request.payload.modelIds)
+					)
+				);
+				// A list of model ids responses currently cached
+				cachedModelIds = Object.keys(this.responseCache[modelName]);
+				// A list of the cached model responses that can safely be deleted, because no queued up request needs it
+				const cachedModelIdsToDelete = cachedModelIds.filter(
+					cachedModelId => !queuedModelIds.includes(cachedModelId)
+				);
+				console.log(`Queued model ids`, modelName, queuedModelIds);
+				console.log(`Cached model ids`, modelName, cachedModelIds);
+				console.log(
+					`Cached model ids to delete`,
+					modelName,
+					cachedModelIdsToDelete
+				);
+
+				// TODO In theory, we could check if any of the queued requests can be resolved here. Not worth it at the moment.
+
+				cachedModelIdsToDelete.forEach(cachedModelIdToDelete => {
+					delete this.responseCache[modelName][cachedModelIdToDelete];
+				});
+			});
+		}, 25);
+	}
+
+	public static fetchModelsByIds(modelName: string, modelIds: string[]) {
+		this.responseCache[modelName] ??= {};
+
+		return new Promise(resolve => {
+			const promise = new DeferredPromise();
+
+			// Listens for the deferred promise response, before we actually push and fetch
+			promise.promise.then(result => {
+				resolve(result);
+			});
+
+			// Pushes the request to the queue
+			this.requestsQueued.push({
+				payload: {
+					modelName,
+					modelIds
+				},
+				promise
+			});
+
+			// Calls the fetch function, which will start a timeout if one isn't already running, which will actually request the model
+			this.fetch();
+		});
+	}
+}

+ 96 - 0
frontend/src/ModelPermissionFetcher.ts

@@ -0,0 +1,96 @@
+import { DeferredPromise } from "@common/DeferredPromise";
+import { forEachIn } from "@common/utils/forEachIn";
+import { useWebsocketStore } from "./stores/websocket";
+
+export interface ModelPermissionFetcherRequest {
+	promise: DeferredPromise;
+	payload: {
+		modelName: string;
+		modelId: string;
+	};
+}
+
+/**
+ * Class used for fetching model permissions in bulk, every 25ms max
+ * So if there's 200 models loaded, it would do only 1 request to fetch model permissions, not 200 separate ones
+ */
+export class ModelPermissionFetcher {
+	private static requestsQueued: ModelPermissionFetcherRequest[] = [];
+
+	private static timeoutActive = false;
+
+	private static fetch() {
+		// If there is no other timeout running, indicate we will run one. Otherwise, return, as a timeout is already running
+		if (!this.timeoutActive) this.timeoutActive = true;
+		else return;
+
+		setTimeout(() => {
+			// Reset timeout active, so another one can run
+			this.timeoutActive = false;
+			// Make a copy of all requests currently queued, and then take those requests out of the queue so we can request them
+			const requests = this.requestsQueued;
+			this.requestsQueued = [];
+
+			// Splits the requests per model
+			const requestsPerModel = {};
+			requests.forEach(request => {
+				const { modelName } = request.payload;
+				if (!Array.isArray(requestsPerModel[modelName]))
+					requestsPerModel[modelName] = [];
+				requestsPerModel[modelName].push(request);
+			});
+
+			const modelNames = Object.keys(requestsPerModel);
+
+			const { runJob } = useWebsocketStore();
+
+			// Runs the requests per model
+			forEachIn(modelNames, async modelName => {
+				// Gets a unique list of all model ids for the current model that we want to request permissions for
+				const modelIds = Array.from(
+					new Set(
+						requestsPerModel[modelName].map(
+							request => request.payload.modelId
+						)
+					)
+				);
+
+				const result = await runJob("data.users.getModelPermissions", {
+					modelName,
+					modelIds
+				});
+
+				const requests = requestsPerModel[modelName];
+				// For all requests, resolve the deferred promise with the returned permissions for the model that request requested
+				requests.forEach(request => {
+					const { payload, promise } = request;
+					const { modelId } = payload;
+					promise.resolve(result[modelId]);
+				});
+			});
+		}, 25);
+	}
+
+	public static fetchModelPermissions(modelName, modelId) {
+		return new Promise(resolve => {
+			const promise = new DeferredPromise();
+
+			// Listens for the deferred promise response, before we actually push and fetch
+			promise.promise.then(result => {
+				resolve(result);
+			});
+
+			// Pushes the request to the queue
+			this.requestsQueued.push({
+				payload: {
+					modelName,
+					modelId
+				},
+				promise
+			});
+
+			// Calls the fetch function, which will start a timeout if one isn't already running, which will actually request the permissions
+			this.fetch();
+		});
+	}
+}

+ 8 - 147
frontend/src/stores/model.ts

@@ -4,151 +4,7 @@ import { generateUuid } from "@common/utils/generateUuid";
 import { forEachIn } from "@common/utils/forEachIn";
 import { useWebsocketStore } from "./websocket";
 import Model from "@/Model";
-
-class DeferredPromise<T = any> {
-	promise: Promise<T>;
-
-	reject;
-
-	resolve;
-
-	// eslint-disable-next-line require-jsdoc
-	constructor() {
-		this.promise = new Promise<T>((resolve, reject) => {
-			this.reject = reject;
-			this.resolve = resolve;
-		});
-	}
-}
-
-interface ModelFetcherRequest {
-	promise: DeferredPromise;
-	payload: {
-		modelName: string;
-		modelIds: string[];
-	};
-}
-
-// TODO combine the ModelFetcher and the ModelPermissionFetcher
-/**
- * Class used for fetching models in bulk, every 25ms max, per model type
- * So if we tried to fetch 100 different minifiedUser models separately, it would do only 1 request to fetch the models, not 100 separate ones
- */
-class ModelFetcher {
-	private static requestsQueued: ModelFetcherRequest[] = [];
-
-	private static timeoutActive = false;
-
-	private static responseCache = {};
-
-	private static fetch() {
-		// If there is no other timeout running, indicate we will run one. Otherwise, return, as a timeout is already running
-		if (!this.timeoutActive) this.timeoutActive = true;
-		else return;
-
-		setTimeout(() => {
-			// Reset timeout active, so another one can run
-			this.timeoutActive = false;
-			// Make a copy of all requests currently queued, and then take those requests out of the queue so we can request them
-			const requests = this.requestsQueued;
-			this.requestsQueued = [];
-
-			// Splits the requests per model
-			const requestsPerModel = {};
-			requests.forEach(request => {
-				const { modelName } = request.payload;
-				if (!Array.isArray(requestsPerModel[modelName]))
-					requestsPerModel[modelName] = [];
-				requestsPerModel[modelName].push(request);
-			});
-
-			const modelNames = Object.keys(requestsPerModel);
-
-			const { runJob } = useWebsocketStore();
-
-			// TODO somehow make the following forEachIn run at the same time for all modelNames
-			// Runs the requests per model
-			forEachIn(modelNames, async modelName => {
-				// All already cached model ids
-				let cachedModelIds = Object.keys(this.responseCache[modelName]);
-
-				// Gets a unique list of all model ids for the current model that we want to request permissions for, that are not already cached
-				const modelIds = Array.from(
-					new Set(
-						requestsPerModel[modelName].flatMap(
-							request => request.payload.modelIds
-						)
-					)
-				).filter((modelId: string) => !cachedModelIds.includes(modelId));
-
-				// Only do a request if more than one model isn't already cached
-				if (modelIds.length > 0) {
-					console.log(`Requesting model ids`, modelName, modelIds);
-					const result = await runJob(`data.${modelName}.findManyById`, {
-						_ids: modelIds
-					}) as any[];
-					
-					// Cache the responses for the requested model ids
-					modelIds.forEach(modelId => {
-						const model = result.find(model => model._id === modelId);
-						console.log(`Caching ${modelName}.${modelId}`, model);
-						this.responseCache[modelName][modelId] = model;
-					});
-				}
-
-				const requests = requestsPerModel[modelName];
-				// For all requests, resolve the deferred promise with the returned model(s) that was requested
-				requests.forEach(request => {
-					const { payload, promise } = request;
-					const { modelIds } = payload;
-					const models = modelIds.map(modelId => this.responseCache[modelName][modelId]).filter(model => model);
-					promise.resolve(models);
-				});
-
-				// A unique list of model ids that are will be requested in the next batch for the current model type
-				const queuedModelIds = Array.from(new Set(this.requestsQueued.filter(request => request.payload.modelName).flatMap(request => request.payload.modelIds)));
-				// A list of model ids responses currently cached
-				cachedModelIds = Object.keys(this.responseCache[modelName]);
-				// A list of the cached model responses that can safely be deleted, because no queued up request needs it
-				const cachedModelIdsToDelete = cachedModelIds.filter(cachedModelId => !queuedModelIds.includes(cachedModelId));
-				console.log(`Queued model ids`, modelName, queuedModelIds);
-				console.log(`Cached model ids`, modelName, cachedModelIds);
-				console.log(`Cached model ids to delete`, modelName, cachedModelIdsToDelete);
-
-				// TODO In theory, we could check if any of the queued requests can be resolved here. Not worth it at the moment.
-
-				cachedModelIdsToDelete.forEach(cachedModelIdToDelete => {
-					delete this.responseCache[modelName][cachedModelIdToDelete];
-				});
-			});
-		}, 25);
-	}
-
-	public static fetchModelsByIds(modelName: string, modelIds: string[]) {
-		this.responseCache[modelName] ??= {};
-
-		return new Promise(resolve => {
-			const promise = new DeferredPromise();
-
-			// Listens for the deferred promise response, before we actually push and fetch
-			promise.promise.then(result => {
-				resolve(result);
-			});
-
-			// Pushes the request to the queue
-			this.requestsQueued.push({
-				payload: {
-					modelName,
-					modelIds
-				},
-				promise
-			});
-
-			// Calls the fetch function, which will start a timeout if one isn't already running, which will actually request the model
-			this.fetch();
-		});
-	}
-}
+import { ModelFetcher } from "@/ModelFetcher";
 
 /**
  * Pinia store for managing models
@@ -337,7 +193,9 @@ export const useModelStore = defineStore("model", () => {
 
 		if (existingModel) return existingModel;
 
-		const [model] = await ModelFetcher.fetchModelsByIds(modelName, [modelId]) as unknown[];
+		const [model] = (await ModelFetcher.fetchModelsByIds(modelName, [
+			modelId
+		])) as unknown[];
 
 		return model;
 	};
@@ -360,7 +218,10 @@ export const useModelStore = defineStore("model", () => {
 
 		let fetchedModels = [];
 		if (missingModelIds.length > 0)
-			fetchedModels = await ModelFetcher.fetchModelsByIds(modelName, missingModelIds) as unknown[];
+			fetchedModels = (await ModelFetcher.fetchModelsByIds(
+				modelName,
+				missingModelIds
+			)) as unknown[];
 
 		const allModels = existingModels.concat(fetchedModels);