]> git.saurik.com Git - logizomai.git/blob - lib/index.ts
41da6f5a3917e0b24ba6b7626b63ebb654069963
[logizomai.git] / lib / index.ts
1 /* Logizomai - Reference Counting for TypeScript
2 * Copyright (C) 2017 Jay Freeman (saurik)
3 */
4
5 /* GNU Affero General Public License, Version 3 {{{ */
6 /*
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 export abstract class Resource {
23 private retainers: number;
24 public finalizes: Array<() => void>;
25
26 constructor() {
27 this.retainers = 0;
28 this.finalizes = [];
29 }
30
31 public retain(): void {
32 ++this.retainers;
33 }
34
35 public release(): void {
36 if (--this.retainers === 0)
37 this.finalize();
38 }
39
40 protected finalize(): void {
41 for (let prototype = this; prototype !== null; prototype = Object.getPrototypeOf(prototype))
42 if (prototype.hasOwnProperty("@resource"))
43 for (const property of (prototype as any)["@resource"] as string[])
44 (this as any)[property] = null;
45 for (const finalizes of this.finalizes)
46 finalizes();
47 }
48 }
49
50 export function resource(target: any, property: string): void {
51 let resources = target["@resource"];
52 if (resources === undefined)
53 target["@resource"] = resources = [];
54 resources.push(property);
55
56 const mangled = "@resource " + property;
57
58 Object.defineProperty(target, mangled, {
59 enumerable: false,
60 writable: true,
61 value: null,
62 });
63
64 Object.defineProperty(target, property, {
65 enumerable: false,
66
67 get: function() {
68 return (this as any)[mangled] as Resource | null;
69 },
70
71 set: function(value) {
72 const old = (this as any)[mangled] as Resource | null;
73 if (old === value)
74 return;
75 if (value !== null)
76 value.retain();
77 (this as any)[mangled] = value;
78 if (old !== null)
79 old.release();
80 },
81 });
82 }
83
84 export function using<Type extends Resource, Value>(resource: Type, code: (resource: Type) => Value): Value {
85 resource.retain();
86 let release = true;
87 try {
88 const value = code(resource);
89 if (!(value instanceof Promise))
90 return value;
91 release = false;
92 return (value as any as Promise<any>).then((value) => {
93 resource.release();
94 return value;
95 }) as any as Value;
96 } finally {
97 if (release)
98 resource.release();
99 }
100 }
101
102 interface Waitable<Value> {
103 size: number;
104 values(): IterableIterator<Value>;
105 waiters: Set<(value: Value | null) => void> | null;
106 }
107
108 function Get<Value>(set: Waitable<Value>, code?: () => void): Promise<Value> {
109 return new Promise<Value>((resolve, reject) => {
110 if (set.size !== 0)
111 resolve(set.values().next().value);
112 else {
113 if (set.waiters === null)
114 set.waiters = new Set<(value: Value | null) => void>();
115 set.waiters.add((value: Value | null) => {
116 if (value === null)
117 reject();
118 else
119 resolve(value);
120 });
121 if (code !== undefined)
122 code();
123 }
124 });
125 }
126
127 export class ResourceSet<Value extends Resource> extends Resource {
128 private readonly set: Set<Value>;
129 public waiters: Set<(value: Value | null) => void> | null;
130
131 constructor() { super();
132 this.set = new Set<Value>();
133 this.waiters = null;
134 }
135
136 protected finalize(): void {
137 this.cancel();
138 this.clear();
139 super.finalize();
140 }
141
142 public clear(): void {
143 for (const value of this.set.values())
144 value.release();
145 return this.set.clear();
146 }
147
148 public cancel(): void {
149 const waiters = this.waiters;
150 this.waiters = null;
151 if (waiters !== null)
152 for (const waiter of waiters)
153 waiter(null);
154 }
155
156 public has(value: Value): boolean {
157 return this.set.has(value);
158 }
159
160 public get(code?: () => void): Promise<Value> {
161 return Get(this, code);
162 }
163
164 public add(value: Value): this {
165 // .add() should return a boolean
166 // this is simply incompetence :/
167 if (!this.set.has(value)) {
168 value.retain();
169 this.set.add(value);
170 }
171
172 const waiters = this.waiters;
173 this.waiters = null;
174 if (waiters !== null)
175 for (const waiter of waiters)
176 waiter(value);
177
178 return this;
179 }
180
181 public delete(value: Value): boolean {
182 const deleted = this.set.delete(value);
183 if (deleted)
184 value.release();
185 return deleted;
186 }
187
188 public values(): IterableIterator<Value> {
189 return this.set.values();
190 }
191
192 public get size(): number {
193 return this.set.size;
194 }
195
196 public [Symbol.iterator](): IterableIterator<Value> {
197 return this.set[Symbol.iterator]();
198 }
199 }
200
201 export class FutureSet<Value> extends Resource {
202 private readonly set: Set<Value>;
203 public waiters: Set<(value: Value | null) => void> | null;
204
205 constructor() { super();
206 this.set = new Set<Value>();
207 this.waiters = null;
208 }
209
210 protected finalize(): void {
211 this.cancel();
212 this.clear();
213 super.finalize();
214 }
215
216 public clear(): void {
217 return this.set.clear();
218 }
219
220 public cancel(): void {
221 const waiters = this.waiters;
222 this.waiters = null;
223 if (waiters !== null)
224 for (const waiter of waiters)
225 waiter(null);
226 }
227
228 public has(value: Value): boolean {
229 return this.set.has(value);
230 }
231
232 public get(code?: () => void): Promise<Value> {
233 return Get(this, code);
234 }
235
236 public add(value: Value): this {
237 this.set.add(value);
238 return this;
239 }
240
241 public delete(value: Value): boolean {
242 return this.set.delete(value);
243 }
244
245 public values(): IterableIterator<Value> {
246 return this.set.values();
247 }
248
249 public get size(): number {
250 return this.set.size;
251 }
252
253 public [Symbol.iterator](): IterableIterator<Value> {
254 return this.set[Symbol.iterator]();
255 }
256 }
257
258 export class ResourceMap<Key, Value extends Resource> extends Resource {
259 private readonly map: Map<Key, Value>;
260
261 constructor() { super();
262 this.map = new Map<Key, Value>();
263 }
264
265 protected finalize(): void {
266 this.clear();
267 super.finalize();
268 }
269
270 public clear(): void {
271 for (const value of this.map.values())
272 value.release();
273 return this.map.clear();
274 }
275
276 public has(key: Key): boolean {
277 return this.map.has(key);
278 }
279
280 public get(key: Key): Value | undefined {
281 return this.map.get(key);
282 }
283
284 public set(key: Key, value: Value): this {
285 // .set() should return old value
286 // this is simply incompetence :/
287 const old = this.map.get(key);
288 if (old !== value) {
289 if (value !== undefined && value !== null)
290 value.retain();
291 this.map.set(key, value);
292 if (old !== undefined && old !== null)
293 old.release();
294 }
295 return this;
296 }
297
298 public vet(key: Key, code: () => Value): Value {
299 const old = this.map.get(key);
300 if (old !== undefined)
301 return old;
302 const value = code();
303 if (value !== null)
304 value.retain();
305 this.map.set(key, value);
306 return value;
307 }
308
309 public delete(key: Key): boolean {
310 // .delete() should return old value
311 // since undefined is also a *value*
312 // you can't use .get() to .delete()
313 // this is all stupid incompetent :/
314 const old = this.map.get(key);
315 const deleted = this.map.delete(key);
316 if (old !== undefined)
317 old.release();
318 return deleted;
319 }
320
321 public keys(): IterableIterator<Key> {
322 return this.map.keys();
323 }
324
325 public values(): IterableIterator<Value> {
326 return this.map.values();
327 }
328
329 public get size(): number {
330 return this.map.size;
331 }
332 }
333
334 export class ResourceArray<Value extends Resource | null> extends Resource {
335 private readonly array: Value[];
336
337 constructor(size: number = 0) { super();
338 this.array = new Array(size).fill(null);
339 }
340
341 protected finalize(): void {
342 for (const value of this.array)
343 if (value !== null)
344 value.release();
345 this.array.length = 0;
346 super.finalize();
347 }
348
349 public fill(value: Value): this {
350 const array = this.array;
351 for (let index = 0; index !== array.length; ++index) {
352 const old = array[index];
353 if (old === value)
354 continue;
355
356 if (value !== null)
357 value.retain();
358 array[index] = value;
359 if (old !== null)
360 old.release();
361 }
362
363 return this;
364 }
365
366 public map<T>(code: (value: Value) => T): T[] {
367 return this.array.map(code);
368 }
369
370 public get length(): number {
371 return this.array.length;
372 }
373
374 public get(index: number): Value {
375 if (index < 0 || (index | 0) !== index) throw new Error();
376 if (index >= this.array.length) throw new Error();
377 return this.array[index];
378 }
379
380 public set(index: number, value: Value): void {
381 if (index < 0 || (index | 0) !== index) throw new Error();
382 if (index >= this.array.length) throw new Error();
383 const old = this.array[index];
384 if (value !== null)
385 value.retain();
386 this.array[index] = value;
387 if (old !== null)
388 old.release();
389 }
390
391 public [Symbol.iterator](): IterableIterator<Value> {
392 return this.array[Symbol.iterator]();
393 }
394 }
395
396 export class Scoped<T> extends Resource {
397 public readonly value: T;
398 private readonly remove: () => void;
399
400 constructor(value: T, remove: () => void) { super();
401 this.value = value;
402 this.remove = remove;
403 }
404
405 protected finalize(): void {
406 this.remove();
407 super.finalize();
408 }
409 }