-/* Logizomai - Reference Counting for TypeScript
- * Copyright (C) 2017 Jay Freeman (saurik)
-*/
-
-/* GNU Affero General Public License, Version 3 {{{ */
-/*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
-
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
-
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
-**/
-/* }}} */
-
-export abstract class Resource {
- private retainers: number;
- public finalizes: Array<() => void>;
-
- constructor() {
- this.retainers = 0;
- this.finalizes = [];
- }
-
- public async _(): Promise<this> {
- return this;
- }
-
- public retain(): void {
- ++this.retainers;
- }
-
- public release(): void {
- if (--this.retainers === 0)
- this.finalize();
- }
-
- protected finalize(): void {
- for (let prototype = this; prototype !== null; prototype = Object.getPrototypeOf(prototype))
- if (prototype.hasOwnProperty("@resource"))
- for (const property of (prototype as any)["@resource"] as string[])
- (this as any)[property] = null;
- for (const finalizes of this.finalizes)
- finalizes();
- }
-}
-
-export function resource(target: any, property: string): void {
- let resources = target["@resource"];
- if (resources === undefined)
- target["@resource"] = resources = [];
- resources.push(property);
-
- const mangled = "@resource " + property;
-
- Object.defineProperty(target, mangled, {
- enumerable: false,
- writable: true,
- value: null,
- });
-
- Object.defineProperty(target, property, {
- enumerable: false,
-
- get: function() {
- return (this as any)[mangled] as Resource | null;
- },
-
- set: function(value) {
- const old = (this as any)[mangled] as Resource | null;
- if (old === value)
- return;
- if (value !== null)
- value.retain();
- (this as any)[mangled] = value;
- if (old !== null)
- old.release();
- },
- });
-}
-
-export function using<Type extends Resource, Value>(resource: Type, code: (resource: Type) => Value): Value {
- resource.retain();
- let release = true;
- try {
- const value = code(resource);
- if (!(value instanceof Promise))
- return value;
- release = false;
- return (value as any as Promise<any>).then((value) => {
- resource.release();
- return value;
- }) as any as Value;
- } finally {
- if (release)
- resource.release();
- }
-}
-
-export function construct(target: any, property: string, descriptor: PropertyDescriptor) {
- const old = descriptor.value;
- descriptor.value = async function(this: Resource): Promise<any> {
- const parent = target.__proto__;
- await parent._.call(this); try {
- return await old.call(this);
- } catch (error) {
- parent.finalize.call(this);
- throw error; }
- };
-}
-
-interface Waitable<Value> {
- size: number;
- values(): IterableIterator<Value>;
- waiters: Set<(value: Value | null) => void> | null;
-}
-
-function Get<Value>(set: Waitable<Value>, code?: () => void): Promise<Value> {
- return new Promise<Value>((resolve, reject) => {
- if (set.size !== 0)
- resolve(set.values().next().value);
- else {
- if (set.waiters === null)
- set.waiters = new Set<(value: Value | null) => void>();
- set.waiters.add((value: Value | null) => {
- if (value === null)
- reject();
- else
- resolve(value);
- });
- if (code !== undefined)
- code();
- }
- });
-}
-
-export class ResourceSet<Value extends Resource> extends Resource {
- private readonly set: Set<Value>;
- public waiters: Set<(value: Value | null) => void> | null;
-
- constructor() { super();
- this.set = new Set<Value>();
- this.waiters = null;
- }
-
- protected finalize(): void {
- this.cancel();
- this.clear();
- super.finalize();
- }
-
- public clear(): void {
- for (const value of this.set.values())
- value.release();
- return this.set.clear();
- }
-
- public cancel(): void {
- const waiters = this.waiters;
- this.waiters = null;
- if (waiters !== null)
- for (const waiter of waiters)
- waiter(null);
- }
-
- public has(value: Value): boolean {
- return this.set.has(value);
- }
-
- public get(code?: () => void): Promise<Value> {
- return Get(this, code);
- }
-
- public add(value: Value): this {
- // .add() should return a boolean
- // this is simply incompetence :/
- if (!this.set.has(value)) {
- value.retain();
- this.set.add(value);
- }
-
- const waiters = this.waiters;
- this.waiters = null;
- if (waiters !== null)
- for (const waiter of waiters)
- waiter(value);
-
- return this;
- }
-
- public delete(value: Value): boolean {
- const deleted = this.set.delete(value);
- if (deleted)
- value.release();
- return deleted;
- }
-
- public values(): IterableIterator<Value> {
- return this.set.values();
- }
-
- public get size(): number {
- return this.set.size;
- }
-
- public [Symbol.iterator](): IterableIterator<Value> {
- return this.set[Symbol.iterator]();
- }
-}
-
-export class FutureSet<Value> extends Resource {
- private readonly set: Set<Value>;
- public waiters: Set<(value: Value | null) => void> | null;
-
- constructor() { super();
- this.set = new Set<Value>();
- this.waiters = null;
- }
-
- protected finalize(): void {
- this.cancel();
- this.clear();
- super.finalize();
- }
-
- public clear(): void {
- return this.set.clear();
- }
-
- public cancel(): void {
- const waiters = this.waiters;
- this.waiters = null;
- if (waiters !== null)
- for (const waiter of waiters)
- waiter(null);
- }
-
- public has(value: Value): boolean {
- return this.set.has(value);
- }
-
- public get(code?: () => void): Promise<Value> {
- return Get(this, code);
- }
-
- public add(value: Value): this {
- this.set.add(value);
- return this;
- }
-
- public delete(value: Value): boolean {
- return this.set.delete(value);
- }
-
- public values(): IterableIterator<Value> {
- return this.set.values();
- }
-
- public get size(): number {
- return this.set.size;
- }
-
- public [Symbol.iterator](): IterableIterator<Value> {
- return this.set[Symbol.iterator]();
- }
-}
-
-export class ResourceMap<Key, Value extends Resource> extends Resource {
- private readonly map: Map<Key, Value>;
-
- constructor() { super();
- this.map = new Map<Key, Value>();
- }
-
- protected finalize(): void {
- this.clear();
- super.finalize();
- }
-
- public clear(): void {
- for (const value of this.map.values())
- value.release();
- return this.map.clear();
- }
-
- public has(key: Key): boolean {
- return this.map.has(key);
- }
-
- public get(key: Key): Value | undefined {
- return this.map.get(key);
- }
-
- public set(key: Key, value: Value): this {
- // .set() should return old value
- // this is simply incompetence :/
- const old = this.map.get(key);
- if (old !== value) {
- if (value !== undefined && value !== null)
- value.retain();
- this.map.set(key, value);
- if (old !== undefined && old !== null)
- old.release();
- }
- return this;
- }
-
- public vet(key: Key, code: () => Value): Value {
- const old = this.map.get(key);
- if (old !== undefined)
- return old;
- const value = code();
- if (value !== null)
- value.retain();
- this.map.set(key, value);
- return value;
- }
-
- public delete(key: Key): boolean {
- // .delete() should return old value
- // since undefined is also a *value*
- // you can't use .get() to .delete()
- // this is all stupid incompetent :/
- const old = this.map.get(key);
- const deleted = this.map.delete(key);
- if (old !== undefined)
- old.release();
- return deleted;
- }
-
- public keys(): IterableIterator<Key> {
- return this.map.keys();
- }
-
- public values(): IterableIterator<Value> {
- return this.map.values();
- }
-
- public get size(): number {
- return this.map.size;
- }
-}
-
-export class ResourceArray<Value extends Resource | null> extends Resource {
- private readonly array: Value[];
-
- constructor(size: number = 0) { super();
- this.array = new Array(size).fill(null);
- }
-
- protected finalize(): void {
- for (const value of this.array)
- if (value !== null)
- value.release();
- this.array.length = 0;
- super.finalize();
- }
-
- public fill(value: Value): this {
- const array = this.array;
- for (let index = 0; index !== array.length; ++index) {
- const old = array[index];
- if (old === value)
- continue;
-
- if (value !== null)
- value.retain();
- array[index] = value;
- if (old !== null)
- old.release();
- }
-
- return this;
- }
-
- public map<T>(code: (value: Value) => T): T[] {
- return this.array.map(code);
- }
-
- public get length(): number {
- return this.array.length;
- }
-
- public get(index: number): Value {
- if (index < 0 || (index | 0) !== index) throw new Error();
- if (index >= this.array.length) throw new Error();
- return this.array[index];
- }
-
- public set(index: number, value: Value): void {
- if (index < 0 || (index | 0) !== index) throw new Error();
- if (index >= this.array.length) throw new Error();
- const old = this.array[index];
- if (value !== null)
- value.retain();
- this.array[index] = value;
- if (old !== null)
- old.release();
- }
-
- public [Symbol.iterator](): IterableIterator<Value> {
- return this.array[Symbol.iterator]();
- }
-}
-
-export class Scoped<T> extends Resource {
- public readonly value: T;
- private readonly remove: () => void;
-
- constructor(value: T, remove: () => void) { super();
- this.value = value;
- this.remove = remove;
- }
-
- protected finalize(): void {
- this.remove();
- super.finalize();
- }
-}