import * as Scheduler from 'scheduler';
const {
unstable_scheduleCallback: scheduleCallback,
unstable_IdlePriority: IdlePriority,
} = Scheduler;
type Entry<T> = {
value: T,
onDelete: () => mixed,
previous: Entry<T>,
next: Entry<T>,
};
type LRU<T> = {
add(value: Object, onDelete: () => mixed): Entry<Object>,
update(entry: Entry<T>, newValue: T): void,
access(entry: Entry<T>): T,
setLimit(newLimit: number): void,
};
export function createLRU<T>(limit: number): LRU<T> {
let LIMIT = limit;
let first: Entry<T> | null = null;
let size: number = 0;
let cleanUpIsScheduled: boolean = false;
function scheduleCleanUp() {
if (cleanUpIsScheduled === false && size > LIMIT) {
cleanUpIsScheduled = true;
scheduleCallback(IdlePriority, cleanUp);
}
}
function cleanUp() {
cleanUpIsScheduled = false;
deleteLeastRecentlyUsedEntries(LIMIT);
}
function deleteLeastRecentlyUsedEntries(targetSize: number) {
if (first !== null) {
const resolvedFirst: Entry<T> = (first: any);
let last: null | Entry<T> = resolvedFirst.previous;
while (size > targetSize && last !== null) {
const onDelete = last.onDelete;
const previous = last.previous;
last.onDelete = (null: any);
last.previous = last.next = (null: any);
if (last === first) {
first = last = null;
} else {
(first: any).previous = previous;
previous.next = (first: any);
last = previous;
}
size -= 1;
onDelete();
}
}
}
function add(value: Object, onDelete: () => mixed): Entry<Object> {
const entry = {
value,
onDelete,
next: (null: any),
previous: (null: any),
};
if (first === null) {
entry.previous = entry.next = entry;
first = entry;
} else {
const last = first.previous;
last.next = entry;
entry.previous = last;
first.previous = entry;
entry.next = first;
first = entry;
}
size += 1;
return entry;
}
function update(entry: Entry<T>, newValue: T): void {
entry.value = newValue;
}
function access(entry: Entry<T>): T {
const next = entry.next;
if (next !== null) {
const resolvedFirst: Entry<T> = (first: any);
if (first !== entry) {
const previous = entry.previous;
previous.next = next;
next.previous = previous;
const last = resolvedFirst.previous;
last.next = entry;
entry.previous = last;
resolvedFirst.previous = entry;
entry.next = resolvedFirst;
first = entry;
}
} else {
}
scheduleCleanUp();
return entry.value;
}
function setLimit(newLimit: number): void {
LIMIT = newLimit;
scheduleCleanUp();
}
return {
add,
update,
access,
setLimit,
};
}