Skip to content

Commit

Permalink
Using a linked list instead of an array for consumers
Browse files Browse the repository at this point in the history
  • Loading branch information
divdavem committed Jan 16, 2025
1 parent 8b41fe1 commit 5341f9b
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 32 deletions.
3 changes: 2 additions & 1 deletion src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import type { RawStoreWritable } from './internal/storeWritable';

const expectCorrectlyCleanedUp = <T>(store: StoreInput<T>) => {
const rawStore = (store as any)[rawStoreSymbol] as RawStoreWritable<T>;
expect(rawStore.consumerLinks.length).toBe(0);
expect(rawStore.consumerFirst).toBe(null);
expect(rawStore.consumerLast).toBe(null);
expect(rawStore.flags & RawStoreFlags.START_USE_CALLED).toBeFalsy();
};

Expand Down
4 changes: 2 additions & 2 deletions src/internal/storeTrackingUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export abstract class RawStoreTrackingUsage<T> extends RawStoreWritable<T> {
// Ignoring coverage for the following lines because, unless there is a bug in tansu (which would have to be fixed!)
// there should be no way to trigger this error.
/* v8 ignore next 3 */
if (!this.extraUsages && !this.consumerLinks.length) {
if (!this.extraUsages && !this.consumerFirst) {
throw new Error('assert failed: untracked producer usage');
}
this.flags |= RawStoreFlags.START_USE_CALLED;
Expand All @@ -49,7 +49,7 @@ export abstract class RawStoreTrackingUsage<T> extends RawStoreWritable<T> {

override checkUnused(): void {
const flags = this.flags;
if (flags & RawStoreFlags.START_USE_CALLED && !this.extraUsages && !this.consumerLinks.length) {
if (flags & RawStoreFlags.START_USE_CALLED && !this.extraUsages && !this.consumerFirst) {
if (inFlushUnused || flags & RawStoreFlags.HAS_VISIBLE_ONUSE) {
this.flags &= ~RawStoreFlags.START_USE_CALLED;
untrack(() => this.endUse());
Expand Down
72 changes: 43 additions & 29 deletions src/internal/storeWritable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Subscriber, UnsubscribeFunction, UnsubscribeObject, Updater } from '../types';
import { batch } from './batch';
import { equal } from './equal';
import type { Consumer, RawStore } from './store';
import type { BaseLink, Consumer, RawStore } from './store';
import { RawStoreFlags } from './store';
import { SubscribeConsumer } from './subscribeConsumer';
import { activeConsumer } from './untrack';
Expand All @@ -16,11 +16,12 @@ export const checkNotInNotificationPhase = (): void => {

export let epoch = 0;

export interface ProducerConsumerLink<T> {
export interface ProducerConsumerLink<T> extends BaseLink<T> {
value: T;
version: number;
producer: RawStore<T, ProducerConsumerLink<T>>;
indexInProducer: number;
prevInProducer: ProducerConsumerLink<T> | null;
nextInProducer: ProducerConsumerLink<T> | null;
consumer: Consumer;
skipMarkDirty: boolean;
}
Expand All @@ -31,14 +32,16 @@ export class RawStoreWritable<T> implements RawStore<T, ProducerConsumerLink<T>>
private version = 0;
equalFn = equal<T>;
private equalCache: Record<number, boolean> | null = null;
consumerLinks: ProducerConsumerLink<T>[] = [];
consumerFirst: ProducerConsumerLink<T> | null = null;
consumerLast: ProducerConsumerLink<T> | null = null;

newLink(consumer: Consumer): ProducerConsumerLink<T> {
return {
version: -1,
value: undefined as any,
producer: this,
indexInProducer: 0,
nextInProducer: null,
prevInProducer: null,
consumer,
skipMarkDirty: false,
};
Expand Down Expand Up @@ -71,30 +74,40 @@ export class RawStoreWritable<T> implements RawStore<T, ProducerConsumerLink<T>>
}

registerConsumer(link: ProducerConsumerLink<T>): ProducerConsumerLink<T> {
const consumerLinks = this.consumerLinks;
const indexInProducer = consumerLinks.length;
link.indexInProducer = indexInProducer;
consumerLinks[indexInProducer] = link;
// Ignoring coverage for the following lines because, unless there is a bug in tansu (which would have to be fixed!)
// there should be no way to trigger this error.
/* v8 ignore next 3 */
if (link.nextInProducer || link.prevInProducer) {
throw new Error('assert failed: registerConsumer with already invalid link');
}
link.prevInProducer = this.consumerLast;
const last = this.consumerLast;
if (last) {
last.nextInProducer = link;
} else {
this.consumerFirst = link;
}
this.consumerLast = link;
return link;
}

unregisterConsumer(link: ProducerConsumerLink<T>): void {
const consumerLinks = this.consumerLinks;
const index = link.indexInProducer;
// Ignoring coverage for the following lines because, unless there is a bug in tansu (which would have to be fixed!)
// there should be no way to trigger this error.
/* v8 ignore next 3 */
if (consumerLinks[index] !== link) {
throw new Error('assert failed: invalid indexInProducer');
const next = link.nextInProducer;
const prev = link.prevInProducer;
link.nextInProducer = null;
link.prevInProducer = null;
if (next) {
next.prevInProducer = prev;
} else {
this.consumerLast = prev;
}
// swap with the last item to avoid shifting the array
const lastConsumerLink = consumerLinks.pop()!;
const isLast = link === lastConsumerLink;
if (!isLast) {
consumerLinks[index] = lastConsumerLink;
lastConsumerLink.indexInProducer = index;
} else if (index === 0) {
this.checkUnused();
if (prev) {
prev.nextInProducer = next;
} else {
this.consumerFirst = next;
if (!next) {
this.checkUnused();
}
}
}

Expand Down Expand Up @@ -132,11 +145,12 @@ export class RawStoreWritable<T> implements RawStore<T, ProducerConsumerLink<T>>
const prevNotificationPhase = notificationPhase;
notificationPhase = true;
try {
const consumerLinks = this.consumerLinks;
for (let i = 0, l = consumerLinks.length; i < l; i++) {
const link = consumerLinks[i];
if (link.skipMarkDirty) continue;
link.consumer.markDirty();
let link = this.consumerFirst;
while (link) {
if (!link.skipMarkDirty) {
link.consumer.markDirty();
}
link = link.nextInProducer;
}
} finally {
notificationPhase = prevNotificationPhase;
Expand Down

0 comments on commit 5341f9b

Please sign in to comment.