Skip to content

Commit 8a37d79

Browse files
committed
refactor: make event as task
1 parent a1063f9 commit 8a37d79

6 files changed

Lines changed: 237 additions & 123 deletions

File tree

src/batch.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ declare const globalThis: {
77
[BATCH_SCOPE]?: boolean;
88
};
99

10-
export const tasks = /* @__PURE__ */ new Set<{ [BATCH_SCOPE]: () => void }>();
10+
export type BatchTask<O extends object = object> = O & {
11+
[BATCH_SCOPE]: () => void;
12+
};
13+
14+
export const tasks = /* @__PURE__ */ new Set<BatchTask>();
15+
16+
export const toTask = <T extends object>(target: T, fn: () => void): BatchTask<T> => (
17+
((target as BatchTask)[BATCH_SCOPE] = fn), target as BatchTask<T>
18+
);
1119

1220
export const batchStart = (): boolean => (globalThis[BATCH_SCOPE] ? false : (globalThis[BATCH_SCOPE] = true));
1321

src/collections/reactiveMap.ts

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { batch } from "../batch";
1+
import { batch, type BatchTask, tasks, toTask } from "../batch";
22
import { type AddEventListener, event, send, size } from "../event";
33
import { writable } from "../readable";
44
import { type Disposer, type OwnedWritable, type Readable } from "../typings";
@@ -24,65 +24,53 @@ export class OwnedReactiveMap<K, V> extends Map<K, V> {
2424
* @returns A disposer function to unsubscribe from the event.
2525
*/
2626
public onChanged(fn: (changed: ReactiveMapChanged<K, V>) => void): Disposer {
27-
if (!this._onChanged_) {
28-
this._onChanged_ = event({ delete: new Set(), upsert: new Map() });
29-
const handler = () => {
30-
if (this._onChanged_ && size(this._onChanged_)) {
31-
const { data_ } = this._onChanged_;
32-
if (data_.upsert.size > 0 || data_.delete.size > 0) {
33-
const changedData = {
34-
upsert: [...data_.upsert],
35-
delete: [...data_.delete],
36-
};
37-
data_.upsert.clear();
38-
data_.delete.clear();
39-
send(this._onChanged_, changedData);
40-
}
41-
} else {
42-
this._onChanged_ = undefined;
43-
this.$.unsubscribe(handler);
27+
return (this._onChanged_ ??= toTask(event({ delete: new Set(), upsert: new Map() }), () => {
28+
if (this._onChanged_ && size(this._onChanged_)) {
29+
const { data_ } = this._onChanged_;
30+
if (data_.upsert.size > 0 || data_.delete.size > 0) {
31+
const changedData = {
32+
upsert: [...data_.upsert],
33+
delete: [...data_.delete],
34+
};
35+
data_.upsert.clear();
36+
data_.delete.clear();
37+
send(this._onChanged_, changedData);
4438
}
45-
};
46-
this.$.onReaction_(handler);
47-
}
48-
return this._onChanged_(fn);
39+
} else {
40+
this._onChanged_ = undefined;
41+
}
42+
}))(fn);
4943
}
5044

5145
/**
52-
* Subscribe to value removal events.
53-
* This is useful for further processing values that are removed from the map.
46+
* Subscribe to events when a value is needed to be disposed.
5447
*
55-
* A value is considered removed when:
48+
* A value is considered for disposal when:
5649
* - it is deleted from the map.
5750
* - it is replaced by another value (the old value is removed).
5851
* - it is cleared from the map.
52+
* - the map is disposed.
5953
*
6054
* Note that for performance reasons, it does not handle the case where multiple keys map to the same value.
6155
*
62-
* @param fn - The function to call when a value is removed.
56+
* @param fn - The function to call when a value is needed to be disposed.
6357
* @returns A disposer function to unsubscribe from the event.
6458
*/
65-
public onValueRemoved(fn: (value: V) => void): Disposer {
66-
if (!this._onValueRemoved_) {
67-
this._onValueRemoved_ = event(new Set<V>());
68-
const handler = () => {
69-
if (this._onValueRemoved_ && size(this._onValueRemoved_)) {
70-
const { data_ } = this._onValueRemoved_;
71-
if (data_.size) {
72-
const removedValues = [...data_];
73-
data_.clear();
74-
for (const value of removedValues) {
75-
send(this._onValueRemoved_, value);
76-
}
59+
public onDisposeValue(fn: (value: V) => void): Disposer {
60+
return (this._onDisposeValue_ ??= toTask(event(new Set<V>()), () => {
61+
if (this._onDisposeValue_ && size(this._onDisposeValue_)) {
62+
const { data_ } = this._onDisposeValue_;
63+
if (data_.size) {
64+
const removedValues = [...data_];
65+
data_.clear();
66+
for (const value of removedValues) {
67+
send(this._onDisposeValue_, value);
7768
}
78-
} else {
79-
this._onValueRemoved_ = undefined;
80-
this.$.unsubscribe(handler);
8169
}
82-
};
83-
this.$.onReaction_(handler);
84-
}
85-
return this._onValueRemoved_(fn);
70+
} else {
71+
this._onDisposeValue_ = undefined;
72+
}
73+
}))(fn);
8674
}
8775

8876
public constructor(entries?: readonly (readonly [K, V])[] | null) {
@@ -98,16 +86,32 @@ export class OwnedReactiveMap<K, V> extends Map<K, V> {
9886
}
9987

10088
public dispose(): void {
101-
this._$?.dispose();
102-
this._onChanged_ = this._onValueRemoved_ = undefined;
103-
super.clear();
89+
if (this._disposed_) return;
90+
if (process.env.NODE_ENV !== "production") {
91+
this._disposed_ = new Error("[embra] ReactiveMap disposed at:");
92+
} else {
93+
this._disposed_ = true;
94+
}
95+
if (this._onDisposeValue_) {
96+
const { data_ } = this._onDisposeValue_;
97+
for (const value of this.values()) {
98+
data_.add(value);
99+
}
100+
if (data_.size) {
101+
batch(() => {
102+
tasks.add(this._onDisposeValue_!);
103+
});
104+
}
105+
}
106+
this._$ = this._onChanged_ = this._onDisposeValue_ = undefined;
104107
}
105108

106109
public override set(key: K, value: V): this {
107110
if (this.has(key)) {
108111
const oldValue = this.get(key)!;
109112
if (!strictEqual(oldValue, value)) {
110-
this._onValueRemoved_?.data_.add(oldValue);
113+
// task added in this._upsert_
114+
this._onDisposeValue_?.data_.add(oldValue);
111115
this._upsert_(key, value);
112116
}
113117
} else {
@@ -118,25 +122,37 @@ export class OwnedReactiveMap<K, V> extends Map<K, V> {
118122

119123
public override delete(key: K): boolean {
120124
if (this.has(key)) {
121-
this._onValueRemoved_?.data_.add(this.get(key)!);
122-
this._onChanged_?.data_.delete.add(key);
123-
this._onChanged_?.data_.upsert.delete(key);
124-
this._$?.set(this);
125+
if (this._onDisposeValue_) {
126+
this._onDisposeValue_.data_.add(this.get(key)!);
127+
tasks.add(this._onDisposeValue_);
128+
}
129+
if (this._onChanged_) {
130+
this._onChanged_.data_.delete.add(key);
131+
this._onChanged_.data_.upsert.delete(key);
132+
tasks.add(this._onChanged_);
133+
}
134+
this._notify_();
125135
}
126136
return super.delete(key);
127137
}
128138

129139
public override clear(): void {
130140
if (this.size) {
131-
if (this._onValueRemoved_ || this._onChanged_) {
141+
if (this._onDisposeValue_ || this._onChanged_) {
132142
for (const [key, value] of this) {
133-
this._onValueRemoved_?.data_.add(value);
134-
this._onChanged_?.data_.delete.add(key);
135-
this._onChanged_?.data_.upsert.delete(key);
143+
if (this._onDisposeValue_) {
144+
this._onDisposeValue_.data_.add(value);
145+
tasks.add(this._onDisposeValue_);
146+
}
147+
if (this._onChanged_) {
148+
this._onChanged_.data_.delete.add(key);
149+
this._onChanged_.data_.upsert.delete(key);
150+
tasks.add(this._onChanged_);
151+
}
136152
}
137153
}
138154
super.clear();
139-
this._$?.set(this);
155+
this._notify_();
140156
}
141157
}
142158

@@ -150,27 +166,51 @@ export class OwnedReactiveMap<K, V> extends Map<K, V> {
150166
});
151167
}
152168

169+
/**
170+
* @internal
171+
*/
172+
private _disposed_?: Error | true;
173+
153174
/** @internal */
154175
private _$?: OwnedWritable<this>;
155176

156177
/** @internal */
157-
private _onChanged_?: AddEventListener<
158-
ReactiveMapChanged<K, V>,
159-
{
160-
readonly upsert: Map<K, V>;
161-
readonly delete: Set<K>;
162-
}
178+
private _onChanged_?: BatchTask<
179+
AddEventListener<
180+
ReactiveMapChanged<K, V>,
181+
{
182+
readonly upsert: Map<K, V>;
183+
readonly delete: Set<K>;
184+
}
185+
>
163186
>;
164187

165188
/** @internal */
166-
private _onValueRemoved_?: AddEventListener<V, Set<V>>;
189+
private _onDisposeValue_?: BatchTask<AddEventListener<V, Set<V>>>;
167190

168191
/** @internal */
169192
private _upsert_(key: K, value: V): void {
170-
this._onValueRemoved_?.data_.delete(value);
171-
this._onChanged_?.data_.upsert.set(key, value);
172-
this._onChanged_?.data_.delete.delete(key);
193+
if (this._onDisposeValue_) {
194+
this._onDisposeValue_.data_.delete(value);
195+
tasks.add(this._onDisposeValue_);
196+
}
197+
if (this._onChanged_) {
198+
this._onChanged_.data_.upsert.set(key, value);
199+
this._onChanged_.data_.delete.delete(key);
200+
tasks.add(this._onChanged_);
201+
}
173202
super.set(key, value);
203+
this._notify_();
204+
}
205+
206+
/** @internal */
207+
private _notify_() {
208+
if (this._disposed_) {
209+
console.error(new Error("disposed"));
210+
if (process.env.NODE_ENV !== "production") {
211+
console.error(this._disposed_);
212+
}
213+
}
174214
this._$?.set(this);
175215
}
176216
}

src/readable.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class ReadableImpl<TValue = any> {
112112
/**
113113
* @internal
114114
*/
115-
private _disposed_?: Error;
115+
private _disposed_?: Error | true;
116116

117117
/**
118118
* @internal
@@ -187,8 +187,11 @@ export class ReadableImpl<TValue = any> {
187187
}
188188

189189
public dispose(): void {
190+
if (this._disposed_) return;
190191
if (process.env.NODE_ENV !== "production") {
191192
this._disposed_ = new Error("[embra] Readable disposed at:");
193+
} else {
194+
this._disposed_ = true;
192195
}
193196
tasks.delete(this);
194197
this.dependents_ = undefined;
@@ -246,10 +249,10 @@ export class ReadableImpl<TValue = any> {
246249
* @internal
247250
*/
248251
public notify_ = (): void => {
249-
if (process.env.NODE_ENV !== "production") {
250-
if (this._disposed_) {
251-
console.error(new Error("[embra] Updating a disposed Readable."));
252-
console.error((this as any)._DEV_ValDisposed_);
252+
if (this._disposed_) {
253+
console.error(new Error("disposed"));
254+
if (process.env.NODE_ENV !== "production") {
255+
console.error(this._disposed_);
253256
}
254257
}
255258

src/typings.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { type BRAND } from "./utils";
22

3-
/**
4-
* @internal
5-
* @ignore
6-
*/
73
export type $sValueTuple<TValInputs extends readonly Readable[]> = Readonly<{
84
[K in keyof TValInputs]: Extract$Value<TValInputs[K]>;
95
}>;

0 commit comments

Comments
 (0)