@rbxts/rl 0.1.11

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/src/init.luau ADDED
@@ -0,0 +1,247 @@
1
+ --!strict
2
+
3
+ local rl = {}
4
+
5
+ export type state<T> = (() -> T) & ((new_value : T, force : boolean?) -> T) & {
6
+ value : T
7
+ }
8
+
9
+ export type callback<T> = (old_value : T, state_i : number?) -> any
10
+
11
+ local batching = false
12
+ -- {[state] : {{[callback] = true}, old_value, state_i}}
13
+ local batched : {[state<unknown>] : {any}} = {}
14
+
15
+ local current_callback : callback<unknown>? = nil
16
+ local effects : {[callback<unknown>] : number} = {}
17
+
18
+ local function newindex_err()
19
+ error("Cannot mutate a State by editing it's .value!")
20
+ end
21
+
22
+ local function check<T>(v : T) : T
23
+ local vt = type(v)
24
+ assert(vt == "function", `Expected type 'function', got '{vt}'`)
25
+ return v
26
+ end
27
+
28
+ local warn = warn or function(msg : string) : ()
29
+ print(`\x1b[33m{msg}\x1b[0m`)
30
+ end
31
+
32
+ --[=[
33
+ Disconnects all Effects.
34
+ ]=]
35
+ function rl.cleanup() : ()
36
+ table.clear(effects)
37
+ end
38
+
39
+ --[=[
40
+ Creates a State.
41
+
42
+ @param value T -- Initial value.
43
+ @param force boolean? -- Whether Effects connected to this State will trigger, despite the fact that the value didn't change.
44
+ @return state<T> -- State. Call with no arguments for reading with tracking, call with arguments for writing (2nd argument is for forcing), read .value field for reading without tracking.
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
55
+
56
+ mt.__call = function(... : T) : T
57
+ local argv = {...}
58
+ local old_value = value
59
+ local new_value = argv[2]
60
+
61
+ if #argv == 1 then
62
+ if current_callback and not callbacks[current_callback] then
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
68
+
69
+ for callback, state_i in callbacks do
70
+ if not effects[callback] then
71
+ callbacks[callback] = nil
72
+ continue
73
+ end
74
+
75
+ if batching then
76
+ local state_batch = batched[self]
77
+ if state_batch then
78
+ local x = state_batch[1]
79
+ if not x[callback] then
80
+ x[callback] = true
81
+ end
82
+
83
+ state_batch[2] = old_value
84
+ else
85
+ batched[self] = {{[callback] = true}, old_value, state_i}
86
+ end
87
+ else
88
+ callback(old_value, state_i)
89
+ end
90
+ end
91
+ end
92
+
93
+ return value
94
+ end
95
+
96
+ return self
97
+ end
98
+
99
+ --[=[
100
+ Batches any State write operation up until the function is done running.
101
+
102
+ @param f () -> any -- The function.
103
+ ]=]
104
+ function rl.batch(f : () -> any) : ()
105
+ batching = true
106
+ f()
107
+
108
+ for _, result in batched do
109
+ for callback in result[1] do
110
+ callback(result[2], result[3])
111
+ end
112
+ end
113
+
114
+ batching = false
115
+ table.clear(batched :: {any})
116
+ end
117
+
118
+ --[=[
119
+ Creates an Effect, a binding of the given function to the event of States read (by calling) within it being updated.
120
+
121
+ @param callback callback<T> -- The function to be bound.
122
+ @return disconnect () -> () -- Function which disconnects the Effect from the States.
123
+ ]=]
124
+ function rl.effect<T>(callback : callback<T>) : () -> ()
125
+ if current_callback then
126
+ warn("Nested effect")
127
+ end
128
+
129
+ effects[callback] = 0
130
+ current_callback = callback
131
+ callback()
132
+ current_callback = nil
133
+
134
+ return function()
135
+ if not effects[callback] then
136
+ warn("This effect is already disconnected!")
137
+ return
138
+ end
139
+
140
+ effects[callback] = nil
141
+ end
142
+ end
143
+
144
+ --[=[
145
+ Returns a function which calls the given functions.
146
+ Any non-function or function which precedes a "true" value by 2 indices is going to be skipped (the second happens because of derives).
147
+ This is intended to be used for disconnecting Effects in bulk.
148
+
149
+ @param ... () -> () -- The functions.
150
+ @return f () -> () -- The function caller.
151
+ ]=]
152
+ function rl.scope<T>(... : T) : () -> ()
153
+ local t = {...}
154
+ return function()
155
+ for i = 1, #t do
156
+ local v = t[i]
157
+ if type(v) ~= "function" or t[i + 2] == true then continue end
158
+ v()
159
+ end
160
+ end
161
+ end
162
+
163
+ --[=[
164
+ Wraps an Effect around the given callback which sets a newly created State's value
165
+ to be the return value of the callback.
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.
171
+
172
+ @param callback () -> U -- the callback
173
+ @return derived_state state<U> -- the State that was created
174
+ @return disconnect () -> -- the created Effect's disconnect function
175
+ @return flag true -- a flag used internally by rl.scope(); can be ignored.
176
+ ]=]
177
+ function rl.derive<T, U>(callback : () -> U) : (state<U>, () -> (), true)
178
+ local cache = rl.state()
179
+ return cache, rl.effect(function()
180
+ cache(callback())
181
+ end), true
182
+ end
183
+
184
+ --[=[
185
+ Branching function to be used within Effects.
186
+ Pass a condition to be checked, and after it a function to be run if it's successful.
187
+ The last value, if a function, and if not after a condition, will be threated as the "else" branch.
188
+ ]=]
189
+ function rl.branch(...) : ()
190
+ local args = table.pack(...)
191
+ local n = args.n
192
+ assert(n >= 2, "Expected 2 or more arguments!")
193
+
194
+ local i = 1
195
+ while i + 1 <= n do
196
+ if args[i] or current_callback then
197
+ check(args[i + 1])()
198
+ end
199
+
200
+ i += 2
201
+ end
202
+
203
+ if i <= n then
204
+ check(args[i])()
205
+ end
206
+ end
207
+
208
+ --[=[
209
+ Creates an effect for every function or State within the given table, which updates the key to either the return value of the function, or the value of the State.
210
+ Each Effect's disconnect function gets put in the table as t["disconnect_"..key], when key is a string, or as t["disconnect"_..i], where i is the numerical index at which the key was iterated over.
211
+
212
+ @param t {[K] : V} -- The table.
213
+ @return t {[K] : V} -- The same table.
214
+ @return disconnect_all () -> () -- Function which disconnects and eliminates each disconnect function of the created Effects.
215
+ ]=]
216
+ function rl.mirror<K, V>(t : {[K] : V}) : ({[K] : V}, () -> ())
217
+ local disconnects : {[number | string] : () -> ()} = {}
218
+
219
+ for k, v in t do
220
+ local v_type = type(v)
221
+ if v_type == "function" or (v_type == "table" and (v :: {[any] : any}).value) then
222
+ local disconnect = rl.effect(function()
223
+ t[k] = v()
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
233
+ end
234
+
235
+ for name, disconnect in disconnects do
236
+ t["disconnect_"..name] = disconnect
237
+ end
238
+
239
+ return t, function()
240
+ for name, disconnect in disconnects do
241
+ disconnect()
242
+ t["disconnect_"..name] = nil
243
+ end
244
+ end
245
+ end
246
+
247
+ return rl
package/wally.toml ADDED
@@ -0,0 +1,9 @@
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 = ["**"]