mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-05 03:37:50 +08:00
reconstruct: autocraft algorithm; feature: rust-style result
reconstruct: - move queue and sortedarray to dir datatype - move semaphore and readwritelock to dir mutex - reconstruct autocraft search algorithm, use hashmap instead of forloop - adjust some code style feature: - add rust-style result lib
This commit is contained in:
171
src/lib/mutex/Semaphore.ts
Normal file
171
src/lib/mutex/Semaphore.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { SortedArray } from "../datatype/SortedArray";
|
||||
|
||||
const E_CANCELED = new Error("Request canceled");
|
||||
// const E_INSUFFICIENT_RESOURCES = new Error("Insufficient resources");
|
||||
|
||||
interface QueueEntry {
|
||||
resolve(result: [number, () => void]): void;
|
||||
reject(error: unknown): void;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
interface Waiter {
|
||||
resolve(): void;
|
||||
}
|
||||
|
||||
type Releaser = () => void;
|
||||
|
||||
export class Semaphore {
|
||||
private _value: number;
|
||||
private _cancelError: Error;
|
||||
private _queue = new SortedArray<QueueEntry>();
|
||||
private _waiters = new SortedArray<Waiter>();
|
||||
|
||||
constructor(value: number, cancelError: Error = E_CANCELED) {
|
||||
if (value < 0) {
|
||||
throw new Error("Semaphore value must be non-negative");
|
||||
}
|
||||
this._value = value;
|
||||
this._cancelError = cancelError;
|
||||
}
|
||||
|
||||
acquire(weight = 1, priority = 0): Promise<[number, Releaser]> {
|
||||
if (weight <= 0) {
|
||||
throw new Error(`invalid weight ${weight}: must be positive`);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const entry: QueueEntry = { resolve, reject, weight };
|
||||
|
||||
if (this._queue.toArray().length === 0 && weight <= this._value) {
|
||||
this._dispatchItem(entry);
|
||||
} else {
|
||||
this._queue.push({ priority, data: entry });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryAcquire(weight = 1): Releaser | undefined {
|
||||
if (weight <= 0) {
|
||||
throw new Error(`invalid weight ${weight}: must be positive`);
|
||||
}
|
||||
|
||||
if (weight > this._value || this._queue.toArray().length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this._value -= weight;
|
||||
return this._newReleaser(weight);
|
||||
}
|
||||
|
||||
async runExclusive<T>(
|
||||
callback: (value: number) => T | Promise<T>,
|
||||
weight = 1,
|
||||
priority = 0,
|
||||
): Promise<T> {
|
||||
const [value, release] = await this.acquire(weight, priority);
|
||||
try {
|
||||
return await callback(value);
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
waitForUnlock(weight = 1, priority = 0): Promise<void> {
|
||||
if (weight <= 0) {
|
||||
throw new Error(`invalid weight ${weight}: must be positive`);
|
||||
}
|
||||
|
||||
if (this._couldLockImmediately(weight)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const waiter: Waiter = { resolve };
|
||||
this._waiters.push({ priority, data: waiter });
|
||||
});
|
||||
}
|
||||
|
||||
isLocked(): boolean {
|
||||
return this._value <= 0;
|
||||
}
|
||||
|
||||
getValue(): number {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
setValue(value: number): void {
|
||||
if (value < 0) {
|
||||
throw new Error("Semaphore value must be non-negative");
|
||||
}
|
||||
this._value = value;
|
||||
this._dispatchQueue();
|
||||
}
|
||||
|
||||
release(weight = 1): void {
|
||||
if (weight <= 0) {
|
||||
throw new Error(`invalid weight ${weight}: must be positive`);
|
||||
}
|
||||
this._value += weight;
|
||||
this._dispatchQueue();
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
const queueItems = this._queue.toArray();
|
||||
queueItems.forEach((entry) => entry.reject(this._cancelError));
|
||||
this._queue.clear();
|
||||
|
||||
const waiters = this._waiters.toArray();
|
||||
waiters.forEach((waiter) => waiter.resolve());
|
||||
this._waiters.clear();
|
||||
}
|
||||
|
||||
private _dispatchQueue(): void {
|
||||
this._drainWaiters();
|
||||
|
||||
let next = this._peek();
|
||||
while (next && next.weight <= this._value) {
|
||||
const item = this._queue.shift();
|
||||
if (item) {
|
||||
this._dispatchItem(item);
|
||||
}
|
||||
this._drainWaiters();
|
||||
next = this._peek();
|
||||
}
|
||||
}
|
||||
|
||||
private _dispatchItem(item: QueueEntry): void {
|
||||
const previousValue = this._value;
|
||||
this._value -= item.weight;
|
||||
item.resolve([previousValue, this._newReleaser(item.weight)]);
|
||||
}
|
||||
|
||||
private _peek(): QueueEntry | undefined {
|
||||
return this._queue.peek();
|
||||
}
|
||||
|
||||
private _newReleaser(weight: number): Releaser {
|
||||
let called = false;
|
||||
return () => {
|
||||
if (called) return;
|
||||
called = true;
|
||||
this.release(weight);
|
||||
};
|
||||
}
|
||||
|
||||
private _drainWaiters(): void {
|
||||
const waiters = this._waiters.toArray();
|
||||
if (waiters.length === 0) return;
|
||||
|
||||
// If no queue or resources available, resolve all waiters
|
||||
const hasQueue = this._queue.toArray().length > 0;
|
||||
if (!hasQueue || this._value > 0) {
|
||||
waiters.forEach((waiter) => waiter.resolve());
|
||||
this._waiters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private _couldLockImmediately(weight: number): boolean {
|
||||
return this._queue.toArray().length === 0 && weight <= this._value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user