import State from "@infinity-beyond/modules/state.ts";
import { randomUUID } from "node:crypto";
import { EntityWorker } from "@infinity-beyond/classes/entity_worker.ts";
import { Sluggify } from "@infinity-beyond/utils/slug.ts";
import { CustomEventEmitter } from "@infinity-beyond/classes/custom_event_emitter.ts";
import { EntityMeta, type EntityMetaConfig } from "@infinity-beyond/classes/entity_meta.ts";
interface I_Task extends I_ProcessorRequest {
is_static: boolean
instance_data?: any
resolve: VagueFunction
}
type EntityEvents = Record<string, any[]>
export class Entity<T extends Record<string, any[]> = EntityEvents, EMC extends EntityMetaConfig = EntityMetaConfig, PEMC extends EntityMetaConfig = EntityMetaConfig> extends CustomEventEmitter<T> {
private static __tasks_in_progress: Record<string, I_Task> = {}
private static __tasks_by_entity: Record<string, Record<string, I_Task>> = {}
private meta_fields: EMC
private passed_meta_fields: PEMC
meta: EntityMeta<EMC, PEMC>
static readonly __is_typeof_entity = true;
readonly name: string
constructor(name: string, meta_fields: EMC, passed_meta_fields: PEMC) {
super();
this.name = name;
this.meta_fields = meta_fields;
this.passed_meta_fields = passed_meta_fields;
this.meta = new EntityMeta(this.meta_table, meta_fields, passed_meta_fields);
}
static get __tasks() {
return Object.values(this.__tasks_by_entity[this.name] ||= {});
}
get slug() {
return Sluggify(this.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);
}
protected get meta_table() {
return `${this.slug}_meta`;
}
get entity_class() {
return this.constructor as typeof Entity;
}
async Setup() {
const slug = this.slug;
const meta_table = this.meta_table;
const meta_entries = Object.entries({...this.meta_fields, ...this.passed_meta_fields});
if(meta_entries.length) {
await State.PostgresClient.CreateTable(meta_table, {
key: 'varchar(24) NOT NULL',
value: 'varchar(24)',
}, {
async onCreate() {
await State.PostgresClient.query(`CREATE UNIQUE INDEX ${slug}_meta_key_idx ON ${meta_table} (key);`);
const placeholders: string[] = [];
const values: string[] = [];
for(const [key, type] of meta_entries) {
placeholders.push(`($${values.length + 1}, $${values.length + 2})`);
let value: string = '';
if(type == 'int' || type == 'real') value = '0';
values.push(key, value);
}
await State.PostgresClient.query(`INSERT INTO ${meta_table} (key, value) VALUES ${placeholders}`, values);
}
});
}
return this;
}
}
// #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
})