@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.
- package/README.md +60 -15
- package/centurion.luau +10 -0
- package/package.json +13 -23
- package/src/analyse/cursor.luau +25 -0
- package/src/analyse/highlight.luau +91 -0
- package/src/analyse/init.luau +318 -0
- package/src/analyse/leventine.luau +298 -0
- package/src/analyse/match.luau +39 -0
- package/src/analyse/suggest.luau +41 -0
- package/src/app/header.luau +128 -0
- package/src/app/history.luau +258 -0
- package/src/app/init.luau +699 -0
- package/src/app/suggestions.luau +419 -0
- package/src/button.luau +13 -0
- package/src/components/background.luau +37 -0
- package/src/components/container.luau +33 -0
- package/src/components/corner.luau +11 -0
- package/src/components/divider.luau +28 -0
- package/src/components/flex.luau +108 -0
- package/src/components/gap.luau +25 -0
- package/src/components/image.luau +33 -0
- package/src/components/padding.luau +59 -0
- package/src/components/stroke.luau +20 -0
- package/src/components/text.luau +57 -0
- package/src/components/textbox.luau +186 -0
- package/src/fade.luau +23 -0
- package/src/init.luau +142 -0
- package/src/input.luau +16 -0
- package/src/px.luau +68 -0
- package/src/rich.luau +14 -0
- package/src/state.luau +33 -0
- package/src/theme.luau +70 -0
- package/src/ts/index.d.ts +60 -0
- package/src/ts/init.luau +5 -0
- package/src/types.luau +85 -0
- package/vide/init.luau +8 -0
- package/vide/src/action.luau +25 -0
- package/vide/src/apply.luau +162 -0
- package/vide/src/batch.luau +23 -0
- package/vide/src/branch.luau +40 -0
- package/vide/src/changed.luau +18 -0
- package/vide/src/cleanup.luau +43 -0
- package/vide/src/context.luau +74 -0
- package/vide/src/create.luau +107 -0
- package/vide/src/defaults.luau +113 -0
- package/vide/src/derive.luau +18 -0
- package/vide/src/effect.luau +12 -0
- package/vide/src/flags.luau +12 -0
- package/vide/src/graph.luau +321 -0
- package/vide/src/implicit_effect.luau +125 -0
- package/vide/src/indexes.luau +121 -0
- package/vide/src/init.luau +10 -0
- package/vide/src/lib.luau +120 -0
- package/vide/src/mount.luau +11 -0
- package/vide/src/read.luau +5 -0
- package/vide/src/root.luau +35 -0
- package/vide/src/show.luau +36 -0
- package/vide/src/source.luau +31 -0
- package/vide/src/spring.luau +328 -0
- package/vide/src/switch.luau +114 -0
- package/vide/src/timeout.luau +27 -0
- package/vide/src/untrack.luau +25 -0
- package/vide/src/values.luau +131 -0
- package/out/app/centurion-app.d.ts +0 -3
- package/out/app/centurion-app.luau +0 -86
- package/out/app/index.d.ts +0 -32
- package/out/app/init.luau +0 -105
- package/out/components/history/history-line.d.ts +0 -9
- package/out/components/history/history-line.luau +0 -88
- package/out/components/history/history-list.d.ts +0 -11
- package/out/components/history/history-list.luau +0 -67
- package/out/components/history/index.d.ts +0 -1
- package/out/components/history/init.luau +0 -7
- package/out/components/index.d.ts +0 -2
- package/out/components/init.luau +0 -10
- package/out/components/suggestions/badge.d.ts +0 -16
- package/out/components/suggestions/badge.luau +0 -40
- package/out/components/suggestions/index.d.ts +0 -1
- package/out/components/suggestions/init.luau +0 -7
- package/out/components/suggestions/main-suggestion.d.ts +0 -9
- package/out/components/suggestions/main-suggestion.luau +0 -232
- package/out/components/suggestions/suggestion-list.d.ts +0 -8
- package/out/components/suggestions/suggestion-list.luau +0 -96
- package/out/components/suggestions/suggestions.d.ts +0 -2
- package/out/components/suggestions/suggestions.luau +0 -64
- package/out/components/suggestions/types.d.ts +0 -6
- package/out/components/suggestions/types.luau +0 -2
- package/out/components/suggestions/util.d.ts +0 -1
- package/out/components/suggestions/util.luau +0 -22
- package/out/components/terminal/command.d.ts +0 -6
- package/out/components/terminal/command.luau +0 -74
- package/out/components/terminal/index.d.ts +0 -1
- package/out/components/terminal/init.luau +0 -7
- package/out/components/terminal/suggestion.d.ts +0 -26
- package/out/components/terminal/suggestion.luau +0 -205
- package/out/components/terminal/terminal-text-field/index.d.ts +0 -1
- package/out/components/terminal/terminal-text-field/init.luau +0 -7
- package/out/components/terminal/terminal-text-field/suggestion.d.ts +0 -5
- package/out/components/terminal/terminal-text-field/suggestion.luau +0 -124
- package/out/components/terminal/terminal-text-field/terminal-text-field.d.ts +0 -13
- package/out/components/terminal/terminal-text-field/terminal-text-field.luau +0 -273
- package/out/components/terminal/terminal.d.ts +0 -2
- package/out/components/terminal/terminal.luau +0 -304
- package/out/components/ui/frame.d.ts +0 -17
- package/out/components/ui/frame.luau +0 -32
- package/out/components/ui/group.d.ts +0 -15
- package/out/components/ui/group.luau +0 -29
- package/out/components/ui/layer.d.ts +0 -8
- package/out/components/ui/layer.luau +0 -26
- package/out/components/ui/outline.d.ts +0 -15
- package/out/components/ui/outline.luau +0 -116
- package/out/components/ui/padding.d.ts +0 -10
- package/out/components/ui/padding.luau +0 -19
- package/out/components/ui/scrolling-frame.d.ts +0 -14
- package/out/components/ui/scrolling-frame.luau +0 -44
- package/out/components/ui/text-field.d.ts +0 -12
- package/out/components/ui/text-field.luau +0 -60
- package/out/components/ui/text.d.ts +0 -17
- package/out/components/ui/text.luau +0 -61
- package/out/constants/options.d.ts +0 -2
- package/out/constants/options.luau +0 -27
- package/out/constants/text.d.ts +0 -3
- package/out/constants/text.luau +0 -9
- package/out/constants/util.d.ts +0 -1
- package/out/constants/util.luau +0 -7
- package/out/hooks/use-client.d.ts +0 -2
- package/out/hooks/use-client.luau +0 -17
- package/out/hooks/use-event.d.ts +0 -14
- package/out/hooks/use-event.luau +0 -58
- package/out/hooks/use-px.d.ts +0 -19
- package/out/hooks/use-px.luau +0 -78
- package/out/index.d.ts +0 -2
- package/out/init.luau +0 -10
- package/out/palette.d.ts +0 -47
- package/out/palette.luau +0 -42
- package/out/store.d.ts +0 -12
- package/out/store.luau +0 -26
- package/out/types.d.ts +0 -44
- package/out/types.luau +0 -2
- package/out/utils/string.d.ts +0 -5
- 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,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
|