Skip to content

Commit 9f58ec4

Browse files
committed
#develop sync state queue
1 parent 38080e1 commit 9f58ec4

File tree

7 files changed

+109
-39
lines changed

7 files changed

+109
-39
lines changed

packages/feature-state/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "feature-state",
3-
"version": "0.0.53",
3+
"version": "0.0.56",
44
"private": false,
55
"description": "Straightforward, typesafe, and feature-based state management library for ReactJs",
66
"keywords": [],

packages/feature-state/src/FlatQueue.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// Based on: https://github.com/mourner/flatqueue/blob/main/index.js
22
// Could not use the original package because it doesn't support CommonJs.
33
export class FlatQueue<GItem> {
4-
private ids: Array<GItem | undefined>;
5-
private values: Array<number>;
4+
private _ids: Array<GItem | undefined>;
5+
private _values: Array<number>;
66
private _length: number;
77

88
constructor() {
9-
this.ids = [];
10-
this.values = [];
9+
this._ids = [];
10+
this._values = [];
1111
this._length = 0;
1212
}
1313

@@ -30,17 +30,17 @@ export class FlatQueue<GItem> {
3030

3131
while (pos > 0) {
3232
const parent = (pos - 1) >> 1;
33-
const parentValue = this.values[parent] as number;
33+
const parentValue = this._values[parent] as number;
3434
if (priority >= parentValue) {
3535
break;
3636
}
37-
this.ids[pos] = this.ids[parent];
38-
this.values[pos] = parentValue;
37+
this._ids[pos] = this._ids[parent];
38+
this._values[pos] = parentValue;
3939
pos = parent;
4040
}
4141

42-
this.ids[pos] = id;
43-
this.values[pos] = priority;
42+
this._ids[pos] = id;
43+
this._values[pos] = priority;
4444
}
4545

4646
/**
@@ -53,14 +53,14 @@ export class FlatQueue<GItem> {
5353
return null;
5454
}
5555

56-
const top = this.ids[0];
56+
const top = this._ids[0];
5757
this._length--;
5858

5959
if (this._length > 0) {
60-
const id = this.ids[this._length] as GItem;
61-
const value = this.values[this._length] as number;
62-
this.ids[0] = id;
63-
this.values[0] = value;
60+
const id = this._ids[this._length] as GItem;
61+
const value = this._values[this._length] as number;
62+
this._ids[0] = id;
63+
this._values[0] = value;
6464

6565
const halfLength = this._length >> 1;
6666
let pos = 0;
@@ -70,15 +70,15 @@ export class FlatQueue<GItem> {
7070
const right = left + 1;
7171

7272
// Initialize with left child values
73-
let bestIndex = this.ids[left] as GItem;
74-
let bestValue = this.values[left] as number;
73+
let bestIndex = this._ids[left] as GItem;
74+
let bestValue = this._values[left] as number;
7575

7676
// Check if right child exists and has lower priority
77-
const rightValue = this.values[right] as number;
77+
const rightValue = this._values[right] as number;
7878

7979
if (right < this._length && rightValue < bestValue) {
8080
left = right;
81-
bestIndex = this.ids[right] as GItem;
81+
bestIndex = this._ids[right] as GItem;
8282
bestValue = rightValue;
8383
}
8484

@@ -88,14 +88,14 @@ export class FlatQueue<GItem> {
8888
}
8989

9090
// Move best child up
91-
this.ids[pos] = bestIndex;
92-
this.values[pos] = bestValue;
91+
this._ids[pos] = bestIndex;
92+
this._values[pos] = bestValue;
9393
pos = left;
9494
}
9595

9696
// Place the current item at its new position
97-
this.ids[pos] = id;
98-
this.values[pos] = value;
97+
this._ids[pos] = id;
98+
this._values[pos] = value;
9999
}
100100

101101
return top ?? null;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { FlatQueue } from './FlatQueue';
2+
import type { TListenerQueueItem } from './types';
3+
4+
export class ListenerQueue extends FlatQueue<TListenerQueueItem> {
5+
public readonly sync: boolean;
6+
7+
constructor(sync = false) {
8+
super();
9+
this.sync = sync;
10+
}
11+
12+
public process(): void | Promise<void> {
13+
if (this.sync) {
14+
return this.processSync();
15+
} else {
16+
return this.processAsync();
17+
}
18+
}
19+
20+
public processSync(): void {
21+
let item: TListenerQueueItem | null;
22+
while ((item = this.pop()) != null) {
23+
void item.callback(item.context);
24+
}
25+
}
26+
27+
public async processAsync(): Promise<void> {
28+
let item: TListenerQueueItem | null;
29+
while ((item = this.pop()) != null) {
30+
await item.callback(item.context);
31+
}
32+
}
33+
}

packages/feature-state/src/create-state.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
1-
import { FlatQueue } from './FlatQueue';
2-
import type { TListener, TListenerContext, TListenerQueueItem, TState } from './types';
1+
import { getQueue } from './queue';
2+
import type { TListener, TListenerContext, TState } from './types';
33

4-
const GLOBAL_LISTENER_QUEUE = new FlatQueue<TListenerQueueItem>();
54
export const SET_SOURCE_KEY = 'state_set';
65

7-
export function createState<GValue>(initialValue: GValue): TState<GValue, []> {
6+
export function createState<GValue>(
7+
initialValue: GValue,
8+
options: TCreateStateOptions = {}
9+
): TState<GValue, []> {
10+
const { queue: queueName = 'async' } = options;
11+
const queue = getQueue(queueName);
12+
if (queue == null) {
13+
throw new Error(`Queue "${queueName}" not found`);
14+
}
15+
816
return {
917
_features: [],
1018
_listeners: [],
1119
_v: initialValue,
20+
_queue: queue,
1221
_notify(notifyOptions = {}) {
1322
const { processListenerQueue = true, listenerContext = {}, prevValue } = notifyOptions;
1423

15-
// Push current state's listeners to the queue
24+
// Push all listeners to the state's queue
1625
for (const listener of this._listeners) {
1726
const context: TListenerContext<GValue> = Object.assign(listenerContext, {
1827
value: this._v,
1928
prevValue
2029
});
2130
if (listener.queueIf == null || listener.queueIf(context)) {
22-
GLOBAL_LISTENER_QUEUE.push(
31+
this._queue.push(
2332
{
2433
context,
2534
callback: listener.callback
@@ -29,9 +38,9 @@ export function createState<GValue>(initialValue: GValue): TState<GValue, []> {
2938
}
3039
}
3140

32-
// Process queue
41+
// Process the state's queue
3342
if (processListenerQueue) {
34-
void processStateQueue();
43+
void this._queue.process();
3544
}
3645
},
3746
get() {
@@ -64,7 +73,7 @@ export function createState<GValue>(initialValue: GValue): TState<GValue, []> {
6473
};
6574
this._listeners.push(listener);
6675

67-
// Undbind
76+
// Unbind
6877
return () => {
6978
const index = this._listeners.indexOf(listener);
7079
if (index !== -1) {
@@ -80,15 +89,12 @@ export function createState<GValue>(initialValue: GValue): TState<GValue, []> {
8089
};
8190
}
8291

92+
interface TCreateStateOptions {
93+
queue?: string;
94+
}
95+
8396
export enum EStateListenerQueuePriority {
8497
EARLY = 50,
8598
DEFAULT = 100,
8699
LATE = 200
87100
}
88-
89-
export async function processStateQueue(): Promise<void> {
90-
let item: TListenerQueueItem | null;
91-
while ((item = GLOBAL_LISTENER_QUEUE.pop()) != null) {
92-
await item.callback(item.context);
93-
}
94-
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './create-state';
22
export * from './features';
33
export * from './is-state-with-features';
4+
export * from './queue';
45
export * from './types';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ListenerQueue } from './ListenerQueue';
2+
3+
const GLOBAL_STATE_QUEUES = new Map<string, ListenerQueue>();
4+
5+
// Initialize default queues
6+
GLOBAL_STATE_QUEUES.set('async', new ListenerQueue(false));
7+
GLOBAL_STATE_QUEUES.set('sync', new ListenerQueue(true));
8+
9+
export function createQueue(name: string, options: { sync: boolean }): void {
10+
GLOBAL_STATE_QUEUES.set(name, new ListenerQueue(options.sync));
11+
}
12+
13+
export function getQueue(name: string): ListenerQueue | undefined {
14+
return GLOBAL_STATE_QUEUES.get(name);
15+
}
16+
17+
export function processQueue(name: string): void {
18+
const queue = GLOBAL_STATE_QUEUES.get(name);
19+
if (queue != null) {
20+
void queue.process();
21+
}
22+
}
23+
24+
export function processAllQueues(): void {
25+
for (const [_, queue] of GLOBAL_STATE_QUEUES) {
26+
void queue.process();
27+
}
28+
}

packages/feature-state/src/types/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { TWithFeatures, type TFeatureDefinition } from '@blgc/types/features';
22
import { type TNestedPath } from '@blgc/utils';
3+
import type { ListenerQueue } from '../ListenerQueue';
34

45
export type TState<GValue, GFeatures extends TFeatureDefinition[]> = TWithFeatures<
56
{
67
_v: GValue;
78
_listeners: TListener<GValue>[];
9+
_queue: ListenerQueue;
810
/**
911
* Triggers all registered listeners to run with the current state value.
1012
*/

0 commit comments

Comments
 (0)