389 lines
8.1 KiB
TypeScript
389 lines
8.1 KiB
TypeScript
const DEFAULT_SIZE = 128;
|
|
|
|
export class FreeList<T>
|
|
{
|
|
#data: T[] = [];
|
|
#free: LinkedList<number> = new LinkedList<number>();
|
|
|
|
#length: number = 0;
|
|
|
|
constructor(arr?: T[])
|
|
{
|
|
if(arr && arr.length > 0)
|
|
this.#data = arr;
|
|
}
|
|
|
|
insert(element: T): number
|
|
{
|
|
if(this.#free.empty)
|
|
{
|
|
return this.#data.push(element) - 1;
|
|
}
|
|
else
|
|
{
|
|
const index = this.#free.pop()!;
|
|
this.#data[index] = element;
|
|
return index;
|
|
}
|
|
}
|
|
erase(n: number): void
|
|
{
|
|
delete this.#data[n];
|
|
this.#free.add(n);
|
|
}
|
|
clear(): void
|
|
{
|
|
this.#data.length = 0;
|
|
this.#free.clear();
|
|
}
|
|
|
|
get(index: number): T
|
|
{
|
|
return this.#data[index];
|
|
}
|
|
set(index: number, value: T): void
|
|
{
|
|
this.#data[index] = value;
|
|
}
|
|
get length(): number
|
|
{
|
|
return this.#length;
|
|
}
|
|
}
|
|
export class IntList
|
|
{
|
|
#data: number[];
|
|
#fields: number;
|
|
#capacity: number = DEFAULT_SIZE;
|
|
#length: number = 0;
|
|
#free: number = -1;
|
|
|
|
constructor(fields: number)
|
|
{
|
|
if(fields <= 0)
|
|
throw new Error("Invalid field count");
|
|
|
|
this.#data = new Array(this.#capacity * fields);
|
|
this.#fields = fields;
|
|
}
|
|
get length(): number
|
|
{
|
|
return this.#length;
|
|
}
|
|
|
|
get(index: number, field: number): number
|
|
{
|
|
if(index >= this.#length || index < 0)
|
|
throw new Error("Invalid index");
|
|
|
|
if(field >= this.#fields || field < 0)
|
|
throw new Error("Invalid field");
|
|
|
|
return this.#data[index*this.#fields + field];
|
|
}
|
|
set(index: number, field: number, value: number): void
|
|
{
|
|
if(index >= this.#length || index < 0)
|
|
throw new Error("Invalid index");
|
|
|
|
if(field >= this.#fields || field < 0)
|
|
throw new Error("Invalid field");
|
|
|
|
this.#data[index*this.#fields + field] = value;
|
|
}
|
|
clear(): void
|
|
{
|
|
//Never edit the array during clear, we'll keep every data since they *must* always be override before being read.
|
|
this.#length = 0;
|
|
this.#free = -1;
|
|
}
|
|
push(): number
|
|
{
|
|
const pos = (this.#length + 1) * this.#fields;
|
|
|
|
if(pos > this.#capacity)
|
|
{
|
|
this.#capacity *= 2;
|
|
|
|
this.#data.length = this.#capacity * this.#fields;
|
|
}
|
|
|
|
return this.#length++;
|
|
}
|
|
pop(): void
|
|
{
|
|
if(this.#length <= 0)
|
|
return;
|
|
|
|
--this.#length;
|
|
}
|
|
insert(): number
|
|
{
|
|
if(this.#free !== -1)
|
|
{
|
|
const index = this.#free;
|
|
this.#free = this.#data[index * this.#fields];
|
|
return index;
|
|
}
|
|
else
|
|
return this.push();
|
|
}
|
|
erase(index: number): void
|
|
{
|
|
if(index >= this.#length || index < 0)
|
|
throw new Error("Invalid index");
|
|
|
|
this.#data[index * this.#fields] = this.#free;
|
|
this.#free = index;
|
|
}
|
|
|
|
toArray(): number[]
|
|
{
|
|
return this.#data.slice(0, this.#length);
|
|
}
|
|
|
|
//DEBUG
|
|
printReadable(names: string[] = []): void
|
|
{
|
|
const size = this.#length * this.#fields;
|
|
for(let i = 0; i < size; i)
|
|
{
|
|
const obj: Record<string, number> = {};
|
|
for(let j = 0; j < this.#fields; ++i, ++j)
|
|
{
|
|
obj[names[j] ?? j] = this.#data[i];
|
|
}
|
|
console.log(obj);
|
|
}
|
|
}
|
|
}
|
|
export class Stack<T>
|
|
{
|
|
#arr: T[];
|
|
|
|
#pos: number; //Index of the last non empty value
|
|
|
|
#bucketSize: number;
|
|
#bucketCount: number = 1;
|
|
|
|
constructor(size?: number)
|
|
{
|
|
this.#bucketSize = size ?? DEFAULT_SIZE;
|
|
|
|
this.#arr = new Array(this.#bucketSize * this.#bucketCount);
|
|
this.#pos = 0;
|
|
}
|
|
push(item: T): void
|
|
{
|
|
if(this.#pos >= this.#arr.length)
|
|
this.#expand();
|
|
|
|
this.#arr[this.#pos] = item;
|
|
this.#pos++;
|
|
}
|
|
pop(): T
|
|
{
|
|
if(this.length === 0)
|
|
throw new Error("Empty queue.");
|
|
|
|
const item = this.#arr[this.#pos];
|
|
delete this.#arr[this.#pos];
|
|
this.#pos--;
|
|
|
|
return item;
|
|
}
|
|
peek(): T
|
|
{
|
|
return this.#arr[this.#pos];
|
|
}
|
|
clear(): void
|
|
{
|
|
this.#pos = 0;
|
|
}
|
|
get length(): number
|
|
{
|
|
return this.#pos;
|
|
}
|
|
#expand(): void
|
|
{
|
|
if(this.length >= this.#bucketSize * this.#bucketCount)
|
|
this.#bucketCount++;
|
|
|
|
this.#arr.length = this.#bucketSize * this.#bucketCount;
|
|
}
|
|
}
|
|
export class Queue<T>
|
|
{
|
|
#arr: T[];
|
|
|
|
#idx: number; //Index of the first non empty value
|
|
#pos: number; //Index of the last non empty value
|
|
|
|
#bucketSize: number;
|
|
#bucketCount: number = 1;
|
|
|
|
constructor(size?: number)
|
|
{
|
|
this.#bucketSize = size ?? DEFAULT_SIZE;
|
|
|
|
this.#arr = new Array(this.#bucketSize * this.#bucketCount);
|
|
this.#idx = 0;
|
|
this.#pos = 0;
|
|
}
|
|
push(item: T): void
|
|
{
|
|
if(this.#pos >= this.#arr.length)
|
|
this.#expand();
|
|
|
|
this.#arr[this.#pos] = item;
|
|
this.#pos++;
|
|
}
|
|
pull(): T
|
|
{
|
|
if(this.length === 0)
|
|
throw new Error("Empty queue.");
|
|
|
|
const item = this.#arr[this.#idx];
|
|
delete this.#arr[this.#idx];
|
|
this.#idx++;
|
|
|
|
return item;
|
|
}
|
|
peek(): T
|
|
{
|
|
return this.#arr[this.#idx];
|
|
}
|
|
clear(): void
|
|
{
|
|
this.#idx = 0;
|
|
this.#pos = 0;
|
|
}
|
|
get length(): number
|
|
{
|
|
return this.#pos - this.#idx;
|
|
}
|
|
#shrink(): void
|
|
{
|
|
this.#arr.splice(0, this.#idx);
|
|
|
|
this.#arr.length = this.#bucketSize * this.#bucketCount;
|
|
this.#idx = 0;
|
|
}
|
|
#expand(): void
|
|
{
|
|
if(this.#idx !== 0)
|
|
this.#shrink();
|
|
|
|
if(this.length >= this.#bucketSize * this.#bucketCount)
|
|
this.#bucketCount++;
|
|
|
|
this.#arr.length = this.#bucketSize * this.#bucketCount;
|
|
}
|
|
}
|
|
export class LinkedList<T>
|
|
{
|
|
#head: LinkedElmt<T> | null;
|
|
constructor()
|
|
{
|
|
this.#head = null;
|
|
}
|
|
get empty(): boolean
|
|
{
|
|
return this.#head === null;
|
|
}
|
|
add(item: T): void
|
|
{
|
|
const head = this.#head;
|
|
|
|
this.#head = { elmt: item, next: head };
|
|
}
|
|
pop(): T | null
|
|
{
|
|
if(this.#head === null)
|
|
return null;
|
|
|
|
const head = this.#head;
|
|
this.#head = head.next;
|
|
|
|
return head.elmt;
|
|
}
|
|
peek(): T | null
|
|
{
|
|
return this.#head?.elmt ?? null;
|
|
}
|
|
forEach(cb: (e: T, i: number) => void): void
|
|
{
|
|
let current = this.#head, i = 0;
|
|
while(current)
|
|
{
|
|
cb(current.elmt, i++);
|
|
|
|
current = current.next;
|
|
}
|
|
}
|
|
toArray(): T[]
|
|
{
|
|
const result: T[] = [];
|
|
this.forEach(e => result.push(e));
|
|
return result;
|
|
}
|
|
clear()
|
|
{
|
|
this.#head = null;
|
|
}
|
|
}
|
|
interface LinkedElmt<T>
|
|
{
|
|
elmt: T;
|
|
|
|
next: LinkedElmt<T> | null;
|
|
}
|
|
export function clamp(x: number, min: number, max: number): number
|
|
{
|
|
return x > max ? max : x < min ? min : x;
|
|
}
|
|
export function lerp(x: number, a: number, b: number): number
|
|
{
|
|
return (1-x)*a+x*b;
|
|
}
|
|
export class Random
|
|
{
|
|
#seed: number;
|
|
#hasher: () => number;
|
|
|
|
constructor(seed: number)
|
|
{
|
|
this.#seed = seed;
|
|
this.#hasher = mb32(hash(seed));
|
|
}
|
|
next(): number
|
|
{
|
|
return this.#hasher();
|
|
}
|
|
nextInt(min?: number, max?: number): number
|
|
{
|
|
return Math.floor(this.nextFloat(min, max));
|
|
}
|
|
nextFloat(min?: number, max?: number): number
|
|
{
|
|
if(min === undefined && max === undefined)
|
|
{
|
|
min = 0;
|
|
max = 1;
|
|
}
|
|
else if(max === undefined)
|
|
{
|
|
max = min;
|
|
min = 0;
|
|
}
|
|
else if(min === undefined)
|
|
{
|
|
min = 0;
|
|
}
|
|
|
|
return lerp(this.#hasher() / 2**32, min!, max!);
|
|
}
|
|
}
|
|
|
|
const hash = (n: number) => Math.imul(n,2654435761) >>> 0;
|
|
const mb32 = (a: number) => (t?: number) => (a = a + 1831565813|0, t = Math.imul(a^a>>>15,1|a), t = t + Math.imul(t^t>>>7,61|t)^t, (t^t>>>14)>>>0); |