Skip to content

Commit 5e711e0

Browse files
authored
Shrink Atom and Reaction using a bitfield (#3901)
1 parent 32c934b commit 5e711e0

File tree

12 files changed

+175
-94
lines changed

12 files changed

+175
-94
lines changed

.changeset/shaggy-monkeys-cover.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"mobx": patch
3+
---
4+
5+
Shrink Atom and Reaction using a bitfield

packages/mobx/__tests__/v4/base/observables.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ test("forcefully tracked reaction should still yield valid results", function ()
11561156
transaction(function () {
11571157
x.set(4)
11581158
a.track(identity)
1159-
expect(a.isScheduled()).toBe(true)
1159+
expect(a.isScheduled).toBe(true)
11601160
expect(z).toBe(4)
11611161
expect(runCount).toBe(2)
11621162
})
@@ -1166,17 +1166,17 @@ test("forcefully tracked reaction should still yield valid results", function ()
11661166

11671167
transaction(function () {
11681168
x.set(5)
1169-
expect(a.isScheduled()).toBe(true)
1169+
expect(a.isScheduled).toBe(true)
11701170
a.track(identity)
11711171
expect(z).toBe(5)
11721172
expect(runCount).toBe(3)
1173-
expect(a.isScheduled()).toBe(true)
1173+
expect(a.isScheduled).toBe(true)
11741174

11751175
x.set(6)
11761176
expect(z).toBe(5)
11771177
expect(runCount).toBe(3)
11781178
})
1179-
expect(a.isScheduled()).toBe(false)
1179+
expect(a.isScheduled).toBe(false)
11801180
expect(z).toBe(6)
11811181
expect(runCount).toBe(4)
11821182
})

packages/mobx/__tests__/v5/base/errorhandling.js

+30-30
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ test("peeking inside erroring computed value doesn't bork (global) state", () =>
485485

486486
expect(a.isPendingUnobservation).toBe(false)
487487
expect(a.observers_.size).toBe(0)
488-
expect(a.diffValue_).toBe(0)
488+
expect(a.diffValue).toBe(0)
489489
expect(a.lowestObserverState_).toBe(-1)
490490
expect(a.hasUnreportedChange_).toBe(false)
491491
expect(a.value_).toBe(1)
@@ -495,7 +495,7 @@ test("peeking inside erroring computed value doesn't bork (global) state", () =>
495495
expect(b.newObserving_).toBe(null)
496496
expect(b.isPendingUnobservation).toBe(false)
497497
expect(b.observers_.size).toBe(0)
498-
expect(b.diffValue_).toBe(0)
498+
expect(b.diffValue).toBe(0)
499499
expect(b.lowestObserverState_).toBe(0)
500500
expect(b.unboundDepsCount_).toBe(0)
501501
expect(() => {
@@ -523,7 +523,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
523523
test("it should update correctly initially", () => {
524524
expect(a.isPendingUnobservation).toBe(false)
525525
expect(a.observers_.size).toBe(1)
526-
expect(a.diffValue_).toBe(0)
526+
expect(a.diffValue).toBe(0)
527527
expect(a.lowestObserverState_).toBe(-1)
528528
expect(a.hasUnreportedChange_).toBe(false)
529529
expect(a.value_).toBe(1)
@@ -533,7 +533,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
533533
expect(b.newObserving_).toBe(null)
534534
expect(b.isPendingUnobservation).toBe(false)
535535
expect(b.observers_.size).toBe(1)
536-
expect(b.diffValue_).toBe(0)
536+
expect(b.diffValue).toBe(0)
537537
expect(b.lowestObserverState_).toBe(0)
538538
expect(b.unboundDepsCount_).toBe(1) // value is always the last bound amount of observers
539539
expect(b.value_).toBe(1)
@@ -542,12 +542,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
542542
expect(c.dependenciesState_).toBe(0)
543543
expect(c.observing_.length).toBe(1)
544544
expect(c.newObserving_).toBe(null)
545-
expect(c.diffValue_).toBe(0)
545+
expect(c.diffValue).toBe(0)
546546
expect(c.unboundDepsCount_).toBe(1)
547-
expect(c.isDisposed_).toBe(false)
548-
expect(c.isScheduled_).toBe(false)
549-
expect(c.isTrackPending_).toBe(false)
550-
expect(c.isRunning_).toBe(false)
547+
expect(c.isDisposed).toBe(false)
548+
expect(c.isScheduled).toBe(false)
549+
expect(c.isTrackPending).toBe(false)
550+
expect(c.isRunning).toBe(false)
551551
checkGlobalState()
552552
})
553553

@@ -560,7 +560,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
560560

561561
expect(a.isPendingUnobservation).toBe(false)
562562
expect(a.observers_.size).toBe(1)
563-
expect(a.diffValue_).toBe(0)
563+
expect(a.diffValue).toBe(0)
564564
expect(a.lowestObserverState_).toBe(0)
565565
expect(a.hasUnreportedChange_).toBe(false)
566566
expect(a.value_).toBe(2)
@@ -570,7 +570,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
570570
expect(b.newObserving_).toBe(null)
571571
expect(b.isPendingUnobservation).toBe(false)
572572
expect(b.observers_.size).toBe(1)
573-
expect(b.diffValue_).toBe(0)
573+
expect(b.diffValue).toBe(0)
574574
expect(b.lowestObserverState_).toBe(0)
575575
expect(b.unboundDepsCount_).toBe(1)
576576
expect(b.isComputing).toBe(false)
@@ -579,12 +579,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
579579
expect(c.dependenciesState_).toBe(0)
580580
expect(c.observing_.length).toBe(1)
581581
expect(c.newObserving_).toBe(null)
582-
expect(c.diffValue_).toBe(0)
582+
expect(c.diffValue).toBe(0)
583583
expect(c.unboundDepsCount_).toBe(1)
584-
expect(c.isDisposed_).toBe(false)
585-
expect(c.isScheduled_).toBe(false)
586-
expect(c.isTrackPending_).toBe(false)
587-
expect(c.isRunning_).toBe(false)
584+
expect(c.isDisposed).toBe(false)
585+
expect(c.isScheduled).toBe(false)
586+
expect(c.isTrackPending).toBe(false)
587+
expect(c.isRunning).toBe(false)
588588
checkGlobalState()
589589
})
590590

@@ -596,7 +596,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
596596

597597
expect(a.isPendingUnobservation).toBe(false)
598598
expect(a.observers_.size).toBe(1)
599-
expect(a.diffValue_).toBe(0)
599+
expect(a.diffValue).toBe(0)
600600
expect(a.lowestObserverState_).toBe(0)
601601
expect(a.hasUnreportedChange_).toBe(false)
602602
expect(a.value_).toBe(3)
@@ -606,7 +606,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
606606
expect(b.newObserving_).toBe(null)
607607
expect(b.isPendingUnobservation).toBe(false)
608608
expect(b.observers_.size).toBe(1)
609-
expect(b.diffValue_).toBe(0)
609+
expect(b.diffValue).toBe(0)
610610
expect(b.lowestObserverState_).toBe(0)
611611
expect(b.unboundDepsCount_).toBe(1)
612612
expect(b.value_).toBe(3)
@@ -615,12 +615,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
615615
expect(c.dependenciesState_).toBe(0)
616616
expect(c.observing_.length).toBe(1)
617617
expect(c.newObserving_).toBe(null)
618-
expect(c.diffValue_).toBe(0)
618+
expect(c.diffValue).toBe(0)
619619
expect(c.unboundDepsCount_).toBe(1)
620-
expect(c.isDisposed_).toBe(false)
621-
expect(c.isScheduled_).toBe(false)
622-
expect(c.isTrackPending_).toBe(false)
623-
expect(c.isRunning_).toBe(false)
620+
expect(c.isDisposed).toBe(false)
621+
expect(c.isScheduled).toBe(false)
622+
expect(c.isTrackPending).toBe(false)
623+
expect(c.isRunning).toBe(false)
624624

625625
checkGlobalState()
626626
})
@@ -630,7 +630,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
630630

631631
expect(a.isPendingUnobservation).toBe(false)
632632
expect(a.observers_.size).toBe(0)
633-
expect(a.diffValue_).toBe(0)
633+
expect(a.diffValue).toBe(0)
634634
expect(a.lowestObserverState_).toBe(0)
635635
expect(a.hasUnreportedChange_).toBe(false)
636636
expect(a.value_).toBe(3)
@@ -640,7 +640,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
640640
expect(b.newObserving_).toBe(null)
641641
expect(b.isPendingUnobservation).toBe(false)
642642
expect(b.observers_.size).toBe(0)
643-
expect(b.diffValue_).toBe(0)
643+
expect(b.diffValue).toBe(0)
644644
expect(b.lowestObserverState_).toBe(0)
645645
expect(b.unboundDepsCount_).toBe(1)
646646
expect(b.value_).not.toBe(3)
@@ -649,12 +649,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
649649
expect(c.dependenciesState_).toBe(-1)
650650
expect(c.observing_.length).toBe(0)
651651
expect(c.newObserving_).toBe(null)
652-
expect(c.diffValue_).toBe(0)
652+
expect(c.diffValue).toBe(0)
653653
expect(c.unboundDepsCount_).toBe(1)
654-
expect(c.isDisposed_).toBe(true)
655-
expect(c.isScheduled_).toBe(false)
656-
expect(c.isTrackPending_).toBe(false)
657-
expect(c.isRunning_).toBe(false)
654+
expect(c.isDisposed).toBe(true)
655+
expect(c.isScheduled).toBe(false)
656+
expect(c.isTrackPending).toBe(false)
657+
expect(c.isRunning).toBe(false)
658658

659659
expect(b.get()).toBe(3)
660660

packages/mobx/__tests__/v5/base/observables.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ test("forcefully tracked reaction should still yield valid results", function ()
11971197
transaction(function () {
11981198
x.set(4)
11991199
a.track(identity)
1200-
expect(a.isScheduled()).toBe(true)
1200+
expect(a.isScheduled).toBe(true)
12011201
expect(z).toBe(4)
12021202
expect(runCount).toBe(2)
12031203
})
@@ -1207,17 +1207,17 @@ test("forcefully tracked reaction should still yield valid results", function ()
12071207

12081208
transaction(function () {
12091209
x.set(5)
1210-
expect(a.isScheduled()).toBe(true)
1210+
expect(a.isScheduled).toBe(true)
12111211
a.track(identity)
12121212
expect(z).toBe(5)
12131213
expect(runCount).toBe(3)
1214-
expect(a.isScheduled()).toBe(true)
1214+
expect(a.isScheduled).toBe(true)
12151215

12161216
x.set(6)
12171217
expect(z).toBe(5)
12181218
expect(runCount).toBe(3)
12191219
})
1220-
expect(a.isScheduled()).toBe(false)
1220+
expect(a.isScheduled).toBe(false)
12211221
expect(z).toBe(6)
12221222
expect(runCount).toBe(4)
12231223
})

packages/mobx/src/api/autorun.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function autorun(
7575
isScheduled = true
7676
scheduler(() => {
7777
isScheduled = false
78-
if (!reaction.isDisposed_) {
78+
if (!reaction.isDisposed) {
7979
reaction.track(reactionRunner)
8080
}
8181
})
@@ -90,7 +90,7 @@ export function autorun(
9090
view(reaction)
9191
}
9292

93-
if(!opts?.signal?.aborted) {
93+
if (!opts?.signal?.aborted) {
9494
reaction.schedule_()
9595
}
9696
return reaction.getDisposer_(opts?.signal)
@@ -160,7 +160,7 @@ export function reaction<T, FireImmediately extends boolean = false>(
160160

161161
function reactionRunner() {
162162
isScheduled = false
163-
if (r.isDisposed_) {
163+
if (r.isDisposed) {
164164
return
165165
}
166166
let changed: boolean = false
@@ -181,7 +181,7 @@ export function reaction<T, FireImmediately extends boolean = false>(
181181
firstTime = false
182182
}
183183

184-
if(!opts?.signal?.aborted) {
184+
if (!opts?.signal?.aborted) {
185185
r.schedule_()
186186
}
187187
return r.getDisposer_(opts?.signal)

packages/mobx/src/api/when.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function _when(predicate: () => boolean, effect: Lambda, opts: IWhenOptions): IR
3838
if (typeof opts.timeout === "number") {
3939
const error = new Error("WHEN_TIMEOUT")
4040
timeoutHandle = setTimeout(() => {
41-
if (!disposer[$mobx].isDisposed_) {
41+
if (!disposer[$mobx].isDisposed) {
4242
disposer()
4343
if (opts.onError) {
4444
opts.onError(error)

packages/mobx/src/core/atom.ts

+29-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
Lambda
1515
} from "../internal"
1616

17+
import { getFlag, setFlag } from "../utils/utils"
18+
1719
export const $mobx = Symbol("mobx administration")
1820

1921
export interface IAtom extends IObservable {
@@ -22,11 +24,13 @@ export interface IAtom extends IObservable {
2224
}
2325

2426
export class Atom implements IAtom {
25-
isPendingUnobservation = false // for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
26-
isBeingObserved = false
27+
private static readonly isBeingObservedMask_ = 0b001
28+
private static readonly isPendingUnobservationMask_ = 0b010
29+
private static readonly diffValueMask_ = 0b100
30+
private flags_ = 0b000
31+
2732
observers_ = new Set<IDerivation>()
2833

29-
diffValue_ = 0
3034
lastAccessedBy_ = 0
3135
lowestObserverState_ = IDerivationState_.NOT_TRACKING_
3236
/**
@@ -35,6 +39,28 @@ export class Atom implements IAtom {
3539
*/
3640
constructor(public name_ = __DEV__ ? "Atom@" + getNextId() : "Atom") {}
3741

42+
// for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
43+
get isBeingObserved(): boolean {
44+
return getFlag(this.flags_, Atom.isBeingObservedMask_)
45+
}
46+
set isBeingObserved(newValue: boolean) {
47+
this.flags_ = setFlag(this.flags_, Atom.isBeingObservedMask_, newValue)
48+
}
49+
50+
get isPendingUnobservation(): boolean {
51+
return getFlag(this.flags_, Atom.isPendingUnobservationMask_)
52+
}
53+
set isPendingUnobservation(newValue: boolean) {
54+
this.flags_ = setFlag(this.flags_, Atom.isPendingUnobservationMask_, newValue)
55+
}
56+
57+
get diffValue(): 0 | 1 {
58+
return getFlag(this.flags_, Atom.diffValueMask_) ? 1 : 0
59+
}
60+
set diffValue(newValue: 0 | 1) {
61+
this.flags_ = setFlag(this.flags_, Atom.diffValueMask_, newValue === 1 ? true : false)
62+
}
63+
3864
// onBecomeObservedListeners
3965
public onBOL: Set<Lambda> | undefined
4066
// onBecomeUnobservedListeners

packages/mobx/src/core/computedvalue.ts

+19-18
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
allowStateChangesEnd
3333
} from "../internal"
3434

35+
import { getFlag, setFlag } from "../utils/utils"
36+
3537
export interface IComputedValue<T> {
3638
get(): T
3739
set(value: T): void
@@ -56,18 +58,6 @@ export type IComputedDidChange<T = any> = {
5658
oldValue: T | undefined
5759
}
5860

59-
function getFlag(flags: number, mask: number) {
60-
return !!(flags & mask)
61-
}
62-
function setFlag(flags: number, mask: number, newValue: boolean): number {
63-
if (newValue) {
64-
flags |= mask
65-
} else {
66-
flags &= ~mask
67-
}
68-
return flags
69-
}
70-
7161
/**
7262
* A node in the state dependency root that observes other nodes, and can be observed itself.
7363
*
@@ -92,7 +82,6 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
9282
observing_: IObservable[] = [] // nodes we are looking at. Our value depends on these nodes
9383
newObserving_ = null // during tracking it's an array with new observed observers
9484
observers_ = new Set<IDerivation>()
95-
diffValue_ = 0
9685
runId_ = 0
9786
lastAccessedBy_ = 0
9887
lowestObserverState_ = IDerivationState_.UP_TO_DATE_
@@ -101,11 +90,12 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
10190
name_: string
10291
triggeredBy_?: string
10392

104-
private static readonly isComputingMask_ = 0b0001
105-
private static readonly isRunningSetterMask_ = 0b0010
106-
private static readonly isBeingObservedMask_ = 0b0100
107-
private static readonly isPendingUnobservationMask_ = 0b1000
108-
private flags_ = 0b0000
93+
private static readonly isComputingMask_ = 0b00001
94+
private static readonly isRunningSetterMask_ = 0b00010
95+
private static readonly isBeingObservedMask_ = 0b00100
96+
private static readonly isPendingUnobservationMask_ = 0b01000
97+
private static readonly diffValueMask_ = 0b10000
98+
private flags_ = 0b00000
10999

110100
derivation: () => T // N.B: unminified as it is used by MST
111101
setter_?: (value: T) => void
@@ -197,6 +187,17 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
197187
this.flags_ = setFlag(this.flags_, ComputedValue.isPendingUnobservationMask_, newValue)
198188
}
199189

190+
get diffValue(): 0 | 1 {
191+
return getFlag(this.flags_, ComputedValue.diffValueMask_) ? 1 : 0
192+
}
193+
set diffValue(newValue: 0 | 1) {
194+
this.flags_ = setFlag(
195+
this.flags_,
196+
ComputedValue.diffValueMask_,
197+
newValue === 1 ? true : false
198+
)
199+
}
200+
200201
/**
201202
* Returns the current value of this computed value.
202203
* Will evaluate its computation first if needed.

0 commit comments

Comments
 (0)