From 4b00f45b04c943a91cd27cfbf57e8408775383b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Fri, 1 Apr 2022 11:19:26 +0200 Subject: [PATCH 01/20] Add `packages/dataverse/docs/GET_STARTED.md` --- packages/dataverse/README.md | 5 +- packages/dataverse/docs/GET_STARTED.md | 136 +++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 packages/dataverse/docs/GET_STARTED.md diff --git a/packages/dataverse/README.md b/packages/dataverse/README.md index 164f4e3ec..073564610 100644 --- a/packages/dataverse/README.md +++ b/packages/dataverse/README.md @@ -1,11 +1,10 @@ # @theatre/dataverse -Dataverse is the reactive dataflow library [Theatre.js](https://www.theatrejs.com) is built on. It is inspired by ideas in [functional reactive programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) and it is optimised for interactivity and animation. +Dataverse is the reactive dataflow library [Theatre.js](https://www.theatrejs.com) is built on. It is inspired by ideas in [functional reactive programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) and it is optimised for interactivity and animation. Check out the [Get started guide](./docs/GET_STARTED.md) for a more practical introduction. Dataverse is currently an internal library. It is used within Theatre.js, but its API is not exposed through Theatre. This is so that we can iterate on the reactive internals while keeping the public API stable. We plan to eventually have an LTS release, but for now, expect a high release cadence and API churn in dataverse while the API in Theatre.js remains stable. ## Documentation * API Docs: [docs.theatrejs.com/api/dataverse](https://docs.theatrejs.com/api/dataverse.html) -* Guide: TODO - +* Get started guide: [GET_STARTED.md](./docs/GET_STARTED.md) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md new file mode 100644 index 000000000..ce4e189e9 --- /dev/null +++ b/packages/dataverse/docs/GET_STARTED.md @@ -0,0 +1,136 @@ +# Into the dataverse - Get started with `@theatre/dataverse` + +Dataverse is the reactive dataflow library +[Theatre.js](https://www.theatrejs.com) is built on. It is inspired by ideas in +[functional reactive programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) +and it is optimised for interactivity and animation. This guide will help you to +get started with the library. + +## Main concepts + +A good analogy for `dataverse` would be a spreadsheet editor application. In a +spreadsheet editor you have cells that store values, cells that store functions, +that manipulate the values of other cells and the cells have identifiers (eg. +A1, B3, etc..., pointers) that are used to reference them in functions. These +are similiar to the set of tools that `dataverse` provides for manipulating +data. Here's a quick comparison: + +| `dataverse` | Spreadsheet editor analogy | role | +| ----------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------- | +| sources (`Box`, `Atom`) | a cell that holds a value | `Box`: holds a simple value, `Atom`: holds an object with (sub)props to be tracked | +| derivations | functions | changes recorded on the value of an `Box` or `Atom` | +| pointers | addresses of the cells (`A1`, `B3`) | they point to a source, don't contain values themselves | +| tappables | - | objects that can be observed for changes to execute a callback function when the change happens | + +Some concepts in `dataverse` go beyond the spreadsheet analogy. For example we +want to make the changes performant, so we make them on-demand and only show +them when it's necessary (eg.: only show a change in an animated value when the +screen gets repainted) which introduces new concepts like `Ticker`-s (we'll have +a look at them later). + +## Setup + +You are encouraged to follow the examples on your machine by cloning the +[`theatre-js/theatre`](https://github.com/theatre-js/theatre/) repo and creating +a new directory and file called `dataverse/index.tsx` in +`theatre/packages/playground/src/personal/` (this directory is already added to +`.gitignore`, so you don't have to worry about that). + +## Examples + +1. [`Box`](#box-storing-simple-values) +2. [Observing values](#observing-values) + +### `Box`: storing simple values + +Let's start with creating a variable that holds a simple value, which we can +change and observe later: + +```typescript +// `theatre/packages/playground/src/personal/dataverse/index.tsx` + +const variableB = new Box('some value') +console.log(variableB.get()) // prints 'some value' in the console +``` + +> As you can see there's a naming convention here for boxes (`variableB`), +> pointers (`variableP`), derivations (`variableP`), etc... + +Now we can change the value: + +```typescript +variableB.set('some new value') +console.log(variableB.get()) // prints 'some new value' in the console +``` + +### Observing values + +Let's say you want to watch the value of `variableB` for changes and execute a +callback when it does change. + +```typescript +const variableB = new Box('some value') +// Change the value of variableB to a random number in every 1000 ms +const interval = setInterval(() => { + variableB.set(Math.random().toString()) + console.log('isHot?', variableB.derivation.isHot) +}, 1000) + +// Watch `variableB` changes and print a message to the console when the value of +// `variableB` changes +const untap = variableB.derivation.changesWithoutValues().tap(() => { + console.log('value of variableB changed', variableB.derivation.getValue()) +}) + +// Stop observing `variableB` after 5000 ms +setTimeout(untap, 5000) + +// Clear the interval after 7000 ms +setTimeout(() => { + clearInterval(interval) + console.log('Interval cleared.') +}, 7000) +``` + +A few notes about the example above: + +- `variableB.derivation.changesWithoutValues()` returns a tappable that we can + tap into (observe). +- The `tap()` method returns the `untap()` function which aborts th +- As long as `variableB` is tapped (observed) `variableB.derivation.isHot` will + bet set to `true` automatically + +What if you want to keep a derivation hot manually even if there's no tappable +attached to it anymore? In this case you can use the `keepHot()` method as seen +below: out this modified version of the previous example: + +```typescript +variableB.set('some new value') +console.log(variableB.get()) // prints 'some new value' in the console + +// Change the value of variableB to a random number in every 1000 ms +const interval = setInterval(() => { + variableB.set(Math.random().toString()) + // This will print 'isHot? true' everytime, since we kept + // the derivation hot by calling the 'keepHot()' method + console.log('isHot?', variableB.derivation.isHot) +}, 1000) + +// Watch `variableB` changes and print a message to the console when the value of +// `variableB` changes +const untap = variableB.derivation.changesWithoutValues().tap(() => { + console.log('value of variableB changed', variableB.derivation.getValue()) +}) + +// Stop observing `variableB` after 5000 ms +setTimeout(untap, 5000) + +// Keep the derivation hot +variableB.derivation.keepHot() + +// Clear the interval after 7000 ms +setTimeout(() => { + clearInterval(interval) + console.log('Interval cleared.') +}, 7000) +``` From 5052c0708a2b1fcd514204472819924c5f6c1aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Mon, 4 Apr 2022 11:35:26 +0200 Subject: [PATCH 02/20] Add map() and prism() to the GET_STARTED.md doc in dataverse --- packages/dataverse/docs/GET_STARTED.md | 105 +++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index ce4e189e9..dc866ac63 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -28,7 +28,20 @@ them when it's necessary (eg.: only show a change in an animated value when the screen gets repainted) which introduces new concepts like `Ticker`-s (we'll have a look at them later). -## Setup +## Practical Examples + +Here we collected a few examples that introduce the main concepts/tools in +`dataverse` through practical examples. We strongly recommend running the +examples on your local machine (see the [Setup](#setup) section to see how to +configure your local environment before running the examples). + +0. [Setup: How to configure your local environment before running the examples](#setup) +1. [`Box`](#box-storing-simple-values) +2. [Observing values](#observing-values) +3. [`map()`](#map) +4. [`prism()`](#prism) + +### Setup You are encouraged to follow the examples on your machine by cloning the [`theatre-js/theatre`](https://github.com/theatre-js/theatre/) repo and creating @@ -36,17 +49,14 @@ a new directory and file called `dataverse/index.tsx` in `theatre/packages/playground/src/personal/` (this directory is already added to `.gitignore`, so you don't have to worry about that). -## Examples - -1. [`Box`](#box-storing-simple-values) -2. [Observing values](#observing-values) - ### `Box`: storing simple values Let's start with creating a variable that holds a simple value, which we can change and observe later: ```typescript +import {Box} from '@theatre/dataverse' + // `theatre/packages/playground/src/personal/dataverse/index.tsx` const variableB = new Box('some value') @@ -69,6 +79,8 @@ Let's say you want to watch the value of `variableB` for changes and execute a callback when it does change. ```typescript +import {Box} from '@theatre/dataverse' + const variableB = new Box('some value') // Change the value of variableB to a random number in every 1000 ms const interval = setInterval(() => { @@ -111,7 +123,7 @@ console.log(variableB.get()) // prints 'some new value' in the console // Change the value of variableB to a random number in every 1000 ms const interval = setInterval(() => { variableB.set(Math.random().toString()) - // This will print 'isHot? true' everytime, since we kept + // This will print 'isHot? true' every time, since we kept // the derivation hot by calling the 'keepHot()' method console.log('isHot?', variableB.derivation.isHot) }, 1000) @@ -134,3 +146,82 @@ setTimeout(() => { console.log('Interval cleared.') }, 7000) ``` + +### `map()` + +It is also possible to create a derivation based on an existing derivation: + +```typescript +const niceNumberB = new Box(5) +const isNiceNumberOddD = niceNumberB.derivation.map((v) => v % 2 === 0) + +// the following line will print '5, false' to the console +console.log(niceNumberB.get(), isNiceNumberOddD.getValue()) +``` + +The new derivation will be always up to date with the value of the original +derivation: + +```typescript +import {Box} from '@theatre/dataverse' + +const niceNumberB = new Box(5) +const isNiceNumberOddD = niceNumberB.derivation.map((v) => + v % 2 === 0 ? 'even' : 'odd', +) + +const untap = isNiceNumberOddD.changesWithoutValues().tap(() => {}) + +const interval1 = setInterval(untap, 5000) +const interval2 = setInterval(() => { + niceNumberB.set(niceNumberB.get() + 1) + console.log( + `${niceNumberB.get()} is an ${isNiceNumberOddD.getValue()} number.`, + ) +}, 1000) + +// clear the intervals +setTimeout(() => { + clearInterval(interval1) + console.log('interval1 is cleared.') +}, 7000) + +setTimeout(() => { + clearInterval(interval2) + console.log('interval2 is cleared.') +}, 7000) +``` + +### `prism()` + +At this point we can make derivations that can track the value of an other +derivation with [the `.map()` method](#map), but what if we want to track the +value of multiple derivations at once for the new derivation? This is where the +`prism()` function comes into play. + +Let's say that we have two derivations and we want to create a derivation that +returns the product of their values. In the spreadsheet analogy it would be like +having two cells with two functions and third cell that contains a function that +calculates the product of the previous two cells. Whenever the first two cells +recalculate their value, the third cell will also do the same. + +Here's how we would solve this problem in `dataverse`: + +```typescript +import {Box, prism} from '@theatre/dataverse' + +const widthB = new Box(1) +const heightB = new Box(2) +const padding = 5 + +const widthWithPaddingD = widthB.derivation.map((w) => w + padding) +const heightWidthPaddingD = heightB.derivation.map((h) => h + padding) + +const areaD = prism(() => { + return widthWithPaddingD.getValue() * heightWidthPaddingD.getValue() +}) + +console.log('area: ', areaD.getValue()) +widthB.set(10) +console.log('new area: ', areaD.getValue()) +``` From 3fbb56ac25d251554d658dd0123b4f4a4607ee02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Mon, 4 Apr 2022 13:49:05 +0200 Subject: [PATCH 03/20] Add docs for `usePrism()` --- packages/react/src/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 85e96730a..9df2b6cf5 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -37,6 +37,13 @@ function useForceUpdate(debugLabel?: string) { return update } +/** + * A React hook that executes the callback function and returns its return value whenever the values in the dependency array change. + * + * @param fn - The callback function + * @param deps - The dependency array + * @param debugLabel - The label used by the debugger + */ export function usePrism( fn: () => T, deps: unknown[], From 7200cf3979c371968b8241b5aed79cd3f5ed2c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Mon, 4 Apr 2022 14:31:44 +0200 Subject: [PATCH 04/20] Extend the documentation of `usePrism()` --- packages/react/src/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 9df2b6cf5..3e90cb80f 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -158,15 +158,17 @@ function queueIfNeeded() { } /** + * A React hook that returns the value of the derivation that it received as the first argument. It works like an implementation of Dataverse's Ticker, except that it runs the side effects in an order where a component's derivation is guaranteed to run before any of its descendents' derivations. + * + * @param der - The derivation + * @param debugLabel - The label used by the debugger + * * @remarks * It looks like this new implementation of useDerivation() manages to: * 1. Not over-calculate the derivations * 2. Render derivation in ancestor -\> descendent order * 3. Not set off React's concurrent mode alarms * - * It works like an implementation of Dataverse's Ticker, except that it runs - * the side effects in an order where a component's derivation is guaranteed - * to run before any of its descendents' derivations. * * I'm happy with how little bookkeeping we ended up doing here. */ From 6480541f15e63588356a9e2da95ebc277164e098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 00:22:14 +0200 Subject: [PATCH 05/20] Add info about `Atom`-s to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 166 +++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index dc866ac63..89c38eaf3 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -40,6 +40,7 @@ configure your local environment before running the examples). 2. [Observing values](#observing-values) 3. [`map()`](#map) 4. [`prism()`](#prism) +5. [`Atom`](#atom) ### Setup @@ -225,3 +226,168 @@ console.log('area: ', areaD.getValue()) widthB.set(10) console.log('new area: ', areaD.getValue()) ``` + +### `usePrism()` + +You can also use derivations inside of React components with the `usePrism()` +hook, which accepts a dependency array for the second argument. All the +derivations whose values are tracked should be included in the dependency array. + +An example for this would be having a Box that contains the width and height of +a div (let's call it `panel`). Imagine that you want to have a button that +changes the width of the `panel` to a random number when clicked. + +```typescriptreact +import {Box} from '@theatre/dataverse' +import {usePrism} from '@theatre/react' +import React from 'react' +import ReactDOM from 'react-dom' + +// Set the original width and height +const panelB = new Box({ + dims: {width: 100, height: 100}, +}) + +function changePanelWidth() { + const oldValue = panelB.get() + // Change `width` to a random number between 1 and 100 + panelB.set({dims: {...oldValue.dims, width: Math.floor(Math.random() * 100)}}) +} + +const Comp = () => { + const render = usePrism(() => { + const {dims} = panelB.derivation.getValue() + return ( + <> + +
+ + ) + }, [panelB]) // Note that `panelB` is in the dependency array + + return render +} + +ReactDOM.render( +
+ +
, + document.querySelector('body'), +) +``` + +### `Atom` + +Remember how we compared `Box`-es to cells in the spreadsheet-analogy? `Atom`-s +are also like cells in the sense that they also hold a value (they only work +with objects), but there's a huge difference in how their value gets updated. + +`Box` uses strict equality for comparing new and old values, while `Atom` tracks +the individual properties and subproperties of an object. The following example +illustrates the difference between these two update mechanisms pretty well: + +```typescript +import {Atom, Box, val, valueDerivation} from '@theatre/dataverse' + +const originalValue = {width: 200, height: 100} + +// Create a `Box` that holds an object +const panelB = new Box(originalValue) + +console.log('old value (Box): ', panelB.derivation.getValue()) +// Print the new value of `panelB` to the console +// every time it changes +panelB.derivation + .changesWithoutValues() + .tap(() => console.log('new value: (Box) ', panelB.derivation.getValue())) + +// Set the value of the `panelB` to a new object that has +// the same properties with the same values as `panelB`. +// Note that this will get recognized as a change, since +// the two objects are not strictly equal. +panelB.set({...panelB.get()}) + +// Create an `Atom` that holds an object +const panelA = new Atom({width: 200, height: 100}) + +console.log('old value (Atom):', val(panelA.pointer)) + +// Create a derivation to track the value of `panelA` +// There are a lot of new information here, we'll come back +// to this line later. +const panelFromAtomD = valueDerivation(panelA.pointer) + +// Print the new value of `panelA` to the console +// every time it changes +panelFromAtomD + .changesWithoutValues() + .tap(() => console.log('new value (Atom):', val(panelA.pointer))) + +// Since the next line sets changes the value of `panelA` to what it +// already holds, it does not get recognized as a change. +// The `.setIn()` method is also new, we'll cover it later. +panelA.setIn(['width'], 200) + +// The next line will trigger a change as expected +panelA.setIn(['width'], 400) +``` + +#### Pointers + +You might have wondered what `val(panelA.pointer)` meant when you read this +line: + +```typescript +console.log('old value (Atom):', val(panelA.pointer)) +``` + +`dataverse` uses pointers that point to the properties and nested properties of +the object that the `Atom` instance holds as its value. + +You can use the pointers to get the value of the property they point to, or to +convert them to a derivation using the `val()` and `useDerivation()` functions: + +```typescript +const panelA = new Atom({width: 200, height: 100}) + +// Create a derivation +const panelFromAtomD = valueDerivation(panelA.pointer) +// Print the value of the property that belongs to the pointer +console.log(val(panelA.pointer)) // prints `{width: 200, height: 100}` +console.log(val(panelA.pointer.width)) // prints `100` +console.log(val(panelA.pointer.height)) // prints `200` +``` + +#### Updating the values of `Atoms` + +If you want to update the value of the `Atom`, you have to select first the +property/subproperty that you want to update. Then you can use the names of its +ancestor properties in an array to define the path to the property for the +`setIn()` method: + +```typescript +const panelA = new Atom({dims: {width: 200, height: 100}}) + +// Sets the value of panelA to `{dims: {width: 400, height: 100}}` +panelA.setIn(['dims', 'width'], 400) +``` From 2e804efba374f2257e718795b2b017c66a0034a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 11:54:51 +0200 Subject: [PATCH 06/20] Update the docs of `usePrism()` --- packages/react/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 3e90cb80f..d0a318df2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -38,7 +38,10 @@ function useForceUpdate(debugLabel?: string) { } /** - * A React hook that executes the callback function and returns its return value whenever the values in the dependency array change. + * A React hook that executes the callback function and returns its return value + * whenever there's a change in the values of the dependency array, or in the + * derivations that are used within the callback function. + * * * @param fn - The callback function * @param deps - The dependency array From 20a26cbe050da60a506875482c9ba3fa632e1062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 14:14:48 +0200 Subject: [PATCH 07/20] Add more info to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 217 +++++++++++++++++++------ 1 file changed, 168 insertions(+), 49 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 89c38eaf3..5861d9765 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -1,32 +1,27 @@ # Into the dataverse - Get started with `@theatre/dataverse` -Dataverse is the reactive dataflow library -[Theatre.js](https://www.theatrejs.com) is built on. It is inspired by ideas in +This guide will help you to get started with `dataverse`, the reactive dataflow +library that [Theatre.js](https://www.theatrejs.com) is built on. It is inspired +by ideas in [functional reactive programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) -and it is optimised for interactivity and animation. This guide will help you to -get started with the library. +and it is optimised for interactivity and animation. ## Main concepts A good analogy for `dataverse` would be a spreadsheet editor application. In a spreadsheet editor you have cells that store values, cells that store functions, -that manipulate the values of other cells and the cells have identifiers (eg. -A1, B3, etc..., pointers) that are used to reference them in functions. These -are similiar to the set of tools that `dataverse` provides for manipulating -data. Here's a quick comparison: - -| `dataverse` | Spreadsheet editor analogy | role | -| ----------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------- | -| sources (`Box`, `Atom`) | a cell that holds a value | `Box`: holds a simple value, `Atom`: holds an object with (sub)props to be tracked | -| derivations | functions | changes recorded on the value of an `Box` or `Atom` | -| pointers | addresses of the cells (`A1`, `B3`) | they point to a source, don't contain values themselves | -| tappables | - | objects that can be observed for changes to execute a callback function when the change happens | - -Some concepts in `dataverse` go beyond the spreadsheet analogy. For example we -want to make the changes performant, so we make them on-demand and only show -them when it's necessary (eg.: only show a change in an animated value when the -screen gets repainted) which introduces new concepts like `Ticker`-s (we'll have -a look at them later). +that manipulate the values of other cells. The cells have identifiers (e.g. +`A1`, `B3`, etc...) that are used to reference them in the functions. These are +similar to the set of tools that `dataverse` provides for manipulating data. +Here's a quick comparison: + +| `dataverse` | Spreadsheet editor analogy | role | +| ----------------------- | ----------------------------------- | ---------------------------------------------------------------------------------- | +| sources (`Box`, `Atom`) | a cell that holds a value | `Box`: holds a simple value, `Atom`: holds an object with (sub)props to be tracked | +| derivations | functions | changes recorded on the value of an `Box` or `Atom` | +| pointers | addresses of the cells (`A1`, `B3`) | they point to a (sub)prop of an `Atom` | + +Note that some concepts in `dataverse` go beyond the spreadsheet analogy. ## Practical Examples @@ -40,7 +35,8 @@ configure your local environment before running the examples). 2. [Observing values](#observing-values) 3. [`map()`](#map) 4. [`prism()`](#prism) -5. [`Atom`](#atom) +5. [`usePrism()` (from `@theatre/react`)](#useprism) +6. [`Atom`](#atom) ### Setup @@ -154,10 +150,10 @@ It is also possible to create a derivation based on an existing derivation: ```typescript const niceNumberB = new Box(5) -const isNiceNumberOddD = niceNumberB.derivation.map((v) => v % 2 === 0) +const isNiceNumberEvenD = niceNumberB.derivation.map((v) => v % 2 === 0) // the following line will print '5, false' to the console -console.log(niceNumberB.get(), isNiceNumberOddD.getValue()) +console.log(niceNumberB.get(), isNiceNumberEvenD.getValue()) ``` The new derivation will be always up to date with the value of the original @@ -167,17 +163,17 @@ derivation: import {Box} from '@theatre/dataverse' const niceNumberB = new Box(5) -const isNiceNumberOddD = niceNumberB.derivation.map((v) => +const isNiceNumberEvenD = niceNumberB.derivation.map((v) => v % 2 === 0 ? 'even' : 'odd', ) -const untap = isNiceNumberOddD.changesWithoutValues().tap(() => {}) +const untap = isNiceNumberEvenD.changesWithoutValues().tap(() => {}) const interval1 = setInterval(untap, 5000) const interval2 = setInterval(() => { niceNumberB.set(niceNumberB.get() + 1) console.log( - `${niceNumberB.get()} is an ${isNiceNumberOddD.getValue()} number.`, + `${niceNumberB.get()} is an ${isNiceNumberEvenD.getValue()} number.`, ) }, 1000) @@ -195,7 +191,7 @@ setTimeout(() => { ### `prism()` -At this point we can make derivations that can track the value of an other +At this point we can make derivations that track the value of an other derivation with [the `.map()` method](#map), but what if we want to track the value of multiple derivations at once for the new derivation? This is where the `prism()` function comes into play. @@ -230,12 +226,22 @@ console.log('new area: ', areaD.getValue()) ### `usePrism()` You can also use derivations inside of React components with the `usePrism()` -hook, which accepts a dependency array for the second argument. All the -derivations whose values are tracked should be included in the dependency array. +hook from the `@theatre/react` package, which accepts a dependency array for the +second argument. All the values that are tracked should be included in the +dependency array. + +> Note that all the changes of the derivations' values inside the callback +> function (first argument of `usePrism()`) will be tracked, since `usePrism()` +> uses the `prism()` function under the hood. However, if the derivations are +> not provided in the dependency array of `usePrism()`, then it will not know if +> the derivations themselves have changed. Don't worry if it sounds confusing, +> we'll cover this behavior later. + +#### A simple example -An example for this would be having a Box that contains the width and height of -a div (let's call it `panel`). Imagine that you want to have a button that -changes the width of the `panel` to a random number when clicked. +Here's a simple example: we have a Box that contains the width and height of a +div (let's call it `panel`). Imagine that we want to have a button that changes +the width of the `panel` to a random number when clicked. ```typescriptreact import {Box} from '@theatre/dataverse' @@ -245,13 +251,13 @@ import ReactDOM from 'react-dom' // Set the original width and height const panelB = new Box({ - dims: {width: 100, height: 100}, + dims: {width: 200, height: 100}, }) function changePanelWidth() { const oldValue = panelB.get() - // Change `width` to a random number between 1 and 100 - panelB.set({dims: {...oldValue.dims, width: Math.floor(Math.random() * 100)}}) + // Change `width` to a random number between 0 and 200 + panelB.set({dims: {...oldValue.dims, width: Math.round(Math.random() * 200)}}) } const Comp = () => { @@ -261,9 +267,7 @@ const Comp = () => { <>
{ + const render = usePrism(() => { + // ... + }, []) // Here we removed `panelB` from the dependency array + + return render +} +// ... +``` + +The reason behind this behavior is that even though the value of `panelB` - the +`Box` instance - is cached, the cached `Box` instance's value is still tracked +inside the callback function (which uses `prism()` under the hood, and handles +every derivation inside as its dependency). However, if you change the value of +the `panelB` variable to another `Box` instance, then that change won't be +recognized inside the callback function if `panelB` is not included in the +dependency array of `usePrism()`. Let's look at another example to make things a +bit more clear: + +```typescript +// ... + +// Set the original width and height +const panelB = new Box({ + dims: {width: 200, height: 100}, +}) + +// Create two new `Box` instances +const theme1B = new Box({backgroundColor: '#bd6888', opacity: 1}) +const theme2B = new Box({backgroundColor: '#5ac777', opacity: 1}) + +function changePanelWidthAndThemeOpacity() { + const oldValue = panelB.get() + // Change `width` to a random number between 0 and 200 + const width = Math.round(Math.random() * 200) + panelB.set({dims: {...oldValue.dims, width}}) + // Change opacity in the themes: + const opacity = width > 100 ? width / 200 : width / 100 + theme1B.set({...theme1B.get(), opacity}) + theme2B.set({...theme2B.get(), opacity}) +} + +// DEPENDENCY ARRAYS DEMO +const Comp = () => { + // Get the width of the panel + const {width} = panelB.derivation.getValue().dims + // If the width of the panel is greater than 100, then + // set the value of the `theme` variable to `theme1B`, + // otherwise use `theme2B` + const theme = width > 100 ? theme1B : theme2B + + const render = usePrism(() => { + const {dims} = panelB.derivation.getValue() + const {backgroundColor, opacity} = theme.get() + return ( + <> + +
+ + ) + // Note that if the `theme` variable weren't included in the + // dependency array, then the background color of the div + // wouldn't be updated (the opacity still would). + // (Feel free to try it out.) + }, [theme]) + + return render +} + +// ... +``` + +If you omit the `theme` variable from the previous example, then the background +color of the `div` element will not be updated when the value of the `theme` +variable does, while the opacity would track the changes of the width. This +happens, because in that case the callback function in `usePrism()` caches the +value of `theme`, which is `theme1B` when `usePrims()` is called for the first +time, and updates whenever `theme1B` changes. If you pass down `theme` as a +dependency to `usePrism()`, then the callback function will always use new new +value of `theme` (which is set to `theme2B` if the `div`'s width is smaller than +or equal to `100`), whenever it changes. + ### `Atom` Remember how we compared `Box`-es to cells in the spreadsheet-analogy? `Atom`-s -are also like cells in the sense that they also hold a value (they only work -with objects), but there's a huge difference in how their value gets updated. +are also like cells in the sense that they also hold a value (although they only +work with objects), but there's a huge difference in how their value gets +updated. + +#### `Atom` vs `Box` `Box` uses strict equality for comparing new and old values, while `Atom` tracks -the individual properties and subproperties of an object. The following example -illustrates the difference between these two update mechanisms pretty well: +the individual properties and nested properties of an object. The following +example illustrates this difference between the two pretty well: ```typescript import {Atom, Box, val, valueDerivation} from '@theatre/dataverse' @@ -372,6 +484,7 @@ const panelA = new Atom({width: 200, height: 100}) // Create a derivation const panelFromAtomD = valueDerivation(panelA.pointer) + // Print the value of the property that belongs to the pointer console.log(val(panelA.pointer)) // prints `{width: 200, height: 100}` console.log(val(panelA.pointer.width)) // prints `100` @@ -380,9 +493,9 @@ console.log(val(panelA.pointer.height)) // prints `200` #### Updating the values of `Atoms` -If you want to update the value of the `Atom`, you have to select first the -property/subproperty that you want to update. Then you can use the names of its -ancestor properties in an array to define the path to the property for the +If you want to update the value of the `Atom`, you have first choose the +property/nested property that you want to update. Then you can use the names of +its ancestor properties in an array to define the path to the property for the `setIn()` method: ```typescript @@ -391,3 +504,9 @@ const panelA = new Atom({dims: {width: 200, height: 100}}) // Sets the value of panelA to `{dims: {width: 400, height: 100}}` panelA.setIn(['dims', 'width'], 400) ``` + +## Summary + +We only covered the basics, there are much more to `Box`-es, `Atom`-s and +everything else in `dataverse`. You can always check the source code for more +information. From bbdd55f137bf914d4a4b8373c56d96526147ba95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 16:03:45 +0200 Subject: [PATCH 08/20] Update the info about `usePrism()` in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 5861d9765..99aa91bef 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -227,16 +227,10 @@ console.log('new area: ', areaD.getValue()) You can also use derivations inside of React components with the `usePrism()` hook from the `@theatre/react` package, which accepts a dependency array for the -second argument. All the values that are tracked should be included in the +second argument. If the prism uses a value that is not a derivation (such as a +simple number, or a pointer), then you need to provide that value to the dependency array. -> Note that all the changes of the derivations' values inside the callback -> function (first argument of `usePrism()`) will be tracked, since `usePrism()` -> uses the `prism()` function under the hood. However, if the derivations are -> not provided in the dependency array of `usePrism()`, then it will not know if -> the derivations themselves have changed. Don't worry if it sounds confusing, -> we'll cover this behavior later. - #### A simple example Here's a simple example: we have a Box that contains the width and height of a From a41f09d3c48130de981aed7d47f2827041b73491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 16:51:00 +0200 Subject: [PATCH 09/20] Fix the syntax highlighting in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 99aa91bef..37e2f6d7c 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -237,7 +237,7 @@ Here's a simple example: we have a Box that contains the width and height of a div (let's call it `panel`). Imagine that we want to have a button that changes the width of the `panel` to a random number when clicked. -```typescriptreact +```typescript import {Box} from '@theatre/dataverse' import {usePrism} from '@theatre/react' import React from 'react' From 2fdecb14fccd890effdd24151d0e13176442af35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Tue, 5 Apr 2022 17:50:57 +0200 Subject: [PATCH 10/20] Add basic docs to `effect()` --- packages/dataverse/src/derivations/prism/prism.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/dataverse/src/derivations/prism/prism.ts b/packages/dataverse/src/derivations/prism/prism.ts index a9f2d3f09..c8e78b2fb 100644 --- a/packages/dataverse/src/derivations/prism/prism.ts +++ b/packages/dataverse/src/derivations/prism/prism.ts @@ -198,6 +198,14 @@ function ref(key: string, initialValue: T): IRef { } } +/** + * An effect hook, similar to react's `useEffect()`. + * + * @param key: the key for the effect + * @param cb: the callback function + * @param deps?: the dependency array + * + */ function effect(key: string, cb: () => () => void, deps?: unknown[]): void { const scope = hookScopeStack.peek() if (!scope) { From ab90193e5eab72679ed3278afc4a474b65c888c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:22:33 +0200 Subject: [PATCH 11/20] Add info about the `prism.state` and `prism.effect()` hooks to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 37e2f6d7c..23fae3092 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -223,6 +223,76 @@ widthB.set(10) console.log('new area: ', areaD.getValue()) ``` +#### `prism.state()` and `prism.effect()` + +Prisms don't always follow the rules of functional programming: they can have +internal states and perform side effects using the `prism.state()` and +`prism.effect()` methods. Their concept and API is very similar to React's +`useState()` and `useEffect()` hooks. + +The important thing to know about them is that: + +- `prism.state()` returns a state variable and a function that updates it. +- `prism.state()` receives a callback function as an argument that gets executed + when the derivation is created (or the dependencies in the dependency array + change). The callback function may return a clean up function that runs when + the derivation gets updated or removed. + +Let's say you want to create a derivation that tracks the position of the mouse. +This would require the derivation to do the following steps: + +1. Create an internal state where the position of the mouse is stored +2. Attach an event listener that listens to `mousemove` events to the `document` +3. Update the internal state of the position whenever the `mousemove` event is + fired +4. Remove the event listener once the derivation is gone (clean up) + +This is how this derivation would look like in code: + +```typescript +import {prism} from '@theatre/dataverse' + +const mousePositionD = prism(() => { + // Create an internal state (`pos`) where the position of the mouse + // will be stored, and a function that updates it (`setPos`) + const [pos, setPos] = prism.state<[x: number, y: number]>('pos', [0, 0]) + + // Create a side effect that attaches the `mousemove` event listeners + // to the `document` + prism.effect( + 'setupListeners', + () => { + const handleMouseMove = (e: MouseEvent) => { + setPos([e.screenX, e.screenY]) + } + document.addEventListener('mousemove', handleMouseMove) + + // Clean up after the derivation is gone (remove the event + // listener) + return () => { + document.removeEventListener('mousemove', handleMouseMove) + } + }, + [], + ) + + return pos +}) + +// Display the current position of the mouse using a `h2` element +const p = document.createElement('h2') +const [x, y] = mousePositionD.getValue() +p.textContent = `Position of the cursor: [${x}, ${y}]` +document.querySelector('body')?.append(p) + +// Update the element's content when the position of the mouse +// changes +mousePositionD.changesWithoutValues().tap(() => { + const [x, y] = mousePositionD.getValue() + p.textContent = `Position of the cursor: [${x}, ${y}]` +}) +``` + ### `usePrism()` You can also use derivations inside of React components with the `usePrism()` From 6b445dcaaaccd59bc56276bea224a95fe997b02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:30:31 +0200 Subject: [PATCH 12/20] Add more information about the arguments of `prism.effect()` to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 23fae3092..4adbfe9e4 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -233,10 +233,13 @@ internal states and perform side effects using the `prism.state()` and The important thing to know about them is that: - `prism.state()` returns a state variable and a function that updates it. -- `prism.state()` receives a callback function as an argument that gets executed - when the derivation is created (or the dependencies in the dependency array - change). The callback function may return a clean up function that runs when - the derivation gets updated or removed. +- `prism.effect()` receives two arguments: + 1. The first one is a key (a string), which should be unique to this effect + inside the prism + 2. The second one is a callback function as an argument that gets executed + when the derivation is created (or the dependencies in the dependency array + change). The callback function may return a clean up function that runs + when the derivation gets updated or removed. Let's say you want to create a derivation that tracks the position of the mouse. This would require the derivation to do the following steps: From 7221735ea797828b9b15631a5c2edf16581039c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:33:37 +0200 Subject: [PATCH 13/20] Update the documentation of `prism.effect()` --- packages/dataverse/src/derivations/prism/prism.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dataverse/src/derivations/prism/prism.ts b/packages/dataverse/src/derivations/prism/prism.ts index c8e78b2fb..4a19b389c 100644 --- a/packages/dataverse/src/derivations/prism/prism.ts +++ b/packages/dataverse/src/derivations/prism/prism.ts @@ -201,8 +201,8 @@ function ref(key: string, initialValue: T): IRef { /** * An effect hook, similar to react's `useEffect()`. * - * @param key: the key for the effect - * @param cb: the callback function + * @param key: the key for the effect. Should be uniqe inside of the prism. + * @param cb: the callback function. Optionally returns a cleanup function. * @param deps?: the dependency array * */ From 51500a63725c6a9eae743e6c465055240d2b3d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:45:06 +0200 Subject: [PATCH 14/20] Mention the other methods of `prism` in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 4adbfe9e4..254389320 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -296,6 +296,14 @@ mousePositionD.changesWithoutValues().tap(() => { }) ``` +#### Other methods of `prism` + +Prism has other methods (`prism.memo()`, `prism.scope()`, `prism.ref()`, etc) +inspired by React hooks, but they aren't used that much in `@theatre/core` and +`@theatre/studio`. You can check out the +[tests](../src/derivations/prism/prism.test.ts) or the +[source code](../src/derivations/prism/prism.ts) to get more familiar with them. + ### `usePrism()` You can also use derivations inside of React components with the `usePrism()` From 79eb8095ba4ff4f003a051b3e8a31fffd4f46d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:56:21 +0200 Subject: [PATCH 15/20] Add a new note to pointers in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 254389320..450d0b019 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -561,6 +561,8 @@ const panelA = new Atom({width: 200, height: 100}) const panelFromAtomD = valueDerivation(panelA.pointer) // Print the value of the property that belongs to the pointer +// Note that `panelA.pointer` and `panelA.pointer.width` are both +// pointers. console.log(val(panelA.pointer)) // prints `{width: 200, height: 100}` console.log(val(panelA.pointer.width)) // prints `100` console.log(val(panelA.pointer.height)) // prints `200` From b92f732b39b61a65e0e170e46938061679e14fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 10:59:10 +0200 Subject: [PATCH 16/20] Include the h4-s in the TOC of `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 450d0b019..43e42c726 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -23,7 +23,7 @@ Here's a quick comparison: Note that some concepts in `dataverse` go beyond the spreadsheet analogy. -## Practical Examples +## Practical Introduction Here we collected a few examples that introduce the main concepts/tools in `dataverse` through practical examples. We strongly recommend running the @@ -35,8 +35,14 @@ configure your local environment before running the examples). 2. [Observing values](#observing-values) 3. [`map()`](#map) 4. [`prism()`](#prism) + - [A basic example](#a-basic-example) + - [`prism.state()` and `prism.effect()`](#prism.state-and-prism.effect) + - [Other methods of `prism()`](#other-methods-of-prism) 5. [`usePrism()` (from `@theatre/react`)](#useprism) 6. [`Atom`](#atom) + - [`Atom` vs `Box`](#atom-vs-box) + - [`Pointers`](#pointers) + - [Updating the values of an `Atom`](#updating-the-value-of-an-atom) ### Setup @@ -568,9 +574,9 @@ console.log(val(panelA.pointer.width)) // prints `100` console.log(val(panelA.pointer.height)) // prints `200` ``` -#### Updating the values of `Atoms` +#### Updating the of an `Atom` -If you want to update the value of the `Atom`, you have first choose the +If you want to update the value of an `Atom`, you have first choose the property/nested property that you want to update. Then you can use the names of its ancestor properties in an array to define the path to the property for the `setIn()` method: From fcade3054416b9e563762fb585c12dee45385a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 11:04:16 +0200 Subject: [PATCH 17/20] Fix some errors in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 43e42c726..c657dc1bc 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -36,7 +36,7 @@ configure your local environment before running the examples). 3. [`map()`](#map) 4. [`prism()`](#prism) - [A basic example](#a-basic-example) - - [`prism.state()` and `prism.effect()`](#prism.state-and-prism.effect) + - [`prism.state()` and `prism.effect()`](#prismstate-and-prismeffect) - [Other methods of `prism()`](#other-methods-of-prism) 5. [`usePrism()` (from `@theatre/react`)](#useprism) 6. [`Atom`](#atom) @@ -202,6 +202,8 @@ derivation with [the `.map()` method](#map), but what if we want to track the value of multiple derivations at once for the new derivation? This is where the `prism()` function comes into play. +#### A basic example + Let's say that we have two derivations and we want to create a derivation that returns the product of their values. In the spreadsheet analogy it would be like having two cells with two functions and third cell that contains a function that @@ -574,7 +576,7 @@ console.log(val(panelA.pointer.width)) // prints `100` console.log(val(panelA.pointer.height)) // prints `200` ``` -#### Updating the of an `Atom` +#### Updating the values of an `Atom` If you want to update the value of an `Atom`, you have first choose the property/nested property that you want to update. Then you can use the names of From 2590f4f6bca1e1d2419c8dec9f99f3f9dfeb22f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 11:07:14 +0200 Subject: [PATCH 18/20] Add some changes to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index c657dc1bc..6b1f7d025 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -30,9 +30,9 @@ Here we collected a few examples that introduce the main concepts/tools in examples on your local machine (see the [Setup](#setup) section to see how to configure your local environment before running the examples). -0. [Setup: How to configure your local environment before running the examples](#setup) -1. [`Box`](#box-storing-simple-values) -2. [Observing values](#observing-values) +0. [Setup your local environment for running the examples](#setup) +1. [`Box`](#box-store-simple-values) +2. [Observe values](#observe-values) 3. [`map()`](#map) 4. [`prism()`](#prism) - [A basic example](#a-basic-example) @@ -42,7 +42,7 @@ configure your local environment before running the examples). 6. [`Atom`](#atom) - [`Atom` vs `Box`](#atom-vs-box) - [`Pointers`](#pointers) - - [Updating the values of an `Atom`](#updating-the-value-of-an-atom) + - [Update the value of an `Atom`](#update-the-value-of-an-atom) ### Setup @@ -52,7 +52,7 @@ a new directory and file called `dataverse/index.tsx` in `theatre/packages/playground/src/personal/` (this directory is already added to `.gitignore`, so you don't have to worry about that). -### `Box`: storing simple values +### `Box`: store simple values Let's start with creating a variable that holds a simple value, which we can change and observe later: @@ -76,7 +76,7 @@ variableB.set('some new value') console.log(variableB.get()) // prints 'some new value' in the console ``` -### Observing values +### Observe values Let's say you want to watch the value of `variableB` for changes and execute a callback when it does change. @@ -576,7 +576,7 @@ console.log(val(panelA.pointer.width)) // prints `100` console.log(val(panelA.pointer.height)) // prints `200` ``` -#### Updating the values of an `Atom` +#### Update the value of an `Atom` If you want to update the value of an `Atom`, you have first choose the property/nested property that you want to update. Then you can use the names of From a71ce4777854b7f2136a935f562f362ff37e072e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 12:17:21 +0200 Subject: [PATCH 19/20] Add info about the `Ticker` class to `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 6b1f7d025..128981349 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -590,6 +590,81 @@ const panelA = new Atom({dims: {width: 200, height: 100}}) panelA.setIn(['dims', 'width'], 400) ``` +### `Ticker` and `studioTicker` + +The `Ticker` class helps us to schedule callbacks using a strategy. One such +strategy could by synchronizing the execution of certain actions with the +browser's repaint schedule to avoid changes that are invisible for the user and +would only worsen the performance. This could be implemented using the +`studioTicker` from the `@theatre/studio` package. + +Here's an example: we want to increase the value of a counter variable by 1 in +every 10 ms and print the current value on every repaint for 1 s: + +```typescript +import {Box} from '@theatre/dataverse' +import studioTicker from '@theatre/studio/studioTicker' + +// Clear the console to make tracking the relevant logs easier +console.clear() + +// Create a counter variable +const counterB = new Box(0) +// Create a variable to track the number of repaints +let numberOfRepaints = 0 + +// Increase the value of the counter variable by 1 +// in every 10 ms +const interval = setInterval(() => { + counterB.set(counterB.get() + 1) + console.log(counterB.get()) +}, 10) + +// Increase the number of repaints by one every time +// a repaint happens +const untap = counterB.derivation + .changes(studioTicker) + .tap((newCounterValue) => { + ++numberOfRepaints + console.log(`VALUE ON REPAINT: ${newCounterValue}`) + }) + +// Clean up everything after 1 s +setTimeout(() => { + clearInterval(interval) + untap() + console.log('interval is cleared.') + console.log(`Number of times when the counter got updated: ${counterB.get()}`) + console.log(`Number of repaints: ${numberOfRepaints}`) +}, 1000) +``` + +When I run the example above using a 60 Hz refresh rate monitor (or when the +browser itself limits the repaints to 60 times per second), I see something like +this in the console: + +``` +Number of times when the counter got updated: 98 +Number of repaints: 60 +``` + +What happens is that the counter gets updated about `1000ms / 10ms = 100` times, +but only 60 of these changes can be shown on screen due to the refresh rate of +my monitor. The values of the counter when the repaints happen are also printed +to the console: + +``` +... +94 +VALUE ON REPAINT: 94 +95 +96 +VALUE ON REPAINT: 96 +97 +98 +... +``` + ## Summary We only covered the basics, there are much more to `Box`-es, `Atom`-s and From d4d2acd350e589f3664a4516013997f56e0cbc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=BCl=C3=B6p=20Kov=C3=A1cs?= Date: Wed, 6 Apr 2022 12:18:56 +0200 Subject: [PATCH 20/20] Add the `Ticker`-section to the TOC in `GET_STARTED.md` --- packages/dataverse/docs/GET_STARTED.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataverse/docs/GET_STARTED.md b/packages/dataverse/docs/GET_STARTED.md index 128981349..443f777db 100644 --- a/packages/dataverse/docs/GET_STARTED.md +++ b/packages/dataverse/docs/GET_STARTED.md @@ -43,6 +43,7 @@ configure your local environment before running the examples). - [`Atom` vs `Box`](#atom-vs-box) - [`Pointers`](#pointers) - [Update the value of an `Atom`](#update-the-value-of-an-atom) +7. [`Ticker`](#ticker-and-studioticker) ### Setup