diff --git a/packages/dataverse/src/Atom.ts b/packages/dataverse/src/Atom.ts index b6e9b06..500bc25 100644 --- a/packages/dataverse/src/Atom.ts +++ b/packages/dataverse/src/Atom.ts @@ -4,10 +4,10 @@ import last from 'lodash-es/last' import type {Prism} from './prism/Interface' import {isPrism} from './prism/Interface' import type {Pointer, PointerType} from './pointer' +import {getPointerParts} from './pointer' import {isPointer} from './pointer' import pointer, {getPointerMeta} from './pointer' import type {$FixMe, $IntentionalAny} from './types' -import type {PathBasedReducer} from './utils/PathBasedReducer' import updateDeep from './utils/updateDeep' import prism from './prism/prism' @@ -176,39 +176,22 @@ export default class Atom implements IdentityPrismProvider { return path.length === 0 ? this.getState() : get(this.getState(), path) } - /** - * Creates a new state object from the current one, where the value at `path` - * is replaced by the return value of `reducer`, then sets it. - * - * @remarks - * Doesn't mutate the old state, and preserves referential equality between - * values of the old state and the new state where possible. - * - * @example - * ```ts - * someAtom.getIn(['a']) // 1 - * someAtom.reduceState(['a'], (state) => state + 1); - * someAtom.getIn(['a']) // 2 - * ``` - * - * @param path - The path to call the reducer at. - * @param reducer - The function to use for creating the new state. - */ - // TODO: Why is this a property and not a method? - reduceState: PathBasedReducer = ( - path: $IntentionalAny[], - reducer: $IntentionalAny, - ) => { - const newState = updateDeep(this.getState(), path, reducer) - this.setState(newState) - return newState + reduce(fn: (state: State) => State) { + this.set(fn(this.get())) } - /** - * Sets the state of the atom at `path`. - */ - setIn(path: $FixMe[], val: $FixMe) { - return this.reduceState(path, () => val) + reduceByPointer( + fn: (p: Pointer) => Pointer, + reducer: (s: S) => S, + ) { + const pointer = fn(this.pointer) + const path = getPointerParts(pointer).path + const newState = updateDeep(this.get(), path, reducer) + this.set(newState) + } + + setByPointer(fn: (p: Pointer) => Pointer, val: S) { + this.reduceByPointer(fn, () => val) } private _checkUpdates(scope: Scope, oldState: unknown, newState: unknown) { diff --git a/packages/dataverse/src/prism/iterateOver.test.ts b/packages/dataverse/src/prism/iterateOver.test.ts index 9bbf3d0..b81eda3 100644 --- a/packages/dataverse/src/prism/iterateOver.test.ts +++ b/packages/dataverse/src/prism/iterateOver.test.ts @@ -6,16 +6,16 @@ import iterateOver from './iterateOver' describe(`iterateOver()`, () => { test('it should work', () => { - const a = new Atom({a: 0}) - let iter = iterateOver(a.pointer.a) + const a = new Atom(0) + let iter = iterateOver(a.pointer) expect(iter.next().value).toEqual(0) - a.setIn(['a'], 1) - a.setIn(['a'], 2) + a.set(1) + a.set(2) expect(iter.next()).toMatchObject({value: 2, done: false}) iter.return() - iter = iterateOver(a.pointer.a) + iter = iterateOver(a.pointer) expect(iter.next().value).toEqual(2) - a.setIn(['a'], 3) + a.set(3) expect(iter.next()).toMatchObject({done: false, value: 3}) iter.return() }) diff --git a/packages/dataverse/src/prism/prism.test.ts b/packages/dataverse/src/prism/prism.test.ts index bb34caf..e86a76e 100644 --- a/packages/dataverse/src/prism/prism.test.ts +++ b/packages/dataverse/src/prism/prism.test.ts @@ -25,7 +25,7 @@ describe('prism', () => { changes.push(c) }) - o.reduceState(['foo'], () => 'foo2') + o.reduce(({foo}) => ({foo: 'foo2'})) ticker.tick() expect(changes).toMatchObject(['foo2boo']) }) @@ -43,11 +43,11 @@ describe('prism', () => { describe('prism.ref()', () => { it('should work', () => { - const theAtom: Atom<{n: number}> = new Atom({n: 2}) + const theAtom: Atom = new Atom(2) const isEvenD = prism((): {isEven: boolean} => { const ref = prism.ref<{isEven: boolean} | undefined>('cache', undefined) - const currentN = val(theAtom.pointer.n) + const currentN = val(theAtom.pointer) const isEven = currentN % 2 === 0 if (ref.current && ref.current.isEven === isEven) { @@ -60,20 +60,20 @@ describe('prism', () => { const iterator = iterateAndCountTicks(isEvenD) - theAtom.reduceState(['n'], () => 3) + theAtom.reduce(() => 3) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 0, }) - theAtom.reduceState(['n'], () => 5) - theAtom.reduceState(['n'], () => 7) + theAtom.reduce(() => 5) + theAtom.reduce(() => 7) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 1, }) - theAtom.reduceState(['n'], () => 2) - theAtom.reduceState(['n'], () => 4) + theAtom.reduce(() => 2) + theAtom.reduce(() => 4) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 1, @@ -91,10 +91,10 @@ describe('prism', () => { const sequence: unknown[] = [] let deps: unknown[] = [] - const a = new Atom({letter: 'a'}) + const a = new Atom('a') const prsm = prism(() => { - const n = val(a.pointer.letter) + const n = val(a.pointer) const iterationAtTimeOfCall = iteration sequence.push({prismCall: iterationAtTimeOfCall}) @@ -120,14 +120,14 @@ describe('prism', () => { sequence.length = 0 iteration++ - a.setIn(['letter'], 'b') + a.set('b') ticker.tick() expect(sequence).toMatchObject([{prismCall: 1}, {change: 'b'}]) sequence.length = 0 deps = [1] iteration++ - a.setIn(['letter'], 'c') + a.set('c') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 2}, @@ -151,10 +151,10 @@ describe('prism', () => { const sequence: unknown[] = [] let deps: unknown[] = [] - const a = new Atom({letter: 'a'}) + const a = new Atom('a') const prsm = prism(() => { - const n = val(a.pointer.letter) + const n = val(a.pointer) const iterationAtTimeOfCall = iteration sequence.push({prismCall: iterationAtTimeOfCall}) @@ -184,7 +184,7 @@ describe('prism', () => { sequence.length = 0 iteration++ - a.setIn(['letter'], 'b') + a.set('b') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 1}, @@ -195,7 +195,7 @@ describe('prism', () => { deps = [1] iteration++ - a.setIn(['letter'], 'c') + a.set('c') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 2}, diff --git a/packages/dataverse/src/utils/PathBasedReducer.ts b/packages/dataverse/src/utils/PathBasedReducer.ts deleted file mode 100644 index b3380de..0000000 --- a/packages/dataverse/src/utils/PathBasedReducer.ts +++ /dev/null @@ -1,133 +0,0 @@ -export type PathBasedReducer = { - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - A6 extends keyof S[A0][A1][A2][A3][A4][A5], - A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], - A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], - A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8], - A10 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], - >( - addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10], - reducer: ( - d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10], - ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - A6 extends keyof S[A0][A1][A2][A3][A4][A5], - A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], - A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], - A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8], - >( - addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9], - reducer: ( - d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], - ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - A6 extends keyof S[A0][A1][A2][A3][A4][A5], - A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], - A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], - >( - addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8], - reducer: ( - d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8], - ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - A6 extends keyof S[A0][A1][A2][A3][A4][A5], - A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], - >( - addr: [A0, A1, A2, A3, A4, A5, A6, A7], - reducer: ( - d: S[A0][A1][A2][A3][A4][A5][A6][A7], - ) => S[A0][A1][A2][A3][A4][A5][A6][A7], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - A6 extends keyof S[A0][A1][A2][A3][A4][A5], - >( - addr: [A0, A1, A2, A3, A4, A5, A6], - reducer: ( - d: S[A0][A1][A2][A3][A4][A5][A6], - ) => S[A0][A1][A2][A3][A4][A5][A6], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - A5 extends keyof S[A0][A1][A2][A3][A4], - >( - addr: [A0, A1, A2, A3, A4, A5], - reducer: (d: S[A0][A1][A2][A3][A4][A5]) => S[A0][A1][A2][A3][A4][A5], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - A4 extends keyof S[A0][A1][A2][A3], - >( - addr: [A0, A1, A2, A3, A4], - reducer: (d: S[A0][A1][A2][A3][A4]) => S[A0][A1][A2][A3][A4], - ): ReturnType - - < - A0 extends keyof S, - A1 extends keyof S[A0], - A2 extends keyof S[A0][A1], - A3 extends keyof S[A0][A1][A2], - >( - addr: [A0, A1, A2, A3], - reducer: (d: S[A0][A1][A2][A3]) => S[A0][A1][A2][A3], - ): ReturnType - - ( - addr: [A0, A1, A2], - reducer: (d: S[A0][A1][A2]) => S[A0][A1][A2], - ): ReturnType - - ( - addr: [A0, A1], - reducer: (d: S[A0][A1]) => S[A0][A1], - ): ReturnType - - (addr: [A0], reducer: (d: S[A0]) => S[A0]): ReturnType - - (addr: undefined[], reducer: (d: S) => S): ReturnType -} diff --git a/theatre/core/src/projects/Project.ts b/theatre/core/src/projects/Project.ts index c583e88..1b0e5c1 100644 --- a/theatre/core/src/projects/Project.ts +++ b/theatre/core/src/projects/Project.ts @@ -240,11 +240,11 @@ export default class Project { sheetId: SheetId, instanceId: SheetInstanceId = 'default' as SheetInstanceId, ): Sheet { - let template = this._sheetTemplates.getState()[sheetId] + let template = this._sheetTemplates.get()[sheetId] if (!template) { template = new SheetTemplate(this, sheetId) - this._sheetTemplates.setIn([sheetId], template) + this._sheetTemplates.reduce((s) => ({...s, [sheetId]: template})) } return template.getInstance(instanceId) diff --git a/theatre/core/src/projects/projectsSingleton.ts b/theatre/core/src/projects/projectsSingleton.ts index 8999632..82dbf58 100644 --- a/theatre/core/src/projects/projectsSingleton.ts +++ b/theatre/core/src/projects/projectsSingleton.ts @@ -14,11 +14,11 @@ class ProjectsSingleton { * We're trusting here that each project id is unique */ add(id: ProjectId, project: Project) { - this.atom.reduceState(['projects', id], () => project) + this.atom.setByPointer((p) => p.projects[id], project) } get(id: ProjectId): Project | undefined { - return this.atom.getState().projects[id] + return this.atom.get().projects[id] } has(id: ProjectId) { diff --git a/theatre/core/src/sequences/playbackControllers/AudioPlaybackController.ts b/theatre/core/src/sequences/playbackControllers/AudioPlaybackController.ts index 23a020d..138a318 100644 --- a/theatre/core/src/sequences/playbackControllers/AudioPlaybackController.ts +++ b/theatre/core/src/sequences/playbackControllers/AudioPlaybackController.ts @@ -120,11 +120,11 @@ export default class AudioPlaybackController implements IPlaybackController { } private get _playing() { - return this._state.getState().playing + return this._state.get().playing } private set _playing(playing: boolean) { - this._state.setIn(['playing'], playing) + this._state.setByPointer((p) => p.playing, playing) } destroy() {} @@ -140,11 +140,11 @@ export default class AudioPlaybackController implements IPlaybackController { } private _updatePositionInState(time: number) { - this._state.reduceState(['position'], () => time) + this._state.reduce((s) => ({...s, position: time})) } getCurrentPosition() { - return this._state.getState().position + return this._state.get().position } play( diff --git a/theatre/core/src/sequences/playbackControllers/DefaultPlaybackController.ts b/theatre/core/src/sequences/playbackControllers/DefaultPlaybackController.ts index 11a8835..51fdf1c 100644 --- a/theatre/core/src/sequences/playbackControllers/DefaultPlaybackController.ts +++ b/theatre/core/src/sequences/playbackControllers/DefaultPlaybackController.ts @@ -66,19 +66,19 @@ export default class DefaultPlaybackController implements IPlaybackController { } private _updatePositionInState(time: number) { - this._state.reduceState(['position'], () => time) + this._state.setByPointer((p) => p.position, time) } getCurrentPosition() { - return this._state.getState().position + return this._state.get().position } get playing() { - return this._state.getState().playing + return this._state.get().playing } set playing(playing: boolean) { - this._state.setIn(['playing'], playing) + this._state.setByPointer((p) => p.playing, playing) } play( diff --git a/theatre/core/src/sheetObjects/SheetObject.ts b/theatre/core/src/sheetObjects/SheetObject.ts index ee21417..cd82ea8 100644 --- a/theatre/core/src/sheetObjects/SheetObject.ts +++ b/theatre/core/src/sheetObjects/SheetObject.ts @@ -256,7 +256,11 @@ export default class SheetObject implements IdentityPrismProvider { const updateSequenceValueFromItsPrism = () => { const triple = pr.getValue() - if (!triple) return valsAtom.setIn(pathToProp, undefined) + if (!triple) + return valsAtom.setByPointer( + (p) => pointerDeep(p, pathToProp), + undefined, + ) const leftDeserialized = deserializeAndSanitize(triple.left) @@ -266,7 +270,10 @@ export default class SheetObject implements IdentityPrismProvider { : leftDeserialized if (triple.right === undefined) - return valsAtom.setIn(pathToProp, left) + return valsAtom.setByPointer( + (p) => pointerDeep(p, pathToProp), + left, + ) const rightDeserialized = deserializeAndSanitize(triple.right) const right = @@ -274,8 +281,8 @@ export default class SheetObject implements IdentityPrismProvider { ? propConfig.default : rightDeserialized - return valsAtom.setIn( - pathToProp, + return valsAtom.setByPointer( + (p) => pointerDeep(p, pathToProp), interpolate(left, right, triple.progression), ) } diff --git a/theatre/core/src/sheets/Sheet.ts b/theatre/core/src/sheets/Sheet.ts index d5943c2..49cbc15 100644 --- a/theatre/core/src/sheets/Sheet.ts +++ b/theatre/core/src/sheets/Sheet.ts @@ -69,7 +69,7 @@ export default class Sheet { const object = objTemplate.createInstance(this, nativeObject, config) - this._objects.setIn([objectKey], object) + this._objects.setByPointer((p) => p[objectKey], object) return object } @@ -79,7 +79,7 @@ export default class Sheet { } deleteObject(objectKey: ObjectAddressKey) { - this._objects.reduceState([], (state) => { + this._objects.reduce((state) => { const newState = {...state} delete newState[objectKey] return newState diff --git a/theatre/core/src/sheets/SheetTemplate.ts b/theatre/core/src/sheets/SheetTemplate.ts index 680cae7..3712b1f 100644 --- a/theatre/core/src/sheets/SheetTemplate.ts +++ b/theatre/core/src/sheets/SheetTemplate.ts @@ -43,7 +43,7 @@ export default class SheetTemplate { if (!inst) { inst = new Sheet(this, instanceId) - this._instances.setIn([instanceId], inst) + this._instances.setByPointer((p) => p[instanceId], inst) } return inst @@ -65,7 +65,7 @@ export default class SheetTemplate { config, actions, ) - this._objectTemplates.setIn([objectKey], template) + this._objectTemplates.setByPointer((p) => p[objectKey], template) } return template diff --git a/theatre/studio/src/Studio.ts b/theatre/studio/src/Studio.ts index e2cd5a7..d3aa260 100644 --- a/theatre/studio/src/Studio.ts +++ b/theatre/studio/src/Studio.ts @@ -188,7 +188,7 @@ export class Studio { setCoreBits(coreBits: CoreBits) { this._corePrivateApi = coreBits.privateAPI - this._coreAtom.setIn(['core'], coreBits.coreExports) + this._coreAtom.setByPointer((p) => p.core, coreBits.coreExports) this._setProjectsP(coreBits.projectsP) } diff --git a/theatre/studio/src/panels/SequenceEditorPanel/FrameStampPositionProvider.tsx b/theatre/studio/src/panels/SequenceEditorPanel/FrameStampPositionProvider.tsx index 0a21482..ca91a9e 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/FrameStampPositionProvider.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/FrameStampPositionProvider.tsx @@ -49,7 +49,7 @@ let lastLockId = 0 const FrameStampPositionProvider: React.FC<{ layoutP: Pointer }> = ({children, layoutP}) => { - const locksAtom = useMemo(() => new Atom<{list: LockItem[]}>({list: []}), []) + const locksAtom = useMemo(() => new Atom([]), []) const currentD = useMemo( () => prism(() => { @@ -57,7 +57,7 @@ const FrameStampPositionProvider: React.FC<{ .memo('p', () => pointerPositionInUnitSpace(layoutP), [layoutP]) .getValue() - const locks = val(locksAtom.pointer.list) + const locks = val(locksAtom.pointer) if (locks.length > 0) { return last(locks)!.position @@ -69,7 +69,7 @@ const FrameStampPositionProvider: React.FC<{ ) const getLock = useCallback(() => { const id = lastLockId++ - locksAtom.reduceState(['list'], (list) => [ + locksAtom.reduce((list) => [ ...list, { id, @@ -78,13 +78,11 @@ const FrameStampPositionProvider: React.FC<{ ]) const unlock = () => { - locksAtom.reduceState(['list'], (list) => - list.filter((lock) => lock.id !== id), - ) + locksAtom.reduce((list) => list.filter((lock) => lock.id !== id)) } const set = (posInUnitSpace: number) => { - locksAtom.reduceState(['list'], (list) => { + locksAtom.reduce((list) => { const index = list.findIndex((lock) => lock.id === id) if (index === -1) { console.warn(`Lock is already freed. This is a bug.`) diff --git a/theatre/studio/src/uiComponents/usePresence.tsx b/theatre/studio/src/uiComponents/usePresence.tsx index 609aeb6..c908d05 100644 --- a/theatre/studio/src/uiComponents/usePresence.tsx +++ b/theatre/studio/src/uiComponents/usePresence.tsx @@ -7,6 +7,7 @@ import {prism, pointerToPrism} from '@theatre/dataverse' import {Atom} from '@theatre/dataverse' import {usePrismInstance} from '@theatre/react' import {selectClosestHTMLAncestor} from '@theatre/studio/utils/selectClosestHTMLAncestor' +import pointerDeep from '@theatre/shared/utils/pointerDeep' /** To mean the presence value */ export enum PresenceFlag { @@ -53,12 +54,15 @@ function createPresenceContext(options: { flag: rel.flag, } const path = [rel.affects, itemKey, relationId] - relationsAtom.setIn(path, presence) + relationsAtom.setByPointer((p) => pointerDeep(p, path), presence) return path }) return () => { for (const pathToUndo of undoAtPaths) { - relationsAtom.setIn(pathToUndo, undefined) + relationsAtom.setByPointer( + (p) => pointerDeep(p, pathToUndo), + undefined, + ) } } }, @@ -98,16 +102,16 @@ function createPresenceContext(options: { return usePrismInstance(focusD) }, setUserHover(itemKeyOpt) { - const prev = currentUserHoverItemB.getState() + const prev = currentUserHoverItemB.get() if (prev === itemKeyOpt) { return } if (prev) { - currentUserHoverFlagItemsAtom.setIn([prev], false) + currentUserHoverFlagItemsAtom.setByPointer((p) => p[prev], false) } - currentUserHoverItemB.setState(itemKeyOpt) + currentUserHoverItemB.set(itemKeyOpt) if (itemKeyOpt) { - currentUserHoverFlagItemsAtom.setIn([itemKeyOpt], true) + currentUserHoverFlagItemsAtom.setByPointer((p) => p[itemKeyOpt], true) } }, } diff --git a/theatre/studio/src/utils/renderInPortalInContext.tsx b/theatre/studio/src/utils/renderInPortalInContext.tsx index 332d2cb..7bce0c7 100644 --- a/theatre/studio/src/utils/renderInPortalInContext.tsx +++ b/theatre/studio/src/utils/renderInPortalInContext.tsx @@ -27,7 +27,7 @@ export const getMounter = () => { props: Props, portalNode: HTMLElement, ) { - theAtom.reduceState([], (s) => { + theAtom.reduce((s) => { return { byId: {...s.byId, [id]: {comp, props, portalNode}}, set: {...s.set, [id]: true}, @@ -36,7 +36,7 @@ export const getMounter = () => { } function unmount() { - theAtom.reduceState([], (s) => { + theAtom.reduce((s) => { const set = {...s.set} const byId = {...s.byId} delete set[id]