Update docs

This commit is contained in:
Aaron Smith 2023-09-27 16:44:39 +01:00
parent 3959f5119e
commit 9b6be14441
14 changed files with 308 additions and 60 deletions

View file

@ -204,22 +204,23 @@ The above code produces a graph like so:
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#fff"
"tertiaryBorderColor": "#1B1B1F"
}
}}%%
flowchart LR
subgraph root
direction LR
show
subgraph subroot[show subroot]
subgraph subroot["show() subroot"]
p1[prop binding]
end
end
visible --> show
count --> p1
show .-> subroot
show -.- subroot
```
This can be recreated without the `show()` control-flow function, with the

View file

@ -15,11 +15,10 @@ worrying about manually updating UI instances.
Some of the main focuses behind Vide's design choices:
- Concise syntax to reduce verbosity as much as possible.
- Concise syntax.
- Being completely typecheckable.
- Independence from instance lifetimes.
- A powerful reactive system that can update specific properties as a result of
state changes, updates are immediate with no diffing needed.
- Real reactivity.
## Structure Of A Vide App
@ -33,8 +32,10 @@ called *components*.
```lua
local function App()
return create "ScreenGui" {
create "TextLabel" { Text = "hi" }
return {
PlayerStats(),
Inventory(),
Settings()
}
end

View file

@ -41,8 +41,30 @@ when the timer component is destroyed, whether that is from unmounting the app
or if it is dynamically created by a control-flow function, which will be
covered next.
On a related note: the reason why `mount()` is used to create your app, is so
that any top-level components that need to be cleaned up, can be cleaned up
when the app is later unmounted, since `mount()` runs in a reactive scope to
track `cleanup()` calls. Vide's entire reactive system is independent from the
life-time of instances; instances are just a side-effect of the reactive system.
This is another reason why `mount()` is used at the top level of your app, so
that any registered cleanups created by your app components can be ran when
they are destroyed.
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#161618"
}
}}%%
flowchart
subgraph root
direction LR
cleanup([cleanup]) ~~~ count
count --> bind[text binding]
end
```

View file

@ -63,6 +63,37 @@ local function JoinMenu()
end
```
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#1C1C1F"
}
}}%%
flowchart
subgraph root ["mount() scope"]
direction LR
joined --> show -.- subroot
subgraph subroot ["show() scope"]
direction LR
Button
end
end
```
The dotted line indicates that the new reactive scope isn't actually connected
to the `show` on the graph, it is only managed internally through code.
## switch()
Similar to `show()`, `switch()`, also condtionally displays one instance at a
@ -113,6 +144,34 @@ switch(menu) {
}
```
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#1C1C1F"
}
}}%%
flowchart
subgraph root ["mount() scope"]
direction LR
joined --> show -.- subroot
subgraph subroot ["switch() scope"]
direction LR
Button
end
end
```
## indexes()
Often, you will have a table of values that will be displayed in a similar
@ -121,7 +180,7 @@ UI element, `indexes()` allows you to create an instance for each table index,
to display the value at that index.
```lua
local todoList = {
local todoList = source {
"finish the crash course",
"star vide's GitHub"
}
@ -153,5 +212,38 @@ given source for that index is updated.
An element is only destroyed if the value of an index is set to `nil`.
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#1C1C1F"
}
}}%%
flowchart
subgraph root ["mount() scope"]
direction LR
todoList --> indexes -.- subroot1 & subroot2
subgraph subroot1 ["indexes() scope 1"]
direction LR
value1[todo] --> prop1["prop binding"]
end
subgraph subroot2 ["indexes() scope 2"]
direction LR
value2[todo] --> prop2[prop binding]
end
end
```
Together, these control flow functions cover the majority of cases where you
need to dynamically create and destroy parts of your UI.

View file

@ -24,6 +24,9 @@ count(count() + 1) -- increment count by 1
Sources can be *derived* by wrapping them in functions. A wrapped source
effectively becomes a new source.
Derived sources should be pure functions. This is where the same output is
always produced for the same input no matter how many times it is reran.
```lua
local count = source(0)
@ -36,7 +39,8 @@ count(1)
print(text()) -- "count: 1"
```
Derived sources should be pure functions. This is where the same output is
always produced for the same input. As well as making source updates more
predictable, knowing that updates are pure allows Vide to use optimizations such
as caching, to avoid updating derived sources if their inputs are the same.
Sources on their own aren't very special, the above can be achieved with plain
variables. The real use for sources become apparent when used in combination
with Vide's *reactive scopes*. When a source is read from within a reactive
scope, it can automatically rerun the scope that reads it when the source is
updated in the future.

View file

@ -1,8 +1,8 @@
# 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
which are pure.
called effects because they cause *side-effects* when reacting to source
updates.
Effects are created using `effect()`.
@ -26,10 +26,10 @@ from inside a reactive scope will be tracked, so that if any of those sources
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.
track sources used.
Effects also work with derived sources, it doesn't matter how deeply nested a
source is.
Effects also work with derived sources, it doesn't matter how deeply nested
inside a function a source is.
```lua
local source = vide.source
@ -50,15 +50,7 @@ count(2)
-- "doubled count: 4" printed
```
Derived sources should be a *pure computation*. A pure computation is one where
the same input will always produce the same output.
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, the following graph is formed. Anywhere
an update occures, everything further down the graph is updated.
The reactive graph for the above example:
```mermaid
%%{init: {
@ -69,25 +61,12 @@ an update occures, everything further down the graph is updated.
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#161618"
"tertiaryBorderColor": "#fff"
}
}}%%
flowchart LR
count --> doubled --> effect
count --> 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.
## Root Reactive Scopes
Effects must be created within another reactive scope. This is so that the
effect itself can be tracked and later freed when the parent reactive scope is
destroyed, such as from unmounting an app. The example code above will not
actually work unless it is ran inside a root reactive scope, such as one created
by `vide.mount(function)`. This generally isn't a concern since you can assume
that all your components will be created within a single `mount()` call, which
happens only once at the top level, where you put together your UI and parent it
to a ScreenGUI.

