]> git.saurik.com Git - logizomai.git/blame - lib/index.ts
Make finalize protected, to clean up declarations.
[logizomai.git] / lib / index.ts
CommitLineData
b8d9821a
JF
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
22export abstract class Resource {
23 private retainers: number;
24
25 constructor() {
26 this.retainers = 0;
27 }
28
29 public retain(): void {
30 ++this.retainers;
31 }
32
33 public release(): void {
34 if (--this.retainers === 0)
35 this.finalize();
36 }
37
5bf7f887 38 protected finalize(): void {
b8d9821a
JF
39 for (let prototype = this; prototype !== null; prototype = Object.getPrototypeOf(prototype))
40 if (prototype.hasOwnProperty("@resource"))
41 for (const property of (prototype as any)["@resource"] as string[])
42 (this as any)[property] = null;
43 }
44}
45
46export function resource(target: any, property: string): void {
47 let resources = target["@resource"];
48 if (resources === undefined)
49 target["@resource"] = resources = [];
50 resources.push(property);
51
52 const mangled = "@resource " + property;
53
54 Object.defineProperty(target, mangled, {
55 enumerable: false,
56 writable: true,
57 value: null,
58 });
59
60 Object.defineProperty(target, property, {
61 enumerable: false,
62
63 get: function() {
64 return (this as any)[mangled] as Resource | null;
65 },
66
67 set: function(value) {
68 const old = (this as any)[mangled] as Resource | null;
69 if (old === value)
70 return;
71 if (value !== null)
72 value.retain();
73 (this as any)[mangled] = value;
74 if (old !== null)
75 old.release();
76 },
77 });
78}
79
80export function using<Type extends Resource, Value>(resource: Type, code: (resource: Type) => Value): Value {
81 resource.retain();
82 let release = true;
83 try {
84 const value = code(resource);
85 if (!(value instanceof Promise))
86 return value;
87 release = false;
88 return (value as any as Promise<any>).then((value) => {
89 resource.release();
90 return value;
91 }) as any as Value;
92 } finally {
93 if (release)
94 resource.release();
95 }
96}
97
98export class ResourceSet<Value extends Resource> extends Resource {
99 private readonly set: Set<Value>;
100
101 constructor() { super();
102 this.set = new Set<Value>();
103 }
104
5bf7f887 105 protected finalize(): void {
b8d9821a
JF
106 this.clear();
107 super.finalize();
108 }
109
110 public clear(): void {
111 for (const value of this.set.values())
112 value.release();
113 return this.set.clear();
114 }
115
116 public has(value: Value): boolean {
117 return this.set.has(value);
118 }
119
120 public add(value: Value): this {
121 // .add() should return a boolean
122 // this is simply incompetence :/
123 if (!this.set.has(value)) {
124 value.retain();
125 this.set.add(value);
126 }
127 return this;
128 }
129
130 public delete(value: Value): boolean {
131 const deleted = this.set.delete(value);
132 if (deleted)
133 value.release();
134 return deleted;
135 }
136
137 public values(): IterableIterator<Value> {
138 return this.set.values();
139 }
140
141 public get size(): number {
142 return this.set.size;
143 }
144
145 public [Symbol.iterator](): IterableIterator<Value> {
146 return this.set[Symbol.iterator]();
147 }
148}
149
150export class FutureSet<Value extends Resource> extends ResourceSet<Value> {
151 private waiters: Set<(value: Value | null) => void> | null;
152
153 constructor() { super();
154 this.waiters = null;
155 }
156
5bf7f887 157 protected finalize(): void {
b8d9821a
JF
158 this.cancel();
159 super.finalize();
160 }
161
162 public cancel(): void {
163 const waiters = this.waiters;
164 this.waiters = null;
165 if (waiters !== null)
166 for (const waiter of waiters)
167 waiter(null);
168 }
169
170 public get(code?: () => void): Promise<Value> {
171 return new Promise<Value>((resolve, reject) => {
172 if (this.size !== 0)
173 resolve(this.values().next().value);
174 else {
175 if (this.waiters === null)
176 this.waiters = new Set<(value: Value | null) => void>();
177 this.waiters.add((value: Value | null) => {
178 if (value === null)
179 reject();
180 else
181 resolve(value);
182 });
183 if (code !== undefined)
184 code();
185 }
186 });
187 }
188
189 public add(value: Value): this {
190 const result = super.add(value);
191 const waiters = this.waiters;
192 this.waiters = null;
193 if (waiters !== null)
194 for (const waiter of waiters)
195 waiter(value);
196 return result;
197 }
198}
199
200export class ResourceMap<Key, Value extends Resource> extends Resource {
201 private readonly map: Map<Key, Value>;
202
203 constructor() { super();
204 this.map = new Map<Key, Value>();
205 }
206
5bf7f887 207 protected finalize(): void {
b8d9821a
JF
208 this.clear();
209 super.finalize();
210 }
211
212 public clear(): void {
213 for (const value of this.map.values())
214 value.release();
215 return this.map.clear();
216 }
217
218 public has(key: Key): boolean {
219 return this.map.has(key);
220 }
221
222 public get(key: Key): Value | undefined {
223 return this.map.get(key);
224 }
225
226 public set(key: Key, value: Value): this {
227 // .set() should return old value
228 // this is simply incompetence :/
229 const old = this.map.get(key);
230 if (old !== value) {
231 if (value !== undefined && value !== null)
232 value.retain();
233 this.map.set(key, value);
234 if (old !== undefined && old !== null)
235 old.release();
236 }
237 return this;
238 }
239
240 public vet(key: Key, code: () => Value): Value {
241 const old = this.map.get(key);
242 if (old !== undefined)
243 return old;
244 const value = code();
245 if (value !== null)
246 value.retain();
247 this.map.set(key, value);
248 return value;
249 }
250
251 public delete(key: Key): boolean {
252 // .delete() should return old value
253 // since undefined is also a *value*
254 // you can't use .get() to .delete()
255 // this is all stupid incompetent :/
256 const old = this.map.get(key);
257 const deleted = this.map.delete(key);
258 if (old !== undefined)
259 old.release();
260 return deleted;
261 }
262
263 public values(): IterableIterator<Value> {
264 return this.map.values();
265 }
266
267 public get size(): number {
268 return this.map.size;
269 }
270}
271
272export class ResourceArray<Value extends Resource | null> extends Resource {
273 private readonly array: Value[];
274
275 constructor(size: number = 0) { super();
276 this.array = new Array(size).fill(null);
277 }
278
5bf7f887 279 protected finalize(): void {
b8d9821a
JF
280 for (const value of this.array)
281 if (value !== null)
282 value.release();
283 this.array.length = 0;
284 }
285
286 public fill(value: Value): this {
287 const array = this.array;
288 for (let index = 0; index !== array.length; ++index) {
289 const old = array[index];
290 if (old === value)
291 continue;
292
293 if (value !== null)
294 value.retain();
295 array[index] = value;
296 if (old !== null)
297 old.release();
298 }
299
300 return this;
301 }
302
303 public map<T>(code: (value: Value) => T): T[] {
304 return this.array.map(code);
305 }
306
307 public get length(): number {
308 return this.array.length;
309 }
310
311 public get(index: number): Value {
312 if (index < 0 || (index | 0) !== index) throw new Error();
313 if (index >= this.array.length) throw new Error();
314 return this.array[index];
315 }
316
317 public set(index: number, value: Value): void {
318 if (index < 0 || (index | 0) !== index) throw new Error();
319 if (index >= this.array.length) throw new Error();
320 const old = this.array[index];
321 if (value !== null)
322 value.retain();
323 this.array[index] = value;
324 if (old !== null)
325 old.release();
326 }
327
328 public [Symbol.iterator](): IterableIterator<Value> {
329 return this.array[Symbol.iterator]();
330 }
331}
332
333export class Scoped<T> extends Resource {
334 public readonly value: T;
335 private readonly remove: () => void;
336
337 constructor(value: T, remove: () => void) { super();
338 this.value = value;
339 this.remove = remove;
340 }
341
5bf7f887 342 protected finalize(): void {
b8d9821a
JF
343 this.remove();
344 super.finalize();
345 }
346}