Skip to content

Commit

Permalink
Unimplement Ref (#363)
Browse files Browse the repository at this point in the history
  • Loading branch information
dphfox authored Jul 3, 2024
1 parent b1fa275 commit 89d9fa5
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 203 deletions.
30 changes: 0 additions & 30 deletions docs/api-reference/roblox/members/ref.md

This file was deleted.

10 changes: 6 additions & 4 deletions docs/api-reference/state/types/value.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,21 @@ can be used to tell types of state object apart.
<h3 markdown>
set
<span class="fusiondoc-api-type">
-> ()
-> T
</span>
</h3>

```Lua
function Value:set(
newValue: T
): ()
): T
```

Updates the value of this state object.
Updates the value of this state object. Other objects using the value are
notified of the change.

Other objects using the value are notified immediately of the change.
The `newValue` is always returned, so that `:set()` can be used to capture
values inside of expressions.

-----

Expand Down
116 changes: 116 additions & 0 deletions docs/tutorials/best-practices/references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
At some point, you might need to refer to another part of the UI. There are
various techniques that can let you do this.

```Lua hl_lines="8"
local ui = scope:New "Folder" {
[Children] = {
scope:New "SelectionBox" {
-- the box should adorn to the part, but how do you reference it?
Adornee = ???,
},
scope:New "Part" {
Name = "Selection Target",
}
}
}
```

-----

## Constants

The first technique is simple - instead of creating the UI all at once, you can
extract part of the UI that you want to reference later.

In practice, that means you'll move some of the creation code into a new `local`
constant, so that you can refer to it later by name.

```Lua
-- the part is now constructed first, whereas before it was constructed second
local selectionTarget = scope:New "Part" {
Name = "Selection Target",
}

local ui = scope:New "Folder" {
[Children] = {
scope:New "SelectionBox" {
Adornee = selectionTarget
},
selectionTarget
}
}
```

While this is a simple and robust technique, it has some disadvantages:

- By moving parts of your UI code into different local variables, your UI will
be constructed in a different order based on which local variables come first
- Refactoring code in this way can be bothersome and inelegant, disrupting the
structure of the code
- You can't have two pieces of UI refer to each other cyclically

Constants work well for trivial examples, but you should consider a more
flexible technique if those disadvantages are relevant.

-----

## Value Objects

Where it's impossible or inelegant to use named constants, you can use
[value objects](../../fundamentals/values) to easily set up references.

Because their `:set()` method returns the value that's passed in, you can use
`:set()` to reference part of your code without disrupting its structure:

```Lua
-- `selectionTarget` will show as `nil` to all code trying to use it, until the
-- `:set()` method is called later on.
local selectionTarget = scope:Value(nil :: Part?)

local ui = scope:New "Folder" {
[Children] = {
scope:New "SelectionBox" {
Adornee = selectionTarget
},
selectionTarget:set(
scope:New "Part" {
Name = "Selection Target",
}
)
}
}
```

It's important to note that the value object will briefly be `nil` (or whichever
default value you provide in the constructor). This is because it takes time to
reach the `:set()` call, so any in-between code will see the `nil`.

In the above example, the `Adornee` is briefly set to `nil`, but because
`selectionTarget` is a value object, it will change to the part instance when
the `:set()` method is called.

While dealing with the brief `nil` value can be annoying, it is also useful,
because this lets you refer to parts of your UI that haven't yet been created.
In particular, this lets you create cyclic references.

```Lua
local aliceRef = scope:Value(nil :: Instance?)
local bobRef = scope:Value(nil :: Instance?)

-- These two `ObjectValue` instances will refer to each other once the code has
-- finished running.
local alice = aliceRef:set(
scope:New "ObjectValue" {
Value = bobRef
}
)
local bob = bobRef:set(
scope:New "ObjectValue" {
Value = aliceRef
}
)
```

Value objects are generally easier to work with than named constants, so they're
often used as the primary way of referencing UI, but feel free to mix both
techniques based on what your code needs.
15 changes: 15 additions & 0 deletions docs/tutorials/fundamentals/values.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ health:set(25)
print(peek(health)) --> 25
```

??? tip "`:set()` returns the value you give it"
You can use `:set()` in the middle of calculations:

```Lua
local myNumber = scope:Value(0)
local computation = 10 + myNumber:set(2 + 2)
print(computation) --> 14
print(peek(myNumber)) --> 4
```

This is useful when building complex expressions. On a later page, you'll
see one such use case.

Generally though, it's better to keep your expressions simple.

Value objects are Fusion's simplest 'state object'. State objects contain a
single value - their *state*, you might say - and that single value can be read
out at any time using `peek()`.
Expand Down
89 changes: 0 additions & 89 deletions docs/tutorials/roblox/references.md

This file was deleted.

3 changes: 1 addition & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ nav:
- Events: tutorials/roblox/events.md
- Change Events: tutorials/roblox/change-events.md
- Outputs: tutorials/roblox/outputs.md
- References: tutorials/roblox/references.md
- Best Practices:
- Components: tutorials/best-practices/components.md
- Instance Handling: tutorials/best-practices/instance-handling.md
- References: tutorials/best-practices/references.md
- Callbacks: tutorials/best-practices/callbacks.md
- State: tutorials/best-practices/state.md
- Sharing Values: tutorials/best-practices/sharing-values.md
Expand Down Expand Up @@ -153,7 +153,6 @@ nav:
- OnChange: api-reference/roblox/members/onchange.md
- OnEvent: api-reference/roblox/members/onevent.md
- Out: api-reference/roblox/members/out.md
- Ref: api-reference/roblox/members/ref.md
- Animation:
- Types:
- Animatable: api-reference/animation/types/animatable.md
Expand Down
43 changes: 0 additions & 43 deletions src/Instances/Ref.luau

This file was deleted.

3 changes: 2 additions & 1 deletion src/State/Value.luau
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ local CLASS_METATABLE = {__index = class}
function class:set(
newValue: unknown,
force: boolean?
)
): unknown
local self = self :: InternalTypes.Value<unknown, unknown>
local oldValue = self._value
if force or not isSimilar(oldValue, newValue) then
self._value = newValue
updateAll(self)
end
return newValue
end

--[[
Expand Down
5 changes: 2 additions & 3 deletions src/Types.luau
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export type Use = <T>(target: UsedAs<T>) -> T
-- A state object whose value can be set at any time by the user.
export type Value<T, S = T> = StateObject<T> & {
kind: "State",
set: (Value<T, S>, newValue: S, force: boolean?) -> (),
____phantom_setType: (never) -> S -- phantom data so this contains a T
set: (Value<T, S>, newValue: S, force: boolean?) -> S,
____phantom_setType: (never) -> S -- phantom data so this contains a T
}
export type ValueConstructor = <T>(
scope: Scope<unknown>,
Expand Down Expand Up @@ -256,7 +256,6 @@ export type Fusion = {
New: NewConstructor,
Hydrate: HydrateConstructor,

Ref: SpecialKey,
Child: ({Child}) -> Child,
Children: SpecialKey,
Out: (propertyName: string) -> SpecialKey,
Expand Down
1 change: 0 additions & 1 deletion src/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ local Fusion: Types.Fusion = table.freeze {
OnChange = require(script.Instances.OnChange),
OnEvent = require(script.Instances.OnEvent),
Out = require(script.Instances.Out),
Ref = require(script.Instances.Ref),

-- Animation
Tween = require(script.Animation.Tween),
Expand Down
Loading

0 comments on commit 89d9fa5

Please sign in to comment.