]> git.saurik.com Git - logizomai.git/blob - lib/index.ts
e2f70228a630e2bdf62ba3069e2f9fbfc590ff28
[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 export class ResourceSet<Value extends Resource> extends Resource {
103 private readonly set: Set<Value>;
104
105 constructor() { super();
106 this.set = new Set<Value>();
107 }
108
109 protected finalize(): void {
110 this.clear();
111 super.finalize();
112 }
113
114 public clear(): void {
115 for (const value of this.set.values())
116 value.release();
117 return this.set.clear();
118 }
119
120 public has(value: Value): boolean {
121 return this.set.has(value);
122 }
123
124 public add(value: Value): this {
125 // .add() should return a boolean
126 // this is simply incompetence :/
127 if (!this.set.has(value)) {
128 value.retain();
129 this.set.add(value);
130 }
131 return this;
132 }
133
134 public delete(value: Value): boolean {
135 const deleted = this.set.delete(value);
136 if (deleted)
137 value.release();
138 return deleted;
139 }
140
141 public values(): IterableIterator<Value> {
142 return this.set.values();
143 }
144
145 public get size(): number {
146 return this.set.size;
147 }
148
149 public [Symbol.iterator](): IterableIterator<Value> {
150 return this.set[Symbol.iterator]();
151 }
152 }
153
154 export class FutureSet<Value extends Resource> extends ResourceSet<Value> {
155 private waiters: Set<(value: Value | null) => void> | null;
156
157 constructor() { super();
158 this.waiters = null;
159 }
160
161 protected finalize(): void {
162 this.cancel();
163 super.finalize();
164 }
165
166 public cancel(): void {
167 const waiters = this.waiters;
168 this.waiters = null;
169 if (waiters !== null)
170 for (const waiter of waiters)
171 waiter(null);
172 }
173
174 public get(code?: () => void): Promise<Value> {
175 return new Promise<Value>((resolve, reject) => {
176 if (this.size !== 0)
177 resolve(this.values().next().value);
178 else {
179 if (this.waiters === null)
180 this.waiters = new Set<(value: Value | null) => void>();
181 this.waiters.add((value: Value | null) => {
182 if (value === null)
183 reject();
184 else
185 resolve(value);
186 });
187 if (code !== undefined)
188 code();
189 }
190 });
191 }
192
193 public add(value: Value): this {
194 const result = super.add(value);
195 const waiters = this.waiters;
196 this.waiters = null;
197 if (waiters !== null)
198 for (const waiter of waiters)
199 waiter(value);
200 return result;
201 }
202 }
203
204 export class ResourceMap<Key, Value extends Resource> extends Resource {
205 private readonly map: Map<Key, Value>;
206
207 constructor() { super();
208 this.map = new Map<Key, Value>();
209 }
210
211 protected finalize(): void {
212 this.clear();
213 super.finalize();
214 }
215
216 public clear(): void {
217 for (const value of this.map.values())
218 value.release();
219 return this.map.clear();
220 }
221
222 public has(key: Key): boolean {
223 return this.map.has(key);
224 }
225
226 public get(key: Key): Value | undefined {
227 return this.map.get(key);
228 }
229
230 public set(key: Key, value: Value): this {
231 // .set() should return old value
232 // this is simply incompetence :/
233 const old = this.map.get(key);
234 if (old !== value) {
235 if (value !== undefined && value !== null)
236 value.retain();
237 this.map.set(key, value);
238 if (old !== undefined && old !== null)
239 old.release();
240 }
241 return this;
242 }
243
244 public vet(key: Key, code: () => Value): Value {
245 const old = this.map.get(key);
246 if (old !== undefined)
247 return old;
248 const value = code();
249 if (value !== null)
250 value.retain();
251 this.map.set(key, value);
252 return value;
253 }
254
255 public delete(key: Key): boolean {
256 // .delete() should return old value
257 // since undefined is also a *value*
258 // you can't use .get() to .delete()
259 // this is all stupid incompetent :/
260 const old = this.map.get(key);
261 const deleted = this.map.delete(key);
262 if (old !== undefined)
263 old.release();
264 return deleted;
265 }
266
267 public values(): IterableIterator<Value> {
268 return this.map.values();
269 }
270
271 public get size(): number {
272 return this.map.size;
273 }
274 }
275
276 export class ResourceArray<Value extends Resource | null> extends Resource {
277 private readonly array: Value[];
278
279 constructor(size: number = 0) { super();
280 this.array = new Array(size).fill(null);
281 }
282
283 protected finalize(): void {
284 for (const value of this.array)
285 if (value !== null)
286 value.release();
287 this.array.length = 0;
288 }
289
290 public fill(value: Value): this {
291 const array = this.array;
292 for (let index = 0; index !== array.length; ++index) {
293 const old = array[index];
294 if (old === value)
295 continue;
296
297 if (value !== null)
298 value.retain();
299 array[index] = value;
300 if (old !== null)
301 old.release();
302 }
303
304 return this;
305 }
306
307 public map<T>(code: (value: Value) => T): T[] {
308 return this.array.map(code);
309 }
310
311 public get length(): number {
312 return this.array.length;
313 }
314
315 public get(index: number): Value {
316 if (index < 0 || (index | 0) !== index) throw new Error();
317 if (index >= this.array.length) throw new Error();
318 return this.array[index];
319 }
320
321 public set(index: number, value: Value): void {
322 if (index < 0 || (index | 0) !== index) throw new Error();
323 if (index >= this.array.length) throw new Error();
324 const old = this.array[index];
325 if (value !== null)
326 value.retain();
327 this.array[index] = value;
328 if (old !== null)
329 old.release();
330 }
331
332 public [Symbol.iterator](): IterableIterator<Value> {
333 return this.array[Symbol.iterator]();
334 }
335 }
336
337 export class Scoped<T> extends Resource {
338 public readonly value: T;
339 private readonly remove: () => void;
340
341 constructor(value: T, remove: () => void) { super();
342 this.value = value;
343 this.remove = remove;
344 }
345
346 protected finalize(): void {
347 this.remove();
348 super.finalize();
349 }
350 }