/
StepUtils.lua
204 lines (163 loc) · 4.78 KB
/
StepUtils.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
--[=[
Utility functions primarily used to bind animations into update loops of the Roblox engine.
@class StepUtils
]=]
local HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local StepUtils = {}
--[=[
Binds the given update function to [RunService.RenderStepped].
```lua
local spring = Spring.new(0)
local maid = Maid.new()
local startAnimation, maid._stopAnimation = StepUtils.bindToRenderStep(function()
local animating, position = SpringUtils.animating(spring)
print(position)
return animating
end)
spring.t = 1
startAnimation()
```
:::tip
Be sure to call the disconnect function when cleaning up, otherwise you may memory leak.
:::
@param update () -> boolean -- should return true while it needs to update
@return (...) -> () -- Connect function
@return () -> () -- Disconnect function
]=]
function StepUtils.bindToRenderStep(update)
return StepUtils.bindToSignal(RunService.RenderStepped, update)
end
--[=[
Yields until the frame deferral is done
]=]
function StepUtils.deferWait()
local signal = Instance.new("BindableEvent")
task.defer(function()
signal:Fire()
signal:Destroy()
end)
signal.Event:Wait()
end
--[=[
Binds the given update function to [RunService.Stepped]. See [StepUtils.bindToRenderStep] for details.
:::tip
Be sure to call the disconnect function when cleaning up, otherwise you may memory leak.
:::
@param update () -> boolean -- should return true while it needs to update
@return (...) -> () -- Connect function
@return () -> () -- Disconnect function
]=]
function StepUtils.bindToStepped(update)
return StepUtils.bindToSignal(RunService.Stepped, update)
end
--[=[
Binds an update event to a signal until the update function stops returning a truthy
value.
@param signal Signal | RBXScriptSignal
@param update () -> boolean -- should return true while it needs to update
@return (...) -> () -- Connect function
@return () -> () -- Disconnect function
]=]
function StepUtils.bindToSignal(signal, update)
if typeof(signal) ~= "RBXScriptSignal" then
error("signal must be of type RBXScriptSignal")
end
if type(update) ~= "function" then
error(("update must be of type function, got %q"):format(type(update)))
end
local conn = nil
local function disconnect()
if conn then
conn:Disconnect()
conn = nil
end
end
local function connect(...)
-- Ignore if we have an existing connection
if conn and conn.Connected then
return
end
-- Check to see if we even need to bind an update
if not update(...) then
return
end
-- Avoid reentrance, if update() triggers another connection, we'll already be connected.
if conn and conn.Connected then
return
end
-- Usually contains just the self arg!
local args = {...}
-- Bind to render stepped
conn = signal:Connect(function()
if not update(unpack(args)) then
disconnect()
end
end)
end
return connect, disconnect
end
--[=[
Calls the function once at the given priority level, unless the cancel callback is
invoked.
@param priority number
@param func function -- Function to call
@return function -- Call this function to cancel call
]=]
function StepUtils.onceAtRenderPriority(priority, func)
assert(type(priority) == "number", "Bad priority")
assert(type(func) == "function", "Bad func")
local key = ("StepUtils.onceAtPriority_%s"):format(HttpService:GenerateGUID(false))
local function cleanup()
RunService:UnbindFromRenderStep(key)
end
RunService:BindToRenderStep(key, priority, function()
cleanup()
func()
end)
return cleanup
end
--[=[
Invokes the function once at stepped, unless the cancel callback is called.
```lua
-- Sometimes you need to defer the execution of code to make physics happy
maid:GiveTask(StepUtils.onceAtStepped(function()
part.CFrame = CFrame.new(0, 0, )
end))
```
@param func function -- Function to call
@return function -- Call this function to cancel call
]=]
function StepUtils.onceAtStepped(func)
return StepUtils.onceAtEvent(RunService.Stepped, func)
end
--[=[
Invokes the function once at renderstepped, unless the cancel callback is called.
@param func function -- Function to call
@return function -- Call this function to cancel call
]=]
function StepUtils.onceAtRenderStepped(func)
return StepUtils.onceAtEvent(RunService.RenderStepped, func)
end
--[=[
Invokes the function once at the given event, unless the cancel callback is called.
@param event Signal | RBXScriptSignal
@param func function -- Function to call
@return function -- Call this function to cancel call
]=]
function StepUtils.onceAtEvent(event, func)
assert(type(func) == "function", "Bad func")
local conn
local function cleanup()
if conn then
conn:Disconnect()
conn = nil
end
end
conn = event:Connect(function(...)
cleanup()
func(...)
end)
return cleanup
end
return StepUtils