@rbxts/centurion-ui 1.0.7 → 2.0.0-rc.1

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.
Files changed (141) hide show
  1. package/README.md +60 -15
  2. package/centurion.luau +10 -0
  3. package/package.json +13 -23
  4. package/src/analyse/cursor.luau +25 -0
  5. package/src/analyse/highlight.luau +91 -0
  6. package/src/analyse/init.luau +318 -0
  7. package/src/analyse/leventine.luau +298 -0
  8. package/src/analyse/match.luau +39 -0
  9. package/src/analyse/suggest.luau +41 -0
  10. package/src/app/header.luau +128 -0
  11. package/src/app/history.luau +258 -0
  12. package/src/app/init.luau +699 -0
  13. package/src/app/suggestions.luau +419 -0
  14. package/src/button.luau +13 -0
  15. package/src/components/background.luau +37 -0
  16. package/src/components/container.luau +33 -0
  17. package/src/components/corner.luau +11 -0
  18. package/src/components/divider.luau +28 -0
  19. package/src/components/flex.luau +108 -0
  20. package/src/components/gap.luau +25 -0
  21. package/src/components/image.luau +33 -0
  22. package/src/components/padding.luau +59 -0
  23. package/src/components/stroke.luau +20 -0
  24. package/src/components/text.luau +57 -0
  25. package/src/components/textbox.luau +186 -0
  26. package/src/fade.luau +23 -0
  27. package/src/init.luau +142 -0
  28. package/src/input.luau +16 -0
  29. package/src/px.luau +68 -0
  30. package/src/rich.luau +14 -0
  31. package/src/state.luau +33 -0
  32. package/src/theme.luau +70 -0
  33. package/src/ts/index.d.ts +60 -0
  34. package/src/ts/init.luau +5 -0
  35. package/src/types.luau +85 -0
  36. package/vide/init.luau +8 -0
  37. package/vide/src/action.luau +25 -0
  38. package/vide/src/apply.luau +162 -0
  39. package/vide/src/batch.luau +23 -0
  40. package/vide/src/branch.luau +40 -0
  41. package/vide/src/changed.luau +18 -0
  42. package/vide/src/cleanup.luau +43 -0
  43. package/vide/src/context.luau +74 -0
  44. package/vide/src/create.luau +107 -0
  45. package/vide/src/defaults.luau +113 -0
  46. package/vide/src/derive.luau +18 -0
  47. package/vide/src/effect.luau +12 -0
  48. package/vide/src/flags.luau +12 -0
  49. package/vide/src/graph.luau +321 -0
  50. package/vide/src/implicit_effect.luau +125 -0
  51. package/vide/src/indexes.luau +121 -0
  52. package/vide/src/init.luau +10 -0
  53. package/vide/src/lib.luau +120 -0
  54. package/vide/src/mount.luau +11 -0
  55. package/vide/src/read.luau +5 -0
  56. package/vide/src/root.luau +35 -0
  57. package/vide/src/show.luau +36 -0
  58. package/vide/src/source.luau +31 -0
  59. package/vide/src/spring.luau +328 -0
  60. package/vide/src/switch.luau +114 -0
  61. package/vide/src/timeout.luau +27 -0
  62. package/vide/src/untrack.luau +25 -0
  63. package/vide/src/values.luau +131 -0
  64. package/out/app/centurion-app.d.ts +0 -3
  65. package/out/app/centurion-app.luau +0 -86
  66. package/out/app/index.d.ts +0 -32
  67. package/out/app/init.luau +0 -105
  68. package/out/components/history/history-line.d.ts +0 -9
  69. package/out/components/history/history-line.luau +0 -88
  70. package/out/components/history/history-list.d.ts +0 -11
  71. package/out/components/history/history-list.luau +0 -67
  72. package/out/components/history/index.d.ts +0 -1
  73. package/out/components/history/init.luau +0 -7
  74. package/out/components/index.d.ts +0 -2
  75. package/out/components/init.luau +0 -10
  76. package/out/components/suggestions/badge.d.ts +0 -16
  77. package/out/components/suggestions/badge.luau +0 -40
  78. package/out/components/suggestions/index.d.ts +0 -1
  79. package/out/components/suggestions/init.luau +0 -7
  80. package/out/components/suggestions/main-suggestion.d.ts +0 -9
  81. package/out/components/suggestions/main-suggestion.luau +0 -232
  82. package/out/components/suggestions/suggestion-list.d.ts +0 -8
  83. package/out/components/suggestions/suggestion-list.luau +0 -96
  84. package/out/components/suggestions/suggestions.d.ts +0 -2
  85. package/out/components/suggestions/suggestions.luau +0 -64
  86. package/out/components/suggestions/types.d.ts +0 -6
  87. package/out/components/suggestions/types.luau +0 -2
  88. package/out/components/suggestions/util.d.ts +0 -1
  89. package/out/components/suggestions/util.luau +0 -22
  90. package/out/components/terminal/command.d.ts +0 -6
  91. package/out/components/terminal/command.luau +0 -74
  92. package/out/components/terminal/index.d.ts +0 -1
  93. package/out/components/terminal/init.luau +0 -7
  94. package/out/components/terminal/suggestion.d.ts +0 -26
  95. package/out/components/terminal/suggestion.luau +0 -205
  96. package/out/components/terminal/terminal-text-field/index.d.ts +0 -1
  97. package/out/components/terminal/terminal-text-field/init.luau +0 -7
  98. package/out/components/terminal/terminal-text-field/suggestion.d.ts +0 -5
  99. package/out/components/terminal/terminal-text-field/suggestion.luau +0 -124
  100. package/out/components/terminal/terminal-text-field/terminal-text-field.d.ts +0 -13
  101. package/out/components/terminal/terminal-text-field/terminal-text-field.luau +0 -273
  102. package/out/components/terminal/terminal.d.ts +0 -2
  103. package/out/components/terminal/terminal.luau +0 -304
  104. package/out/components/ui/frame.d.ts +0 -17
  105. package/out/components/ui/frame.luau +0 -32
  106. package/out/components/ui/group.d.ts +0 -15
  107. package/out/components/ui/group.luau +0 -29
  108. package/out/components/ui/layer.d.ts +0 -8
  109. package/out/components/ui/layer.luau +0 -26
  110. package/out/components/ui/outline.d.ts +0 -15
  111. package/out/components/ui/outline.luau +0 -116
  112. package/out/components/ui/padding.d.ts +0 -10
  113. package/out/components/ui/padding.luau +0 -19
  114. package/out/components/ui/scrolling-frame.d.ts +0 -14
  115. package/out/components/ui/scrolling-frame.luau +0 -44
  116. package/out/components/ui/text-field.d.ts +0 -12
  117. package/out/components/ui/text-field.luau +0 -60
  118. package/out/components/ui/text.d.ts +0 -17
  119. package/out/components/ui/text.luau +0 -61
  120. package/out/constants/options.d.ts +0 -2
  121. package/out/constants/options.luau +0 -27
  122. package/out/constants/text.d.ts +0 -3
  123. package/out/constants/text.luau +0 -9
  124. package/out/constants/util.d.ts +0 -1
  125. package/out/constants/util.luau +0 -7
  126. package/out/hooks/use-client.d.ts +0 -2
  127. package/out/hooks/use-client.luau +0 -17
  128. package/out/hooks/use-event.d.ts +0 -14
  129. package/out/hooks/use-event.luau +0 -58
  130. package/out/hooks/use-px.d.ts +0 -19
  131. package/out/hooks/use-px.luau +0 -78
  132. package/out/index.d.ts +0 -2
  133. package/out/init.luau +0 -10
  134. package/out/palette.d.ts +0 -47
  135. package/out/palette.luau +0 -42
  136. package/out/store.d.ts +0 -12
  137. package/out/store.luau +0 -26
  138. package/out/types.d.ts +0 -44
  139. package/out/types.luau +0 -2
  140. package/out/utils/string.d.ts +0 -5
  141. package/out/utils/string.luau +0 -25
