Skip to content

Nitwel/Rdot

Repository files navigation

Rdot (JavaScript Signals for Godot! 🥳)

logo

What is Rdot?

Rdot is inspired by most modern Web Frameworks that use a reactive programming model. It enables you to write GDScript with syntax similar to Vue / Solid / Qwik and many other Frameworks... but in Godot!

example

The most common use case for Rdot is to synchronize the state of your game with the UI, although it can be used for other things as well.

⚠️ Disclaimer ⚠️

This should be seen as a proof of concept and not ready for production. While most logic was ported over, there still needs to be more testing to be done.

Logic was sourced from proposal-signals and translated to GDScript. Big shoutout to everyone who contributed to the proposal or worked on the demo in said proposal. ❤️

Usage

var counter = R.state(0)

var displayText = R.computed(func(_ignore):
    return "Counter: " + str(counter.value)
)

$AddButton.button_down.connect(func(): counter.value += 1)
$SubtractButton.button_down.connect(func(): counter.value -= 1)

R.effect(func(_ignore):
    $CounterLabel.text = displayText.value
)

See Explanation for a walkthrough of the code. More examples can be found in the demo folder.

Stores

Stores offer the ability to quickly create many reactive states at once. They are useful for managing the state of your game.

var store = R.store({ # or R.state({ ... })
    "counter": 0,
    "name": "John Doe"
})

R.effect(func(_ignore):
    $CounterLabel.text = "Counter: " + str(store.counter)
)

The benefit of this is that you can avoid having to access the value via the value property. Stores support also nested objects, allowing you to create more complex states.

R.state automatically creates a store when an object is passed as an argument.

Binding

Binding is a way to synchronize the state of a reactive value with the UI. It can be used to create 1-way or 2-way bindings.

var number = R.state(0)
var message = R.state("Hello World")

# 1-way binding
R.bind($Label, "text", message)

# 2-way binding
R.bind($Slider, "value", number, $Slider.value_changed)

There is an edge-case for bindings using Stores. When binding to a store, you have to pass the store as well as the key you want to bind to.

var store = R.store({
    "counter": 0,
    "name": "John Doe"
})

R.bind($Label, "text", store, "name")
R.bind($Slider, "value", store, "counter", $Slider.value_changed)

Explanation

R. (R-dot) is the namespace for all Rdot functions.

R.state is a function that creates a reactive state. It returns an object with a value property that you can read and write to. The methods do_get and do_set(value) are also available but behave the same as value.

var counter = R.state(0)

R.computed recalculated it's value each time a reactive value in the function changes. It returns an object with a value property that you can read. The first argument can be ignored but has to be there for Godot to not complain.

var double = R.computed(func(_ignore):
    return counter.value * 2
)

R.effect can be used to synchronize the reactive state with the UI. It its rerun each time a reactive value in the function changes.

R.effect(func(_ignore):
    print("Double changed: ", double.value)
)

API

## Creates a reactive state
R.state(initialValue: Variant) -> R.RdotState | RdotStore

## Creates a computed state based on the returned value of the function
R.computed(func: Callable) -> R.RdotComputed

## Runs the function each time a reactive value in the function changes
## Returns a function to stop the effect
R.effect(func: Callable) -> Callable

## Creates a Store based off of a Dictionary
R.store(initialValue: Dictionary) -> RdotStore

## Updates the target property when the value changes
## When a watch_signal is provided, the value will be updated when the signal is emitted (2 way binding)
## Returns a function to stop the binding
R.bind(target: Object, property: String, value: R.RdotState | R.RdotComputed, watch_signal: Signal = null) -> Callable

## For binding to a store
R.bind(target: Object, property: String, store: RdotStore, key: String, watch_signal: Signal = null) -> Callable

Releases

No releases published

Packages

No packages published