mirror of
https://github.com/jaronpate/unicord.git
synced 2026-10-18 21:47:40 +00:00
starting a huge rewrite
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import type { Unicord } from '..';
|
||||
import { Trait } from '../types/common';
|
||||
|
||||
export class UnicordObjectCache<Entity> {
|
||||
cache: LRUCache<string, Entity>;
|
||||
requests = new Map<string, Promise<Entity>>();
|
||||
readonly type: string;
|
||||
|
||||
constructor(
|
||||
private readonly self: Unicord,
|
||||
type: string,
|
||||
private readonly factory: { [Trait.fromDiscord]: (data: any) => Entity } & (new (data: any) => Entity),
|
||||
events: string[],
|
||||
maxCapacity: number = 10_000,
|
||||
) {
|
||||
this.cache = new LRUCache<string, Entity>(maxCapacity);
|
||||
this.type = type;
|
||||
for (const event of events) {
|
||||
this.self.onEvent(event, async (_context, e) => {
|
||||
this.cache.set(e.id, factory[Trait.fromDiscord](e));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set(object_id: string, object: Entity): void {
|
||||
this.cache.set(object_id, object);
|
||||
}
|
||||
|
||||
// update(object_id: string, object: Partial<T>): void {
|
||||
// const existing = this.cache.get(object_id);
|
||||
|
||||
// if (exists(existing)) {
|
||||
// // TODO: Idk wtf happens if this is called on two classes
|
||||
// this.cache.set(object_id, { ...existing, ...object });
|
||||
// } else {
|
||||
// this.cache.set(object_id, object as T);
|
||||
// }
|
||||
// }
|
||||
|
||||
async fetch(object_id: string, ..._args: any[]): Promise<Entity> {
|
||||
if (this.requests.has(object_id)) {
|
||||
return this.requests.get(object_id)!;
|
||||
// } else if (this.cache.has(object_id)) {
|
||||
// return Promise.resolve(this.cache.get(object_id)!);
|
||||
} else {
|
||||
// Dispatch the request
|
||||
const req = this.self.discordAPI.get(`/${this.type}/${object_id}`).then((data) => {
|
||||
// Create the object
|
||||
const object = this.factory[Trait.fromDiscord](data);
|
||||
// Save the object
|
||||
this.cache.set(object_id, object);
|
||||
// Return the object
|
||||
return object;
|
||||
});
|
||||
// Save the request
|
||||
this.requests.set(object_id, req);
|
||||
// Remove the request from the cache when it finishes
|
||||
req.finally(() => this.requests.delete(object_id));
|
||||
// Return the request
|
||||
return req;
|
||||
}
|
||||
}
|
||||
|
||||
async get(object_id: string, ..._args: any[]): Promise<Entity> {
|
||||
if (this.cache.has(object_id)) {
|
||||
return await Promise.resolve(this.cache.get(object_id)!);
|
||||
} else {
|
||||
return this.fetch(object_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ListNode<K, V> {
|
||||
key: K;
|
||||
value: V;
|
||||
prev: ListNode<K, V> | null = null;
|
||||
next: ListNode<K, V> | null = null;
|
||||
|
||||
constructor(key: K, value: V) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private map: Map<K, ListNode<K, V>>;
|
||||
private head: ListNode<K, V>;
|
||||
private tail: ListNode<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
this.map = new Map();
|
||||
|
||||
// Dummy head and tail to simplify boundary conditions
|
||||
this.head = new ListNode<K, V>(null as any, null as any);
|
||||
this.tail = new ListNode<K, V>(null as any, null as any);
|
||||
this.head.next = this.tail;
|
||||
this.tail.prev = this.head;
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
if (!this.map.has(key)) return undefined;
|
||||
|
||||
const node = this.map.get(key)!;
|
||||
this.moveToFront(node);
|
||||
return node.value;
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
if (this.map.has(key)) {
|
||||
// Update existing node value and move it to front
|
||||
const node = this.map.get(key)!;
|
||||
node.value = value;
|
||||
this.moveToFront(node);
|
||||
} else {
|
||||
// Create a new node
|
||||
const newNode = new ListNode(key, value);
|
||||
this.map.set(key, newNode);
|
||||
this.addToFront(newNode);
|
||||
|
||||
// If over capacity, remove the least recently used (LRU) node
|
||||
if (this.map.size > this.capacity) {
|
||||
this.removeLRU();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private moveToFront(node: ListNode<K, V>): void {
|
||||
this.removeNode(node);
|
||||
this.addToFront(node);
|
||||
}
|
||||
|
||||
private addToFront(node: ListNode<K, V>): void {
|
||||
node.next = this.head.next;
|
||||
node.prev = this.head;
|
||||
this.head.next!.prev = node;
|
||||
this.head.next = node;
|
||||
}
|
||||
|
||||
private removeNode(node: ListNode<K, V>): void {
|
||||
node.prev!.next = node.next;
|
||||
node.next!.prev = node.prev;
|
||||
}
|
||||
|
||||
private removeLRU(): void {
|
||||
const lruNode = this.tail.prev!;
|
||||
this.removeNode(lruNode);
|
||||
this.map.delete(lruNode.key);
|
||||
}
|
||||
|
||||
*[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||
let current = this.head.next;
|
||||
while (current && current !== this.tail) {
|
||||
yield [current.key, current.value];
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user