Roactive - A lightweight and fast reactive state library

Roactive

:file_folder: GitHub | v0.2.0 | Releases :floppy_disk:


:wave: Introduction

Welcome to Roactive, a lightweight and fast reactive state library. You may have heard or even use other state management solutions such as Rodux. While these can prove to be effective, especially when paired with other libraries and frameworks, Roactive aims to be a general-purpose library. Moreover, Roactive is reactive making it extremely simple and powerful. No need for confusing APIs or having to deal with immutability! This reactivity is inspired by Vue.js and Fusion.


:arrow_double_up: Advantages

:arrows_counterclockwise: Reactive

State objects can have dependents making them reactive! What does this mean? Here’s an example of how easy it is to manage state with Roactive.

local myState = Roactive.State(false)

Roactive.Watcher(function()
  print(myState:Get()) --> Outputs: true
end)

myState:Set(true)

Roactive Watchers capture dependencies and can listen for state changes. This is an extremely powerful feature and is inspired by Vue.js watchers. Roactive also has many other useful objects to help manage state.

:fast_forward: Fast

Many might think that because state is reactive it is slow. This actually isn’t the case with Roactive. This library is built in a way that works bottom up meaning dependencies will update their dependents which will update their dependents, etc. This is pretty standard. However, instead of a two way system where another library might also keep track of dependencies, we only keep track of dependents making updates one way. This makes the library smaller and faster with the only tradeoff being limitations in the different types of state objects we can develop.


:blue_book: Documentation

This library is so small that all documentation can be found in the README file of the GitHub repository. Examples demonstrating various features of the library are also available in the examples directory.

8 Likes

What is the difference between this and fusion? This seems a lot like the Computed object.

nice

how did you watch for the variable being changed? If you’re saying while loops I’m gonna die

Fusion is marketed as a UI library whereas Roactive is general-purpose. Fusion comes packaged with tools specifically meant towards working with UI such as New, Spring, Tween, and ComputedPairs. Roactive has other tools that are made for handling state in general. Moreover, Roactive is a lot smaller and overall faster. Feel free to compare Fusion’s updateAll() method to Roactive’s update method.

EDIT: Also worth noting that Roactive being general-purpose means you can make your own Fusion-like library. In Roactive’s examples directory you can find a mock version of Fusion’s New method. This should give you a better idea of how Watchers work and how Roactive is more lower level.

Nope no loops are used here. We use Vue.js’ method of capturing dependencies. Basically when a Watcher is passed a function that function is called and visible to the rest of Roactive. While that function is running it might call :Get() on a state object. When this happens the state object is able to see that there’s a Watcher with an active target function being run and sets that function as a dependent of itself. Then whenever you set state with :Set() we just loop through that object’s dependents, in this case the Watcher, and update them respectively.

1 Like

This is amazing!
This is just what I needed for my engine!

1 Like

It seems fascinating, but I’m not sure whether I’ll ever use it.

The complexity in Fusion’s updateAll function comes from implementing glitch-free updates. Are Roactive dependent updates glitch-free?

See:

That was an interesting read thanks. I wasn’t aware of glitches but they make perfect sense and will be working to counter them. Also to be honest I’ve used Roactive extensively and haven’t yet encountered a glitch as described. User error is not accounted for however and I’m sure they can occur on larger more complex projects. Fusion’s updateAll() method might solve this specifically for instances but doesn’t do so in a general manner. Take the following example taken straight from the wikipedia article you linked:

local seconds = Fusion.State(0)
local t, g = seconds:get() + 1, nil

Fusion.Compat(seconds):onChange(function()
	t = seconds:get() + 1
	print(t, g)
end)

Fusion.Compat(seconds):onChange(function()
	g = t > seconds:get()
	print('\t', t, g)
end)

while task.wait(1) do
	seconds:set(seconds:get() + 1, true)
end

A glitch occurs here as t and g are out of sync. Maybe I used the wrong object though (perhaps Computed is better but I couldn’t seem to get it to properly work outside of Fusion’s New). The target functions aren’t even executed in proper order here. Roactive Watchers do follow the order however they also end up glitching. After more research even very mature reactive libraries in other languages like Rx struggle with glitches. That said, I will end up updating Roactive to provide tools to counteract these glitches but I disagree with topological sorting as a solution and believe it to be more expensive than necessary.

Indeed the correct object to use here would be Computed:

local seconds = State(0)

local t = Computed(function()
    return seconds:get() + 1
end)

local g = Computed(function()
    return t:get() > seconds:get()
end)

while task.wait(1) do
    seconds:set(seconds:get() + 1)
end

The above should be glitch-free. Compat doesn’t follow the same rules by design - it’s mostly a way to get information out of the reactive graph and back into imperative code.

Anyway, it’s good to hear you plan to address this. Best of luck :slight_smile:

3 Likes