import EventEmitter from "node:events";
import State from "@infinity-beyond/modules/state.ts";
import { randomUUID } from "node:crypto";
import { EntityWorker } from "@infinity-beyond/modules/entity_worker.ts";
import { Sluggify } from "@infinity-beyond/utils/slug.ts";
interface I_Task extends I_ProcessorRequest {
is_static: boolean
instance_data?: any
resolve: VagueFunction
}
export type EventMap<T extends Record<string, any[]>> = T;
type EntityEvents = EventMap<{
/**
* Triggered when a new class instance is created
*
* @param entity The instance that was created
*/
create: [Entity<any>]
}>
export class Entity<T extends Record<string, any[]>> extends EventEmitter<T | EntityEvents> {
private static __tasks_in_progress: Record<string, I_Task> = {}
private static __tasks_by_entity: Record<string, Record<string, I_Task>> = {}
static readonly __is_typeof_entity = true;
readonly name: string
constructor(name: string) {
super();
this.name = name;
this.emit('create', this);
}
static get __tasks() {
return Object.values(this.__tasks_by_entity[this.name] ||= {});
}
protected get slug() {
return Sluggify((this.constructor as typeof Entity).name);
}
private static __workers: EntityWorker[] = []
static get __worker_count() {
return this.__workers.length;
}
protected static get __worker_methods() { return this.___worker_methods[this.name] ||= []; }
private static ___worker_methods: Record<string, string[]> = {}
private static __delegation_index = 0
protected static get __next_worker() {
if(++this.__delegation_index >= this.__workers.length) this.__delegation_index = 0;
return this.__workers[this.__delegation_index];
}
// #region Queueing and Delegation
protected static __task_queue: I_Task[] = []
protected static __queue_processing = false;
protected static __ProcessQueue() {
if(this.__queue_processing) return;
if(!this.__task_queue.length) return this.__queue_processing = false;
if(!this.__workers.length) {
this.warn('::no_workers -', `${this.__task_queue.length} tasks waiting for a worker!`);
return;
}
this.__queue_processing = true;
while(this.__task_queue.length) {
const task = this.__task_queue.shift();
if(!task) continue;
this.__next_worker.dispatch(task).then(response => {
task.resolve(response);
});
}
this.__queue_processing = false;
}
static __delegate_instance(instance: Entity<any>, method: string, args: any[]) {
return new Promise(resolve => {
let id: string;
do {
id = randomUUID();
} while(this.__tasks_in_progress[id!]);
const instance_data = Object.assign(
{},
...Object.entries(instance)
.map(([key, value]) => {
if(Object.hasOwn(instance, key)) return { [key]: value };
return false;
})
.filter(e => e !== false)
);
Entity.__task_queue.push({
id,
entity_name: instance.name,
is_static: false,
instance_data,
method,
args,
resolve,
});
this.__ProcessQueue();
})
}
static __delegate(method: string, args: any[]) {
return new Promise(resolve => {
let id: string;
do {
id = randomUUID();
} while(this.__tasks_in_progress[id!]);
Entity.__task_queue.push({
id,
entity_name: this.name,
is_static: true,
method,
args,
resolve,
});
this.__ProcessQueue();
})
}
// #region Workers
static __AddWorker(uuid: string, socket: WebSocket) {
const worker = new EntityWorker(uuid, socket, this);
this.__workers.push(worker);
this.__ProcessQueue();
}
static __RemoveWorker(worker: EntityWorker) {
console.log(`Worker lost! [${worker.uuid}]`);
const index = this.__workers.findIndex(w => w.uuid == worker.uuid);
if(index > -1) this.__workers.splice(index, 1);
}
// #region Logging
static log(...args: any[]) {
console.log(`[${this.name}]`, ...args);
}
static warn(...args: any[]) {
console.warn(`[${this.name}]`, ...args);
}
}
// #region Decorators
type OffloadDecoratorMethod = (originalMethod: VagueAsyncFunction, context: ClassMethodDecoratorContext) => void;
type OffloadDecoratorWithWrapper = <T>(wrapper: new (...args: any[]) => T) => OffloadDecoratorMethod;
type OffloadDecorator = {
with_response_wrapper: OffloadDecoratorWithWrapper;
} & OffloadDecoratorMethod;
const run_on_worker_method: OffloadDecoratorMethod = function(this: any, originalMethod: VagueAsyncFunction, { name: methodName, static: isStatic, addInitializer }: ClassMethodDecoratorContext) {
addInitializer(function() {
if(State.IS_PROCESSOR) return;
let cls: typeof Entity;
let instance: Entity<any>;
if(isStatic) {
cls = this as typeof Entity;
} else {
instance = (this as Entity<any>);
cls = instance.constructor as typeof Entity;
}
(cls as any).__worker_methods.push(originalMethod.name);
if(isStatic) {
Object.assign(cls, {
[methodName]: (...args: any[]) => {
return cls.__delegate(originalMethod.name, args);
}
})
} else {
Object.assign(cls.prototype, {
[methodName]: function(this: Entity<any>, ...args: any[]) {
return cls.__delegate_instance(instance, originalMethod.name, args);
}
})
}
})
};
export const run_on_worker: OffloadDecorator = Object.assign(run_on_worker_method, {
with_response_wrapper: ((wrapper) => {
return run_on_worker_method.bind(wrapper);
}) as OffloadDecoratorWithWrapper
})