@pistonite/pure 0.26.8 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/fs/FsFileImpl.ts +3 -9
- package/src/fs/FsFileStandaloneImplHandleAPI.ts +2 -8
- package/src/fs/FsImplEntryAPI.ts +12 -48
- package/src/fs/FsImplFileAPI.ts +3 -12
- package/src/fs/FsImplHandleAPI.ts +12 -47
- package/src/fs/FsOpen.ts +27 -71
- package/src/fs/FsOpenFile.ts +2 -7
- package/src/fs/FsSave.ts +3 -10
- package/src/fs/FsSupportStatus.ts +1 -5
- package/src/fs/index.ts +2 -11
- package/src/log/index.ts +1 -5
- package/src/log/logger.ts +2 -6
- package/src/memory/async_erc.ts +17 -10
- package/src/memory/cell.ts +2 -8
- package/src/memory/emp.ts +88 -0
- package/src/memory/erc.test.ts +123 -126
- package/src/memory/erc.ts +12 -8
- package/src/memory/index.ts +2 -1
- package/src/memory/persist.ts +1 -4
- package/src/pref/device.ts +118 -0
- package/src/pref/index.ts +1 -0
- package/src/pref/locale.ts +6 -18
- package/src/result/index.ts +1 -3
- package/src/sync/RwLock.ts +2 -0
- package/src/sync/batch.test.ts +4 -6
- package/src/sync/batch.ts +4 -21
- package/src/sync/capture.ts +33 -0
- package/src/sync/debounce.ts +1 -6
- package/src/sync/index.ts +1 -0
- package/src/sync/latest.ts +2 -12
- package/src/sync/mutex.ts +21 -0
- package/src/sync/serial.test.ts +1 -2
- package/src/sync/serial.ts +3 -12
- package/src/sync/util.ts +1 -3
package/src/memory/async_erc.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* operations are asynchronous.
|
|
4
4
|
*
|
|
5
5
|
* See {@link makeErcType} for how to use
|
|
6
|
+
*
|
|
7
|
+
* @deprecated use Emp
|
|
6
8
|
*/
|
|
7
9
|
export type AsyncErc<TName, TRepr = number> = {
|
|
8
10
|
readonly type: TName;
|
|
@@ -55,6 +57,8 @@ export type AsyncErc<TName, TRepr = number> = {
|
|
|
55
57
|
* Weak reference to an externally ref-counted object.
|
|
56
58
|
*
|
|
57
59
|
* See {@link makeErcType} for how to use
|
|
60
|
+
*
|
|
61
|
+
* @deprecated use Emp
|
|
58
62
|
*/
|
|
59
63
|
export type AsyncErcRef<TName, TRepr = number> = {
|
|
60
64
|
readonly type: TName;
|
|
@@ -74,11 +78,15 @@ export type AsyncErcRef<TName, TRepr = number> = {
|
|
|
74
78
|
getStrong: () => Promise<AsyncErc<TName, TRepr>>;
|
|
75
79
|
};
|
|
76
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated use Emp
|
|
83
|
+
*/
|
|
77
84
|
export type AsyncErcRefType<T> =
|
|
78
|
-
T extends AsyncErc<infer TName, infer TRepr>
|
|
79
|
-
? AsyncErcRef<TName, TRepr>
|
|
80
|
-
: never;
|
|
85
|
+
T extends AsyncErc<infer TName, infer TRepr> ? AsyncErcRef<TName, TRepr> : never;
|
|
81
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @deprecated use Emp
|
|
89
|
+
*/
|
|
82
90
|
export type AsyncErcTypeConstructor<TName, TRepr> = {
|
|
83
91
|
/**
|
|
84
92
|
* A marker value for the underlying object type.
|
|
@@ -101,7 +109,9 @@ export type AsyncErcTypeConstructor<TName, TRepr> = {
|
|
|
101
109
|
addRef: (value: TRepr) => Promise<TRepr> | TRepr;
|
|
102
110
|
};
|
|
103
111
|
|
|
104
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* @deprecated use Emp
|
|
114
|
+
*/
|
|
105
115
|
export const makeAsyncErcType = <TName, TRepr>({
|
|
106
116
|
marker,
|
|
107
117
|
free,
|
|
@@ -109,12 +119,9 @@ export const makeAsyncErcType = <TName, TRepr>({
|
|
|
109
119
|
}: AsyncErcTypeConstructor<TName, TRepr>): ((
|
|
110
120
|
value: TRepr | undefined,
|
|
111
121
|
) => AsyncErc<TName, TRepr>) => {
|
|
112
|
-
const createStrongRef = (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
let weakRef:
|
|
116
|
-
| (AsyncErcRef<TName, TRepr> & { invalidate: () => void })
|
|
117
|
-
| undefined = undefined;
|
|
122
|
+
const createStrongRef = (value: TRepr | undefined): AsyncErc<TName, TRepr> => {
|
|
123
|
+
let weakRef: (AsyncErcRef<TName, TRepr> & { invalidate: () => void }) | undefined =
|
|
124
|
+
undefined;
|
|
118
125
|
const invalidateWeakRef = () => {
|
|
119
126
|
if (!weakRef) {
|
|
120
127
|
return;
|
package/src/memory/cell.ts
CHANGED
|
@@ -14,10 +14,7 @@ export type CellConstructor<T> = {
|
|
|
14
14
|
export type Cell<T> = {
|
|
15
15
|
get(): T;
|
|
16
16
|
set(value: T): void;
|
|
17
|
-
subscribe(
|
|
18
|
-
callback: (value: T) => void,
|
|
19
|
-
notifyImmediately?: boolean,
|
|
20
|
-
): () => void;
|
|
17
|
+
subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void;
|
|
21
18
|
};
|
|
22
19
|
|
|
23
20
|
class CellImpl<T> implements Cell<T> {
|
|
@@ -41,10 +38,7 @@ class CellImpl<T> implements Cell<T> {
|
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
public subscribe(
|
|
45
|
-
callback: (value: T) => void,
|
|
46
|
-
notifyImmediately?: boolean,
|
|
47
|
-
): () => void {
|
|
41
|
+
public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {
|
|
48
42
|
this.subscribers.push(callback);
|
|
49
43
|
const unsubscribe = () => {
|
|
50
44
|
const index = this.subscribers.indexOf(callback);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A non-null, **E**ngine-**m**anaged **P**ointer
|
|
3
|
+
*
|
|
4
|
+
* This uses the ECMA FinalizationRegistry, to free the resource,
|
|
5
|
+
* once this object is garbage-collected.
|
|
6
|
+
*
|
|
7
|
+
* The free function may be async.
|
|
8
|
+
*
|
|
9
|
+
* ## Use case
|
|
10
|
+
* When JS interoperates with a system language like C/C++ or Rust,
|
|
11
|
+
* it is often needed to transfer objects into JS context. If the object
|
|
12
|
+
* is big, copy-transfer could be expensive, so the more ideal solution
|
|
13
|
+
* is to transfer a "handle", or a pointer, to JS, and leaving the actual
|
|
14
|
+
* object in the native memory. Then, JS code and pass the pointer to external
|
|
15
|
+
* code to use or extract data from the object when needed.
|
|
16
|
+
*
|
|
17
|
+
* This approach is just like passing raw pointers in C, which means it is
|
|
18
|
+
* extremely succeptible to memory corruption bugs like double-free or
|
|
19
|
+
* use-after-free. Unlike C++ or Rust, JS also doesn't have destructors
|
|
20
|
+
* that run when an object leaves the scope (proposal for the `using` keyword exists,
|
|
21
|
+
* but you can actually double-free the object with `using`).
|
|
22
|
+
*
|
|
23
|
+
* C-style manual memory management might be sufficient for simple bindings,
|
|
24
|
+
* but for more complex scenarios, automatic memory management is needed,
|
|
25
|
+
* by tying the free call to the GC of a JS object.
|
|
26
|
+
*
|
|
27
|
+
* ## Recommended practice
|
|
28
|
+
* 1. The external code should transfer the ownership of the object
|
|
29
|
+
* to JS when passing it to JS. JS will then put the handle into an Emp
|
|
30
|
+
* to be automatically managed. This means the external code should now
|
|
31
|
+
* never free the object, and let JS free it instead.
|
|
32
|
+
* 2. The inner value of the Emp must only be used for calling
|
|
33
|
+
* external code, and the Emp must be kept alive for all runtimes, including
|
|
34
|
+
* those with heavy optimization that may reclaim the Emp during the call,
|
|
35
|
+
* if it's not referenced afterwards. See {@link scopedCapture}.
|
|
36
|
+
* The inner value must not dangle around outside of the Emp that owns it.
|
|
37
|
+
*
|
|
38
|
+
* ## Pointer Size
|
|
39
|
+
* In 32-bit context like WASM32, the inner value can be a `number`.
|
|
40
|
+
* In 64-bit context, `number` might be fine for some systems, but `bigint`
|
|
41
|
+
* is recommended.
|
|
42
|
+
*/
|
|
43
|
+
export type Emp<T, TRepr> = {
|
|
44
|
+
/** The type marker for T. This only marks the type for TypeScript and does not exist at runtime */
|
|
45
|
+
readonly __phantom: T;
|
|
46
|
+
/** The underlying pointer value */
|
|
47
|
+
readonly value: TRepr;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type EmpConstructor<T, TRepr> = {
|
|
51
|
+
/**
|
|
52
|
+
* The marker for the Emp type, used to distinguish between multiple types
|
|
53
|
+
*
|
|
54
|
+
* Typically, this is a unique symbol:
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const MyNativeType = Symbol("MyNativeType");
|
|
57
|
+
* export type MyNativeType = typeof MyNativeType;
|
|
58
|
+
*
|
|
59
|
+
* const makeMyNativeTypeEmp = makeEmpType({
|
|
60
|
+
* marker: MyNativeType,
|
|
61
|
+
* free: (ptr) => void freeMyNativeType(ptr)
|
|
62
|
+
* })
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* Note that this is not used in runtime, but just used for type inference,
|
|
66
|
+
* so you can also skip passing it and specify the type parameter instead
|
|
67
|
+
*/
|
|
68
|
+
marker?: T;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Function to free the underlying object. Called when this Emp is garbage-collected
|
|
72
|
+
*/
|
|
73
|
+
free: (ptr: TRepr) => void | Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a factory function for an {@link Emp} type.
|
|
78
|
+
*/
|
|
79
|
+
export const makeEmpType = <T, TRepr>({
|
|
80
|
+
free,
|
|
81
|
+
}: EmpConstructor<T, TRepr>): ((ptr: TRepr) => Emp<T, TRepr>) => {
|
|
82
|
+
const registry = new FinalizationRegistry(free);
|
|
83
|
+
return (ptr: TRepr) => {
|
|
84
|
+
const obj = Object.freeze({ value: ptr });
|
|
85
|
+
registry.register(obj, ptr);
|
|
86
|
+
return obj as Emp<T, TRepr>;
|
|
87
|
+
};
|
|
88
|
+
};
|
package/src/memory/erc.test.ts
CHANGED
|
@@ -119,143 +119,140 @@ describe.each`
|
|
|
119
119
|
indirection | allocator
|
|
120
120
|
${"single"} | ${new Allocator(false)}
|
|
121
121
|
${"double"} | ${new Allocator(true)}
|
|
122
|
-
`(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
allocator.cleanup();
|
|
127
|
-
});
|
|
122
|
+
`("Erc - $indirection indirection", ({ allocator }: { allocator: Allocator }) => {
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
allocator.cleanup();
|
|
125
|
+
});
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
it("allocate and deallocate correctly", () => {
|
|
128
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
129
|
+
expect(allocator.getValue(test.value)).toBe("Hello");
|
|
130
|
+
test.free();
|
|
131
|
+
allocator.expectNoLeak();
|
|
132
|
+
});
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
it("frees if assigned new value", () => {
|
|
135
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
136
|
+
test.assign(allocator.allocValue("World"));
|
|
137
|
+
expect(allocator.getValue(test.value)).toBe("World");
|
|
138
|
+
test.free();
|
|
139
|
+
allocator.expectNoLeak();
|
|
140
|
+
});
|
|
143
141
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
it("does not free when taking value", () => {
|
|
143
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
144
|
+
const raw = test.take();
|
|
145
|
+
expect(allocator.getValue(raw)).toBe("Hello");
|
|
146
|
+
expect(test.value).toBeUndefined();
|
|
147
|
+
if (raw === undefined) {
|
|
148
|
+
throw new Error("Raw value is undefined");
|
|
149
|
+
}
|
|
150
|
+
allocator.makeTestErc(raw).free();
|
|
151
|
+
allocator.expectNoLeak();
|
|
152
|
+
});
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
it("invalidates weak references on free", () => {
|
|
155
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
156
|
+
const testWeak = test.getWeak();
|
|
157
|
+
expect(allocator.getValue(testWeak.value)).toBe("Hello");
|
|
158
|
+
test.free();
|
|
159
|
+
expect(testWeak.value).toBeUndefined();
|
|
160
|
+
allocator.expectNoLeak();
|
|
161
|
+
});
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
it("invalidates weak references on assign", () => {
|
|
164
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
165
|
+
const testWeak = test.getWeak();
|
|
166
|
+
expect(allocator.getValue(testWeak.value)).toBe("Hello");
|
|
167
|
+
test.assign(allocator.allocValue("World"));
|
|
168
|
+
expect(testWeak.value).toBeUndefined();
|
|
169
|
+
test.free();
|
|
170
|
+
allocator.expectNoLeak();
|
|
171
|
+
});
|
|
174
172
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
it("handles assign and take of different references correctly", () => {
|
|
174
|
+
const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
175
|
+
const test2 = allocator.makeTestErc(allocator.allocValue("World"));
|
|
176
|
+
expect(allocator.getValue(test1.value)).toBe("Hello");
|
|
177
|
+
expect(allocator.getValue(test2.value)).toBe("World");
|
|
178
|
+
test1.assign(test2.take());
|
|
179
|
+
expect(allocator.getValue(test1.value)).toBe("World");
|
|
180
|
+
test1.free();
|
|
181
|
+
allocator.expectNoLeak();
|
|
182
|
+
});
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
it("handles assign and take of same references correctly", () => {
|
|
185
|
+
const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
186
|
+
const test2 = test1.getStrong();
|
|
187
|
+
test1.assign(test2.take());
|
|
188
|
+
expect(allocator.getValue(test1.value)).toBe("Hello");
|
|
189
|
+
test1.free();
|
|
190
|
+
test2.free(); // should be no-op
|
|
191
|
+
allocator.expectNoLeak();
|
|
192
|
+
});
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
it("assigning another Erc directly should cause double free", async () => {
|
|
195
|
+
const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
196
|
+
const test2 = test1.getStrong();
|
|
197
|
+
test1.assign(test2.value);
|
|
198
|
+
expect(allocator.getValue(test1.value)).toBe("Hello");
|
|
199
|
+
test1.free();
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
const freeTest2 = async () => {
|
|
202
|
+
test2.free();
|
|
203
|
+
};
|
|
204
|
+
await expect(freeTest2).rejects.toThrow("Double free detected");
|
|
205
|
+
allocator.expectNoLeak();
|
|
206
|
+
});
|
|
209
207
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
it("handles assign and take of same references correctly (same Erc)", () => {
|
|
209
|
+
const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
210
|
+
test1.assign(test1.take());
|
|
211
|
+
expect(allocator.getValue(test1.value)).toBe("Hello");
|
|
212
|
+
test1.free();
|
|
213
|
+
allocator.expectNoLeak();
|
|
214
|
+
});
|
|
217
215
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
it("assigning another Erc directly should cause double free (same Erc)", async () => {
|
|
217
|
+
const test1 = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
218
|
+
test1.assign(test1.value);
|
|
219
|
+
const getTest1Value = async () => {
|
|
220
|
+
allocator.getValue(test1.value);
|
|
221
|
+
};
|
|
222
|
+
await expect(getTest1Value).rejects.toThrow("Dangling pointer");
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
const freeTest1 = async () => {
|
|
225
|
+
test1.free();
|
|
226
|
+
};
|
|
227
|
+
await expect(freeTest1).rejects.toThrow("Double free detected");
|
|
228
|
+
allocator.expectNoLeak();
|
|
229
|
+
});
|
|
232
230
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
231
|
+
it("inc ref count with strong reference", () => {
|
|
232
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
233
|
+
const test2 = test.getStrong();
|
|
234
|
+
expect(allocator.getValue(test.value)).toBe("Hello");
|
|
235
|
+
expect(allocator.getValue(test2.value)).toBe("Hello");
|
|
236
|
+
test.free();
|
|
237
|
+
expect(allocator.getValue(test2.value)).toBe("Hello");
|
|
238
|
+
test2.free();
|
|
239
|
+
allocator.expectNoLeak();
|
|
240
|
+
});
|
|
243
241
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
);
|
|
242
|
+
it("inc ref count with strong reference from weak reference", () => {
|
|
243
|
+
const test = allocator.makeTestErc(allocator.allocValue("Hello"));
|
|
244
|
+
const testWeak = test.getWeak();
|
|
245
|
+
expect(allocator.getValue(testWeak.value)).toBe("Hello");
|
|
246
|
+
const test2 = testWeak.getStrong();
|
|
247
|
+
expect(allocator.getValue(testWeak.value)).toBe("Hello");
|
|
248
|
+
expect(allocator.getValue(test2.value)).toBe("Hello");
|
|
249
|
+
const test2Weak = test2.getWeak();
|
|
250
|
+
test.free();
|
|
251
|
+
expect(testWeak.value).toBeUndefined();
|
|
252
|
+
expect(allocator.getValue(test2.value)).toBe("Hello");
|
|
253
|
+
expect(allocator.getValue(test2Weak.value)).toBe("Hello");
|
|
254
|
+
test2.free();
|
|
255
|
+
expect(test2Weak.value).toBeUndefined();
|
|
256
|
+
allocator.expectNoLeak();
|
|
257
|
+
});
|
|
258
|
+
});
|
package/src/memory/erc.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* A holder for an externally ref-counted object.
|
|
3
3
|
*
|
|
4
4
|
* See {@link makeErcType} for how to use
|
|
5
|
+
* @deprecated use Emp
|
|
5
6
|
*/
|
|
6
7
|
export type Erc<TName, TRepr = number> = {
|
|
7
8
|
readonly type: TName;
|
|
@@ -54,6 +55,7 @@ export type Erc<TName, TRepr = number> = {
|
|
|
54
55
|
* Weak reference to an externally ref-counted object.
|
|
55
56
|
*
|
|
56
57
|
* See {@link makeErcType} for how to use
|
|
58
|
+
* @deprecated use Emp
|
|
57
59
|
*/
|
|
58
60
|
export type ErcRef<TName, TRepr = number> = {
|
|
59
61
|
readonly type: TName;
|
|
@@ -73,9 +75,14 @@ export type ErcRef<TName, TRepr = number> = {
|
|
|
73
75
|
getStrong: () => Erc<TName, TRepr>;
|
|
74
76
|
};
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated use Emp
|
|
80
|
+
*/
|
|
81
|
+
export type ErcRefType<T> = T extends Erc<infer TName, infer TRepr> ? ErcRef<TName, TRepr> : never;
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated use Emp
|
|
85
|
+
*/
|
|
79
86
|
export type ErcTypeConstructor<TName, TRepr> = {
|
|
80
87
|
/**
|
|
81
88
|
* A marker value for the underlying object type.
|
|
@@ -241,18 +248,15 @@ export type ErcTypeConstructor<TName, TRepr> = {
|
|
|
241
248
|
* myFoo1.assign(myFoo1.value); // this will free the value since ref count is 0, and result in a dangling pointer
|
|
242
249
|
* ```
|
|
243
250
|
*
|
|
251
|
+
* @deprecated use Emp
|
|
244
252
|
*/
|
|
245
253
|
export const makeErcType = <TName, TRepr>({
|
|
246
254
|
marker,
|
|
247
255
|
free,
|
|
248
256
|
addRef,
|
|
249
|
-
}: ErcTypeConstructor<TName, TRepr>): ((
|
|
250
|
-
value: TRepr | undefined,
|
|
251
|
-
) => Erc<TName, TRepr>) => {
|
|
257
|
+
}: ErcTypeConstructor<TName, TRepr>): ((value: TRepr | undefined) => Erc<TName, TRepr>) => {
|
|
252
258
|
const createStrongRef = (value: TRepr | undefined): Erc<TName, TRepr> => {
|
|
253
|
-
let weakRef:
|
|
254
|
-
| (ErcRef<TName, TRepr> & { invalidate: () => void })
|
|
255
|
-
| undefined = undefined;
|
|
259
|
+
let weakRef: (ErcRef<TName, TRepr> & { invalidate: () => void }) | undefined = undefined;
|
|
256
260
|
const invalidateWeakRef = () => {
|
|
257
261
|
if (!weakRef) {
|
|
258
262
|
return;
|
package/src/memory/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { cell, type CellConstructor, type Cell } from "./cell.ts";
|
|
8
8
|
export { persist, type PersistConstructor, type Persist } from "./persist.ts";
|
|
9
|
-
export * from "./erc.ts";
|
|
10
9
|
export * from "./async_erc.ts";
|
|
10
|
+
export * from "./emp.ts";
|
|
11
|
+
export * from "./erc.ts";
|
|
11
12
|
export * from "./idgen.ts";
|
package/src/memory/persist.ts
CHANGED
|
@@ -106,10 +106,7 @@ class PersistImpl<T> implements Persist<T> {
|
|
|
106
106
|
this.cell.set(value);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
public subscribe(
|
|
110
|
-
callback: (value: T) => void,
|
|
111
|
-
notifyImmediately?: boolean,
|
|
112
|
-
): () => void {
|
|
109
|
+
public subscribe(callback: (value: T) => void, notifyImmediately?: boolean): () => void {
|
|
113
110
|
return this.cell.subscribe(callback, notifyImmediately);
|
|
114
111
|
}
|
|
115
112
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { ilog } from "../log/internal.ts";
|
|
2
|
+
import { cell } from "../memory";
|
|
3
|
+
|
|
4
|
+
let cachedIsMobile: boolean | undefined = undefined;
|
|
5
|
+
|
|
6
|
+
/** Check if the current UserAgent is a mobile device */
|
|
7
|
+
export const isMobile = (): boolean => {
|
|
8
|
+
// just very primitive UA parsing for now,
|
|
9
|
+
// we don't need to take another dependency just to detect
|
|
10
|
+
// a few platforms
|
|
11
|
+
if (cachedIsMobile === undefined) {
|
|
12
|
+
const ua = navigator?.userAgent || "";
|
|
13
|
+
if (ua.match(/(Windows NT|Macintosh|)/i)) {
|
|
14
|
+
cachedIsMobile = false;
|
|
15
|
+
} else if (ua.match(/(Mobil|iPhone|Android|iPod|iPad)/)) {
|
|
16
|
+
cachedIsMobile = true;
|
|
17
|
+
} else if (ua.includes("Linux")) {
|
|
18
|
+
cachedIsMobile = false;
|
|
19
|
+
} else {
|
|
20
|
+
ilog.warn("unable to determine device type, assuming desktop");
|
|
21
|
+
cachedIsMobile = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return cachedIsMobile;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// stores current display mode
|
|
28
|
+
const displayMode = cell({ initial: "" });
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for display mode detection
|
|
32
|
+
*
|
|
33
|
+
* See {@link initDisplayMode}
|
|
34
|
+
*/
|
|
35
|
+
export type DisplayModeOptions<T extends string> = {
|
|
36
|
+
/**
|
|
37
|
+
* Set the initial value, if the platform doesn't support detecting
|
|
38
|
+
* the display mode. `detect()` will be used instead if supported
|
|
39
|
+
*/
|
|
40
|
+
initial?: T;
|
|
41
|
+
/**
|
|
42
|
+
* Function to determine the display mode based on viewport width and height,
|
|
43
|
+
* and if the device is mobile
|
|
44
|
+
*/
|
|
45
|
+
detect: (width: number, height: number, isMobile: boolean) => T;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Initialize display mode detection for the app.
|
|
50
|
+
*
|
|
51
|
+
* The display mode may be based on the viewport dimensions
|
|
52
|
+
* and if the device is mobile. Also note that you can use {@link isMobile}
|
|
53
|
+
* without the display mode framework.
|
|
54
|
+
*
|
|
55
|
+
* The display modes are strings that should be passed as a type parameter
|
|
56
|
+
* to {@link initDisplayMode}. You can create an alias in your code for
|
|
57
|
+
* getting the typed version of {@link addDisplayModeSubscriber}, {@link getDisplayMode},
|
|
58
|
+
* and {@link useDisplayMode} from `pure-react`.
|
|
59
|
+
*
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import {
|
|
62
|
+
* addDisplayModeSubscriber as pureAddDisplayModeSubscriber,
|
|
63
|
+
* getDisplayMode as pureGetDisplayMode,
|
|
64
|
+
* } from "@pistonite/pure/pref";
|
|
65
|
+
* import { useDisplayMode as pureUseDisplayMode } from "@pistonite/pure-react";
|
|
66
|
+
*
|
|
67
|
+
* export const MyDisplayModes = ["mode1", "mode2"] as const;
|
|
68
|
+
* export type MyDisplayMode = (typeof MyDisplayModes)[number];
|
|
69
|
+
*
|
|
70
|
+
* export const addDisplayModeSubscriber = pureAddDisplayModeSubscriber<MyDisplayMode>;
|
|
71
|
+
* export const getDisplayMode = pureGetDisplayMode<MyDisplayMode>;
|
|
72
|
+
* export const useDisplayMode = pureUseDisplayMode<MyDisplayMode>;
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* Note that this is not necessary in some simple use cases. For example,
|
|
76
|
+
* adjusting styles based on the viewport width can be done with CSS:
|
|
77
|
+
* ```css
|
|
78
|
+
* @media screen and (max-width: 800px) {
|
|
79
|
+
* /* styles for narrow mode * /
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* Use this only if the display mode needs to be detected programmatically.
|
|
84
|
+
*/
|
|
85
|
+
export const initDisplayMode = <T extends string>(options: DisplayModeOptions<T>) => {
|
|
86
|
+
const detectCallback = () => {
|
|
87
|
+
const mode = options.detect(window.innerWidth, window.innerHeight, isMobile());
|
|
88
|
+
displayMode.set(mode);
|
|
89
|
+
};
|
|
90
|
+
if (
|
|
91
|
+
window &&
|
|
92
|
+
window.addEventListener &&
|
|
93
|
+
window.innerWidth !== undefined &&
|
|
94
|
+
window.innerHeight !== undefined
|
|
95
|
+
) {
|
|
96
|
+
window.addEventListener("resize", detectCallback);
|
|
97
|
+
detectCallback();
|
|
98
|
+
} else if (options.initial !== undefined) {
|
|
99
|
+
displayMode.set(options.initial);
|
|
100
|
+
} else {
|
|
101
|
+
ilog.warn(
|
|
102
|
+
"display mode cannot be initialized, since detection is not supported and initial is not set",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Subscribe to display mode changes */
|
|
108
|
+
export const addDisplayModeSubscriber = <T extends string>(
|
|
109
|
+
subscriber: (mode: T) => void,
|
|
110
|
+
notifyImmediately?: boolean,
|
|
111
|
+
) => {
|
|
112
|
+
return displayMode.subscribe(subscriber as (x: string) => void, notifyImmediately);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Get the current display mode */
|
|
116
|
+
export const getDisplayMode = <T extends string>(): T => {
|
|
117
|
+
return displayMode.get() as T;
|
|
118
|
+
};
|
package/src/pref/index.ts
CHANGED