View file

@ -0,0 +1,49 @@
# Root Reactive Scopes
Any reactive scopes created, such as one from `effect()`, must be done so within
a "root" reactive scope. This is the main purpose of `mount()`, which you use
once at the top level to create your app as shown in the first introduction.
This is so that when the app is unmounted, it can clean up any reactive scopes
created within it, since reactive scopes track any reactive scopes created
within them.
```lua
local source = vide.source
local effect = vide.effect
local function App()
local count = source(0)
effect(function()
print(count())
end)
end
vide.mount(App) -- works!
App() -- will error since effect() was not called within a reactive scope
```
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#161618"
}
}}%%
flowchart
subgraph root
direction LR
count --> effect
end
```

View file

@ -30,6 +30,9 @@ end
Above is an example of a counter component, that when clicked, will increment
its internal count, and automatically update its text to reflect that count.
Making a property update based on a source is also called *property
binding*.
Each instance of `Counter()` will maintain its own independent count, since the
count source is created inside the scope of the component.

View file

@ -2,9 +2,7 @@
Explicitly creating effects to update properties can become verbose when there
are a lot of properties to update. Vide provides a way to *implicitly* create
an effect to update properties on source update. This is also known as
*property binding*, since changes to a source will automatically update the
property.
an effect to update properties on source update.
```lua
local create = vide.create
@ -40,7 +38,9 @@ that are updated.
## Children Binding
Children can also be set in a similar manner.
Children can also be set in a similar manner. Sources bound to properties can
return an instance or an array of instances. Vide will automatically unparent
removed instances and parent new instances.
```lua
local items = source {

View file

@ -0,0 +1,95 @@
# Derived Sources
We have seen the basic way to derive a source:
```lua
local count = source(0)
local text = function()
return "count: " .. tostring(count())
end
print(text()) -- "count: 0"
count(1)
print(text()) -- "count: 1"
```
However, in some cases where this source could be used by multiple effects at
the same time, the function wrapping the source will needlessly rerun to convert
the count into a string for each effect using it.
```lua
local source = vide.source
local effect = vide.effect
local count = source(0)
local text = function()
print "ran"
return "count: " .. tostring(count())
end
effect(function()
text() -- prints "ran"
end)
effect(function()
text() -- prints "ran" again
end)
```
To avoid this, you can use `derive()` to derive a new source instead. This will
run a callback in a new reactive scope only when a dependent source has updated.
Reading this derived source multiple times will just return a cached result from
when it last updated.
```lua
local source = vide.source
local derive = vide.derive
local effect = vide.effect
local count = source(0)
local text = derive(function()
print "ran"
return "count: " .. tostring(count())
end)
effect(function()
text() -- prints "ran"
end)
effect(function()
text() -- does not print
end)
```
Deriving a source in this manner is similar to creating an effect to update
another source. You should never manually do this using an effect however,
improper usage could accidently create infinite loops in the reactive graph.
Always favour deriving when you need one source to update based on another.
`derive()` must also be used within a root reactive scope, just like `effect()`.
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#161618"
}
}}%%
flowchart
subgraph root
direction LR
count --> text --> effect1 & effect2
end
```