-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathuseStream.lua
More file actions
254 lines (207 loc) · 5.98 KB
/
useStream.lua
File metadata and controls
254 lines (207 loc) · 5.98 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
local Workspace = game:GetService("Workspace")
local Package = script.Parent
local Matter = require(Package.Parent.Matter)
local Queue = require(Package.Queue)
--[=[
.descendants boolean? -- Whether to collect events about descendants
.attribute string? -- The attribute to use
@within Hooks
@interface StreamOptions
]=]
export type StreamOptions = {
descendants: boolean?,
attribute: string?,
}
--[=[
An event for an instance that has streamed in.
.adding true
.removing false
.descendant boolean
.instance Instance
@within Hooks
@interface StreamInEvent
]=]
export type StreamInEvent = {
adding: true,
removing: false,
descendant: boolean,
instance: Instance,
}
--[=[
An event for an instance that has streamed out.
.adding false
.removing true
.descendant boolean
.instance Instance
@within Hooks
@interface StreamOutEvent
]=]
export type StreamOutEvent = {
adding: false,
removing: true,
descendant: boolean,
instance: Instance,
}
--[=[
An event for an instance that has streamed in or out. The `adding` and
`removing` fields indicate whether the instance is streaming in or out.
@within Hooks
@type StreamEvent StreamInEvent | StreamOutEvent
]=]
export type StreamEvent = StreamInEvent | StreamOutEvent
local function streamInEvent(instance: Instance, descendant: boolean?): StreamInEvent
return {
adding = true,
removing = false,
descendant = if descendant ~= nil then descendant else false,
instance = instance,
}
end
local function streamOutEvent(instance: Instance, descendant: boolean?): StreamOutEvent
return {
adding = false,
removing = true,
descendant = if descendant ~= nil then descendant else false,
instance = instance,
}
end
type NormalizedOptions = {
descendants: boolean,
attribute: string,
}
local function normalizeOptions(options: StreamOptions?): NormalizedOptions
return {
descendants = if options and options.descendants ~= nil then options.descendants else false,
attribute = if options and options.attribute then options.attribute else "serverEntityId",
}
end
local function cleanup(storage)
storage.addedConnection:Disconnect()
storage.removingConnection:Disconnect()
for _, connections in storage.trackedInstances do
connections.addedConnection:Disconnect()
connections.removingConnection:Disconnect()
end
end
--[=[
Collects instance streaming events for a streaming ID attribute.
Allows iteration over collected events for the instances tagged with an ID
using an attribute. It can optionally collect events for the descendants of
that instance as they stream in and out.
Each streaming event is returned in the order it happened.
```lua
for _, streamEvent in useStream(entityId) do
if streamEvent.adding then
processStreamedIn(streamEvent.instance)
else
processStreamedOut(streamEvent.instance)
end
end
```
If the hook is no longer called, all events will be cleaned up automatically.
:::caution
The events are stored in a queue that must be processed. If events are left in
the queue they will remain for next frame, arriving late.
To avoid this, all events should be processed each frame.
```lua
for _, streamEvent in useStream(entityId) do
if processEvent(streamEvent) then
break -- Uh oh! This can miss events!
end
end
```
:::
The ID can be anything you use to identify instances streamed from the server,
but is typically a server entity ID. The default attribute this hook uses to
discover instances by this ID is `serverEntityId`, but can be optionally
configured.
```lua
for _, streamEvent in
useStream(entityId, {
attribute = "StreamingId",
})
do
processStream(streamEvent)
end
```
If the instance being streamed has descendants that stream in at different
times, you may want to listen for them. This can be configured as well.
```lua
for _, streamEvent in
useStream(entityId, {
descendants = true,
})
do
processStream(streamEvent)
end
```
@within Hooks
@return () -> (number, StreamEvent)? -- The event iterator
]=]
local function useStream(id: unknown, options: StreamOptions?): () -> (number?, StreamEvent)
local storage = Matter.useHookState(id, cleanup)
if not storage.queue then
local options = normalizeOptions(options)
storage.queue = Queue.new()
storage.trackedInstances = {}
local function newDescendant(instance: Instance)
if instance:GetAttribute(options.attribute) ~= id then
return
end
storage.queue:push(streamInEvent(instance))
if not options.descendants then
return
end
if storage.trackedInstances[instance] then
return
end
storage.trackedInstances[instance] = {
addedConnection = instance.DescendantAdded:Connect(function(instance: Instance)
storage.queue:push(streamInEvent(instance, true))
end),
removingConnection = instance.DescendantRemoving:Connect(
function(instance: Instance)
storage.queue:push(streamOutEvent(instance, true))
end
),
}
for _, descendant in instance:GetDescendants() do
storage.queue:push(streamInEvent(descendant, true))
end
end
storage.addedConnection = Workspace.DescendantAdded:Connect(newDescendant)
for _, instance: Instance in Workspace:GetDescendants() do
newDescendant(instance)
end
storage.removingConnection = Workspace.DescendantRemoving:Connect(
function(instance: Instance)
if instance:GetAttribute(options.attribute) ~= id then
return
end
storage.queue:push(streamOutEvent(instance))
if not options.descendants then
return
end
for _, descendant in instance:GetDescendants() do
storage.queue:push(streamOutEvent(descendant, true))
end
local connections = storage.trackedInstances[instance]
if not connections then
return
end
connections.addedConnection:Disconnect()
connections.removingConnection:Disconnect()
end
)
end
local index = 0
return function(): (number?, StreamEvent)
index += 1
local value = storage.queue:shift()
if value then
return index, value
end
return nil, (nil :: unknown) :: StreamEvent
end
end
return useStream