@rbxts/centurion-ui 1.0.8 → 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 -278
  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,121 @@
1
+ local flags = require "./flags"
2
+ local branch = require "./branch"
3
+ local source = require "./source"
4
+ local effect = require "./effect"
5
+ local timeout = require "./timeout" ()
6
+
7
+ type Array<T> = { T }
8
+ type Map<K, V> = { [K]: V }
9
+ type Source<T> = () -> T
10
+
11
+ local function indexes<K, V, Obj>(
12
+ input: Source<Map<K, V>>,
13
+ component: (Source<V>, K, Source<boolean>) -> (Obj, ...number)
14
+ ): Source<Array<Obj>>
15
+ local update_count = 0
16
+ local scopes = {} :: Map<K, {
17
+ destroy: () -> (),
18
+ object: Obj,
19
+ value: V?,
20
+ value_source: (V?) -> V,
21
+ count: number,
22
+ delay: number,
23
+ present: (boolean?) -> boolean,
24
+ timeout: { cancel: boolean }?,
25
+ }>
26
+
27
+ local output = source({} :: Array<Obj>)
28
+ local function update_output()
29
+ local objects = table.create(4)
30
+
31
+ for _, scope in scopes do
32
+ table.insert(objects, scope.object)
33
+ end
34
+
35
+ output(objects)
36
+ end
37
+
38
+ effect(function()
39
+ local data = input()
40
+
41
+ local count = update_count
42
+ update_count += 1
43
+
44
+ local children_need_update = false -- set to true if a scope is created or destroyed
45
+
46
+ -- create or update scopes
47
+ for i, v in data do
48
+ local scope = scopes[i]
49
+
50
+ if scope == nil then -- create new scope and create component
51
+ local value_source = source(v)
52
+ local present = source(false)
53
+
54
+ local delay = nil :: number?
55
+ local destroy, object = branch(function()
56
+ local object, t = component(value_source, i, present)
57
+ delay = t
58
+ return object
59
+ end)
60
+
61
+ present(true)
62
+
63
+ children_need_update = true
64
+
65
+ scopes[i] = {
66
+ destroy = destroy,
67
+ object = object,
68
+ value = v,
69
+ value_source = value_source,
70
+ count = count,
71
+ delay = delay or 0,
72
+ present = present,
73
+ timeout = nil,
74
+ }
75
+ else -- update scope
76
+ scope.count = count
77
+
78
+ if scope.value ~= v then
79
+ if scope.timeout then -- index is in input table again; cancel destruction
80
+ scope.timeout.cancel = true
81
+ scope.timeout = nil
82
+ scope.present(true)
83
+ end
84
+
85
+ scope.value = v
86
+ scope.value_source(v)
87
+ end
88
+ end
89
+ end
90
+
91
+ -- destroy scopes
92
+ for i, scope in scopes do
93
+ if scope.count < count then -- if count is not latest then index is no longer in the input table
94
+ scope.present(false)
95
+
96
+ if scope.delay == 0 then
97
+ scope.destroy()
98
+ scopes[i] = nil
99
+ children_need_update = true
100
+ else
101
+ scope.value = nil -- set to nil for the `scope.value ~= v` check
102
+ if scope.timeout == nil then
103
+ scope.timeout = timeout(scope.delay, function() -- todo: possible redundant updates
104
+ scope.destroy()
105
+ scopes[i] = nil
106
+ update_output()
107
+ end)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ if children_need_update then
114
+ update_output()
115
+ end
116
+ end)
117
+
118
+ return output
119
+ end
120
+
121
+ return indexes
@@ -0,0 +1,10 @@
1
+ assert(game, "when using vide outside of Roblox, require lib.luau instead")
2
+
3
+ local vide = require(script.lib)
4
+
5
+ export type source<T> = vide.source<T>
6
+ export type Source<T> = vide.Source<T>
7
+ export type context<T> = vide.context<T>
8
+ export type Context<T> = vide.Context<T>
9
+
10
+ return vide
@@ -0,0 +1,120 @@
1
+ local version = { major = 0, minor = 4, patch = 1 }
2
+
3
+ local root = require "./root"
4
+ local branch = require "./branch"
5
+ local mount = require "./mount"
6
+ local create = require "./create"
7
+ local apply = require "./apply"
8
+ local source = require "./source"
9
+ local effect = require "./effect"
10
+ local derive = require "./derive"
11
+ local cleanup = require "./cleanup"
12
+ local untrack = require "./untrack"
13
+ local read = require "./read"
14
+ local batch = require "./batch"
15
+ local context = require "./context"
16
+ local switch = require "./switch"
17
+ local show = require "./show"
18
+ local indexes = require "./indexes"
19
+ local values = require "./values"
20
+ local spring, update_springs = require "./spring"()
21
+ local action = require "./action"()
22
+ local changed = require "./changed"
23
+ local timeout, update_timeouts = require "./timeout"()
24
+ local flags = require "./flags"
25
+
26
+ export type Source<T> = source.Source<T>
27
+ export type source<T> = Source<T>
28
+ export type Context<T> = context.Context<T>
29
+ export type context<T> = Context<T>
30
+
31
+ local function step(dt: number)
32
+ if game then debug.profilebegin("VIDE STEP") end
33
+
34
+ if game then debug.profilebegin("VIDE SPRING") end
35
+ update_springs(dt)
36
+ if game then debug.profileend() end
37
+
38
+ if game then debug.profilebegin("VIDE SCHEDULER") end
39
+ update_timeouts(dt)
40
+ if game then debug.profileend() end
41
+
42
+ if game then debug.profileend() end
43
+ end
44
+
45
+ local stepped = game and game:GetService("RunService").Heartbeat:Connect(function(dt: number)
46
+ task.defer(step, dt)
47
+ end)
48
+
49
+ local vide = {
50
+ version = version,
51
+
52
+ -- core
53
+ root = root,
54
+ --branch = branch,
55
+ mount = mount,
56
+ create = create,
57
+ source = source,
58
+ effect = effect,
59
+ derive = derive,
60
+ switch = switch,
61
+ show = show,
62
+ indexes = indexes,
63
+ values = values,
64
+
65
+ -- util
66
+ cleanup = cleanup,
67
+ untrack = untrack,
68
+ read = read,
69
+ batch = batch,
70
+ context = context,
71
+
72
+ -- animations
73
+ spring = spring,
74
+
75
+ -- actions
76
+ action = action,
77
+ changed = changed,
78
+
79
+ -- flags
80
+ strict = (nil :: any) :: boolean,
81
+ defaults = (nil :: any) :: boolean,
82
+ defer_nested_properties = (nil :: any) :: boolean,
83
+
84
+ -- temporary
85
+ apply = function(instance: Instance)
86
+ return function(props: { [any]: any })
87
+ apply(instance, props)
88
+ return instance
89
+ end
90
+ end,
91
+
92
+ -- runtime
93
+ step = function(dt: number)
94
+ if stepped then
95
+ stepped:Disconnect()
96
+ stepped = nil
97
+ end
98
+ step(dt)
99
+ end
100
+ }
101
+
102
+ setmetatable(vide :: any, {
103
+ __index = function(_, index: unknown): ()
104
+ if flags[index] == nil then
105
+ error(`{tostring(index)} is not a valid member of vide`, 0)
106
+ else
107
+ return flags[index]
108
+ end
109
+ end,
110
+
111
+ __newindex = function(_, index: unknown, value: unknown)
112
+ if flags[index] == nil then
113
+ error(`{tostring(index)} is not a valid member of vide, 0`)
114
+ else
115
+ flags[index] = value
116
+ end
117
+ end
118
+ })
119
+
120
+ return vide
@@ -0,0 +1,11 @@
1
+ local root = require "./root"
2
+ local apply = require "./apply"
3
+
4
+ local function mount<T>(component: () -> T, target: Instance?): () -> ()
5
+ return root(function()
6
+ local result = component()
7
+ if target then apply(target, { result }) end
8
+ end)
9
+ end
10
+
11
+ return mount :: (<T>(component: () -> T, target: Instance) -> () -> ()) & ((component: () -> ()) -> () -> ())
@@ -0,0 +1,5 @@
1
+ local function read<T>(value: T | () -> T): T
2
+ return if type(value) == "function" then value() else value
3
+ end
4
+
5
+ return read
@@ -0,0 +1,35 @@
1
+ local graph = require "./graph"
2
+ type Node<T> = graph.Node<T>
3
+ local create_node = graph.create_node
4
+ local push_scope = graph.push_scope
5
+ local pop_scope = graph.pop_scope
6
+ local destroy = graph.destroy
7
+
8
+ local refs = {}
9
+
10
+ local function root<T...>(fn: (destroy: () -> ()) -> T...): (() -> (), T...)
11
+ local node = create_node(false, false, false)
12
+
13
+ refs[node] = true -- prevent gc of root node
14
+
15
+ local destroy = function()
16
+ if not refs[node] then error "root already destroyed" end
17
+ refs[node] = nil
18
+ destroy(node)
19
+ end
20
+
21
+ push_scope(node)
22
+
23
+ local result = { xpcall(fn, debug.traceback, destroy) }
24
+
25
+ pop_scope()
26
+
27
+ if not result[1] then
28
+ destroy()
29
+ error(`error while running root():\n\n{result[2]}`, 0)
30
+ end
31
+
32
+ return destroy, unpack(result :: any, 2)
33
+ end
34
+
35
+ return root :: <T...>(fn: (destroy: () -> ()) -> T...) -> (() -> (), T...)
@@ -0,0 +1,36 @@
1
+ local source = require "./source"
2
+ local derive = require "./derive"
3
+ local effect = require "./effect"
4
+ local untrack = require "./untrack"
5
+ local switch = require "./switch"
6
+
7
+ type Array<T> = { T }
8
+ type Source<T> = () -> T
9
+
10
+ local function show<T, Obj>(
11
+ input: Source<T?>,
12
+ component: (Source<T>, Source<boolean>) -> (Obj, ...number),
13
+ fallback: ((Source<boolean>) -> (Obj, ...number))?
14
+ ): Source<nil | Obj | Array<Obj>>
15
+ local filtered_input = source()
16
+
17
+ effect(function()
18
+ local v = input()
19
+ if v then
20
+ filtered_input(v)
21
+ end
22
+ end)
23
+
24
+ local input_is_truthy = derive(function()
25
+ return not not input()
26
+ end)
27
+
28
+ return switch(input_is_truthy) {
29
+ [true] = function(present)
30
+ return component(filtered_input, present)
31
+ end,
32
+ [false] = fallback
33
+ }
34
+ end
35
+
36
+ return show
@@ -0,0 +1,31 @@
1
+ local graph = require "./graph"
2
+ type Node<T> = graph.Node<T>
3
+ local create_source_node = graph.create_source_node
4
+ local push_scope_as_child_of = graph.push_scope_as_child_of
5
+ local update_descendants = graph.update_descendants
6
+
7
+ export type Source<T> = (() -> T) & ((value: T) -> T)
8
+
9
+ local function source<T>(initial_value: T): Source<T>
10
+ local node = create_source_node(initial_value)
11
+
12
+ local function update_source(...): T
13
+ if select("#", ...) == 0 then -- no args were given
14
+ push_scope_as_child_of(node)
15
+ return node.cache
16
+ end
17
+
18
+ local v = ... :: T
19
+ if node.cache == v and (type(v) ~= "table" or table.isfrozen(v)) then
20
+ return v
21
+ end
22
+
23
+ node.cache = v
24
+ update_descendants(node)
25
+ return v
26
+ end
27
+
28
+ return update_source
29
+ end
30
+
31
+ return source :: (<T>(initial_value: T) -> Source<T>) & (<T>() -> Source<T>)
@@ -0,0 +1,328 @@
1
+ local graph = require "./graph"
2
+ type Node<T> = graph.Node<T>
3
+ type SourceNode<T> = graph.SourceNode<T>
4
+ local create_node = graph.create_node
5
+ local create_source_node = graph.create_source_node
6
+ local assert_stable_scope = graph.assert_stable_scope
7
+ local evaluate_node = graph.evaluate_node
8
+ local update_descendants = graph.update_descendants
9
+ local push_scope_as_child_of = graph.push_scope_as_child_of
10
+
11
+ local UPDATE_RATE = 120
12
+ local TOLERANCE_FACTOR = 10_000
13
+
14
+ type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3
15
+
16
+ --[[
17
+ Unsupported datatypes:
18
+ - bool
19
+ - Vector2int16
20
+ - Vector3int16
21
+ - EnumItem
22
+ ]]
23
+
24
+ type SpringState<T> = {
25
+ k: number, -- spring constant
26
+ c: number, -- damping coeff
27
+
28
+ x0_123: vector, x0_456: vector, -- initial position
29
+ x_123: vector, x_456: vector, -- current position
30
+ x1_123: vector, x1_456: vector, -- target position
31
+ v_123: vector, v_456: vector, -- current velocity
32
+
33
+ source_value: T -- current value of spring input source
34
+ }
35
+
36
+ type SpringSettings<T> = ({
37
+ position: T?,
38
+ velocity: T?,
39
+ impulse: T?
40
+ }) -> ()
41
+
42
+ type TypeToVec6<T> = (T) -> (vector, vector)
43
+ type Vec6ToType<T> = (vector, vector) -> T
44
+
45
+ local type_to_vec6 = {
46
+ number = function(v)
47
+ return vector.create(v, 0, 0), vector.zero
48
+ end :: TypeToVec6<number>,
49
+
50
+ CFrame = function(v)
51
+ return v.Position, vector.create(v:ToEulerAnglesXYZ())
52
+ end :: TypeToVec6<CFrame>,
53
+
54
+ Color3 = function(v)
55
+ -- todo: hsv, oklab?
56
+ return vector.create(v.R, v.G, v.B), vector.zero
57
+ end :: TypeToVec6<Color3>,
58
+
59
+ UDim = function(v)
60
+ return vector.create(v.Scale, v.Offset, 0), vector.zero
61
+ end :: TypeToVec6<UDim>,
62
+
63
+ UDim2 = function(v)
64
+ return vector.create(v.X.Scale, v.X.Offset, v.Y.Scale), vector.create(v.Y.Offset, 0, 0)
65
+ end :: TypeToVec6<UDim2>,
66
+
67
+ Vector2 = function(v)
68
+ return vector.create(v.X, v.Y, 0), vector.zero
69
+ end :: TypeToVec6<Vector2>,
70
+
71
+ Vector3 = function(v)
72
+ return v, vector.zero
73
+ end :: TypeToVec6<Vector3>,
74
+
75
+ Rect = function(v)
76
+ return vector.create(v.Min.X, v.Min.Y, v.Max.X), vector.create(v.Max.Y, 0, 0)
77
+ end :: TypeToVec6<Rect>,
78
+
79
+ table = function(v)
80
+ return vector.create(v[1] or 0, v[2] or 0, v[3] or 0), vector.create(v[4] or 0, 0, 0)
81
+ end :: TypeToVec6<{ number }>
82
+ }
83
+
84
+ local vec6_to_type = {
85
+ number = function(a, b)
86
+ return a.X
87
+ end :: Vec6ToType<number>,
88
+
89
+ CFrame = function(a, b)
90
+ return CFrame.new(a) * CFrame.fromEulerAnglesXYZ(b.X, b.Y, b.Z)
91
+ end :: Vec6ToType<CFrame>,
92
+
93
+ Color3 = function(v)
94
+ return Color3.new(math.clamp(v.X, 0, 1), math.clamp(v.Y, 0, 1), math.clamp(v.Z, 0, 1))
95
+ end :: Vec6ToType<Color3>,
96
+
97
+ UDim = function(v)
98
+ return UDim.new(v.X, math.round(v.Y))
99
+ end :: Vec6ToType<UDim>,
100
+
101
+ UDim2 = function(a, b)
102
+ return UDim2.new(a.X, math.round(a.Y), a.Z, math.round(b.X))
103
+ end :: Vec6ToType<UDim2>,
104
+
105
+ Vector2 = function(v)
106
+ return Vector2.new(v.X, v.Y)
107
+ end :: Vec6ToType<Vector2>,
108
+
109
+ Vector3 = function(v)
110
+ return v
111
+ end :: Vec6ToType<Vector3>,
112
+
113
+ Rect = function(a, b)
114
+ return Rect.new(a.X, a.Y, a.Z, b.X)
115
+ end :: Vec6ToType<Rect>,
116
+
117
+ table = function(a, b)
118
+ return { a.X, a.Y, a.Z, b.X }
119
+ end :: Vec6ToType<{ number }>
120
+ }
121
+
122
+ local invalid_type = {
123
+ __index = function(_, t: string)
124
+ error(`cannot spring type {t}`, 0)
125
+ end
126
+ }
127
+
128
+ setmetatable(type_to_vec6, invalid_type)
129
+ setmetatable(vec6_to_type, invalid_type)
130
+
131
+ -- maps spring data to its corresponding output node
132
+ -- lifetime of spring data is tied to output node
133
+ local springs: { [SpringState<unknown>]: SourceNode<unknown> } = {}
134
+ setmetatable(springs :: any, { __mode = "v" })
135
+
136
+ local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): (() -> T, SpringSettings<T>)
137
+ local owner = assert_stable_scope()
138
+
139
+ -- https://en.wikipedia.org/wiki/Damping
140
+ local w_n = 2*math.pi / (period or 1)
141
+ local z = damping_ratio or 1
142
+ local k = w_n^2
143
+ local c_c = 2*w_n
144
+ local c = z * c_c
145
+
146
+ -- todo: is there a solution other than reducing step size?
147
+ -- todo: this does not catch all solver exploding cases
148
+ if c > UPDATE_RATE*2 then -- solver will explode if this is true
149
+ error("spring damping too high, consider reducing damping or increasing period", 0)
150
+ end
151
+
152
+ local data: SpringState<T> = {
153
+ k = k,
154
+ c = c,
155
+
156
+ x0_123 = vector.zero,
157
+ x_123 = vector.zero,
158
+ x1_123 = vector.zero,
159
+ v_123 = vector.zero,
160
+
161
+ x0_456 = vector.zero,
162
+ x_456 = vector.zero,
163
+ x1_456 = vector.zero,
164
+ v_456 = vector.zero,
165
+
166
+ source_value = false :: any,
167
+ }
168
+
169
+ local output = create_source_node(false :: any)
170
+
171
+ local function updater_effect()
172
+ local value = source()
173
+ data.x1_123, data.x1_456 = type_to_vec6[typeof(value)](value)
174
+ data.source_value = value
175
+ springs[data] = output
176
+ return value
177
+ end
178
+
179
+ local updater = create_node(owner, updater_effect, false :: any)
180
+ evaluate_node(updater)
181
+
182
+ -- set initial position to goal
183
+ data.x_123, data.x_456 = data.x1_123, data.x1_456
184
+
185
+ -- set output to goal
186
+ output.cache = data.source_value
187
+
188
+ local config = function(p)
189
+ local x = p.position
190
+ local v = p.velocity
191
+ local dv = p.impulse
192
+
193
+ if x then
194
+ local x_123, x_456 = type_to_vec6[typeof(x)](x)
195
+ data.x_123, data.x_456 = x_123, x_456
196
+ data.x0_123, data.x0_456 = x_123, x_456
197
+ end
198
+
199
+ if v then
200
+ data.v_123, data.v_456 = type_to_vec6[typeof(v)](v)
201
+ end
202
+
203
+ if dv then
204
+ local dv_123, dv_456 = type_to_vec6[typeof(dv)](dv)
205
+ data.v_123 += dv_123
206
+ data.v_456 += dv_456
207
+ end
208
+
209
+ -- schedule spring
210
+ springs[data] = output
211
+ end :: SpringSettings<T>
212
+
213
+ return function(...)
214
+ if select("#", ...) == 0 then -- no args were given
215
+ push_scope_as_child_of(output)
216
+ return output.cache
217
+ end
218
+
219
+ -- set current position to value
220
+ local v = ... :: T
221
+ data.x_123, data.x_456 = type_to_vec6[typeof(v)](v)
222
+
223
+ -- reset velocity
224
+ data.v_123 = vector.zero
225
+ data.v_456 = vector.zero
226
+
227
+ -- schedule spring
228
+ springs[data] = output
229
+
230
+ -- set output to value
231
+ output.cache = v
232
+
233
+ return v
234
+ end, config
235
+ end
236
+
237
+ -- calculates a float tolerance, based on the magnitude of the float
238
+ local function get_min_step(x: number): number
239
+ return x/TOLERANCE_FACTOR
240
+ end
241
+ local function get_min_vector_step(direction: vector): vector
242
+ return vector.create(
243
+ get_min_step(direction.x),
244
+ get_min_step(direction.y),
245
+ get_min_step(direction.z)
246
+ )
247
+ end
248
+
249
+ local function step_springs(dt: number)
250
+ for s in springs do
251
+ local k = s.k
252
+ local c = s.c
253
+ local x_123, x_456 = s.x_123, s.x_456
254
+ local x1_123, x1_456 = s.x1_123, s.x1_456
255
+ local u_123, u_456 = s.v_123, s.v_456
256
+
257
+ -- calculate displacement from target
258
+ local dx_123 = x_123 - x1_123
259
+ local dx_456 = x_456 - x1_456
260
+
261
+ -- calculate spring force
262
+ local fs_123 = dx_123*-k
263
+ local fs_456 = dx_456*-k
264
+
265
+ -- calculate friction force
266
+ local ff_123 = u_123*-c
267
+ local ff_456 = u_456*-c
268
+
269
+ -- calculate acceleration
270
+ local a_123 = (fs_123 + ff_123)
271
+ local a_456 = (fs_456 + ff_456)
272
+
273
+ -- step acceleration
274
+ local v_123 = u_123 + a_123*dt
275
+ local v_456 = u_456 + a_456*dt
276
+
277
+ -- step velocity
278
+ local y_123 = x_123 + v_123*dt
279
+ local y_456 = x_456 + v_456*dt
280
+
281
+ s.x_123, s.x_456 = y_123, y_456
282
+ s.v_123, s.v_456 = v_123, v_456
283
+ end
284
+ end
285
+
286
+ local function update_spring_sources()
287
+ for data, output in springs do
288
+ local x0_123, x0_456 = data.x0_123, data.x0_456
289
+ local x_123, x_456 = data.x_123, data.x_456
290
+ local x1_123, x1_456 = data.x1_123, data.x1_456
291
+ local v_123, v_456 = data.v_123, data.v_456
292
+
293
+ local tol_123 = vector.abs(get_min_vector_step(x0_123 - x1_123))
294
+ local tol_456 = vector.abs(get_min_vector_step(x0_456 - x1_456))
295
+
296
+ if
297
+ -- position is at goal (within tolerance)
298
+ vector.max(vector.abs(x_123 - x1_123), tol_123) == tol_123
299
+ and vector.max(vector.abs(x_456 - x1_456), tol_456) == tol_456
300
+
301
+ -- velocity is at 0 (within tolerance)
302
+ and vector.max(vector.abs(v_123/10), tol_123) == tol_123
303
+ and vector.max(vector.abs(v_456/10), tol_456) == tol_456
304
+ then
305
+ springs[data] = nil
306
+ output.cache = data.source_value
307
+ else
308
+ output.cache = vec6_to_type[typeof(data.source_value)](x_123, x_456)
309
+ end
310
+
311
+ update_descendants(output)
312
+ end
313
+ end
314
+
315
+ return function()
316
+ local time_elapsed = 0
317
+
318
+ return spring, function(dt: number)
319
+ time_elapsed += dt
320
+
321
+ while time_elapsed > 1 / UPDATE_RATE do
322
+ time_elapsed -= 1 / UPDATE_RATE
323
+ step_springs(1 / UPDATE_RATE)
324
+ end
325
+
326
+ update_spring_sources()
327
+ end
328
+ end