Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shrink Atom and Reaction using a bitfield #3901

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shaggy-monkeys-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"mobx": patch
---

Shrink Atom and Reaction using a bitfield
8 changes: 4 additions & 4 deletions packages/mobx/__tests__/v4/base/observables.js
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ test("forcefully tracked reaction should still yield valid results", function ()
transaction(function () {
x.set(4)
a.track(identity)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)
expect(z).toBe(4)
expect(runCount).toBe(2)
})
Expand All @@ -1166,17 +1166,17 @@ test("forcefully tracked reaction should still yield valid results", function ()

transaction(function () {
x.set(5)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)
a.track(identity)
expect(z).toBe(5)
expect(runCount).toBe(3)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)

x.set(6)
expect(z).toBe(5)
expect(runCount).toBe(3)
})
expect(a.isScheduled()).toBe(false)
expect(a.isScheduled).toBe(false)
expect(z).toBe(6)
expect(runCount).toBe(4)
})
Expand Down
60 changes: 30 additions & 30 deletions packages/mobx/__tests__/v5/base/errorhandling.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ test("peeking inside erroring computed value doesn't bork (global) state", () =>

expect(a.isPendingUnobservation).toBe(false)
expect(a.observers_.size).toBe(0)
expect(a.diffValue_).toBe(0)
expect(a.diffValue).toBe(0)
expect(a.lowestObserverState_).toBe(-1)
expect(a.hasUnreportedChange_).toBe(false)
expect(a.value_).toBe(1)
Expand All @@ -495,7 +495,7 @@ test("peeking inside erroring computed value doesn't bork (global) state", () =>
expect(b.newObserving_).toBe(null)
expect(b.isPendingUnobservation).toBe(false)
expect(b.observers_.size).toBe(0)
expect(b.diffValue_).toBe(0)
expect(b.diffValue).toBe(0)
expect(b.lowestObserverState_).toBe(0)
expect(b.unboundDepsCount_).toBe(0)
expect(() => {
Expand Down Expand Up @@ -523,7 +523,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
test("it should update correctly initially", () => {
expect(a.isPendingUnobservation).toBe(false)
expect(a.observers_.size).toBe(1)
expect(a.diffValue_).toBe(0)
expect(a.diffValue).toBe(0)
expect(a.lowestObserverState_).toBe(-1)
expect(a.hasUnreportedChange_).toBe(false)
expect(a.value_).toBe(1)
Expand All @@ -533,7 +533,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(b.newObserving_).toBe(null)
expect(b.isPendingUnobservation).toBe(false)
expect(b.observers_.size).toBe(1)
expect(b.diffValue_).toBe(0)
expect(b.diffValue).toBe(0)
expect(b.lowestObserverState_).toBe(0)
expect(b.unboundDepsCount_).toBe(1) // value is always the last bound amount of observers
expect(b.value_).toBe(1)
Expand All @@ -542,12 +542,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(c.dependenciesState_).toBe(0)
expect(c.observing_.length).toBe(1)
expect(c.newObserving_).toBe(null)
expect(c.diffValue_).toBe(0)
expect(c.diffValue).toBe(0)
expect(c.unboundDepsCount_).toBe(1)
expect(c.isDisposed_).toBe(false)
expect(c.isScheduled_).toBe(false)
expect(c.isTrackPending_).toBe(false)
expect(c.isRunning_).toBe(false)
expect(c.isDisposed).toBe(false)
expect(c.isScheduled).toBe(false)
expect(c.isTrackPending).toBe(false)
expect(c.isRunning).toBe(false)
checkGlobalState()
})

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

expect(a.isPendingUnobservation).toBe(false)
expect(a.observers_.size).toBe(1)
expect(a.diffValue_).toBe(0)
expect(a.diffValue).toBe(0)
expect(a.lowestObserverState_).toBe(0)
expect(a.hasUnreportedChange_).toBe(false)
expect(a.value_).toBe(2)
Expand All @@ -570,7 +570,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(b.newObserving_).toBe(null)
expect(b.isPendingUnobservation).toBe(false)
expect(b.observers_.size).toBe(1)
expect(b.diffValue_).toBe(0)
expect(b.diffValue).toBe(0)
expect(b.lowestObserverState_).toBe(0)
expect(b.unboundDepsCount_).toBe(1)
expect(b.isComputing).toBe(false)
Expand All @@ -579,12 +579,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(c.dependenciesState_).toBe(0)
expect(c.observing_.length).toBe(1)
expect(c.newObserving_).toBe(null)
expect(c.diffValue_).toBe(0)
expect(c.diffValue).toBe(0)
expect(c.unboundDepsCount_).toBe(1)
expect(c.isDisposed_).toBe(false)
expect(c.isScheduled_).toBe(false)
expect(c.isTrackPending_).toBe(false)
expect(c.isRunning_).toBe(false)
expect(c.isDisposed).toBe(false)
expect(c.isScheduled).toBe(false)
expect(c.isTrackPending).toBe(false)
expect(c.isRunning).toBe(false)
checkGlobalState()
})

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

expect(a.isPendingUnobservation).toBe(false)
expect(a.observers_.size).toBe(1)
expect(a.diffValue_).toBe(0)
expect(a.diffValue).toBe(0)
expect(a.lowestObserverState_).toBe(0)
expect(a.hasUnreportedChange_).toBe(false)
expect(a.value_).toBe(3)
Expand All @@ -606,7 +606,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(b.newObserving_).toBe(null)
expect(b.isPendingUnobservation).toBe(false)
expect(b.observers_.size).toBe(1)
expect(b.diffValue_).toBe(0)
expect(b.diffValue).toBe(0)
expect(b.lowestObserverState_).toBe(0)
expect(b.unboundDepsCount_).toBe(1)
expect(b.value_).toBe(3)
Expand All @@ -615,12 +615,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(c.dependenciesState_).toBe(0)
expect(c.observing_.length).toBe(1)
expect(c.newObserving_).toBe(null)
expect(c.diffValue_).toBe(0)
expect(c.diffValue).toBe(0)
expect(c.unboundDepsCount_).toBe(1)
expect(c.isDisposed_).toBe(false)
expect(c.isScheduled_).toBe(false)
expect(c.isTrackPending_).toBe(false)
expect(c.isRunning_).toBe(false)
expect(c.isDisposed).toBe(false)
expect(c.isScheduled).toBe(false)
expect(c.isTrackPending).toBe(false)
expect(c.isRunning).toBe(false)

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

expect(a.isPendingUnobservation).toBe(false)
expect(a.observers_.size).toBe(0)
expect(a.diffValue_).toBe(0)
expect(a.diffValue).toBe(0)
expect(a.lowestObserverState_).toBe(0)
expect(a.hasUnreportedChange_).toBe(false)
expect(a.value_).toBe(3)
Expand All @@ -640,7 +640,7 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(b.newObserving_).toBe(null)
expect(b.isPendingUnobservation).toBe(false)
expect(b.observers_.size).toBe(0)
expect(b.diffValue_).toBe(0)
expect(b.diffValue).toBe(0)
expect(b.lowestObserverState_).toBe(0)
expect(b.unboundDepsCount_).toBe(1)
expect(b.value_).not.toBe(3)
Expand All @@ -649,12 +649,12 @@ describe("peeking inside autorun doesn't bork (global) state", () => {
expect(c.dependenciesState_).toBe(-1)
expect(c.observing_.length).toBe(0)
expect(c.newObserving_).toBe(null)
expect(c.diffValue_).toBe(0)
expect(c.diffValue).toBe(0)
expect(c.unboundDepsCount_).toBe(1)
expect(c.isDisposed_).toBe(true)
expect(c.isScheduled_).toBe(false)
expect(c.isTrackPending_).toBe(false)
expect(c.isRunning_).toBe(false)
expect(c.isDisposed).toBe(true)
expect(c.isScheduled).toBe(false)
expect(c.isTrackPending).toBe(false)
expect(c.isRunning).toBe(false)

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

Expand Down
8 changes: 4 additions & 4 deletions packages/mobx/__tests__/v5/base/observables.js
Original file line number Diff line number Diff line change
Expand Up @@ -1197,7 +1197,7 @@ test("forcefully tracked reaction should still yield valid results", function ()
transaction(function () {
x.set(4)
a.track(identity)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)
expect(z).toBe(4)
expect(runCount).toBe(2)
})
Expand All @@ -1207,17 +1207,17 @@ test("forcefully tracked reaction should still yield valid results", function ()

transaction(function () {
x.set(5)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)
a.track(identity)
expect(z).toBe(5)
expect(runCount).toBe(3)
expect(a.isScheduled()).toBe(true)
expect(a.isScheduled).toBe(true)

x.set(6)
expect(z).toBe(5)
expect(runCount).toBe(3)
})
expect(a.isScheduled()).toBe(false)
expect(a.isScheduled).toBe(false)
expect(z).toBe(6)
expect(runCount).toBe(4)
})
Expand Down
8 changes: 4 additions & 4 deletions packages/mobx/src/api/autorun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function autorun(
isScheduled = true
scheduler(() => {
isScheduled = false
if (!reaction.isDisposed_) {
if (!reaction.isDisposed) {
reaction.track(reactionRunner)
}
})
Expand All @@ -90,7 +90,7 @@ export function autorun(
view(reaction)
}

if(!opts?.signal?.aborted) {
if (!opts?.signal?.aborted) {
reaction.schedule_()
}
return reaction.getDisposer_(opts?.signal)
Expand Down Expand Up @@ -160,7 +160,7 @@ export function reaction<T, FireImmediately extends boolean = false>(

function reactionRunner() {
isScheduled = false
if (r.isDisposed_) {
if (r.isDisposed) {
return
}
let changed: boolean = false
Expand All @@ -181,7 +181,7 @@ export function reaction<T, FireImmediately extends boolean = false>(
firstTime = false
}

if(!opts?.signal?.aborted) {
if (!opts?.signal?.aborted) {
r.schedule_()
}
return r.getDisposer_(opts?.signal)
Expand Down
2 changes: 1 addition & 1 deletion packages/mobx/src/api/when.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function _when(predicate: () => boolean, effect: Lambda, opts: IWhenOptions): IR
if (typeof opts.timeout === "number") {
const error = new Error("WHEN_TIMEOUT")
timeoutHandle = setTimeout(() => {
if (!disposer[$mobx].isDisposed_) {
if (!disposer[$mobx].isDisposed) {
disposer()
if (opts.onError) {
opts.onError(error)
Expand Down
32 changes: 29 additions & 3 deletions packages/mobx/src/core/atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
Lambda
} from "../internal"

import { getFlag, setFlag } from "../utils/utils"

export const $mobx = Symbol("mobx administration")

export interface IAtom extends IObservable {
Expand All @@ -22,11 +24,13 @@ export interface IAtom extends IObservable {
}

export class Atom implements IAtom {
isPendingUnobservation = false // for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
isBeingObserved = false
private static readonly isBeingObservedMask_ = 0b001
private static readonly isPendingUnobservationMask_ = 0b010
private static readonly diffValueMask_ = 0b100
private flags_ = 0b000

observers_ = new Set<IDerivation>()

diffValue_ = 0
lastAccessedBy_ = 0
lowestObserverState_ = IDerivationState_.NOT_TRACKING_
/**
Expand All @@ -35,6 +39,28 @@ export class Atom implements IAtom {
*/
constructor(public name_ = __DEV__ ? "Atom@" + getNextId() : "Atom") {}

// for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
get isBeingObserved(): boolean {
return getFlag(this.flags_, Atom.isBeingObservedMask_)
}
set isBeingObserved(newValue: boolean) {
this.flags_ = setFlag(this.flags_, Atom.isBeingObservedMask_, newValue)
}

get isPendingUnobservation(): boolean {
return getFlag(this.flags_, Atom.isPendingUnobservationMask_)
}
set isPendingUnobservation(newValue: boolean) {
this.flags_ = setFlag(this.flags_, Atom.isPendingUnobservationMask_, newValue)
}

get diffValue(): 0 | 1 {
return getFlag(this.flags_, Atom.diffValueMask_) ? 1 : 0
}
set diffValue(newValue: 0 | 1) {
this.flags_ = setFlag(this.flags_, Atom.diffValueMask_, newValue === 1 ? true : false)
}

// onBecomeObservedListeners
public onBOL: Set<Lambda> | undefined
// onBecomeUnobservedListeners
Expand Down
37 changes: 19 additions & 18 deletions packages/mobx/src/core/computedvalue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
allowStateChangesEnd
} from "../internal"

import { getFlag, setFlag } from "../utils/utils"

export interface IComputedValue<T> {
get(): T
set(value: T): void
Expand All @@ -56,18 +58,6 @@ export type IComputedDidChange<T = any> = {
oldValue: T | undefined
}

function getFlag(flags: number, mask: number) {
return !!(flags & mask)
}
function setFlag(flags: number, mask: number, newValue: boolean): number {
if (newValue) {
flags |= mask
} else {
flags &= ~mask
}
return flags
}

/**
* A node in the state dependency root that observes other nodes, and can be observed itself.
*
Expand All @@ -92,7 +82,6 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
observing_: IObservable[] = [] // nodes we are looking at. Our value depends on these nodes
newObserving_ = null // during tracking it's an array with new observed observers
observers_ = new Set<IDerivation>()
diffValue_ = 0
runId_ = 0
lastAccessedBy_ = 0
lowestObserverState_ = IDerivationState_.UP_TO_DATE_
Expand All @@ -101,11 +90,12 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
name_: string
triggeredBy_?: string

private static readonly isComputingMask_ = 0b0001
private static readonly isRunningSetterMask_ = 0b0010
private static readonly isBeingObservedMask_ = 0b0100
private static readonly isPendingUnobservationMask_ = 0b1000
private flags_ = 0b0000
private static readonly isComputingMask_ = 0b00001
private static readonly isRunningSetterMask_ = 0b00010
private static readonly isBeingObservedMask_ = 0b00100
private static readonly isPendingUnobservationMask_ = 0b01000
private static readonly diffValueMask_ = 0b10000
private flags_ = 0b00000

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

get diffValue(): 0 | 1 {
return getFlag(this.flags_, ComputedValue.diffValueMask_) ? 1 : 0
}
set diffValue(newValue: 0 | 1) {
this.flags_ = setFlag(
this.flags_,
ComputedValue.diffValueMask_,
newValue === 1 ? true : false
)
}

/**
* Returns the current value of this computed value.
* Will evaluate its computation first if needed.
Expand Down
Loading