@@ -0,0 +1,113 @@
1
+ local Enum = game and Enum or require "../test/mock".Enum :: never
2
+ local Color3 = game and Color3 or require "../test/mock".Color3 :: never
3
+
4
+ return {
5
+ Part = {
6
+ Material = Enum.Material.SmoothPlastic,
7
+ Size = vector.create(1, 1, 1),
8
+ Anchored = true
9
+ },
10
+
11
+ BillboardGui = {
12
+ ResetOnSpawn = false,
13
+ ZIndexBehavior = Enum.ZIndexBehavior.Sibling
14
+ },
15
+
16
+ CanvasGroup = nil,
17
+
18
+ Frame = {
19
+ BackgroundColor3 = Color3.new(1, 1, 1),
20
+ BorderColor3 = Color3.new(0, 0, 0),
21
+ BorderSizePixel = 0
22
+ },
23
+
24
+ ImageButton = {
25
+ BackgroundColor3 = Color3.new(1, 1, 1),
26
+ BorderColor3 = Color3.new(0, 0, 0),
27
+ BorderSizePixel = 0,
28
+ AutoButtonColor = false
29
+ },
30
+
31
+ ImageLabel = {
32
+ BackgroundColor3 = Color3.new(1, 1, 1),
33
+ BorderColor3 = Color3.new(0, 0, 0),
34
+ BorderSizePixel = 0,
35
+ },
36
+
37
+ ScreenGui = {
38
+ ResetOnSpawn = false,
39
+ ZIndexBehavior = Enum.ZIndexBehavior.Sibling
40
+ },
41
+
42
+ ScrollingFrame = {
43
+ BackgroundColor3 = Color3.new(1, 1, 1),
44
+ BorderColor3 = Color3.new(0, 0, 0),
45
+ BorderSizePixel = 0,
46
+ ScrollBarImageColor3 = Color3.new(0, 0, 0)
47
+ },
48
+
49
+ SurfaceGui = {
50
+ ResetOnSpawn = false,
51
+ ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
52
+
53
+ PixelsPerStud = 50,
54
+ SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
55
+ },
56
+
57
+ TextBox = {
58
+ BackgroundColor3 = Color3.new(1, 1, 1),
59
+ BorderColor3 = Color3.new(0, 0, 0),
60
+ BorderSizePixel = 0,
61
+ ClearTextOnFocus = false,
62
+ Font = Enum.Font.SourceSans,
63
+ Text = "",
64
+ TextColor3 = Color3.new(0, 0, 0)
65
+ },
66
+
67
+ TextButton = {
68
+ BackgroundColor3 = Color3.new(1, 1, 1),
69
+ BorderColor3 = Color3.new(0, 0, 0),
70
+ BorderSizePixel = 0,
71
+ AutoButtonColor = false,
72
+ Font = Enum.Font.SourceSans,
73
+ Text = "",
74
+ TextColor3 = Color3.new(0, 0, 0)
75
+ },
76
+
77
+ TextLabel = {
78
+ BackgroundColor3 = Color3.new(1, 1, 1),
79
+ BorderColor3 = Color3.new(0, 0, 0),
80
+ BorderSizePixel = 0,
81
+ Font = Enum.Font.SourceSans,
82
+ Text = "",
83
+ TextColor3 = Color3.new(0, 0, 0)
84
+ },
85
+
86
+ UIListLayout = {
87
+ SortOrder = Enum.SortOrder.LayoutOrder
88
+ },
89
+
90
+ UIGridLayout = {
91
+ SortOrder = Enum.SortOrder.LayoutOrder
92
+ },
93
+
94
+ UITableLayout = {
95
+ SortOrder = Enum.SortOrder.LayoutOrder
96
+ },
97
+
98
+ UIPageLayout = {
99
+ SortOrder = Enum.SortOrder.LayoutOrder
100
+ },
101
+
102
+ VideoFrame = {
103
+ BackgroundColor3 = Color3.new(1, 1, 1),
104
+ BorderColor3 = Color3.new(0, 0, 0),
105
+ BorderSizePixel = 0
106
+ },
107
+
108
+ ViewportFrame = {
109
+ BackgroundColor3 = Color3.new(1, 1, 1),
110
+ BorderColor3 = Color3.new(0, 0, 0),
111
+ BorderSizePixel = 0
112
+ }
113
+ } :: { [string]: { [string]: unknown} }
@@ -0,0 +1,18 @@
1
+ local graph = require "./graph"
2
+ local create_node = graph.create_node
3
+ local push_scope_as_child_of = graph.push_scope_as_child_of
4
+ local assert_stable_scope = graph.assert_stable_scope
5
+ local evaluate_node = graph.evaluate_node
6
+
7
+ local function derive<T>(source: () -> T): () -> T
8
+ local node = create_node(assert_stable_scope(), source, false :: any)
9
+
10
+ evaluate_node(node)
11
+
12
+ return function()
13
+ push_scope_as_child_of(node)
14
+ return node.cache
15
+ end
16
+ end
17
+
18
+ return derive
@@ -0,0 +1,12 @@
1
+ local graph = require "./graph"
2
+ local create_node = graph.create_node
3
+ local assert_stable_scope = graph.assert_stable_scope
4
+ local evaluate_node = graph.evaluate_node
5
+
6
+ local function effect<T>(callback: (T) -> T, initial_value: T)
7
+ local node = create_node(assert_stable_scope(), callback, initial_value)
8
+
9
+ evaluate_node(node)
10
+ end
11
+
12
+ return effect :: (<T>(callback: (T) -> T, initial_value: T) -> ()) & ((callback: () -> ()) -> ())
@@ -0,0 +1,12 @@
1
+ local function inline_test(): string
2
+ return debug.info(1, "n")
3
+ end
4
+
5
+ local is_O2 = inline_test() ~= "inline_test"
6
+
7
+ return {
8
+ strict = not is_O2,
9
+ defaults = true,
10
+ defer_nested_properties = true,
11
+ batch = false,
12
+ }
@@ -0,0 +1,321 @@
1
+ local flags = require "./flags"
2
+
3
+ export type SourceNode<T> = {
4
+ cache: T,
5
+ [number]: Node<T>
6
+ }
7
+
8
+ export type Node<T> = {
9
+ cache: T,
10
+ effect: ((T) -> T) | false,
11
+ cleanups: { () -> () } | false,
12
+
13
+ context: { [number]: unknown } | false,
14
+
15
+ owned: { Node<T> } | false,
16
+ owner: Node<T> | false,
17
+
18
+ parents: { SourceNode<T> },
19
+ [number]: Node<T> -- children
20
+ }
21
+
22
+ local scopes = { n = 0 } :: { [number]: Node<any>, n: number } -- scopes stack
23
+
24
+ local function efn(err: string)
25
+ local trace = debug.traceback(err, 2)
26
+
27
+ if string.find(err, "^effect error stacktrace") then -- if effect error is nested
28
+ trace = string.gsub(" " .. trace, "\n", function() -- indent entire error
29
+ return "\n "
30
+ end)
31
+ end
32
+
33
+ trace ..= "\nsource update stacktrace:"
34
+ return trace
35
+ end
36
+
37
+ local function ycall<T, U>(fn: (T) -> U, arg: T): (boolean, string|U)
38
+
39
+ local thread = coroutine.create(xpcall)
40
+ --local function efn(err: string) return debug.traceback(err, 3) end
41
+ local resume_ok, run_ok, result = coroutine.resume(thread, fn, efn, arg)
42
+
43
+ assert(resume_ok)
44
+
45
+ if coroutine.status(thread) ~= "dead" then
46
+ return false, debug.traceback(thread, "attempt to yield in reactive scope")
47
+ end
48
+
49
+ return run_ok, result
50
+ end
51
+
52
+ local function get_scope(): Node<unknown>?
53
+ return scopes[scopes.n]
54
+ end
55
+
56
+ local function assert_stable_scope(): Node<unknown>
57
+ local scope = get_scope()
58
+
59
+ if not scope then
60
+ local caller_name = debug.info(2, "n")
61
+ return error(`cannot use {caller_name}() outside a stable or reactive scope`, 0)
62
+ elseif scope.effect then
63
+ error("cannot create a new reactive scope inside another reactive scope", 0)
64
+ end
65
+
66
+ return scope
67
+ end
68
+
69
+ local function push_child<T>(parent: SourceNode<any>, child: Node<any>)
70
+ table.insert(parent, child)
71
+ table.insert(child.parents, parent)
72
+ end
73
+
74
+ local function push_scope<T>(node: Node<T>)
75
+ local n = scopes.n + 1
76
+ scopes.n = n
77
+ scopes[n] = node
78
+ end
79
+
80
+ local function pop_scope()
81
+ local n = scopes.n
82
+ scopes.n = n - 1
83
+ scopes[n] = nil
84
+ end
85
+
86
+ local function push_cleanup<T>(node: Node<T>, cleanup: () -> ())
87
+ if node.cleanups then
88
+ table.insert(node.cleanups, cleanup)
89
+ else
90
+ node.cleanups = { cleanup }
91
+ end
92
+ end
93
+
94
+ local function flush_cleanups<T>(node: Node<T>)
95
+ if node.cleanups then
96
+ for _, fn in node.cleanups do
97
+ local ok, err: string? = xpcall(fn, debug.traceback)
98
+ if not ok then error(`cleanup error: {err}`, 0) end
99
+ end
100
+
101
+ table.clear(node.cleanups)
102
+ end
103
+ end
104
+
105
+ local function find_and_swap_pop<T>(t: { T }, v: T)
106
+ local i = table.find(t, v) :: number
107
+ local n = #t
108
+ t[i] = t[n]
109
+ t[n] = nil
110
+ end
111
+
112
+ local function unparent<T>(node: Node<T>)
113
+ local parents = node.parents
114
+
115
+ for i, parent in parents do
116
+ find_and_swap_pop(parent, node)
117
+ parents[i] = nil
118
+ end
119
+ end
120
+
121
+ local function destroy<T>(node: Node<T>)
122
+ if flags.strict and table.find(scopes, node) then
123
+ error("attempt to destroy an active scope", 0)
124
+ end
125
+
126
+ flush_cleanups(node)
127
+ unparent(node)
128
+
129
+ if node.owner then
130
+ find_and_swap_pop(node.owner.owned :: { Node<T> }, node)
131
+ node.owner = false
132
+ end
133
+
134
+ if node.owned then
135
+ local owned = node.owned
136
+ while owned[1] do destroy(owned[1]) end
137
+ end
138
+ end
139
+
140
+ local function destroy_owned<T>(node: Node<T>)
141
+ if node.owned then
142
+ local owned = node.owned
143
+ while owned[1] do destroy(owned[1]) end
144
+ end
145
+ end
146
+
147
+ local update_queue = { n = 0 } :: { n: number, [number]: Node<any> }
148
+
149
+ local function evaluate_node<T>(node: Node<T>)
150
+ if flags.strict then
151
+ if table.find(scopes, node) then
152
+ error("a scope, that should rerun due to the update of a source, is already active", 0)
153
+ end
154
+
155
+ local initial_value = node.cache
156
+
157
+ for i = 1, 2 do
158
+ local cur_value = node.cache
159
+
160
+ flush_cleanups(node)
161
+ destroy_owned(node)
162
+
163
+ push_scope(node)
164
+ local ok, new_value = ycall(node.effect :: (T) -> T, cur_value)
165
+ pop_scope()
166
+
167
+ if not ok then
168
+ table.clear(update_queue)
169
+ update_queue.n = 0
170
+ error(`effect error stacktrace\n{new_value :: string}`, 0)
171
+ end
172
+
173
+ node.cache = new_value :: T
174
+ end
175
+
176
+ return initial_value ~= node.cache
177
+ else
178
+ local cur_value = node.cache
179
+
180
+ flush_cleanups(node)
181
+ destroy_owned(node)
182
+
183
+ push_scope(node)
184
+ local ok, new_value = pcall(node.effect :: (T) -> T, node.cache)
185
+ pop_scope()
186
+
187
+ if not ok then
188
+ table.clear(update_queue)
189
+ update_queue.n = 0
190
+ error(`effect error:\n{new_value}\n`, 0)
191
+ end
192
+
193
+ node.cache = new_value
194
+ return cur_value ~= new_value
195
+ end
196
+ end
197
+
198
+ local function queue_children_for_update<T>(node: SourceNode<T>)
199
+ local i = update_queue.n
200
+ while node[1] do
201
+ i += 1
202
+ update_queue[i] = node[1]
203
+ unparent(node[1])
204
+ end
205
+ update_queue.n = i
206
+ end
207
+
208
+ local function get_update_queue_length()
209
+ return update_queue.n
210
+ end
211
+
212
+ local function flush_update_queue(from: number)
213
+ local i = from + 1
214
+ while i <= update_queue.n do
215
+ local node = update_queue[i]
216
+ --assert(node.effect)
217
+
218
+ if node.owner and evaluate_node(node) then
219
+ queue_children_for_update(node)
220
+ end
221
+
222
+ update_queue[i] = false :: any
223
+ i += 1
224
+ end
225
+
226
+ update_queue.n = from
227
+ end
228
+
229
+ local function update_descendants<T>(root: SourceNode<T>)
230
+ local n0 = update_queue.n
231
+ queue_children_for_update(root)
232
+
233
+ if flags.batch then return end
234
+
235
+ local i = n0 + 1
236
+ while i <= update_queue.n do
237
+ local node = update_queue[i]
238
+ --assert(node.effect)
239
+
240
+ -- check if node is still owned in case destroyed after queued
241
+ if node.owner and evaluate_node(node) then
242
+ queue_children_for_update(node)
243
+ end
244
+
245
+ update_queue[i] = false :: any -- false instead of nil to avoid sparse
246
+ i += 1
247
+ end
248
+
249
+ update_queue.n = n0
250
+ end
251
+
252
+ local function push_scope_as_child_of<T>(node: SourceNode<T>)
253
+ local scope = get_scope()
254
+ if scope and scope.effect then -- do not track nodes with no effect
255
+ push_child(node, scope)
256
+ end
257
+ end
258
+
259
+ local function create_node<T>(owner: false | Node<any>, effect: false | (T) -> T, value: T): Node<T>
260
+ local node: Node<T> = {
261
+ cache = value,
262
+ effect = effect,
263
+ cleanups = false,
264
+
265
+ context = false,
266
+
267
+ owner = owner,
268
+ owned = false,
269
+
270
+ parents = {},
271
+ }
272
+
273
+ if owner then
274
+ if owner.owned then
275
+ table.insert(owner.owned, node)
276
+ else
277
+ owner.owned = { node }
278
+ end
279
+ end
280
+
281
+ return node
282
+ end
283
+
284
+ local function create_source_node<T>(value: T): SourceNode<T>
285
+ return { cache = value }
286
+ end
287
+
288
+ local function get_children<T>(node: Node<T>): { Node<unknown> }
289
+ return { unpack(node) } :: { Node<any> }
290
+ end
291
+
292
+ local function set_context<T>(node: Node<T>, key: number, value: unknown)
293
+ if node.context then
294
+ node.context[key] = value
295
+ else
296
+ node.context = { [key] = value }
297
+ end
298
+ end
299
+
300
+ return table.freeze {
301
+ push_scope = push_scope,
302
+ pop_scope = pop_scope,
303
+ evaluate_node = evaluate_node,
304
+ get_scope = get_scope,
305
+ assert_stable_scope = assert_stable_scope,
306
+ push_cleanup = push_cleanup,
307
+ destroy = destroy,
308
+ flush_cleanups = flush_cleanups,
309
+ push_scope_as_child_of = push_scope_as_child_of,
310
+ update_descendants = update_descendants,
311
+ push_child = push_child,
312
+ create_node = create_node,
313
+ create_source_node = create_source_node,
314
+ get_children = get_children,
315
+ flush_update_queue = flush_update_queue,
316
+ get_update_queue_length = get_update_queue_length,
317
+ set_context = set_context,
318
+ scopes = scopes,
319
+
320
+ q = update_queue
321
+ }
@@ -0,0 +1,125 @@
1
+ local graph = require "./graph"
2
+ type Node<T> = graph.Node<T>
3
+ local create_node = graph.create_node
4
+ local assert_stable_scope = graph.assert_stable_scope
5
+ local get_scope = graph.get_scope
6
+ local evaluate_node = graph.evaluate_node
7
+ local push_cleanup = graph.push_cleanup
8
+
9
+ local function update_property_effect(p: {
10
+ instance: Instance,
11
+ property: string,
12
+ source: () -> unknown
13
+ })
14
+ (p.instance :: any)[p.property] = p.source()
15
+ return p
16
+ end
17
+
18
+ local function update_parent_effect(p: {
19
+ instance: Instance,
20
+ source: () -> Instance
21
+ })
22
+ p.instance.Parent = p.source()
23
+ return p
24
+ end
25
+
26
+ -- todo: investigate if "count" method used in indexes() and values() can improve performance here
27
+ local function update_children_effect(p: {
28
+ instance: Instance,
29
+ cur_children_set: { [Instance]: true },
30
+ new_children_set: { [Instance]: true },
31
+ source: () -> Instance | { Instance }
32
+ })
33
+ local cur_children_set: { [Instance]: true } = p.cur_children_set -- cache of all children parented before update
34
+ local new_children_set: { [Instance]: true } = p.new_children_set -- cache of all children parented after update
35
+
36
+ local new_children = p.source() -- all (and only) children that should be parented after this update
37
+
38
+ local function process_child(child: Instance | { Instance })
39
+ if type(child) == "userdata" then
40
+ if new_children_set[child] then return end -- stops redundant reparenting
41
+
42
+ new_children_set[child] = true -- record child set from this update
43
+
44
+ if not cur_children_set[child] then
45
+ child.Parent = p.instance -- if child wasn't already parented then parent it
46
+ else
47
+ cur_children_set[child] = nil -- remove child from cache if it was already in cache
48
+ end
49
+ elseif type(child) == "table" then
50
+ for _, child in child do
51
+ process_child(child)
52
+ end
53
+ elseif type(child) == "function" then
54
+ local node = create_node(assert(get_scope()), update_children_effect, {
55
+ instance = p.instance,
56
+ cur_children_set = {},
57
+ new_children_set = {},
58
+ source = child
59
+ })
60
+
61
+ evaluate_node(node)
62
+
63
+ push_cleanup(assert(get_scope()), function()
64
+ for child in node.cache.cur_children_set do
65
+ child.Parent = nil
66
+ end
67
+ end)
68
+ end
69
+ end
70
+
71
+ process_child(new_children)
72
+
73
+ for child in cur_children_set do
74
+ child.Parent = nil -- unparent all children that weren't in the new children set
75
+ end
76
+
77
+ table.clear(cur_children_set) -- clear cache, preserve capacity
78
+ p.cur_children_set, p.new_children_set = new_children_set, cur_children_set
79
+
80
+ return p
81
+ end
82
+
83
+ return {
84
+ property = function(instance, property, source)
85
+ local node = create_node(assert_stable_scope(), update_property_effect, {
86
+ instance = instance,
87
+ property = property,
88
+ source = source
89
+ })
90
+
91
+ evaluate_node(node)
92
+
93
+ return node
94
+ end,
95
+
96
+ parent = function(instance, parent)
97
+ local node = create_node(assert_stable_scope(), update_parent_effect, {
98
+ instance = instance,
99
+ source = parent
100
+ })
101
+
102
+ evaluate_node(node)
103
+
104
+ return node
105
+ end,
106
+
107
+ children = function(instance, children)
108
+ local node = create_node(assert_stable_scope(), update_children_effect, {
109
+ instance = instance,
110
+ cur_children_set = {},
111
+ new_children_set = {},
112
+ source = children
113
+ })
114
+
115
+ evaluate_node(node)
116
+
117
+ push_cleanup(assert_stable_scope(), function()
118
+ for child in node.cache.cur_children_set do
119
+ child.Parent = nil
120
+ end
121
+ end)
122
+
123
+ return node
124
+ end
125
+ }