It's just an experiment, and I don't have the energy to refactor yet.
enum EventSpecialKey {
MiddlewareReady = '__isReady__'
}
type PromisifyCallbackRetType = number | boolean | Promise<any>
interface Middleware {
setup?: (...args: any[]) => void;
updated: (next: () => number, ...args: any[]) => PromisifyCallbackRetType;
unmounted?: (...args: any[]) => void;
}
type PlayerEventCallback = 'connect' | 'disconnect';
function promisifyCallback(result: PromisifyCallbackRetType, defaultValue: boolean = true) {
if (result instanceof Promise) {
return +defaultValue;
}
const ret = +result;
return isNaN(ret) ? +defaultValue : ret;
}
function executeMiddlewares(event: { middlewares: Record<string, Middleware[]> }, callback: string, defaultValue: boolean, ...args: any[]) {
const middlewares = event.middlewares[callback];
for (const m of middlewares) {
if (m.setup && !Reflect.get(m, EventSpecialKey.MiddlewareReady)) {
m.setup(...args);
Reflect.set(m, EventSpecialKey.MiddlewareReady, true);
}
}
let index = 0;
const next = () => {
index++;
if (index < middlewares.length) {
return promisifyCallback(middlewares[index].updated(next, ...args));
}
for (const m of middlewares) {
if (m.unmounted && Reflect.get(m, EventSpecialKey.MiddlewareReady)) {
m.unmounted(...args);
Reflect.set(m, EventSpecialKey.MiddlewareReady, false);
}
}
return +defaultValue;
};
if (middlewares.length > 0) {
return promisifyCallback(middlewares[index].updated(next, ...args))
}
return +defaultValue;
}
function OnPlayerConnect() {
// Math.round(Math.random() * 10)
return executeMiddlewares(PlayerEvent, 'connect', true, 0);
}
function OnPlayerDisconnect() {
// Math.round(Math.random() * 10)
return executeMiddlewares(PlayerEvent, 'disconnect', true, 0);
}
class Player {
name: string = ''
constructor(public readonly id: number) {
}
}
class PlayerEvent<T extends Player> {
static readonly middlewares: Record<PlayerEventCallback, Middleware[]> = {
'connect': [],
'disconnect': []
};
static readonly players = new Map<number, { context: PlayerEvent<any>, value: Player }[]>();
constructor(private playerConstructor: new (id: number) => T) {
}
private getProxyPlayer(player: T) {
return new Proxy(player, {
set(target, p, newValue) {
PlayerEvent.players.get(player.id)?.forEach(item => {
const has = Reflect.has(item.value, p);
const descriptor = Reflect.getOwnPropertyDescriptor(item.value, p)
const writeable = descriptor && descriptor.writable;
if (has && writeable) {
Reflect.set(item.value, p, newValue)
}
})
return true
},
})
}
private setupMiddleware(playerId: number) {
let players = PlayerEvent.players.get(playerId);
if (!players) {
players = [{ context: this, value: new this.playerConstructor(playerId) }];
PlayerEvent.players.set(playerId, players);
}
let player = players.find(p => p.context === this);
if (!player) {
const addPlayer = { context: this, value: new this.playerConstructor(playerId) };
players.push(addPlayer);
player = addPlayer;
}
}
private unmountedMiddleware(playerId: number) {
let players = PlayerEvent.players.get(playerId);
if (!players) return;
let playerIdx = players.findIndex(p => p.context === this);
if (playerIdx === -1) return;
players.splice(playerIdx, 1);
}
onConnect(fn: (next: () => ReturnType<typeof promisifyCallback>, player: T) => PromisifyCallbackRetType) {
PlayerEvent.middlewares['connect'].push({
setup: this.setupMiddleware.bind(this),
updated: (next, playerId: number) => {
const players = PlayerEvent.players.get(playerId)!;
const player = players.find(p => p.context === this)!;
const proxyPlayer = this.getProxyPlayer(player.value as T);
return fn(next, proxyPlayer);
}
})
}
onDisconnect(fn: (next: () => ReturnType<typeof promisifyCallback>, player: T) => PromisifyCallbackRetType) {
PlayerEvent.middlewares['disconnect'].push({
setup: this.setupMiddleware.bind(this),
updated: (next, playerId: number) => {
const players = PlayerEvent.players.get(playerId);
if (!players) return next();
const player = players.find(p => p.context === this);
if (!player) return next();
const proxyPlayer = this.getProxyPlayer(player.value as T);
return fn(next, proxyPlayer);
},
unmounted: this.unmountedMiddleware.bind(this)
})
}
}
const playerEvent = new PlayerEvent(Player);
const playerEvent2 = new PlayerEvent(Player);
playerEvent.onConnect((next, player) => {
player.name = 'a';
return next();
})
playerEvent2.onConnect((next, player) => {
console.log(player)
return next();
})
playerEvent.onConnect((next, player) => {
player.name = 'b';
return next();
})
playerEvent.onDisconnect((next, player) => {
console.log(player);
return next();
})
playerEvent.onDisconnect((next, player) => {
console.log(player);
next();
console.log(PlayerEvent.players)
return true;
})
playerEvent2.onDisconnect((next, player) => {
console.log(player);
return next();
})
console.log(OnPlayerConnect());
setTimeout(() => {
console.log(OnPlayerDisconnect())
}, 1500)