mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
fab66a9d6b
commit
574f4400bc
21 changed files with 224 additions and 337 deletions
|
|
@ -3,15 +3,21 @@
|
||||||
This is a brief tutorial designed to give you a quick run through the usage of
|
This is a brief tutorial designed to give you a quick run through the usage of
|
||||||
Vide.
|
Vide.
|
||||||
|
|
||||||
Vide is largely inspired by Solid.
|
Vide is largely inspired by Solid and Fusion.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
## Why Vide?
|
||||||
|
|
||||||
|
Creating UI is a slow and tedious process. The purpose of Vide is to make UI
|
||||||
|
declarative and concise, making it faster to create and more importantly easier
|
||||||
|
to maintain. Vide achieves this using a reactive style of programming which
|
||||||
|
allows you to focus on the flow of data through your application without
|
||||||
|
worrying about manually updating UI instances.
|
||||||
|
|
||||||
## Creating UI Instances
|
## Creating UI Instances
|
||||||
|
|
||||||
In Vide, it is intended to create all UI instances through code.
|
Instances are created using [`create()`](../api/creation#create).
|
||||||
|
|
||||||
Instances are created using [`vide.create()`](../api/creation#create).
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local vide = require(vide)
|
local vide = require(vide)
|
||||||
|
|
@ -28,112 +34,79 @@ local frame = create "Frame" {
|
||||||
`create()` returns a constructor for a given class which then takes a table of
|
`create()` returns a constructor for a given class which then takes a table of
|
||||||
properties to assign when creating a new instance for that class.
|
properties to assign when creating a new instance for that class.
|
||||||
|
|
||||||
Sometimes you want to do more than setting properties, such as setting children or connecting to events.
|
String keys are assumed to be properties, integer keys are assumed to be
|
||||||
Vide uses special keys called *symbols* which provide unique functionality like the above mentioned.
|
children.
|
||||||
|
|
||||||
Children can be assigned to instances using the `Children` symbol.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Children = vide.Children
|
create "ScreenGui" {
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local screenGui = create("ScreenGui") {
|
|
||||||
Parent = game.StarterGui,
|
Parent = game.StarterGui,
|
||||||
|
|
||||||
[Children] = create("Frame") {
|
create("Frame") {
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
Position = UDim2.fromScale(0.5, 0.5),
|
||||||
Size = UDim2.fromScale(0.4, 0.7),
|
Size = UDim2.fromScale(0.4, 0.7),
|
||||||
|
|
||||||
[Children] = {
|
|
||||||
create("TextLabel") {
|
create("TextLabel") {
|
||||||
Text = "Hi"
|
Text = "hi"
|
||||||
},
|
},
|
||||||
|
|
||||||
create("TextLabel") {
|
create("TextLabel") {
|
||||||
Text = "Bye"
|
Text = "bye"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, we import the symbol [`vide.Children`](../api/creation#Children).
|
To connect to an event, just set the event property name to a function.
|
||||||
|
|
||||||
This symbol can accept an instance, an array of instances and nested arrays of instances.
|
|
||||||
All given instances will be parented to the instance the symbol was used on.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## Connecting To Events
|
|
||||||
|
|
||||||
Built-in instance events and property changed events can be connected to using two other symbols, [`vide.Event`](../api/creation#Event) and [`vide.Changed`](../api/creation#Changed).
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local Event = vide.Event
|
create "TextButton" {
|
||||||
local Changed = vide.Changed
|
Activated = function()
|
||||||
```
|
print "clicked!"
|
||||||
|
|
||||||
```lua
|
|
||||||
local textBox = create("TextBox") {
|
|
||||||
PlaceholderText = "Enter text",
|
|
||||||
|
|
||||||
[Event.Focused] = function(...)
|
|
||||||
print("User is focusing on text box")
|
|
||||||
end,
|
|
||||||
|
|
||||||
[Changed.Text] = function(newText)
|
|
||||||
print("New text: " .. newText)
|
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Both of these symbols can be indexed into to get a specific symbol for an event to connect to.
|
All event arguments are passed into the function.
|
||||||
The callback function for `Event` receives any event-specific arguments and the callback function for
|
|
||||||
`Changed` receives the new property value as the only argument (unlike `Instance:GetPropertyChangedSignal()`).
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## State
|
## State
|
||||||
|
|
||||||
*State* is the condition something is in at a specific time. The state of a program is simply the data it contains at some timepoint.
|
State in Vide are special objects that store data.
|
||||||
|
|
||||||
The purpose of all UI is to take some state and reflect that state visually.
|
A state object in Vide can be created using
|
||||||
|
[`source()`](../api/reactivity-core#source).
|
||||||
In Vide, UI state is represented using special objects simply called *state*.
|
|
||||||
|
|
||||||
A state object in Vide can be created using [`vide.wrap`](../api/reactivity-core#wrap).
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
```
|
```
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local isVisible = wrap(false)
|
local visible = source(false)
|
||||||
|
|
||||||
local image = create("ImageLabel") {
|
local image = create("ImageLabel") {
|
||||||
Image = "rbxassetid://xxx",
|
Visible = visible -- bind property to state
|
||||||
Visible = isVisible
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while true do
|
visible(false) -- image label is hidden
|
||||||
wait(1)
|
|
||||||
isVisible.Value = not isVisible.Value
|
visible(true) -- image label is shown
|
||||||
end
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The function `wrap` will *wrap* any given value with a state object of type `State<T>` which can be read from/wrote to through its `.value` property.
|
`source()` creates a new data source which can be set by calling it with the new
|
||||||
|
value to set.
|
||||||
|
|
||||||
In the above code, the `ImageLabel.Visible` property is assigned a state. Now any time that state's value is assigned to, `ImageLabel.Visible` will also update with the new value assigned, without you having to explicitly set the property. The above code gives the effect of the image label toggling visibility at a 1 second interval forever.
|
Any time the value is set, anything depending on it will automatically be
|
||||||
|
updated using the new value.
|
||||||
|
|
||||||
|
Vide detects when you assign a state object as a property value. This is known
|
||||||
|
as *binding* and doing so will cause the property to *automatically* update
|
||||||
|
whenever that state object's value is changed.
|
||||||
|
|
||||||
There are a few reasons why we use state objects instead of plain variables:
|
You as the programmer do not have to worry about manually updating variables or
|
||||||
|
UI instances, you can just focus on defining how the data maps to UI and
|
||||||
1. Vide detects when you assign a state object as a property value. This is known as *binding* and doing so will cause the property to *automatically* update whenever that state object's value is changed.
|
everything will update when changes occur.
|
||||||
2. We can create new state objects that derive from other state objects, which again, *automatically* update when the derived state objects change.
|
|
||||||
|
|
||||||
The reason why this is useful, is that you as the programmer do not have to worry about manually updating variables or UI instances, you can just focus on defining how the data maps to UI and everything will automatically update when changes occur.
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
@ -141,6 +114,28 @@ The reason why this is useful, is that you as the programmer do not have to worr
|
||||||
|
|
||||||
You can create new state from other states. This is known as *deriving state*.
|
You can create new state from other states. This is known as *deriving state*.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
local function text()
|
||||||
|
return "count: " .. count()
|
||||||
|
end
|
||||||
|
|
||||||
|
create "TextLabel" {
|
||||||
|
Text = text
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To read from a state, you call without any arguments which returns its stored
|
||||||
|
value.
|
||||||
|
|
||||||
|
Assigning a non-event property a function will bind that property to that
|
||||||
|
function, anytime a state being read from inside that function is changed, the
|
||||||
|
function will be re-ran and the property value updated.
|
||||||
|
|
||||||
|
Sometimes when using expensive computations to derive state, you only want to
|
||||||
|
recalculate it when a source state has changed.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
```
|
```
|
||||||
|
|
@ -148,38 +143,34 @@ local derive = vide.derive
|
||||||
```lua
|
```lua
|
||||||
local count = wrap(0)
|
local count = wrap(0)
|
||||||
|
|
||||||
local text = derive(function(from)
|
local factorial = derive(function()
|
||||||
return "Count: " .. from(count)
|
local n = 1
|
||||||
|
for i = 2, count() do
|
||||||
|
n *= i
|
||||||
|
end
|
||||||
|
return n
|
||||||
end)
|
end)
|
||||||
|
|
||||||
print(text.value) -- "Count: 0"
|
|
||||||
|
|
||||||
count.value += 1
|
|
||||||
|
|
||||||
print(text.value) -- "Count: 1"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use [`vide.derive`](../api/reactivity-core#derive) to *derive* a new state `text` which depends on `count`.
|
`derive()` will cache and return the same value until a source state has
|
||||||
|
changed, where it will recompute and cache a new value.
|
||||||
A function is used to transform the value of `count`, where the value returned becomes the new value of `text`. The function receives an argument named `from` which is used to *capture* dependent states. This is used to link `count` to `text`, so that whenever `count` is updated, `text` will be too.
|
|
||||||
|
|
||||||
Whenever `count`'s value is changed, `text` will recompute its value and update anything dependent on `text`, such as UI.
|
|
||||||
|
|
||||||
States can be derived in a more concise manner when doing single operations such as concatenation:
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local text = "Count: " .. count
|
create "TextLabel" {
|
||||||
```
|
Text = function()
|
||||||
|
"factorial: " .. factorial()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
You can derive new states using any Luau operator in this manner.
|
count(3) -- displays "factorial: 6"
|
||||||
|
count(4) -- displays "factorial: 24"
|
||||||
|
```
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
*Components* in UI are just custom-made reusable pieces of UI made from other pieces of UI.
|
Components are custom-made reusable pieces of UI made from other pieces of UI.
|
||||||
|
|
||||||
The recommended way to create components is to use functions that take a table of properties as an argument and return the new UI instance.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local function Background(args)
|
local function Background(args)
|
||||||
|
|
@ -196,97 +187,128 @@ local background = Background {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Above is a simple example of a frame component with its background color set to black.
|
Above is a simple example of a frame component with its background color set to
|
||||||
|
black.
|
||||||
|
|
||||||
A single parameter `args` is used to pass properties to the component.
|
A single parameter `args` is used to pass properties to the component.
|
||||||
|
|
||||||
Components allow you to *encapsulate* behavior. You can only modify the component in ways that are defined in the component.
|
Components allow you to *encapsulate* behavior. You can only modify the
|
||||||
Looking at the above example, the only properties you are allowed to modify is `Position` and `Size`.
|
component in ways that you allow in the component.
|
||||||
This is a good approach to use for organised code.
|
|
||||||
|
|
||||||
However, properties concering layout (positional and size properties) aren't usually intrinsinc to the component. In most cases the user would want to be able to pass these properties without having to manually pass each one in the component.
|
This also promotes code reusability. Anytime you want a black frame all you do
|
||||||
|
is call `Background {}` instead of creating a new frame and settings it color
|
||||||
For these cases, the [`vide.Layout`](../api/creation#Layout) symbol can be used.
|
each time.
|
||||||
|
|
||||||
```lua
|
|
||||||
local Layout = vide.Layout
|
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local function Background(props)
|
|
||||||
return create("Frame") {
|
|
||||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
||||||
[Layout] = props[Layout],
|
|
||||||
[Children] = props[Children]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local background = Background {
|
|
||||||
[Layout] = {
|
|
||||||
AnchorPoint = Vector2.new(),
|
|
||||||
Position = UDim2.new(),
|
|
||||||
Size = UDim2.new(),
|
|
||||||
},
|
|
||||||
|
|
||||||
[Children] = {
|
|
||||||
create("TextLabel") {},
|
|
||||||
create("ImageLabel") {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, the `Layout` symbol automatically assigns those layout-specific properties without having to explicitly assign each one in the component definition. This is a very common case and for this reason it is recommended to only assign layout properties using the `Layout` symbol for consistency when dealing with components.
|
|
||||||
|
|
||||||
This allows you to pass through layout properties without breaking encapsulation.
|
|
||||||
|
|
||||||
Additionally, the above example shows how children can be passed to components in a similar manner.
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## Stateful components
|
## Stateful components
|
||||||
|
|
||||||
Often, you need components that maintain their own internal state, such as a toggle button or a counter.
|
Often, you need components that maintain their own internal state, such as a
|
||||||
|
toggle-able button or a counter.
|
||||||
|
|
||||||
Below you can see how a simple counter component can be implemented.
|
Below you can see how a simple counter component can be implemented.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local function Counter(props: {
|
local function Counter()
|
||||||
Layout: Layout
|
|
||||||
})
|
|
||||||
-- create internal state unique to each component instance
|
|
||||||
local count = source(0)
|
local count = source(0)
|
||||||
|
|
||||||
return create "TextButton" {
|
return create "TextButton" {
|
||||||
Text = function()
|
Text = function()
|
||||||
return "Count: " .. count
|
return "count: " .. count()
|
||||||
end
|
end
|
||||||
|
|
||||||
Activated = function()
|
Activated = function()
|
||||||
count(count() + 1)
|
count(count() + 1)
|
||||||
end,
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
Layout = props.Layout
|
Each time you call `Counter {}`, it will create a new counter component which
|
||||||
|
each maintains their own count state.
|
||||||
|
|
||||||
|
Clicking on the UI element will automatically increment and display its count.
|
||||||
|
|
||||||
|
## Nested Properties and Typechecking
|
||||||
|
|
||||||
|
When a key is assigned a table, Vide does not attempt to assign it to a
|
||||||
|
property, instead, the table is iterated and processed just like the nesting
|
||||||
|
table.
|
||||||
|
|
||||||
|
Below is an example of how you can use this to pass groups of similar properties
|
||||||
|
together such as position and size, while also using typechecking.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
type Layout = {
|
||||||
|
Layout = {
|
||||||
|
Position: UDim2?,
|
||||||
|
Size: UDim2?,
|
||||||
|
AnchorPoint: Vector2?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local function Button(args: Layout & {
|
||||||
|
Text: string,
|
||||||
|
Callback: () -> ()
|
||||||
|
})
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
return create "TextButton" {
|
||||||
|
Text = args.Text
|
||||||
|
Activated = args.Callback,
|
||||||
|
Layout = args.Layout
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
create "ScreenGui" {
|
Button {
|
||||||
Parent = game.StarterGui,
|
Text = "Click me!",
|
||||||
|
|
||||||
|
Callback = function()
|
||||||
|
print "clicked me!"
|
||||||
|
end,
|
||||||
|
|
||||||
Counter {
|
|
||||||
Layout = {
|
Layout = {
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
Position = UDim2.new(),
|
||||||
Position = UDim2.fromScale(0.5, 0),
|
Size = UDim2.new()
|
||||||
Size = UDim2.fromScale(0.3, 0.1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here a reusable counter component is created, that when clicked on will increase its count and display it independent from other counter instances.
|
Here the button component is assigned a position and size as if you passed those
|
||||||
|
properties directly.
|
||||||
|
|
||||||
|
The same can be done for properties such as children.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
type Children = {
|
||||||
|
Children = Array<Instance>
|
||||||
|
}
|
||||||
|
|
||||||
|
local function List(args: Children & Layout)
|
||||||
|
return create "Frame" {
|
||||||
|
Layout = args.Layout,
|
||||||
|
Children = args.Children,
|
||||||
|
|
||||||
|
create "UIListLayout" {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
List {
|
||||||
|
Layout = {
|
||||||
|
Position = UDim2.new()
|
||||||
|
},
|
||||||
|
|
||||||
|
Children = {
|
||||||
|
create "TextLabel" { Text = "1" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Tables of data
|
## Tables of data
|
||||||
|
|
||||||
basic inventory
|
Vide has functions for dealing with table states.
|
||||||
|
|
||||||
|
Below is an example using the above `List` class.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
type Item = {
|
type Item = {
|
||||||
|
|
@ -294,44 +316,28 @@ type Item = {
|
||||||
Icon: number
|
Icon: number
|
||||||
}
|
}
|
||||||
|
|
||||||
local items = wrap({} :: Array<Item>)
|
local items = source({} :: Array<Item>)
|
||||||
|
|
||||||
local function ItemSlot(args)
|
List {
|
||||||
return create("Frame") {
|
Children = map(items, function(item, i)
|
||||||
[Layout] = args[Layout]
|
return create "ImageLabel" {
|
||||||
|
Image = function()
|
||||||
|
return "rbxassetid://" .. item().Icon
|
||||||
|
end,
|
||||||
|
|
||||||
[Children] = {
|
LayoutOrder = i
|
||||||
create("TextLabel") {
|
|
||||||
Name = args.Name,
|
|
||||||
[Layout] = ...
|
|
||||||
},
|
|
||||||
|
|
||||||
create("ImageLabel") {
|
|
||||||
Image = "rbxassetid://" .. args.Icon,
|
|
||||||
[Layout] = ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function Inventory(args)
|
|
||||||
return create("Frame") {
|
|
||||||
[Layout] = args[Layout],
|
|
||||||
|
|
||||||
[Children] = {
|
|
||||||
create("UIListLayout") {},
|
|
||||||
|
|
||||||
map(items, function(i, item)
|
|
||||||
return ItemSlot {
|
|
||||||
Name = item.Name,
|
|
||||||
Icon = item.Icon,
|
|
||||||
[Layout] = { LayoutOrder = i, ... }
|
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
end
|
|
||||||
|
|
||||||
|
Here we map each element in `items` to a value returned by a callback.
|
||||||
|
|
||||||
|
The callback is called only *once* per key. The first argument given to the
|
||||||
|
callback is a state that has the value of the table key's value.
|
||||||
|
|
||||||
More comprehensive tutorials are in the works. To find out more refer to the [`API documentation`](../../README#API).
|
Anytime the value of the corresponding table key changes, the state value
|
||||||
|
changes too. This saves us from having to recreate a UI element any time a
|
||||||
|
table index changes.
|
||||||
|
|
||||||
|
## WIP
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
if not game then script = (require :: any) "test/wrap-require" end
|
|
||||||
|
|
||||||
local memoize = require(script.Parent.memoize)
|
|
||||||
local bind = require(script.Parent.bind)
|
|
||||||
|
|
||||||
local graph = require(script.Parent.graph)
|
|
||||||
type State<T> = graph.State<T>
|
|
||||||
type MaybeState<T> = graph.MaybeState<T>
|
|
||||||
local create = graph.create
|
|
||||||
local get = graph.get
|
|
||||||
local link = graph.link
|
|
||||||
local wrapped = graph.wrapped
|
|
||||||
|
|
||||||
local Types = require(script.Parent.Types)
|
|
||||||
|
|
||||||
type Listener = (unknown) -> ()
|
|
||||||
|
|
||||||
local getChangeSymbol = memoize(function(name: string): Types.Symbol<MaybeState<Listener>>
|
|
||||||
return {
|
|
||||||
priority = 2,
|
|
||||||
run = function(instance: Instance, listener: MaybeState<Listener>)
|
|
||||||
local event: RBXScriptSignal<nil> = instance:GetPropertyChangedSignal(name)
|
|
||||||
|
|
||||||
if type(listener) == "function" then
|
|
||||||
event:Connect(function()
|
|
||||||
listener( (instance :: any)[name] )
|
|
||||||
end)
|
|
||||||
elseif wrapped(listener) then
|
|
||||||
local state = create(nil)
|
|
||||||
|
|
||||||
link(listener :: State<Listener>, state, function()
|
|
||||||
local newListener = get(listener :: State<Listener>)
|
|
||||||
return newListener and function()
|
|
||||||
newListener( (instance :: any)[name] )
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
state.__updated = true
|
|
||||||
bind.event(state :: State<any>, instance, event)
|
|
||||||
else
|
|
||||||
error("Attempt to connect non-function to changed event", 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
local Changed = table.freeze(setmetatable({}, {__index = function(_, index: string)
|
|
||||||
return getChangeSymbol(index)
|
|
||||||
end})) :: any
|
|
||||||
|
|
||||||
return Changed :: { [string]: unknown }
|
|
||||||
|
|
@ -18,10 +18,6 @@ local hold: { Instance? } = {}
|
||||||
local weak: { Instance? } = setmetatable({}, { __mode = "v" }) :: any
|
local weak: { Instance? } = setmetatable({}, { __mode = "v" }) :: any
|
||||||
local bindcount = 0
|
local bindcount = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local srcs do
|
local srcs do
|
||||||
local src1 = debug.info(1, "s")
|
local src1 = debug.info(1, "s")
|
||||||
local srctrunc = string.sub(src1, 1, #src1-4)
|
local srctrunc = string.sub(src1, 1, #src1-4)
|
||||||
|
|
@ -52,20 +48,18 @@ function setup(instance: Instance, setter: (Instance) -> ())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local nodes = table.clone((capture(setter :: (Instance) -> unknown, instance)))
|
local nodes = (capture(setter :: () -> unknown, instance))
|
||||||
|
|
||||||
for _, node in next, nodes do
|
for _, node in next, nodes do
|
||||||
set_effect(node, setter, instance)
|
set_effect(node, setter, instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
bindcount += 1
|
bindcount += 1
|
||||||
local key = bindcount
|
local key = bindcount
|
||||||
|
|
||||||
weak[key] = instance
|
weak[key] = instance
|
||||||
|
|
||||||
local function ref()
|
local function ref()
|
||||||
--todo:local _ = nodes -- prevent gc of state while instance exists
|
|
||||||
local _ = setter
|
local _ = setter
|
||||||
local instance = weak[key] :: Instance
|
local instance = weak[key] :: Instance
|
||||||
hold[key] = instance.Parent and instance or nil -- prevent gc of instance while parented
|
hold[key] = instance.Parent and instance or nil -- prevent gc of instance while parented
|
||||||
|
|
@ -11,10 +11,6 @@ local watch = require(script.watch)
|
||||||
local cleanup, clean_garbage = require(script.cleanup)()
|
local cleanup, clean_garbage = require(script.cleanup)()
|
||||||
local derive = require(script.derive)
|
local derive = require(script.derive)
|
||||||
local map = require(script.map)
|
local map = require(script.map)
|
||||||
|
|
||||||
-- local Changed = require(script.Change)
|
|
||||||
-- local Created = require(script.Created)
|
|
||||||
|
|
||||||
local spring, update_springs = require(script.spring)()
|
local spring, update_springs = require(script.spring)()
|
||||||
|
|
||||||
local flags = require(script.flags)
|
local flags = require(script.flags)
|
||||||
|
|
@ -6,7 +6,6 @@ local create = graph.create
|
||||||
local set = graph.set
|
local set = graph.set
|
||||||
local capture = graph.capture
|
local capture = graph.capture
|
||||||
local link = graph.link
|
local link = graph.link
|
||||||
local capture_and_link = graph.capture_and_link
|
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
type Map<K, V> = { [K]: V }
|
||||||
|
|
||||||
|
|
@ -2,153 +2,94 @@
|
||||||
-- benchmark.lua
|
-- benchmark.lua
|
||||||
------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
local BENCH, START = require("test/testkit").getBenchmarkTools()
|
local BENCH, START = require("test/testkit").benchmark()
|
||||||
|
|
||||||
local vide = require "src/init"
|
local vide = require "src/init"
|
||||||
type State<T> = vide.State<T>
|
|
||||||
|
|
||||||
local function gc(n: number?)
|
local N = 1e6
|
||||||
for i = 1, n or 3 do
|
|
||||||
(collectgarbage :: any)("collect")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local N = 1e5
|
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Create state", function()
|
BENCH("Create state", function()
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = wrap(1)
|
cache[i] = source(1)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Get state value", function()
|
BENCH("Get state value", function()
|
||||||
local state = vide.wrap(1)
|
local state = vide.source(1)
|
||||||
local unwrap = vide.unwrap
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
unwrap(state)
|
state()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Set state value", function()
|
BENCH("Set state value", function()
|
||||||
local _, set = vide.wrap(1)
|
local state = vide.source(1)
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
set(i)
|
state(i)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
BENCH("Derive 1 state", function()
|
||||||
|
|
||||||
BENCH("Derive state", function()
|
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
local state = vide.wrap(1)
|
local state = vide.source(1)
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = derive(function(from)
|
cache[i] = derive(function()
|
||||||
return from(state)
|
return state()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
BENCH("Derive 2 states", function()
|
||||||
|
|
||||||
BENCH("Derive state (2)", function()
|
|
||||||
local cache = table.create(N)
|
local cache = table.create(N)
|
||||||
local state = vide.wrap(1)
|
local state = vide.source(1)
|
||||||
local state2 = vide.wrap(2)
|
local state2 = vide.source(2)
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = derive(function(from)
|
cache[i] = derive(function()
|
||||||
return from(state) + from(state2)
|
return state() + state2()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
BENCH("Set state value derived", function()
|
||||||
|
local state = vide.source(1)
|
||||||
BENCH("Derive state (shorthand)", function()
|
local _derived = vide.derive(state)
|
||||||
local cache = table.create(N)
|
|
||||||
local state = vide.wrap(1)
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
cache[i] = state + 1
|
state(i)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
BENCH("Apply 4 properties", function()
|
||||||
|
local apply = require "src/apply"
|
||||||
BENCH("Derive state (shorthand 2)", function()
|
|
||||||
local cache = table.create(N)
|
|
||||||
local state = vide.wrap(1)
|
|
||||||
local state2 = vide.wrap(2)
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
|
||||||
cache[i] = state + state2
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Derived state update", function()
|
|
||||||
local stateA, setA = vide.wrap(1)
|
|
||||||
local stateB = vide.derive(function(from) return from(stateA) end)
|
|
||||||
local unwrap = vide.unwrap
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
|
||||||
setA(i)
|
|
||||||
unwrap(stateB)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Derived state update (shorthand)", function()
|
|
||||||
local stateA, setA = vide.wrap(1)
|
|
||||||
local stateB = stateA + 1
|
|
||||||
local unwrap = vide.unwrap
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
|
||||||
setA(i)
|
|
||||||
unwrap(stateB)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Apply single property", function()
|
|
||||||
local instance = vide.create("Frame") {}
|
local instance = vide.create("Frame") {}
|
||||||
local apply = vide.apply
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
apply(instance) {
|
apply(instance, {
|
||||||
Name = i
|
Name = i,
|
||||||
}
|
Name2 = i,
|
||||||
|
Name3 = i,
|
||||||
|
Name4 = i
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
gc()
|
|
||||||
|
|
||||||
BENCH("Bind state", function()
|
BENCH("Bind state", function()
|
||||||
|
local apply = require "src/apply"
|
||||||
local instance = vide.create("Frame") {}
|
local instance = vide.create("Frame") {}
|
||||||
local state = vide.wrap(1)
|
local state = vide.source(1)
|
||||||
local apply = vide.apply
|
|
||||||
|
|
||||||
for i = 1, START(N) do
|
for i = 1, START(N) do
|
||||||
apply(instance) {
|
apply(instance, {
|
||||||
Name = state
|
Name = state
|
||||||
}
|
})
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -824,6 +824,7 @@ TEST("create()", function()
|
||||||
CHECK(#frame:GetChildren() == 0)
|
CHECK(#frame:GetChildren() == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
do CASE "Parent set to nil by state does not allow gc"
|
do CASE "Parent set to nil by state does not allow gc"
|
||||||
local frame = create "Frame" { Name = "Parent" }
|
local frame = create "Frame" { Name = "Parent" }
|
||||||
local parent = source(frame :: Frame?)
|
local parent = source(frame :: Frame?)
|
||||||
|
|
@ -845,6 +846,7 @@ TEST("create()", function()
|
||||||
gc()
|
gc()
|
||||||
CHECK(not wref[1])
|
CHECK(not wref[1])
|
||||||
end
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
do CASE "GC test"
|
do CASE "GC test"
|
||||||
local wref
|
local wref
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue