From e9742e88fdbd46a0e5c52afd8eeb77b2cb426137 Mon Sep 17 00:00:00 2001 From: Aaron Smith <83140718+centau@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:15:32 +0100 Subject: [PATCH] Add mermaid diagrams to docs --- docs/.vitepress/config.ts | 5 +- docs/package.json | 3 +- docs/tut/advanced/reactive-scoping.md | 116 +++++++++++++++++++++++--- docs/tut/crash-course/5-effect.md | 25 +++++- 4 files changed, 131 insertions(+), 18 deletions(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 18e36cc..dec9146 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,7 +1,8 @@ -import { defineConfig } from "vitepress" +//import { defineConfig } from "vitepress" +import { withMermaid } from "vitepress-plugin-mermaid"; // https://vitepress.dev/reference/site-config -export default defineConfig({ +export default withMermaid({ title: "Vide", titleTemplate: ":title - A reactive UI library for Luau", description: "A reactive UI library for Luau.", diff --git a/docs/package.json b/docs/package.json index 921ee39..e156bea 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { - "vitepress": "^1.0.0-rc.4" + "vitepress": "^1.0.0-rc.4", + "vitepress-plugin-mermaid": "^2.0.14" } } diff --git a/docs/tut/advanced/reactive-scoping.md b/docs/tut/advanced/reactive-scoping.md index 5b983ab..99a09f4 100644 --- a/docs/tut/advanced/reactive-scoping.md +++ b/docs/tut/advanced/reactive-scoping.md @@ -1,13 +1,54 @@ # Reactive Scoping This is a brief document designed to give the user more insight into how Vide's -reactive graph works. +reactive system works. + +Vide's reactivity can be pictured as a graph, where each source, derived source, +and effect is a node on that graph. For example: + +```lua + +root(function() + local forename = source "quan" + local surname = source "xi" + + local name = derive(function() + return forename() .. " " .. surname() + end) + + effect(function() + print("new name: " .. name()) + end) +end) +``` Each time you create and derive sources, a new node representing that source is created and added to the reactive graph. Each node stores a value and a side-effect function. Each node also keeps track of its parents and children, as well as any cleanups registered. +This code will produce a graph that looks like so: + +```mermaid +%%{init: { + "theme": "base", + "themeVariables": { + "primaryColor": "#1B1B1F", + "primaryTextColor": "#fff", + "primaryBorderColor": "#1B1B1F", + "lineColor": "#79B8FF", + "tertiaryColor": "#161618", + "tertiaryBorderColor": "#161618" + } +}}%% + +flowchart + subgraph root + forename & surname --> name + name --> effect + end +``` + Any time a node is updated, Vide will traverse and update that node's children, its children's children, etc, until all nodes descending from that node has been updated. Traversal will stop at a node if that node's cached value does not @@ -30,32 +71,85 @@ the root node which will track any node created or derived inside its scope, or any cleanups registered. Without it, nodes could be garbage collected without a chance to run pending cleanups which can cause memory leakage. +Nodes created by `source()` can actually exist outside of root nodes, since +they do not have direct side-effects or cleanups, they do not have to be +explicitly destroyed. + Control flow functions in Vide are special, as they can dynamically create and destroy new root scopes. It is the combination of the above which allows us to write components like so: ```lua -local function Counter() +local function Counter(props: { text: string }) local count = source(0) local connection = stepped:Connect(function() count(count() + 1) end) cleanup(function() connection:Disconnect() end) - effect(function() print(count()) end) - return create "TextLabel" { Text = count } + return create "TextLabel" { + Text = function() + return props.text() .. ": " .. count() + end + } end ``` Vide doesn't recognise this as a "component", that is a user abstraction. Vide just sees this as a function that creates nodes in the reactive graph. -Whenever the reactive scope that calls this function is destroyed, like by a -control flow function, the registered cleanup will be called, and the effect -(which is just a node on the reactive graph) is destroyed. The returned instance -and the bound `count` source is just considered to be a side-effect, and with -the reactive scope from which the side-effects stem from destroyed, the instance -can be garbage collected - everything is nicely cleaned up. +```lua +root(function() + local counters = { "A", "B" } -> todo: add graphics + indexes(counters, function(name) + return Counter { text = name } + end) +end) +``` + +This code produces a graph like so: + +```mermaid +%%{init: { + "theme": "base", + "themeVariables": { + "primaryColor": "#1B1B1F", + "primaryTextColor": "#fff", + "primaryBorderColor": "#1B1B1F", + "lineColor": "#79B8FF", + "tertiaryColor": "#161618", + "tertiaryBorderColor": "#161618" + } +}}%% + +flowchart LR + subgraph root + counters --> indexes + end + + subgraph root1[subroot 1] + n1[name] --> p1[prop binding] + end + + subgraph root2[subroot 2] + n2[name] --> p2[prop binding] + end + + indexes .-> root1 & root2 +``` + +This shows how the `indexes()` control flow function creates and manages new +root scopes. The function creates an effect seen as `indexes` in the graph, +which manages the new roots `subroot 1` and `subroot 2`, as well as the sources +`name` for which one exists for each index value in the input table. + +When the input table changes, `indexes()` can automatically destroy and create +subroots based on the changed indexes. Destroyed nodes run any cleanups made, in +this case it is the cleanups to disconnect the counters connection. The same +applies to all other control flow functions. + +Whenever the root reactive scope is destroyed, all its children, `counters` and +`indexes` will be destroyed too, which means that `indexes` children, the +subroots, will also be destroyed. Everything is nicely cleaned up. diff --git a/docs/tut/crash-course/5-effect.md b/docs/tut/crash-course/5-effect.md index c875de9..f75d21a 100644 --- a/docs/tut/crash-course/5-effect.md +++ b/docs/tut/crash-course/5-effect.md @@ -1,4 +1,4 @@ -# Effect +# Effects Effects are functions that are ran in response to source updates. They are called effects because they cause *side-effects* when reacting to source updates. @@ -20,9 +20,9 @@ count(1) -- "count: 1" printed ``` -The callback given to `effect()` is ran in a *reactive-scope*. Any source read +The callback given to `effect()` is ran in a *reactive scope*. Any source read from inside a reactive scope will be tracked, so that if any of those sources -update, the effect will be re-ran too. +update, the effect will be reran too. The callback is first ran immediately inside the `effect()` call to initially figure out what sources are being used. @@ -56,9 +56,26 @@ All observable changes to the user are considered to be side-effects of pure computations. Sources, derived sources, and effects form what is called a *reactive graph*. -In the above example a graph `count -> doubled -> effect` is formed. Anywhere +In the above example, the following graph is formed. Anywhere an update occures, everything further down the graph is updated. +```mermaid +%%{init: { + "theme": "base", + "themeVariables": { + "primaryColor": "#1B1B1F", + "primaryTextColor": "#fff", + "primaryBorderColor": "#1B1B1F", + "lineColor": "#79B8FF", + "tertiaryColor": "#161618", + "tertiaryBorderColor": "#161618" + } +}}%% + +flowchart LR + count --> doubled --> effect +``` + You should not update other sources using an effect. Improper usage can lead to a cyclic loop in the graph, causing an infinite loop when it tries to update. Sources should be derived instead.