Unify Derivation and Prism 11/n

`useDerivation()` => `usePrismInstance()`
This commit is contained in:
Aria Minaei 2022-12-01 14:59:03 +01:00
parent 1236900ddf
commit b2116e9a5d
10 changed files with 51 additions and 51 deletions

View file

@ -8,7 +8,7 @@ import type {$FixMe, $IntentionalAny} from './types'
import prism from './prisms/prism/prism' import prism from './prisms/prism/prism'
/** /**
* Allows creating pointer-derivations where the pointer can be switched out. * Allows creating pointer-prisms where the pointer can be switched out.
* *
* @remarks * @remarks
* This allows reacting not just to value changes at a certain pointer, but changes * This allows reacting not just to value changes at a certain pointer, but changes

View file

@ -144,7 +144,7 @@ export const getPointerParts = <_>(
* Creates a pointer to a (nested) property of an {@link Atom}. * Creates a pointer to a (nested) property of an {@link Atom}.
* *
* @remarks * @remarks
* Pointers are used to make derivations of properties or nested properties of * Pointers are used to make prisms of properties or nested properties of
* {@link Atom|Atoms}. * {@link Atom|Atoms}.
* *
* Pointers also allow easy construction of new pointers pointing to nested members * Pointers also allow easy construction of new pointers pointing to nested members

View file

@ -5,15 +5,15 @@ import type {Prism} from './Interface'
import {isPrism} from './Interface' import {isPrism} from './Interface'
export default function* iterateAndCountTicks<V>( export default function* iterateAndCountTicks<V>(
pointerOrDerivation: Prism<V> | Pointer<V>, pointerOrPrism: Prism<V> | Pointer<V>,
): Generator<{value: V; ticks: number}, void, void> { ): Generator<{value: V; ticks: number}, void, void> {
let d let d
if (isPointer(pointerOrDerivation)) { if (isPointer(pointerOrPrism)) {
d = pointerToPrism(pointerOrDerivation) as Prism<V> d = pointerToPrism(pointerOrPrism) as Prism<V>
} else if (isPrism(pointerOrDerivation)) { } else if (isPrism(pointerOrPrism)) {
d = pointerOrDerivation d = pointerOrPrism
} else { } else {
throw new Error(`Only pointers and derivations are supported`) throw new Error(`Only pointers and prisms are supported`)
} }
let ticksCountedSinceLastYield = 0 let ticksCountedSinceLastYield = 0

View file

@ -6,15 +6,15 @@ import type {Prism} from './Interface'
import {isPrism} from './Interface' import {isPrism} from './Interface'
export default function* iterateOver<V>( export default function* iterateOver<V>(
pointerOrDerivation: Prism<V> | Pointer<V>, pointerOrPrism: Prism<V> | Pointer<V>,
): Generator<V, void, void> { ): Generator<V, void, void> {
let d let d
if (isPointer(pointerOrDerivation)) { if (isPointer(pointerOrPrism)) {
d = pointerToPrism(pointerOrDerivation) as Prism<V> d = pointerToPrism(pointerOrPrism) as Prism<V>
} else if (isPrism(pointerOrDerivation)) { } else if (isPrism(pointerOrPrism)) {
d = pointerOrDerivation d = pointerOrPrism
} else { } else {
throw new Error(`Only pointers and derivations are supported`) throw new Error(`Only pointers and prisms are supported`)
} }
const ticker = new Ticker() const ticker = new Ticker()

View file

@ -96,7 +96,7 @@ describe('prism', () => {
const prsm = prism(() => { const prsm = prism(() => {
const n = val(a.pointer.letter) const n = val(a.pointer.letter)
const iterationAtTimeOfCall = iteration const iterationAtTimeOfCall = iteration
sequence.push({derivationCall: iterationAtTimeOfCall}) sequence.push({prismCall: iterationAtTimeOfCall})
prism.effect( prism.effect(
'f', 'f',
@ -116,13 +116,13 @@ describe('prism', () => {
sequence.push({change}) sequence.push({change})
}) })
expect(sequence).toMatchObject([{derivationCall: 0}, {effectCall: 0}]) expect(sequence).toMatchObject([{prismCall: 0}, {effectCall: 0}])
sequence.length = 0 sequence.length = 0
iteration++ iteration++
a.setIn(['letter'], 'b') a.setIn(['letter'], 'b')
ticker.tick() ticker.tick()
expect(sequence).toMatchObject([{derivationCall: 1}, {change: 'b'}]) expect(sequence).toMatchObject([{prismCall: 1}, {change: 'b'}])
sequence.length = 0 sequence.length = 0
deps = [1] deps = [1]
@ -130,7 +130,7 @@ describe('prism', () => {
a.setIn(['letter'], 'c') a.setIn(['letter'], 'c')
ticker.tick() ticker.tick()
expect(sequence).toMatchObject([ expect(sequence).toMatchObject([
{derivationCall: 2}, {prismCall: 2},
{cleanupCall: 0}, {cleanupCall: 0},
{effectCall: 2}, {effectCall: 2},
{change: 'c'}, {change: 'c'},
@ -156,7 +156,7 @@ describe('prism', () => {
const prsm = prism(() => { const prsm = prism(() => {
const n = val(a.pointer.letter) const n = val(a.pointer.letter)
const iterationAtTimeOfCall = iteration const iterationAtTimeOfCall = iteration
sequence.push({derivationCall: iterationAtTimeOfCall}) sequence.push({prismCall: iterationAtTimeOfCall})
const resultOfMemo = prism.memo( const resultOfMemo = prism.memo(
'memo', 'memo',
@ -177,7 +177,7 @@ describe('prism', () => {
}) })
expect(sequence).toMatchObject([ expect(sequence).toMatchObject([
{derivationCall: 0}, {prismCall: 0},
{memoCall: 0}, {memoCall: 0},
{resultOfMemo: 0}, {resultOfMemo: 0},
]) ])
@ -187,7 +187,7 @@ describe('prism', () => {
a.setIn(['letter'], 'b') a.setIn(['letter'], 'b')
ticker.tick() ticker.tick()
expect(sequence).toMatchObject([ expect(sequence).toMatchObject([
{derivationCall: 1}, {prismCall: 1},
{resultOfMemo: 0}, {resultOfMemo: 0},
{change: 'b'}, {change: 'b'},
]) ])
@ -198,7 +198,7 @@ describe('prism', () => {
a.setIn(['letter'], 'c') a.setIn(['letter'], 'c')
ticker.tick() ticker.tick()
expect(sequence).toMatchObject([ expect(sequence).toMatchObject([
{derivationCall: 2}, {prismCall: 2},
{memoCall: 2}, {memoCall: 2},
{resultOfMemo: 2}, {resultOfMemo: 2},
{change: 'c'}, {change: 'c'},

View file

@ -51,7 +51,7 @@ class HotHandle<V> {
constructor( constructor(
private readonly _fn: () => V, private readonly _fn: () => V,
private readonly _prismInstance: PrismDerivation<V>, private readonly _prismInstance: PrismInstance<V>,
) { ) {
for (const d of this._dependencies) { for (const d of this._dependencies) {
d._addDependent(this._reactToDependencyGoingStale) d._addDependent(this._reactToDependencyGoingStale)
@ -198,7 +198,7 @@ class HotHandle<V> {
const emptyObject = {} const emptyObject = {}
class PrismDerivation<V> implements Prism<V> { class PrismInstance<V> implements Prism<V> {
/** /**
* Whether the object is a prism. * Whether the object is a prism.
*/ */
@ -336,8 +336,8 @@ class PrismDerivation<V> implements Prism<V> {
* Design constraints: * Design constraints:
* - This fix should not have a perf-penalty in production. Perhaps use a global flag + `process.env.NODE_ENV !== 'production'` * - This fix should not have a perf-penalty in production. Perhaps use a global flag + `process.env.NODE_ENV !== 'production'`
* to enable it. * to enable it.
* - In the case of `DerivationValuelessEmitter`, we don't control when the user calls * - In the case of `onStale()`, we don't control when the user calls
* `getValue()` (as opposed to `DerivationEmitter` which calls `getValue()` directly). * `getValue()` (as opposed to `onChange()` which calls `getValue()` directly).
* Perhaps we can disable the check in that case. * Perhaps we can disable the check in that case.
* - Probably the best place to add this check is right here in this method plus some changes to `reportResulutionStart()`, * - Probably the best place to add this check is right here in this method plus some changes to `reportResulutionStart()`,
* which would have to be changed to let the caller know if there is an actual collector (a prism) * which would have to be changed to let the caller know if there is an actual collector (a prism)
@ -700,7 +700,7 @@ function inPrism(): boolean {
return !!hookScopeStack.peek() return !!hookScopeStack.peek()
} }
const possibleDerivationToValue = <P extends Prism<$IntentionalAny> | unknown>( const possiblePrismToValue = <P extends Prism<$IntentionalAny> | unknown>(
input: P, input: P,
): P extends Prism<infer T> ? T : P => { ): P extends Prism<infer T> ? T : P => {
if (isPrism(input)) { if (isPrism(input)) {
@ -742,7 +742,7 @@ type IPrismFn = {
* @param fn - The function to rerun when the prisms referenced in it change. * @param fn - The function to rerun when the prisms referenced in it change.
*/ */
const prism: IPrismFn = (fn) => { const prism: IPrismFn = (fn) => {
return new PrismDerivation(fn) return new PrismInstance(fn)
} }
class ColdScope implements PrismScope { class ColdScope implements PrismScope {

View file

@ -65,7 +65,7 @@ export function usePrism<T>(
boxRef.current.set(fnAsCallback) boxRef.current.set(fnAsCallback)
} }
const pr = useMemo( const prsm = useMemo(
() => () =>
prism(() => { prism(() => {
const fn = boxRef.current.prism.getValue() const fn = boxRef.current.prism.getValue()
@ -74,7 +74,7 @@ export function usePrism<T>(
[], [],
) )
return useDerivation(pr, debugLabel) return usePrismInstance(prsm, debugLabel)
} }
export const useVal: typeof val = (p: $IntentionalAny, debugLabel?: string) => { export const useVal: typeof val = (p: $IntentionalAny, debugLabel?: string) => {
@ -107,7 +107,7 @@ type QueueItem<T = unknown> = {
*/ */
debug?: { debug?: {
/** /**
* The `debugLabel` given to `usePrism()/useDerivation()` * The `debugLabel` given to `usePrism()/usePrismInstance()`
*/ */
label?: string label?: string
/** /**
@ -115,8 +115,8 @@ type QueueItem<T = unknown> = {
*/ */
traceOfFirstTimeRender: Error traceOfFirstTimeRender: Error
/** /**
* An array of the operations done on/about this useDerivation. This is helpful to trace * An array of the operations done on/about this usePrismInstance. This is helpful to trace
* why a useDerivation's update was added to the queue and why it re-rendered * why a usePrismInstance's update was added to the queue and why it re-rendered
*/ */
history: Array< history: Array<
/** /**
@ -150,11 +150,11 @@ type QueueItem<T = unknown> = {
*/ */
lastValue: T lastValue: T
/** /**
* Would be set to true if the element hosting the `useDerivation()` was unmounted * Would be set to true if the element hosting the `usePrismInstance()` was unmounted
*/ */
unmounted: boolean unmounted: boolean
/** /**
* Adds the `useDerivation` to the update queue * Adds the `usePrismInstance` to the update queue
*/ */
queueUpdate: () => void queueUpdate: () => void
/** /**
@ -224,7 +224,7 @@ function queueIfNeeded() {
item.debug?.history.push(`queue: der.getValue() errored`) item.debug?.history.push(`queue: der.getValue() errored`)
} }
console.error( console.error(
'A `der.getValue()` in `useDerivation(der)` threw an error. ' + 'A `der.getValue()` in `usePrismInstance(der)` threw an error. ' +
"This may be a zombie child issue, so we're gonna try to get its value again in a normal react render phase." + "This may be a zombie child issue, so we're gonna try to get its value again in a normal react render phase." +
'If you see the same error again, then you either have an error in your prism code, or the deps array in `usePrism(fn, deps)` is missing ' + 'If you see the same error again, then you either have an error in your prism code, or the deps array in `usePrism(fn, deps)` is missing ' +
'a dependency and causing the prism to read stale values.', 'a dependency and causing the prism to read stale values.',
@ -268,19 +268,19 @@ function queueIfNeeded() {
* *
* # Remove cold prism reads * # Remove cold prism reads
* *
* Prior to the latest change, the first render of every `useDerivation()` resulted in a cold read of its inner prism. * Prior to the latest change, the first render of every `usePrismInstance()` resulted in a cold read of its inner prism.
* Cold reads are predictably slow. The reason we'd run cold reads was to comply with react's rule of not running side-effects * Cold reads are predictably slow. The reason we'd run cold reads was to comply with react's rule of not running side-effects
* during render. (Turning a prism hot is _technically_ a side-effect). * during render. (Turning a prism hot is _technically_ a side-effect).
* *
* However, now that users are animating scenes with hundreds of objects in the same sequence, the lag started to be noticable. * However, now that users are animating scenes with hundreds of objects in the same sequence, the lag started to be noticable.
* *
* This commit changes `useDerivation()` so that it turns its prism hot before rendering them. * This commit changes `usePrismInstance()` so that it turns its prism hot before rendering them.
* *
* # Freshen prisms before render * # Freshen prisms before render
* *
* Previously in order to avoid the zombie child problem (https://kaihao.dev/posts/stale-props-and-zombie-children-in-redux) * Previously in order to avoid the zombie child problem (https://kaihao.dev/posts/stale-props-and-zombie-children-in-redux)
* we deferred freshening the prisms to the render phase of components. This meant that if a prism's dependencies * we deferred freshening the prisms to the render phase of components. This meant that if a prism's dependencies
* changed, `useDerivation()` would schedule a re-render, regardless of whether that change actually affected the prism's * changed, `usePrismInstance()` would schedule a re-render, regardless of whether that change actually affected the prism's
* value. Here is a contrived example: * value. Here is a contrived example:
* *
* ```ts * ```ts
@ -288,7 +288,7 @@ function queueIfNeeded() {
* const isPositiveD = prism(() => num.prism.getValue() >= 0) * const isPositiveD = prism(() => num.prism.getValue() >= 0)
* *
* const Comp = () => { * const Comp = () => {
* return <div>{useDerivation(isPositiveD)}</div> * return <div>{usePrismInstance(isPositiveD)}</div>
* } * }
* *
* num.set(2) // would cause Comp to re-render- even though 1 is still a positive number * num.set(2) // would cause Comp to re-render- even though 1 is still a positive number
@ -301,9 +301,9 @@ function queueIfNeeded() {
* the mounting tree. * the mounting tree.
* *
* On the off-chance that one of them still turns out to be a zombile child, `runQueue` will defer that particular * On the off-chance that one of them still turns out to be a zombile child, `runQueue` will defer that particular
* `useDerivation()` to be read inside a normal react render phase. * `usePrismInstance()` to be read inside a normal react render phase.
*/ */
export function useDerivation<T>(der: Prism<T>, debugLabel?: string): T { export function usePrismInstance<T>(der: Prism<T>, debugLabel?: string): T {
const _forceUpdate = useForceUpdate(debugLabel) const _forceUpdate = useForceUpdate(debugLabel)
const ref = useRef<QueueItem<T>>(undefined as $IntentionalAny) const ref = useRef<QueueItem<T>>(undefined as $IntentionalAny)
@ -347,7 +347,7 @@ export function useDerivation<T>(der: Prism<T>, debugLabel?: string): T {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
if (der !== ref.current.der) { if (der !== ref.current.der) {
console.error( console.error(
'Argument `der` in `useDerivation(der)` should not change between renders.', 'Argument `der` in `usePrismInstance(der)` should not change between renders.',
) )
} }
} }
@ -389,7 +389,7 @@ export function usePrismWithoutReRender<T>(
): Prism<T> { ): Prism<T> {
const pr = useMemo(() => prism(fn), deps) const pr = useMemo(() => prism(fn), deps)
return useDerivationWithoutReRender(pr) return usePrismInstanceWithoutReRender(pr)
} }
/** /**
@ -398,7 +398,7 @@ export function usePrismWithoutReRender<T>(
* return the value of the prism, and it does not * return the value of the prism, and it does not
* re-render the component if the value of the prism changes. * re-render the component if the value of the prism changes.
*/ */
export function useDerivationWithoutReRender<T>(der: Prism<T>): Prism<T> { export function usePrismInstanceWithoutReRender<T>(der: Prism<T>): Prism<T> {
useEffect(() => { useEffect(() => {
const untap = der.keepHot() const untap = der.keepHot()

View file

@ -7,7 +7,7 @@ import getStudio from '@theatre/studio/getStudio'
import type Scrub from '@theatre/studio/Scrub' import type Scrub from '@theatre/studio/Scrub'
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import getDeep from '@theatre/shared/utils/getDeep' import getDeep from '@theatre/shared/utils/getDeep'
import {useDerivation} from '@theatre/react' import {usePrismInstance} from '@theatre/react'
import type { import type {
$IntentionalAny, $IntentionalAny,
SerializablePrimitive as SerializablePrimitive, SerializablePrimitive as SerializablePrimitive,
@ -355,7 +355,7 @@ export function useEditingToolsForSimplePropInDetailsPanel<
propConfig: PropTypeConfig_AllSimples, propConfig: PropTypeConfig_AllSimples,
): EditingTools<T> { ): EditingTools<T> {
const der = getDerivation(pointerToProp, obj, propConfig) const der = getDerivation(pointerToProp, obj, propConfig)
return useDerivation(der) return usePrismInstance(der)
} }
type Shade = type Shade =

View file

@ -5,7 +5,7 @@ import {useEffect} from 'react'
import {useLogger} from './useLogger' import {useLogger} from './useLogger'
import {Box, prism, pointerToPrism} from '@theatre/dataverse' import {Box, prism, pointerToPrism} from '@theatre/dataverse'
import {Atom} from '@theatre/dataverse' import {Atom} from '@theatre/dataverse'
import {useDerivation} from '@theatre/react' import {usePrismInstance} from '@theatre/react'
import {selectClosestHTMLAncestor} from '@theatre/studio/utils/selectClosestHTMLAncestor' import {selectClosestHTMLAncestor} from '@theatre/studio/utils/selectClosestHTMLAncestor'
/** To mean the presence value */ /** To mean the presence value */
@ -95,7 +95,7 @@ function createPresenceContext(options: {
} }
}) })
}, [itemKey]) }, [itemKey])
return useDerivation(focusD) return usePrismInstance(focusD)
}, },
setUserHover(itemKeyOpt) { setUserHover(itemKeyOpt) {
const prev = currentUserHoverItemB.get() const prev = currentUserHoverItemB.get()

View file

@ -1,6 +1,6 @@
import {isPrism, prism, val} from '@theatre/dataverse' import {isPrism, prism, val} from '@theatre/dataverse'
import type {Prism, Pointer} from '@theatre/dataverse' import type {Prism, Pointer} from '@theatre/dataverse'
import {useDerivation} from '@theatre/react' import {usePrismInstance} from '@theatre/react'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import React, {useMemo, useRef} from 'react' import React, {useMemo, useRef} from 'react'
import {invariant} from './invariant' import {invariant} from './invariant'
@ -86,7 +86,7 @@ export function deriver<Props extends {}>(
) )
const allD = useMemo(() => deriveAllD(observables), observableArr) const allD = useMemo(() => deriveAllD(observables), observableArr)
const observedPropState = useDerivation(allD) const observedPropState = usePrismInstance(allD)
return ( return (
observedPropState && observedPropState &&