@rbxts/rl 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/docs/api_ref.md +70 -69
- package/docs/{concepts.md → intro.md} +2 -2
- package/package.json +9 -3
- package/src/index.d.ts +78 -65
- package/src/init.luau +166 -151
- package/PESDE_README.md +0 -39
- package/base_pesde.toml +0 -10
- package/build.luau +0 -30
- package/pesde.toml +0 -14
- package/publish.sh +0 -3
- package/rokit.toml +0 -4
- package/wally.toml +0 -9
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
## Wally
|
|
11
11
|
Add the following to your `wally.toml`, in the `[dependencies]` section:
|
|
12
12
|
```toml
|
|
13
|
-
rl = "
|
|
13
|
+
rl = "wiam77/rl@<version>"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Then run:
|
|
@@ -18,6 +18,11 @@ Then run:
|
|
|
18
18
|
# wally install
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## npm
|
|
22
|
+
```console
|
|
23
|
+
# npm install @rbxts/rl
|
|
24
|
+
```
|
|
25
|
+
|
|
21
26
|
# API Reference
|
|
22
27
|
You can read the API reference in `rl`'s [Pesde package page](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref).
|
|
23
28
|
|
package/docs/api_ref.md
CHANGED
|
@@ -1,47 +1,74 @@
|
|
|
1
1
|
# rl API Reference
|
|
2
2
|
## Types
|
|
3
|
-
###
|
|
4
|
-
|
|
3
|
+
### callback
|
|
4
|
+
Function which gets called when its [effect](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#effect) is triggered.
|
|
5
|
+
Takes the previous value of the state which triggered the effect, as well as its "index", determined by the order in which it appears within the function.
|
|
5
6
|
|
|
6
7
|
```luau
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
}
|
|
8
|
+
type callback<T = unknown> = ((old_value : T?, state_i : number?) -> unknown)
|
|
9
|
+
| ((old_value : T?, state_i : number?) -> ())
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
###
|
|
13
|
-
|
|
14
|
-
Takes the previous value of the state which triggered the effect, as well as its "index", determined by the order in which it appears within the function.
|
|
12
|
+
### state\<T>
|
|
13
|
+
The [state](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#state) type.
|
|
15
14
|
|
|
16
15
|
```luau
|
|
17
|
-
type
|
|
16
|
+
type state<T = unknown> = {
|
|
17
|
+
value : T,
|
|
18
|
+
always_force : boolean,
|
|
19
|
+
read callbacks : {[callback<T>] : number}
|
|
20
|
+
} & ( | (() -> T)
|
|
21
|
+
| ((new_value : T?, force : boolean?) -> T))
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
## Functions
|
|
21
25
|
### state
|
|
22
|
-
|
|
26
|
+
Returns a new [state](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#state), with the given initial value, and whether to always [force](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#forcing).
|
|
23
27
|
|
|
24
28
|
```luau
|
|
25
|
-
function state<T>(value : T?,
|
|
29
|
+
function state<T>(value : T?, always_force : boolean?) : state<T>
|
|
26
30
|
|
|
27
|
-
-- Example
|
|
28
31
|
local health = state(100)
|
|
29
32
|
local stamina = state(100, true)
|
|
30
33
|
```
|
|
31
34
|
|
|
35
|
+
### effect
|
|
36
|
+
Creates a new [effect](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#effect), with the given [callback](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref#callback), and returns its disconnect function.
|
|
37
|
+
|
|
38
|
+
```luau
|
|
39
|
+
function effect<T>(callback : callback<T>) : () -> ()
|
|
40
|
+
|
|
41
|
+
effect(function()
|
|
42
|
+
print(`new health: {health()}`)
|
|
43
|
+
end)
|
|
44
|
+
|
|
45
|
+
health(10) -- "new health: 10"
|
|
46
|
+
health(10) -- doesn't print because the new value is the same as the current one
|
|
47
|
+
health(10, true) -- "new health: 10"; this prints because we forced it
|
|
48
|
+
|
|
49
|
+
local disconnect = effect(function()
|
|
50
|
+
print(`new stamina: {stamina()}`)
|
|
51
|
+
end)
|
|
52
|
+
|
|
53
|
+
stamina(90) -- "new stamina: 90"
|
|
54
|
+
stamina(90) -- "new stamina: 90"; this prints because `stamina` is always forced.
|
|
55
|
+
disconnect()
|
|
56
|
+
|
|
57
|
+
stamina(40) -- doesn't print because we disconnected the effect.
|
|
58
|
+
```
|
|
59
|
+
|
|
32
60
|
### derive
|
|
33
|
-
Wraps an Effect around the given
|
|
34
|
-
to be the return value of the
|
|
35
|
-
Returns the created State
|
|
61
|
+
Wraps an Effect around the given Callback which sets a newly created State's value
|
|
62
|
+
to be the return value of the Callback.
|
|
63
|
+
Returns the created State and the Effect's disconnect function.
|
|
36
64
|
|
|
37
65
|
This is useful for States which depend on the value of another State, since their value will
|
|
38
66
|
be updated only when the State they depend on is also updated, and not every time it is read,
|
|
39
67
|
as how would happen with a function which's return value depends on a State.
|
|
40
68
|
|
|
41
69
|
```luau
|
|
42
|
-
function derive<T
|
|
70
|
+
function derive<T>(callback : (() -> T) | (() -> ())) : (state<T>, () -> ())
|
|
43
71
|
|
|
44
|
-
-- Example
|
|
45
72
|
-- without derive() (normal function)
|
|
46
73
|
local half_health = function()
|
|
47
74
|
print("some computation")
|
|
@@ -64,39 +91,12 @@ print(derive_half_health()) -- 10
|
|
|
64
91
|
print(derive_half_health()) -- 10
|
|
65
92
|
```
|
|
66
93
|
|
|
67
|
-
### effect
|
|
68
|
-
Creates a new [effect](https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts#effect), with the given [callback](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref#callback) function, and returns its disconnect function.
|
|
69
|
-
|
|
70
|
-
```luau
|
|
71
|
-
function effect<T>(callback : callback<T>) : () -> ()
|
|
72
|
-
|
|
73
|
-
-- Example
|
|
74
|
-
effect(function()
|
|
75
|
-
print(`new health: {health()}`)
|
|
76
|
-
end)
|
|
77
|
-
|
|
78
|
-
health(10) -- "new health: 10"
|
|
79
|
-
health(10) -- doesn't print because the new value is the same as the current one
|
|
80
|
-
health(10, true) -- "new health: 10"; this prints because we forced it
|
|
81
|
-
|
|
82
|
-
local disconnect = effect(function()
|
|
83
|
-
print(`new stamina: {stamina()}`)
|
|
84
|
-
end)
|
|
85
|
-
|
|
86
|
-
stamina(90) -- "new stamina: 90"
|
|
87
|
-
stamina(90) -- "new stamina: 90"; this prints because `stamina` is always forced.
|
|
88
|
-
disconnect()
|
|
89
|
-
|
|
90
|
-
stamina(40) -- doesn't print because we disconnected the effect.
|
|
91
|
-
```
|
|
92
|
-
|
|
93
94
|
### batch
|
|
94
95
|
Batches any State write operation up until the function is done running.
|
|
95
96
|
|
|
96
97
|
```luau
|
|
97
|
-
function batch(f : () ->
|
|
98
|
+
function batch(f : (() -> unknown) | () -> ()) : ()
|
|
98
99
|
|
|
99
|
-
-- Example
|
|
100
100
|
batch(function()
|
|
101
101
|
health(420)
|
|
102
102
|
health(1337, true)
|
|
@@ -104,18 +104,15 @@ batch(function()
|
|
|
104
104
|
end)
|
|
105
105
|
|
|
106
106
|
-- "new health: 69"
|
|
107
|
-
|
|
108
107
|
```
|
|
109
108
|
|
|
110
109
|
### scope
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
This is intended to be used for disconnecting Effects in bulk.
|
|
110
|
+
Wraps the given callbacks into Effects.
|
|
111
|
+
Returns a function which disconnects all the created Effects.
|
|
114
112
|
|
|
115
113
|
```luau
|
|
116
|
-
function scope
|
|
114
|
+
function scope(... : callback) : () -> ()
|
|
117
115
|
|
|
118
|
-
-- Example
|
|
119
116
|
-- without scope
|
|
120
117
|
local disconnect_health = effect(function()
|
|
121
118
|
print(`your health is : {health()}`)
|
|
@@ -132,12 +129,12 @@ disconnect_stamina()
|
|
|
132
129
|
|
|
133
130
|
-- with scope
|
|
134
131
|
local vitals_scope = scope(
|
|
135
|
-
|
|
132
|
+
function()
|
|
136
133
|
print(`your health is : {health()}`)
|
|
137
|
-
end
|
|
138
|
-
|
|
134
|
+
end,
|
|
135
|
+
function()
|
|
139
136
|
print(`your stamina is : {stamina()}`)
|
|
140
|
-
end
|
|
137
|
+
end
|
|
141
138
|
)
|
|
142
139
|
|
|
143
140
|
-- ...
|
|
@@ -146,14 +143,13 @@ vitals_scope()
|
|
|
146
143
|
```
|
|
147
144
|
|
|
148
145
|
### branch
|
|
149
|
-
|
|
146
|
+
`if`-like function to be used within effects. Learn more about it in the [tracking section of the intro page](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#tracking).
|
|
150
147
|
Pass a condition to be checked, and after it a function to be run if it's successful.
|
|
151
148
|
The last value, if a function, and if not after a condition, will be threated as the "else" branch.
|
|
152
149
|
|
|
153
150
|
```luau
|
|
154
|
-
function branch(...) : ()
|
|
151
|
+
function branch(... : any) : ()
|
|
155
152
|
|
|
156
|
-
-- Example
|
|
157
153
|
-- A single effect for the same functionality as the examples above, thanks to the use of state_i and the branching function.
|
|
158
154
|
effect(function(_, state_i)
|
|
159
155
|
branch(
|
|
@@ -168,35 +164,40 @@ end)
|
|
|
168
164
|
```
|
|
169
165
|
|
|
170
166
|
### mirror
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
Iterates over the given table and creates an Effect for each key that is
|
|
168
|
+
either a function or a State, updating the key's value to be the return value of the
|
|
169
|
+
function, or the State's value.
|
|
170
|
+
Returns table containing each key's Effect's disconnect function.
|
|
174
171
|
|
|
175
172
|
```luau
|
|
176
|
-
function
|
|
173
|
+
type function indexer(t : type) : type
|
|
174
|
+
return (t:indexer() :: any).index
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
type identifier<T> = keyof<T> | indexer<T>
|
|
178
|
+
|
|
179
|
+
function mirror<T>(t : T) : (T, {[identifier<T>] : () -> ()})
|
|
177
180
|
|
|
178
|
-
-- Example
|
|
179
181
|
local name = state("john")
|
|
180
|
-
local data = mirror {
|
|
181
|
-
username = name
|
|
182
|
+
local data, data_disconnects = mirror {
|
|
183
|
+
username = name
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
print(data.username) -- "john"
|
|
185
187
|
name("joe")
|
|
186
188
|
print(data.username) -- "joe"
|
|
187
|
-
|
|
189
|
+
data_disconnects.username()
|
|
188
190
|
|
|
189
191
|
name("doe")
|
|
190
|
-
print({data.username}) -- "joe"; didn't update because we disconnected the
|
|
192
|
+
print({data.username}) -- "joe"; didn't update because we disconnected the Effect.
|
|
191
193
|
```
|
|
192
194
|
|
|
193
195
|
### cleanup
|
|
194
|
-
Disconnects all [effects](https://pesde.dev/packages/wiam/rl/latest/any/docs/
|
|
196
|
+
Disconnects all [effects](https://pesde.dev/packages/wiam/rl/latest/any/docs/intro#effect).
|
|
195
197
|
|
|
196
198
|
```luau
|
|
197
199
|
function cleanup() : ()
|
|
198
200
|
|
|
199
|
-
-- Example
|
|
200
201
|
cleanup()
|
|
201
202
|
health(77) -- doesn't print because no effect is connected to this state (we just disconnected)
|
|
202
203
|
```
|
|
@@ -11,10 +11,10 @@ Instead, `rl` solves this by providing the `branch` function, which works like s
|
|
|
11
11
|
|
|
12
12
|
## State
|
|
13
13
|
A State is a container for a value.\
|
|
14
|
-
Internally, it is an userdata that has a metatable with the `__index` and `__newindex` metamethods, which permit reading and writing the value with special mechanisms.\
|
|
15
14
|
Call it with no arguments for reading with tracking.\
|
|
16
15
|
Call with arguments for writing, where the first argument is the new value, and the second one is whether to force for this specific call.\
|
|
17
|
-
Access the `.value` field for reading without tracking.
|
|
16
|
+
Access the `.value` field for reading without tracking.
|
|
17
|
+
Modify the `.value` field for writing without tracking.
|
|
18
18
|
|
|
19
19
|
## Forcing
|
|
20
20
|
Forcing is when you write a new value to a State, and always trigger its Effects, even when the new value is the same as the old one.\
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/rl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Simple reactive library. Docs: https://pesde.dev/packages/wiam/rl/latest/any/docs/intro",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -12,5 +12,11 @@
|
|
|
12
12
|
"license": "GPL-3.0-or-later",
|
|
13
13
|
"author": "wi_am",
|
|
14
14
|
"main": "src/init.luau",
|
|
15
|
-
"typings": "src/index.d.ts"
|
|
15
|
+
"typings": "src/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"src/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"COPYING",
|
|
20
|
+
"default.project.json"
|
|
21
|
+
]
|
|
16
22
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,74 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
(
|
|
3
|
-
(): T
|
|
4
|
-
readonly value: T
|
|
5
|
-
}
|
|
1
|
+
declare namespace rl {
|
|
2
|
+
type callback<T = unknown> = (old_value?: T, state_i?: number) => unknown
|
|
6
3
|
|
|
7
|
-
export type
|
|
4
|
+
export type state<T = unknown> = {
|
|
5
|
+
(new_value: T, force?: boolean): T
|
|
6
|
+
(): T
|
|
7
|
+
value: T
|
|
8
|
+
always_force: boolean
|
|
9
|
+
readonly callbacks: Map<callback<T>, number>
|
|
10
|
+
}
|
|
8
11
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Creates a State.
|
|
14
|
+
* @param initial_value? T -- The initial value for the State, can be nil.
|
|
15
|
+
* @param always_force? boolean -- Whether Effects connected to this State will always trigger, even when the value didn't change.
|
|
16
|
+
* @returns state<T> -- The created State. Call with no arguments for reading with tracking,
|
|
17
|
+
* call with arguments for writing (2nd argument is for forcing),
|
|
18
|
+
* read .value field for reading without tracking,
|
|
19
|
+
* write .value field for writing without triggering Effects.
|
|
20
|
+
*/
|
|
21
|
+
function state<T>(initial_value: T, always_force?: boolean): state<T>
|
|
13
22
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export function state<T>(value: T, force?: boolean): state<T>
|
|
23
|
+
/**
|
|
24
|
+
* Creates an Effect, a binding of the given function to the event of States read (by calling) within it being updated.
|
|
25
|
+
*
|
|
26
|
+
* @param callback callback<T> -- The function to be bound.
|
|
27
|
+
* @returns () => void -- Function which disconnects the Effect from the States.
|
|
28
|
+
*/
|
|
29
|
+
function effect<T>(callback: callback<T>): () => void
|
|
22
30
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Wraps an Effect around the given Callback which sets a newly created State's value
|
|
33
|
+
* to be the return value of the Callback.
|
|
34
|
+
* Returns the created State and the Effect's disconnect function.
|
|
35
|
+
*
|
|
36
|
+
* This is useful for States which depend on the value of another State, since their value will
|
|
37
|
+
* be updated only when the State they depend on is also updated, and not every time it is read,
|
|
38
|
+
* as how would happen with a function which's return value depends on a State.
|
|
39
|
+
*
|
|
40
|
+
* @param callback () => T -- The callback.
|
|
41
|
+
* @returns state<T> -- The State that was created.
|
|
42
|
+
* @returns () => void -- The created Effect's disconnect function.
|
|
43
|
+
*/
|
|
44
|
+
function derive<T>(callback: () => T): [state<T>, () => void]
|
|
28
45
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export function effect<T>(callback: callback<T>): () => void
|
|
46
|
+
/**
|
|
47
|
+
* Batches any State write operation up until the function is done running.
|
|
48
|
+
*
|
|
49
|
+
* @param f () => unknown -- The function.
|
|
50
|
+
*/
|
|
51
|
+
function batch(f: () => unknown): void
|
|
36
52
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Wraps the given callbacks into Effects.
|
|
55
|
+
* Returns a function which disconnects all the created Effects.
|
|
56
|
+
*
|
|
57
|
+
* @param ...args { [K in keyof T]: callback<T[K]> } -- The callbacks.
|
|
58
|
+
* @returns () => void -- The disconnecter.
|
|
59
|
+
*/
|
|
60
|
+
function scope<T extends any[]>(...args: { [K in keyof T]: callback<T[K]> }): () => void
|
|
45
61
|
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* @returns state<U> -- the State that was created
|
|
55
|
-
* @returns () => void -- the created Effect's disconnect function
|
|
56
|
-
* @returns true -- a flag used internally by rl.scope(); can be ignored.
|
|
57
|
-
*/
|
|
58
|
-
export function derive<T, U>(callback: () => U) : [state<U>, () => void, true]
|
|
62
|
+
/**
|
|
63
|
+
* Branching function to be used within Effects.
|
|
64
|
+
* Pass a condition to be checked, and after it a function to be run if it's successful.
|
|
65
|
+
* The last value, if a function, and if not after a condition, will be threated as the "else" branch.
|
|
66
|
+
*
|
|
67
|
+
* @param ...a any -- The values and the branches.
|
|
68
|
+
*/
|
|
69
|
+
function branch(...a: any): void
|
|
59
70
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Iterates over the given table and creates an Effect for each key that is
|
|
73
|
+
* either a function or a State, updating the key's value to be the return value of the
|
|
74
|
+
* function, or the State's value.
|
|
75
|
+
* Returns table containing each key's Effect's disconnect function.
|
|
76
|
+
*
|
|
77
|
+
* @param t T -- The table to iterate.
|
|
78
|
+
* @return T -- The given table.
|
|
79
|
+
* @return {[identifier<T>] : () -> ()} -- The table containing the disconnect functions.
|
|
80
|
+
*/
|
|
81
|
+
function mirror<K, V>(t: Map<K, V>): [Map<K, V>, Map<K, () => void>]
|
|
66
82
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* @returns () => void -- Function which disconnects and eliminates each disconnect function of the created Effects.
|
|
73
|
-
*/
|
|
74
|
-
export function mirror<K, V>(t : Map<K, V>): [Map<K, V>, () => void]
|
|
83
|
+
/**
|
|
84
|
+
* Disconnects all Effects.
|
|
85
|
+
*/
|
|
86
|
+
function cleanup(): void
|
|
87
|
+
}
|
package/src/init.luau
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
TODO:
|
|
4
|
+
When creating a State, if its variable type is annotated,
|
|
5
|
+
and the type of the value passed to rl.state() doesn't match,
|
|
6
|
+
there should be a type error:
|
|
7
|
+
local health : rl.state<number> = rl.state("i'm not a number") -- doesn't error
|
|
2
8
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type callback<T> = (old_value : T, state_i : number?) -> any
|
|
9
|
+
When modifying a State, if the value passed to state() doesn't match its type,
|
|
10
|
+
there should be a type error:
|
|
11
|
+
local health : rl.state<number> = rl.state(100)
|
|
12
|
+
health("i'm not a number")
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
local batched : {[state<unknown>] : {any}} = {}
|
|
14
|
-
|
|
15
|
-
local current_callback : callback<unknown>? = nil
|
|
16
|
-
local effects : {[callback<unknown>] : number} = {}
|
|
14
|
+
- The 2 bugs above could be fixed by using the commented out state<T> type.
|
|
15
|
+
]]--
|
|
17
16
|
|
|
18
|
-
local
|
|
19
|
-
error("Cannot mutate a State by editing it's .value!")
|
|
20
|
-
end
|
|
17
|
+
local rl = {}
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
export type callback<T = unknown> = ((old_value : T?, state_i : number?) -> unknown)
|
|
20
|
+
| ((old_value : T?, state_i : number?) -> ())
|
|
21
|
+
|
|
22
|
+
-- Currently broken, might get fixed in the future.
|
|
23
|
+
-- export type state<T = unknown> = setmetatable<{
|
|
24
|
+
-- value : T,
|
|
25
|
+
-- read callbacks : {[callback<T>] : number},
|
|
26
|
+
-- read always_force : boolean
|
|
27
|
+
-- }, {
|
|
28
|
+
-- __call : (() -> T)
|
|
29
|
+
-- & ((new_value : T?, force : boolean?) -> T)
|
|
30
|
+
-- }>
|
|
31
|
+
|
|
32
|
+
export type state<T = unknown> = {
|
|
33
|
+
value : T,
|
|
34
|
+
always_force : boolean,
|
|
35
|
+
read callbacks : {[callback<T>] : number}
|
|
36
|
+
} & ( | (() -> T)
|
|
37
|
+
| ((new_value : T?, force : boolean?) -> T))
|
|
38
|
+
|
|
39
|
+
local function is_function<T>(v : T) : T
|
|
23
40
|
local vt = type(v)
|
|
24
41
|
assert(vt == "function", `Expected type 'function', got '{vt}'`)
|
|
25
42
|
return v
|
|
@@ -29,97 +46,76 @@ local warn = warn or function(msg : string) : ()
|
|
|
29
46
|
print(`\x1b[33m{msg}\x1b[0m`)
|
|
30
47
|
end
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
]=]
|
|
35
|
-
function rl.cleanup() : ()
|
|
36
|
-
table.clear(effects)
|
|
37
|
-
end
|
|
49
|
+
local batching = false
|
|
50
|
+
local batched : {[state] : {any}} = {} -- {[state] : {[1] : {[callback] = true}, [2] : old_value, [3] : state_i}}
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
]=]
|
|
46
|
-
function rl.state<T>(value : T?, force : boolean?) : state<T>
|
|
47
|
-
local callbacks : {[callback<unknown>] : number} = {}
|
|
48
|
-
local self = newproxy(true)
|
|
49
|
-
local mt = getmetatable(self)
|
|
50
|
-
mt.__newindex = newindex_err
|
|
51
|
-
mt.__index = function(_, k)
|
|
52
|
-
assert(k == "value", `Expected key 'value', got '{k}'!`)
|
|
53
|
-
return value
|
|
54
|
-
end
|
|
52
|
+
local current_callback : callback? = nil
|
|
53
|
+
local effects : {[callback] : number} = {}
|
|
54
|
+
|
|
55
|
+
local function state__call<T>(self : state<T>, new_value : T?, force : boolean?) : T
|
|
56
|
+
local old_value = self.value
|
|
57
|
+
local callbacks = self.callbacks
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
effects[current_callback] += 1
|
|
64
|
-
callbacks[current_callback] = effects[current_callback]
|
|
65
|
-
end
|
|
66
|
-
elseif value :: any ~= new_value or force == true or argv[3] == true then
|
|
67
|
-
value = new_value
|
|
59
|
+
if not (new_value or force) then
|
|
60
|
+
if current_callback and not callbacks[current_callback] then
|
|
61
|
+
effects[current_callback] += 1
|
|
62
|
+
callbacks[current_callback] = effects[current_callback]
|
|
63
|
+
end
|
|
64
|
+
elseif old_value ~= new_value or self.always_force or force then
|
|
65
|
+
self.value = new_value
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
for callback, state_i in callbacks do
|
|
68
|
+
if not effects[callback] then
|
|
69
|
+
callbacks[callback] = nil
|
|
70
|
+
continue
|
|
71
|
+
end
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
state_batch[2] = old_value
|
|
84
|
-
else
|
|
85
|
-
batched[self] = {{[callback] = true}, old_value, state_i}
|
|
73
|
+
if batching then
|
|
74
|
+
local state_batch = batched[self]
|
|
75
|
+
if state_batch then
|
|
76
|
+
local x = state_batch[1]
|
|
77
|
+
if not x[callback] then
|
|
78
|
+
x[callback] = true
|
|
86
79
|
end
|
|
80
|
+
|
|
81
|
+
state_batch[2] = old_value
|
|
87
82
|
else
|
|
88
|
-
callback
|
|
83
|
+
batched[self] = {{[callback] = true}, old_value, state_i}
|
|
89
84
|
end
|
|
85
|
+
else
|
|
86
|
+
callback(old_value, state_i)
|
|
90
87
|
end
|
|
91
88
|
end
|
|
92
|
-
|
|
93
|
-
return value
|
|
94
89
|
end
|
|
95
90
|
|
|
96
|
-
return self
|
|
91
|
+
return self.value
|
|
97
92
|
end
|
|
98
93
|
|
|
99
94
|
--[=[
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@param
|
|
95
|
+
Creates a State.
|
|
96
|
+
|
|
97
|
+
@param initial_value T? -- The initial value for the State, can be nil.
|
|
98
|
+
@param always_force boolean? -- Whether Effects connected to this State will always trigger, even when the value didn't change.
|
|
99
|
+
@return state<T> -- The created State. Call with no arguments for reading with tracking,
|
|
100
|
+
call with arguments for writing (2nd argument is for forcing),
|
|
101
|
+
read .value field for reading without tracking,
|
|
102
|
+
write .value field for writing without triggering Effects.
|
|
103
103
|
]=]
|
|
104
|
-
function rl.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
batching = false
|
|
115
|
-
table.clear(batched :: {any})
|
|
104
|
+
function rl.state<T>(initial_value : T?, always_force : boolean?) : state<T>
|
|
105
|
+
return setmetatable({
|
|
106
|
+
value = initial_value,
|
|
107
|
+
callbacks = {},
|
|
108
|
+
always_force = always_force or false
|
|
109
|
+
}, {
|
|
110
|
+
__call = state__call
|
|
111
|
+
}) :: any
|
|
116
112
|
end
|
|
117
113
|
|
|
118
114
|
--[=[
|
|
119
115
|
Creates an Effect, a binding of the given function to the event of States read (by calling) within it being updated.
|
|
120
116
|
|
|
121
117
|
@param callback callback<T> -- The function to be bound.
|
|
122
|
-
@return
|
|
118
|
+
@return () -> () -- Function which disconnects the Effect from the States.
|
|
123
119
|
]=]
|
|
124
120
|
function rl.effect<T>(callback : callback<T>) : () -> ()
|
|
125
121
|
if current_callback then
|
|
@@ -142,106 +138,125 @@ function rl.effect<T>(callback : callback<T>) : () -> ()
|
|
|
142
138
|
end
|
|
143
139
|
|
|
144
140
|
--[=[
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
Wraps an Effect, around the given Callback, which sets a newly created State's value
|
|
142
|
+
to be the return value of the Callback.
|
|
143
|
+
Returns the created State and the Effect's disconnect function.
|
|
144
|
+
|
|
145
|
+
This is useful for States which depend on the value of another State, since their value will
|
|
146
|
+
be updated only when the State they depend on is also updated, and not every time it is read,
|
|
147
|
+
as how would happen with a function which's return value depends on a State.
|
|
148
|
+
|
|
149
|
+
@param callback () -> T -- The callback.
|
|
150
|
+
@return state<T> -- The State that was created.
|
|
151
|
+
@return () -> () -- The created Effect's disconnect function.
|
|
152
|
+
]=]
|
|
153
|
+
function rl.derive<T>(callback : (() -> T) | (() -> ())) : (state<T>, () -> ())
|
|
154
|
+
local state = rl.state()
|
|
155
|
+
return state, rl.effect(function()
|
|
156
|
+
state(callback())
|
|
157
|
+
end)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
--[=[
|
|
161
|
+
Batches any State write operation up until the given function is done running.
|
|
148
162
|
|
|
149
|
-
@param
|
|
150
|
-
@return f () -> () -- The function caller.
|
|
163
|
+
@param f (() -> unknown) | () -> () -- The function.
|
|
151
164
|
]=]
|
|
152
|
-
function rl.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
function rl.batch(f : (() -> unknown) | () -> ()) : ()
|
|
166
|
+
batching = true
|
|
167
|
+
f()
|
|
168
|
+
|
|
169
|
+
for _, result in batched do
|
|
170
|
+
for callback in result[1] :: {[callback] : true} do
|
|
171
|
+
callback(result[2], result[3] :: number)
|
|
159
172
|
end
|
|
160
173
|
end
|
|
174
|
+
|
|
175
|
+
batching = false
|
|
176
|
+
table.clear(batched)
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
--[=[
|
|
164
|
-
Wraps
|
|
165
|
-
|
|
166
|
-
Returns the created State, the Effect's disconnect function and a flag which can be ignored.
|
|
167
|
-
|
|
168
|
-
This is useful for States which depend on the value of another State, since their value will
|
|
169
|
-
be updated only when the State they depend on is also updated, and not every time it is read,
|
|
170
|
-
as how would happen with a function which's return value depends on a State.
|
|
180
|
+
Wraps the given Callbacks into Effects.
|
|
181
|
+
Returns a function which disconnects all the created Effects.
|
|
171
182
|
|
|
172
|
-
@param
|
|
173
|
-
@return
|
|
174
|
-
@return disconnect () -> -- the created Effect's disconnect function
|
|
175
|
-
@return flag true -- a flag used internally by rl.scope(); can be ignored.
|
|
183
|
+
@param ... () -> () -- The Callbacks.
|
|
184
|
+
@return () -> () -- The disconnecter.
|
|
176
185
|
]=]
|
|
177
|
-
function rl.
|
|
178
|
-
local
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
end
|
|
186
|
+
function rl.scope(... : callback) : () -> ()
|
|
187
|
+
local disconnects = {}
|
|
188
|
+
for _, callback in {...} do
|
|
189
|
+
table.insert(disconnects, rl.effect(callback))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
return function()
|
|
193
|
+
for _, disconnect in disconnects do
|
|
194
|
+
disconnect()
|
|
195
|
+
end
|
|
196
|
+
end
|
|
182
197
|
end
|
|
183
198
|
|
|
184
199
|
--[=[
|
|
185
200
|
Branching function to be used within Effects.
|
|
186
201
|
Pass a condition to be checked, and after it a function to be run if it's successful.
|
|
187
202
|
The last value, if a function, and if not after a condition, will be threated as the "else" branch.
|
|
203
|
+
|
|
204
|
+
@param ... any -- The values and the branches.
|
|
188
205
|
]=]
|
|
189
|
-
function rl.branch(...) : ()
|
|
206
|
+
function rl.branch(... : any) : ()
|
|
190
207
|
local args = table.pack(...)
|
|
191
208
|
local n = args.n
|
|
192
|
-
assert(n
|
|
209
|
+
assert(n > 1, "Expected at least 2 arguments!")
|
|
193
210
|
|
|
194
211
|
local i = 1
|
|
195
212
|
while i + 1 <= n do
|
|
196
213
|
if args[i] or current_callback then
|
|
197
|
-
|
|
214
|
+
is_function(args[i + 1])()
|
|
198
215
|
end
|
|
199
216
|
|
|
200
217
|
i += 2
|
|
201
218
|
end
|
|
202
219
|
|
|
203
220
|
if i <= n then
|
|
204
|
-
|
|
221
|
+
is_function(args[i])()
|
|
205
222
|
end
|
|
206
223
|
end
|
|
207
224
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
225
|
+
type function indexer(t : type) : type
|
|
226
|
+
return (t:indexer() :: any).index
|
|
227
|
+
end
|
|
211
228
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
229
|
+
-- couldn't think of a better name
|
|
230
|
+
type identifier<T> = keyof<T> | indexer<T>
|
|
231
|
+
|
|
232
|
+
--[=[
|
|
233
|
+
Iterates over the given table and creates an Effect for each key that is
|
|
234
|
+
either a function or a State, updating the key's value to be the return value of the
|
|
235
|
+
function, or the State's value.
|
|
236
|
+
Returns table containing each key's Effect's disconnect function.
|
|
237
|
+
|
|
238
|
+
@param t T -- The table to iterate.
|
|
239
|
+
@return T -- The given table.
|
|
240
|
+
@return {[identifier<T>] : () -> ()} -- The table containing the disconnect functions.
|
|
215
241
|
]=]
|
|
216
|
-
function rl.mirror<
|
|
217
|
-
local disconnects : {[
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
end)
|
|
225
|
-
|
|
226
|
-
local k_type = type(k)
|
|
227
|
-
if k_type == "string" then
|
|
228
|
-
disconnects[k] = disconnect
|
|
229
|
-
else
|
|
230
|
-
table.insert(disconnects :: {any}, disconnect)
|
|
231
|
-
end
|
|
232
|
-
end
|
|
242
|
+
function rl.mirror<T>(t : T) : (T, {[identifier<T>] : () -> ()})
|
|
243
|
+
local disconnects : {[identifier<T>] : () -> ()} = {}
|
|
244
|
+
for k, v in (t :: {[identifier<T>] : unknown}) do
|
|
245
|
+
local mt = getmetatable(v :: any)
|
|
246
|
+
if type(v) ~= "function" and not (mt and mt.__call == state__call) then continue end
|
|
247
|
+
disconnects[k] = rl.effect(function()
|
|
248
|
+
(t :: {[identifier<T>] : unknown})[k] = (v :: () -> ())()
|
|
249
|
+
end)
|
|
233
250
|
end
|
|
234
251
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
end
|
|
252
|
+
return t, disconnects
|
|
253
|
+
end
|
|
238
254
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
end
|
|
255
|
+
--[=[
|
|
256
|
+
Disconnects all Effects.
|
|
257
|
+
]=]
|
|
258
|
+
function rl.cleanup() : ()
|
|
259
|
+
table.clear(effects)
|
|
245
260
|
end
|
|
246
261
|
|
|
247
262
|
return rl
|
package/PESDE_README.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# rl
|
|
2
|
-
`rl` is a simple reactive library for Luau, inspired by [Vide](https://centau.github.io/vide)'s sources and effects system.
|
|
3
|
-
|
|
4
|
-
# API Reference
|
|
5
|
-
You can read `rl`'s API reference [here](https://pesde.dev/packages/wiam/rl/latest/any/docs/api_ref).
|
|
6
|
-
|
|
7
|
-
# Demo
|
|
8
|
-
```luau
|
|
9
|
-
--!strict
|
|
10
|
-
|
|
11
|
-
local rl = require("path/to/rl")
|
|
12
|
-
local s = rl.state
|
|
13
|
-
local e = rl.effect
|
|
14
|
-
|
|
15
|
-
local name = s("john", true)
|
|
16
|
-
local login_year = s(2020)
|
|
17
|
-
|
|
18
|
-
local login_d = e(function(last_login : number)
|
|
19
|
-
print(`{name.value}, it's been {login_year() - (last_login or 0)} years!`)
|
|
20
|
-
end)
|
|
21
|
-
|
|
22
|
-
login_year(2025) -- "wiam, it's been 5 years!"
|
|
23
|
-
login_year(2025) -- doesn't print because value didn't change
|
|
24
|
-
login_year(2025, true) -- "wiam, it's been 0 years!" - this prints since it's forced
|
|
25
|
-
|
|
26
|
-
name("doe") -- doesn't print because we are reading with name.value, and not name()
|
|
27
|
-
|
|
28
|
-
e(function(old_name : string)
|
|
29
|
-
print(`hey {old_name}, your name has been changed to '{name()}'`)
|
|
30
|
-
end)
|
|
31
|
-
|
|
32
|
-
name("jane") -- "hey doe, your name has been changed to jane"
|
|
33
|
-
name("jane") -- "hey jane, your name has been changed to jane" - this one is forced by default
|
|
34
|
-
|
|
35
|
-
login_d()
|
|
36
|
-
login_year(2026) -- doesn't print because the effect was disconnected
|
|
37
|
-
|
|
38
|
-
rl.cleanup() -- disconnect all effects
|
|
39
|
-
```
|
package/base_pesde.toml
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
name = "wiam/rl"
|
|
2
|
-
version = "0.1.11"
|
|
3
|
-
description = "Simple reactive library for Luau."
|
|
4
|
-
authors = ["wiam"]
|
|
5
|
-
repository = "https://codeberg.org/wiam/rl"
|
|
6
|
-
license = "GPL-3.0-or-later"
|
|
7
|
-
includes = ["src/*", "pesde.toml", "COPYING", "README.md", "docs/*"]
|
|
8
|
-
|
|
9
|
-
[indices]
|
|
10
|
-
default = "https://github.com/pesde-pkg/index"
|
package/build.luau
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
|
|
3
|
-
local fs = require("@lune/fs")
|
|
4
|
-
local serde = require("@lune/serde")
|
|
5
|
-
|
|
6
|
-
local TARGETS = {"luau", "lune", "roblox"}
|
|
7
|
-
local INCLUDES = {"src", "COPYING", "PESDE_README.md", "docs"}
|
|
8
|
-
|
|
9
|
-
local base_manifest = fs.readFile("base_pesde.toml")
|
|
10
|
-
|
|
11
|
-
if fs.isDir("build") then
|
|
12
|
-
fs.removeDir("build")
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
fs.writeDir("build")
|
|
16
|
-
|
|
17
|
-
for _, target in TARGETS do
|
|
18
|
-
local out_dir = "build/"..target
|
|
19
|
-
fs.writeDir(out_dir)
|
|
20
|
-
|
|
21
|
-
for _, include in INCLUDES do
|
|
22
|
-
fs.copy(include, out_dir.."/"..if include == "PESDE_README.md" then "README.md" else include)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
fs.writeFile(out_dir.."/pesde.toml", base_manifest.."\n"..serde.encode("toml", {target = {
|
|
26
|
-
environment = target,
|
|
27
|
-
lib = "src/init.luau",
|
|
28
|
-
build_files = target == "roblox" and {"src"}
|
|
29
|
-
}}, true))
|
|
30
|
-
end
|
package/pesde.toml
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
name = "wiam/rl_root"
|
|
2
|
-
version = "1.0.0"
|
|
3
|
-
license = "GPL-3.0-or-later"
|
|
4
|
-
workspace_members = ["build/*"]
|
|
5
|
-
private = true
|
|
6
|
-
|
|
7
|
-
[target]
|
|
8
|
-
environment = "lune"
|
|
9
|
-
|
|
10
|
-
[scripts]
|
|
11
|
-
build = "./build.luau"
|
|
12
|
-
|
|
13
|
-
[indices]
|
|
14
|
-
default = "https://github.com/pesde-pkg/index"
|
package/publish.sh
DELETED
package/rokit.toml
DELETED
package/wally.toml
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "wiam77/rl"
|
|
3
|
-
description = "Simple reactive library for Luau. Docs: https://pesde.dev/packages/wiam/rl/latest/any/docs/concepts"
|
|
4
|
-
version = "0.1.11"
|
|
5
|
-
license = "GPL-3.0-or-later"
|
|
6
|
-
registry = "https://github.com/UpliftGames/wally-index"
|
|
7
|
-
realm = "shared"
|
|
8
|
-
include = ["src", "src/*", "wally.toml", "COPYING", "README.md", "default.project.json"]
|
|
9
|
-
exclude = ["**"]
|