]> git.saurik.com Git - logizomai.git/blob - src/index.ts
For some reason I am supposed to commit this file.
[logizomai.git] / src / 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 async _(): Promise<this> {
32 return this;
33 }
34
35 public retain(): void {
36 ++this.retainers;
37 }
38
39 public release(): void {
40 if (--this.retainers === 0)
41 this.finalize();
42 }
43
44 protected finalize(): void {
45 for (let prototype = this; prototype !== null; prototype = Object.getPrototypeOf(prototype))
46 if (prototype.hasOwnProperty("@resource"))
47 for (const property of (prototype as any)["@resource"] as string[])
48 (this as any)[property] = null;
49 for (const finalizes of this.finalizes)
50 finalizes();
51 }
52 }
53
54 export function resource(target: any, property: string): void {
55 let resources = target["@resource"];
56 if (resources === undefined)
57 target["@resource"] = resources = [];
58 resources.push(property);
59
60 const mangled = "@resource " + property;
61
62 Object.defineProperty(target, mangled, {
63 enumerable: false,
64 writable: true,
65 value: null,
66 });
67
68 Object.defineProperty(target, property, {
69 enumerable: false,
70
71 get: function() {
72 return (this as any)[mangled] as Resource | null;
73 },
74
75 set: function(value) {
76 const old = (this as any)[mangled] as Resource | null;
77 if (old === value)
78 return;
79 if (value !== null)
80 value.retain();
81 (this as any)[mangled] = value;
82 if (old !== null)
83 old.release();
84 },
85 });
86 }
87
88 export function using<Type extends Resource, Value>(resource: Type, code: (resource: Type) => Value): Value {
89 resource.retain();
90 let release = true;
91 try {
92 const value = code(resource);
93 if (!(value instanceof Promise))
94 return value;
95 release = false;
96 return (value as any as Promise<any>).then((value) => {
97 resource.release();
98 return value;
99 }) as any as Value;
100 } finally {
101 if (release)
102 resource.release();
103 }
104 }
105
106 export function construct(target: any, property: string, descriptor: PropertyDescriptor) {
107 const old = descriptor.value;
108 descriptor.value = async function(this: Resource): Promise<any> {
109 const parent = target.__proto__;
110 await parent._.call(this); try {
111 return await old.call(this);
112 } catch (error) {
113 parent.finalize.call(this);
114 throw error; }
115 };
116 }
117
118 interface Waitable<Value> {
119 size: number;
120 values(): IterableIterator<Value>;
121 waiters: Set<(value: Value | null) => void> | null;
122 }
123
124 function Get<Value>(set: Waitable<Value>, code?: () => void): Promise<Value> {
125 return new Promise<Value>((resolve, reject) => {
126 if (set.size !== 0)
127 resolve(set.values().next().value);
128 else {
129 if (set.waiters === null)
130 set.waiters = new Set<(value: Value | null) => void>();
131 set.waiters.add((value: Value | null) => {
132 if (value === null)
133 reject();
134 else
135 resolve(value);
136 });
137 if (code !== undefined)
138 code();
139 }
140 });
141 }
142
143 export class ResourceSet<Value extends Resource> extends Resource {
144 private readonly set: Set<Value>;
145 public waiters: Set<(value: Value | null) => void> | null;
146
147 constructor() { super();
148 this.set = new Set<Value>();
149 this.waiters = null;
150 }
151
152 protected finalize(): void {
153 this.cancel();
154 this.clear();
155 super.finalize();
156 }
157
158 public clear(): void {
159 for (const value of this.set.values())
160 value.release();
161 return this.set.clear();
162 }
163
164 public cancel(): void {
165 const waiters = this.waiters;
166 this.waiters = null;
167 if (waiters !== null)
168 for (const waiter of waiters)
169 waiter(null);
170 }
171
172 public has(value: Value): boolean {
173 return this.set.has(value);
174 }
175
176 public get(code?: () => void): Promise<Value> {
177 return Get(this, code);
178 }
179
180 public add(value: Value): this {
181 // .add() should return a boolean
182 // this is simply incompetence :/
183 if (!this.set.has(value)) {
184 value.retain();
185 this.set.add(value);
186 }
187
188 const waiters = this.waiters;
189 this.waiters = null;
190 if (waiters !== null)
191 for (const waiter of waiters)
192 waiter(value);
193
194 return this;
195 }
196
197 public delete(value: Value): boolean {
198 const deleted = this.set.delete(value);
199 if (deleted)
200 value.release();
201 return deleted;
202 }
203
204 public values(): IterableIterator<Value> {
205 return this.set.values();
206 }
207
208 public get size(): number {
209 return this.set.size;
210 }
211
212 public [Symbol.iterator](): IterableIterator<Value> {
213 return this.set[Symbol.iterator]();
214 }
215 }
216
217 export class FutureSet<Value> extends Resource {
218 private readonly set: Set<Value>;
219 public waiters: Set<(value: Value | null) => void> | null;
220
221 constructor() { super();
222 this.set = new Set<Value>();
223 this.waiters = null;
224 }
225
226 protected finalize(): void {
227 this.cancel();
228 this.clear();
229 super.finalize();
230 }
231
232 public clear(): void {
233 return this.set.clear();
234 }
235
236 public cancel(): void {
237 const waiters = this.waiters;
238 this.waiters = null;
239 if (waiters !== null)
240 for (const waiter of waiters)
241 waiter(null);
242 }
243
244 public has(value: Value): boolean {
245 return this.set.has(value);
246 }
247
248 public get(code?: () => void): Promise<Value> {
249 return Get(this, code);
250 }
251
252 public add(value: Value): this {
253 this.set.add(value);
254 return this;
255 }
256
257 public delete(value: Value): boolean {
258 return this.set.delete(value);
259 }
260
261 public values(): IterableIterator<Value> {
262 return this.set.values();
263 }
264
265 public get size(): number {
266 return this.set.size;
267 }
268
269 public [Symbol.iterator](): IterableIterator<Value> {
270 return this.set[Symbol.iterator]();
271 }
272 }
273
274 export class ResourceMap<Key, Value extends Resource> extends Resource {
275 private readonly map: Map<Key, Value>;
276
277 constructor() { super();
278 this.map = new Map<Key, Value>();
279 }
280
281 protected finalize(): void {
282 this.clear();
283 super.finalize();
284 }
285
286 public clear(): void {
287 for (const value of this.map.values())
288 value.release();
289 return this.map.clear();
290 }
291
292 public has(key: Key): boolean {
293 return this.map.has(key);
294 }
295
296 public get(key: Key): Value | undefined {
297 return this.map.get(key);
298 }
299
300 public set(key: Key, value: Value): this {
301 // .set() should return old value
302 // this is simply incompetence :/
303 const old = this.map.get(key);
304 if (old !== value) {
305 if (value !== undefined && value !== null)
306 value.retain();
307 this.map.set(key, value);
308 if (old !== undefined && old !== null)
309 old.release();
310 }
311 return this;
312 }
313
314 public vet(key: Key, code: () => Value): Value {
315 const old = this.map.get(key);
316 if (old !== undefined)
317 return old;
318 const value = code();
319 if (value !== null)
320 value.retain();
321 this.map.set(key, value);
322 return value;
323 }
324
325 public delete(key: Key): boolean {
326 // .delete() should return old value
327 // since undefined is also a *value*
328 // you can't use .get() to .delete()
329 // this is all stupid incompetent :/
330 const old = this.map.get(key);
331 const deleted = this.map.delete(key);
332 if (old !== undefined)
333 old.release();
334 return deleted;
335 }
336
337 public keys(): IterableIterator<Key> {
338 return this.map.keys();
339 }
340
341 public values(): IterableIterator<Value> {
342 return this.map.values();
343 }
344
345 public get size(): number {
346 return this.map.size;
347 }
348 }
349
350 export class ResourceArray<Value extends Resource | null> extends Resource {
351 private readonly array: Value[];
352
353 constructor(size: number = 0) { super();
354 this.array = new Array(size).fill(null);
355 }
356
357 protected finalize(): void {
358 for (const value of this.array)
359 if (value !== null)
360 value.release();
361 this.array.length = 0;
362 super.finalize();
363 }
364
365 public fill(value: Value): this {
366 const array = this.array;
367 for (let index = 0; index !== array.length; ++index) {
368 const old = array[index];
369 if (old === value)
370 continue;
371
372 if (value !== null)
373 value.retain();
374 array[index] = value;
375 if (old !== null)
376 old.release();
377 }
378
379 return this;
380 }
381
382 public map<T>(code: (value: Value) => T): T[] {
383 return this.array.map(code);
384 }
385
386 public get length(): number {
387 return this.array.length;
388 }
389
390 public get(index: number): Value {
391 if (index < 0 || (index | 0) !== index) throw new Error();
392 if (index >= this.array.length) throw new Error();
393 return this.array[index];
394 }
395
396 public set(index: number, value: Value): void {
397 if (index < 0 || (index | 0) !== index) throw new Error();
398 if (index >= this.array.length) throw new Error();
399 const old = this.array[index];
400 if (value !== null)
401 value.retain();
402 this.array[index] = value;
403 if (old !== null)
404 old.release();
405 }
406
407 public [Symbol.iterator](): IterableIterator<Value> {
408 return this.array[Symbol.iterator]();
409 }
410 }
411
412 export class Scoped<T> extends Resource {
413 public readonly value: T;
414 private readonly remove: () => void;
415
416 constructor(value: T, remove: () => void) { super();
417 this.value = value;
418 this.remove = remove;
419 }
420
421 protected finalize(): void {
422 this.remove();
423 super.finalize();
424 }
425 }