@skating/swiftpacket 0.1.4
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/LICENSE +21 -0
- package/README.md +123 -0
- package/package.json +31 -0
- package/src/client.luau +472 -0
- package/src/configuration.luau +74 -0
- package/src/cursor.luau +18 -0
- package/src/index.d.ts +122 -0
- package/src/init.luau +199 -0
- package/src/packet.luau +186 -0
- package/src/rate_limit.luau +38 -0
- package/src/registry.luau +40 -0
- package/src/schema.luau +1606 -0
- package/src/server.luau +691 -0
- package/src/signal.luau +145 -0
- package/src/thread_pool.luau +37 -0
- package/src/wire.luau +218 -0
package/src/server.luau
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
|
|
4
|
+
--[[
|
|
5
|
+
Author: @skaterstudios
|
|
6
|
+
Website: skaterstudios.com
|
|
7
|
+
GitHub: github.com/skatingii/SwiftPacket
|
|
8
|
+
Discord Server: discord.gg/skaterstudios
|
|
9
|
+
|
|
10
|
+
Licensed under the MIT License. You may use, modify, redistribute and sell
|
|
11
|
+
this system freely, as long as this notice and the LICENSE file stay with it.
|
|
12
|
+
]]
|
|
13
|
+
|
|
14
|
+
const Players = game:GetService("Players")
|
|
15
|
+
const RunService = game:GetService("RunService")
|
|
16
|
+
|
|
17
|
+
const Configuration = require("./configuration")
|
|
18
|
+
const Cursor = require("./cursor")
|
|
19
|
+
const RateLimit = require("./rate_limit")
|
|
20
|
+
const Registry = require("./registry")
|
|
21
|
+
const Schema = require("./schema")
|
|
22
|
+
const ThreadPool = require("./thread_pool")
|
|
23
|
+
const Wire = require("./wire")
|
|
24
|
+
|
|
25
|
+
type Definition = Registry.Definition
|
|
26
|
+
type Arguments = Schema.Arguments
|
|
27
|
+
|
|
28
|
+
type Channel = {
|
|
29
|
+
Reliable: Cursor.Cursor,
|
|
30
|
+
Unreliable: { Cursor.Cursor },
|
|
31
|
+
UnreliableCount: number,
|
|
32
|
+
Tracked: boolean,
|
|
33
|
+
Marks: { number },
|
|
34
|
+
MarkCount: number,
|
|
35
|
+
SharedFrom: number,
|
|
36
|
+
SharedInstancesFrom: number,
|
|
37
|
+
LastShared: number,
|
|
38
|
+
LastSharedInstances: number,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type PendingCall = {
|
|
42
|
+
Thread: thread,
|
|
43
|
+
Deadline: number,
|
|
44
|
+
Definition: Definition,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Budget = {
|
|
48
|
+
Tokens: number,
|
|
49
|
+
UpdatedAt: number,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Window = {
|
|
53
|
+
Count: number,
|
|
54
|
+
EndsAt: number,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const Server = {}
|
|
58
|
+
|
|
59
|
+
const Channels: { [Player]: Channel } = {}
|
|
60
|
+
const PendingCalls: { [Player]: { [number]: PendingCall } } = {}
|
|
61
|
+
const NextCallIndex: { [Player]: number } = {}
|
|
62
|
+
const Budgets: { [Player]: Budget } = {}
|
|
63
|
+
const Windows: { [Definition]: { [Player]: Window } } = {}
|
|
64
|
+
const Everyone: { Player } = {}
|
|
65
|
+
const Scratch = Cursor.New()
|
|
66
|
+
const Shared = Cursor.New()
|
|
67
|
+
const Received = Wire.NewBatch()
|
|
68
|
+
const NoInstances: { any } = {}
|
|
69
|
+
const NoBytes = buffer.create(0)
|
|
70
|
+
const ResponseFlag = Wire.ResponseFlag
|
|
71
|
+
const EventIndex = Wire.EventIndex
|
|
72
|
+
|
|
73
|
+
local ReliableRemote: RemoteEvent? = nil
|
|
74
|
+
local UnreliableRemote: UnreliableRemoteEvent? = nil
|
|
75
|
+
local NextId = 1
|
|
76
|
+
|
|
77
|
+
const function GetChannel(Player: Player): Channel
|
|
78
|
+
const Existing = Channels[Player]
|
|
79
|
+
|
|
80
|
+
if Existing then
|
|
81
|
+
return Existing
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
const Created: Channel = {
|
|
85
|
+
Reliable = Cursor.New(),
|
|
86
|
+
Unreliable = { Cursor.New(Configuration.UnreliableByteLimit) },
|
|
87
|
+
UnreliableCount = 1,
|
|
88
|
+
Tracked = false,
|
|
89
|
+
Marks = {},
|
|
90
|
+
MarkCount = 0,
|
|
91
|
+
SharedFrom = Shared.Offset,
|
|
92
|
+
SharedInstancesFrom = Shared.InstancesOffset,
|
|
93
|
+
LastShared = Shared.Offset,
|
|
94
|
+
LastSharedInstances = Shared.InstancesOffset,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Channels[Player] = Created
|
|
98
|
+
|
|
99
|
+
return Created
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
const function Own(Channel: Channel): Cursor.Cursor
|
|
103
|
+
if
|
|
104
|
+
Channel.Tracked
|
|
105
|
+
and (Shared.Offset ~= Channel.LastShared or Shared.InstancesOffset ~= Channel.LastSharedInstances)
|
|
106
|
+
then
|
|
107
|
+
const Marks = Channel.Marks
|
|
108
|
+
const At = Channel.MarkCount * 4
|
|
109
|
+
const Reliable = Channel.Reliable
|
|
110
|
+
|
|
111
|
+
Marks[At + 1] = Shared.Offset
|
|
112
|
+
Marks[At + 2] = Shared.InstancesOffset
|
|
113
|
+
Marks[At + 3] = Reliable.Offset
|
|
114
|
+
Marks[At + 4] = Reliable.InstancesOffset
|
|
115
|
+
|
|
116
|
+
Channel.MarkCount += 1
|
|
117
|
+
Channel.LastShared = Shared.Offset
|
|
118
|
+
Channel.LastSharedInstances = Shared.InstancesOffset
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
return Channel.Reliable
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
const function GetId(Definition: Definition): number
|
|
125
|
+
const Id = Definition.Id
|
|
126
|
+
|
|
127
|
+
if Id == nil then
|
|
128
|
+
error(`[swiftpacket] {Definition.Name} was never registered`, 0)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return Id
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
const function CheckUnreliable(Definition: Definition, Id: number)
|
|
135
|
+
const Size = Wire.Size(Id, nil, Scratch)
|
|
136
|
+
|
|
137
|
+
if Size > Configuration.UnreliableByteLimit then
|
|
138
|
+
error(
|
|
139
|
+
`[swiftpacket] {Definition.Name} is {Size} bytes, over the unreliable limit of {Configuration.UnreliableByteLimit}`,
|
|
140
|
+
0
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
const function DeliverUnreliable(Channel: Channel, Id: number)
|
|
146
|
+
const Size = Wire.Size(Id, nil, Scratch)
|
|
147
|
+
local Segment = Channel.Unreliable[Channel.UnreliableCount]
|
|
148
|
+
|
|
149
|
+
if Segment.Offset > 0 and Segment.Offset + Size > Configuration.UnreliableByteLimit then
|
|
150
|
+
Channel.UnreliableCount += 1
|
|
151
|
+
|
|
152
|
+
const Next = Channel.Unreliable[Channel.UnreliableCount]
|
|
153
|
+
|
|
154
|
+
if Next then
|
|
155
|
+
Segment = Next
|
|
156
|
+
else
|
|
157
|
+
Segment = Cursor.New(Configuration.UnreliableByteLimit)
|
|
158
|
+
Channel.Unreliable[Channel.UnreliableCount] = Segment
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
Wire.Deliver(Segment, Id, nil, nil, Scratch)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
function Server.Register(Definition: Definition)
|
|
166
|
+
const Remote = ReliableRemote
|
|
167
|
+
|
|
168
|
+
if Remote == nil then
|
|
169
|
+
error("[swiftpacket] the server transport has not started", 0)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
const Id = NextId
|
|
173
|
+
NextId += 1
|
|
174
|
+
|
|
175
|
+
Definition.Id = Id
|
|
176
|
+
Registry.ById[Id] = Definition
|
|
177
|
+
Remote:SetAttribute(Definition.Name, Id)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
function Server.SendTo(Player: Player, Definition: Definition, ...: any)
|
|
181
|
+
const Id = GetId(Definition)
|
|
182
|
+
|
|
183
|
+
if Player.Parent == nil then
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if Definition.Reliable then
|
|
188
|
+
Schema.WriteEvent(Own(GetChannel(Player)), Id, Definition.Name, Definition.Arguments, ...)
|
|
189
|
+
|
|
190
|
+
return
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
Schema.WriteBody(Scratch, Definition.Name, Definition.Arguments, ...)
|
|
194
|
+
CheckUnreliable(Definition, Id)
|
|
195
|
+
DeliverUnreliable(GetChannel(Player), Id)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
function Server.SendMany(Recipients: { Player }, Excluded: Player?, Definition: Definition, ...: any)
|
|
199
|
+
const Id = GetId(Definition)
|
|
200
|
+
|
|
201
|
+
Schema.WriteBody(Scratch, Definition.Name, Definition.Arguments, ...)
|
|
202
|
+
|
|
203
|
+
if not Definition.Reliable then
|
|
204
|
+
CheckUnreliable(Definition, Id)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
for Index = 1, #Recipients do
|
|
208
|
+
const Player = Recipients[Index]
|
|
209
|
+
|
|
210
|
+
if Player == Excluded or Player.Parent == nil then
|
|
211
|
+
continue
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
const Channel = GetChannel(Player)
|
|
215
|
+
|
|
216
|
+
if Definition.Reliable then
|
|
217
|
+
Wire.Deliver(Own(Channel), Id, nil, nil, Scratch)
|
|
218
|
+
else
|
|
219
|
+
DeliverUnreliable(Channel, Id)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
function Server.Broadcast(Definition: Definition, Excluded: Player?, ...: any)
|
|
225
|
+
if Excluded == nil and Definition.Reliable then
|
|
226
|
+
Schema.WriteEvent(Shared, GetId(Definition), Definition.Name, Definition.Arguments, ...)
|
|
227
|
+
|
|
228
|
+
return
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
Server.SendMany(Everyone, Excluded, Definition, ...)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
function Server.Invoke(Player: Player, Definition: Definition, ...: any): ...any
|
|
235
|
+
const Id = GetId(Definition)
|
|
236
|
+
|
|
237
|
+
if Player.Parent == nil then
|
|
238
|
+
return Wire.Unpack(Definition.TimeoutValues)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
const Calls = PendingCalls[Player] or {}
|
|
242
|
+
PendingCalls[Player] = Calls
|
|
243
|
+
|
|
244
|
+
local CallIndex = NextCallIndex[Player] or 0
|
|
245
|
+
local Found = false
|
|
246
|
+
|
|
247
|
+
for Attempt = 1, Configuration.CallCapacity do
|
|
248
|
+
if Calls[CallIndex] == nil then
|
|
249
|
+
Found = true
|
|
250
|
+
break
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
CallIndex = (CallIndex + 1) % Configuration.CallCapacity
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if not Found then
|
|
257
|
+
error(`[swiftpacket] {Player.Name} has too many calls waiting on a response`, 0)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
Schema.WriteRequest(Own(GetChannel(Player)), Id, CallIndex, Definition.Name, Definition.Arguments, ...)
|
|
261
|
+
|
|
262
|
+
NextCallIndex[Player] = (CallIndex + 1) % Configuration.CallCapacity
|
|
263
|
+
Calls[CallIndex] = {
|
|
264
|
+
Thread = coroutine.running(),
|
|
265
|
+
Deadline = os.clock() + Definition.TimeoutSeconds,
|
|
266
|
+
Definition = Definition,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return coroutine.yield()
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
const function Respond(Player: Player, Definition: Definition, CallIndex: number, ...: any)
|
|
273
|
+
const Handler = Definition.OnServerInvoke
|
|
274
|
+
local Results: Arguments = { n = 0 }
|
|
275
|
+
|
|
276
|
+
if Handler then
|
|
277
|
+
Results = table.pack(pcall(Handler, Player, ...))
|
|
278
|
+
else
|
|
279
|
+
Results = { n = 2, false, "no OnServerInvoke was set" }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
if Player.Parent == nil then
|
|
283
|
+
return
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
const Ok, Problem = Wire.EncodeResults(Scratch, Definition, Results)
|
|
287
|
+
|
|
288
|
+
if not Ok then
|
|
289
|
+
warn(`[swiftpacket] call from {Player.Name} failed: {Problem}`)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
Wire.DeliverResponse(Own(GetChannel(Player)), GetId(Definition), CallIndex, if Ok then Scratch else nil)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
const function Admit(Player: Player, Definition: Definition, ...: any): boolean
|
|
296
|
+
const Limit = Definition.RateLimitCount
|
|
297
|
+
const Interval = Definition.RateLimitInterval
|
|
298
|
+
|
|
299
|
+
if Limit and Interval then
|
|
300
|
+
const PerPlayer = Windows[Definition] or {}
|
|
301
|
+
Windows[Definition] = PerPlayer
|
|
302
|
+
|
|
303
|
+
const Current = PerPlayer[Player] or { Count = 0, EndsAt = 0 }
|
|
304
|
+
const Allowed, Count, EndsAt = RateLimit.Window(Current.Count, Current.EndsAt, os.clock(), Limit, Interval)
|
|
305
|
+
|
|
306
|
+
Current.Count = Count
|
|
307
|
+
Current.EndsAt = EndsAt
|
|
308
|
+
PerPlayer[Player] = Current
|
|
309
|
+
|
|
310
|
+
if not Allowed then
|
|
311
|
+
Configuration.Warn(`[swiftpacket] {Player.Name} hit the rate limit on {Definition.Name}`)
|
|
312
|
+
|
|
313
|
+
return false
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
const Validator = Definition.Validator
|
|
318
|
+
|
|
319
|
+
if Validator then
|
|
320
|
+
const Ok, Valid = pcall(Validator, Player, ...)
|
|
321
|
+
|
|
322
|
+
if not Ok or Valid ~= true then
|
|
323
|
+
Configuration.Warn(`[swiftpacket] validation rejected {Definition.Name} from {Player.Name}`)
|
|
324
|
+
|
|
325
|
+
return false
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
return true
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
const function Resolve(Player: Player, Definition: Definition, CallIndex: number, Success: boolean, ...: any)
|
|
333
|
+
const Calls = PendingCalls[Player]
|
|
334
|
+
const Call = if Calls then Calls[CallIndex] else nil
|
|
335
|
+
|
|
336
|
+
if Calls == nil or Call == nil or Call.Definition ~= Definition then
|
|
337
|
+
Configuration.Warn(`[swiftpacket] ignored an unexpected response to {Definition.Name} from {Player.Name}`)
|
|
338
|
+
|
|
339
|
+
return
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
Calls[CallIndex] = nil
|
|
343
|
+
|
|
344
|
+
if Success then
|
|
345
|
+
task.defer(Call.Thread, ...)
|
|
346
|
+
else
|
|
347
|
+
task.defer(Call.Thread, Wire.Unpack(Definition.TimeoutValues))
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
const function Charge(Player: Player, Cost: number): boolean
|
|
352
|
+
const Now = os.clock()
|
|
353
|
+
const Rate = Configuration.IncomingBytesPerSecond
|
|
354
|
+
const Current = Budgets[Player] or { Tokens = Rate, UpdatedAt = Now }
|
|
355
|
+
const Tokens = RateLimit.Refill(Current.Tokens, Now - Current.UpdatedAt, Rate, Rate)
|
|
356
|
+
|
|
357
|
+
Current.UpdatedAt = Now
|
|
358
|
+
Budgets[Player] = Current
|
|
359
|
+
|
|
360
|
+
if Tokens < Cost then
|
|
361
|
+
Current.Tokens = Tokens
|
|
362
|
+
|
|
363
|
+
return false
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
Current.Tokens = Tokens - Cost
|
|
367
|
+
|
|
368
|
+
return true
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
const function Dispatch(Player: Player)
|
|
372
|
+
const Values = Received.Values
|
|
373
|
+
|
|
374
|
+
for Index = 1, Received.Count do
|
|
375
|
+
const Definition = Received.Definitions[Index]
|
|
376
|
+
const CallIndex = Received.CallIndices[Index]
|
|
377
|
+
const First = Received.Starts[Index]
|
|
378
|
+
const Last = First + Received.Counts[Index] - 1
|
|
379
|
+
|
|
380
|
+
if CallIndex >= ResponseFlag then
|
|
381
|
+
Resolve(
|
|
382
|
+
Player,
|
|
383
|
+
Definition,
|
|
384
|
+
CallIndex - ResponseFlag,
|
|
385
|
+
Received.Successes[Index],
|
|
386
|
+
table.unpack(Values, First, Last)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
continue
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
if
|
|
393
|
+
(Definition.RateLimitCount or Definition.Validator)
|
|
394
|
+
and not Admit(Player, Definition, table.unpack(Values, First, Last))
|
|
395
|
+
then
|
|
396
|
+
if CallIndex ~= EventIndex then
|
|
397
|
+
Wire.DeliverResponse(Own(GetChannel(Player)), GetId(Definition), CallIndex, nil)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
continue
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if CallIndex ~= EventIndex then
|
|
404
|
+
ThreadPool.Defer(Respond, Player, Definition, CallIndex, table.unpack(Values, First, Last))
|
|
405
|
+
elseif First == Last then
|
|
406
|
+
Definition.OnServerEvent:Fire(Player, Values[First])
|
|
407
|
+
else
|
|
408
|
+
Definition.OnServerEvent:Fire(Player, table.unpack(Values, First, Last))
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
const function Receive(Player: Player, Source: any, Instances: any)
|
|
414
|
+
if typeof(Source) ~= "buffer" then
|
|
415
|
+
Configuration.Warn(`[swiftpacket] rejected a batch from {Player.Name} that was not a buffer`)
|
|
416
|
+
|
|
417
|
+
return
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
local Attached = NoInstances
|
|
421
|
+
|
|
422
|
+
if Instances ~= nil then
|
|
423
|
+
if type(Instances) ~= "table" then
|
|
424
|
+
Configuration.Warn(`[swiftpacket] rejected a batch from {Player.Name} with invalid instances`)
|
|
425
|
+
|
|
426
|
+
return
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
Attached = Instances
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
if not Charge(Player, buffer.len(Source) + #Attached * 8 + 64) then
|
|
433
|
+
Configuration.Warn(`[swiftpacket] {Player.Name} is over the incoming byte budget, dropped a batch`)
|
|
434
|
+
|
|
435
|
+
return
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
const Ok, Result = pcall(Wire.Decode, Received, Source, Attached, true, 0, 0)
|
|
439
|
+
|
|
440
|
+
if not Ok or Result ~= nil then
|
|
441
|
+
Wire.Release(Received)
|
|
442
|
+
|
|
443
|
+
if Ok then
|
|
444
|
+
Configuration.Warn(`[swiftpacket] rejected a batch from {Player.Name} with the unknown packet id {Result}`)
|
|
445
|
+
else
|
|
446
|
+
Configuration.Warn(`[swiftpacket] rejected a batch from {Player.Name}: {tostring(Result)}`)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
return
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
const MessageCount = Received.Count
|
|
453
|
+
|
|
454
|
+
if not Charge(Player, MessageCount * Configuration.MessageCost) then
|
|
455
|
+
Wire.Release(Received)
|
|
456
|
+
Configuration.Warn(`[swiftpacket] {Player.Name} sent too many messages, dropped a batch of {MessageCount}`)
|
|
457
|
+
|
|
458
|
+
return
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
Dispatch(Player)
|
|
462
|
+
Wire.Release(Received)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
const function FireSegment(Remote: RemoteEvent | UnreliableRemoteEvent, Player: Player, Segment: Cursor.Cursor)
|
|
466
|
+
if Segment.Offset == 0 then
|
|
467
|
+
return
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
const Data, Attached = Cursor.Take(Segment)
|
|
471
|
+
Cursor.Reset(Segment)
|
|
472
|
+
|
|
473
|
+
if Remote:IsA("RemoteEvent") then
|
|
474
|
+
if Attached then
|
|
475
|
+
Remote:FireClient(Player, Data, Attached)
|
|
476
|
+
else
|
|
477
|
+
Remote:FireClient(Player, Data)
|
|
478
|
+
end
|
|
479
|
+
elseif Remote:IsA("UnreliableRemoteEvent") then
|
|
480
|
+
if Attached then
|
|
481
|
+
Remote:FireClient(Player, Data, Attached)
|
|
482
|
+
else
|
|
483
|
+
Remote:FireClient(Player, Data)
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
const function CopyBytes(Target: buffer, Written: number, Source: buffer, From: number, To: number): number
|
|
489
|
+
if To <= From then
|
|
490
|
+
return Written
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
buffer.copy(Target, Written, Source, From, To - From)
|
|
494
|
+
|
|
495
|
+
return Written + To - From
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
const function CopyInstances(Target: { any }?, Written: number, Source: { any }, From: number, To: number): number
|
|
499
|
+
if Target == nil then
|
|
500
|
+
return Written
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
local Next = Written
|
|
504
|
+
|
|
505
|
+
for Index = From + 1, To do
|
|
506
|
+
Next += 1
|
|
507
|
+
Target[Next] = Source[Index]
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
return Next
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
const function Compose(Channel: Channel): (buffer, { any }?)
|
|
514
|
+
const Reliable = Channel.Reliable
|
|
515
|
+
const Marks = Channel.Marks
|
|
516
|
+
const InstanceCount = Shared.InstancesOffset - Channel.SharedInstancesFrom + Reliable.InstancesOffset
|
|
517
|
+
const Data = buffer.create(Shared.Offset - Channel.SharedFrom + Reliable.Offset)
|
|
518
|
+
const Attached: { any }? = if InstanceCount > 0 then table.create(InstanceCount) else nil
|
|
519
|
+
|
|
520
|
+
local Written, InstancesWritten = 0, 0
|
|
521
|
+
local OwnAt, OwnInstancesAt = 0, 0
|
|
522
|
+
local SharedAt, SharedInstancesAt = Channel.SharedFrom, Channel.SharedInstancesFrom
|
|
523
|
+
|
|
524
|
+
for Mark = 0, Channel.MarkCount - 1 do
|
|
525
|
+
const At = Mark * 4
|
|
526
|
+
const SharedTo, SharedInstancesTo = Marks[At + 1], Marks[At + 2]
|
|
527
|
+
const OwnTo, OwnInstancesTo = Marks[At + 3], Marks[At + 4]
|
|
528
|
+
|
|
529
|
+
Written = CopyBytes(Data, Written, Reliable.Buffer, OwnAt, OwnTo)
|
|
530
|
+
InstancesWritten = CopyInstances(Attached, InstancesWritten, Reliable.Instances, OwnInstancesAt, OwnInstancesTo)
|
|
531
|
+
Written = CopyBytes(Data, Written, Shared.Buffer, SharedAt, SharedTo)
|
|
532
|
+
InstancesWritten =
|
|
533
|
+
CopyInstances(Attached, InstancesWritten, Shared.Instances, SharedInstancesAt, SharedInstancesTo)
|
|
534
|
+
|
|
535
|
+
OwnAt, OwnInstancesAt = OwnTo, OwnInstancesTo
|
|
536
|
+
SharedAt, SharedInstancesAt = SharedTo, SharedInstancesTo
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
Written = CopyBytes(Data, Written, Reliable.Buffer, OwnAt, Reliable.Offset)
|
|
540
|
+
InstancesWritten =
|
|
541
|
+
CopyInstances(Attached, InstancesWritten, Reliable.Instances, OwnInstancesAt, Reliable.InstancesOffset)
|
|
542
|
+
CopyBytes(Data, Written, Shared.Buffer, SharedAt, Shared.Offset)
|
|
543
|
+
CopyInstances(Attached, InstancesWritten, Shared.Instances, SharedInstancesAt, Shared.InstancesOffset)
|
|
544
|
+
|
|
545
|
+
return Data, Attached
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
const function FireData(Remote: RemoteEvent, Player: Player, Data: buffer, Attached: { any }?)
|
|
549
|
+
if Attached then
|
|
550
|
+
Remote:FireClient(Player, Data, Attached)
|
|
551
|
+
else
|
|
552
|
+
Remote:FireClient(Player, Data)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
const function ResetChannel(Channel: Channel)
|
|
557
|
+
Cursor.Reset(Channel.Reliable)
|
|
558
|
+
table.clear(Channel.Marks)
|
|
559
|
+
|
|
560
|
+
Channel.MarkCount = 0
|
|
561
|
+
Channel.SharedFrom = 0
|
|
562
|
+
Channel.SharedInstancesFrom = 0
|
|
563
|
+
Channel.LastShared = 0
|
|
564
|
+
Channel.LastSharedInstances = 0
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
const function Flush()
|
|
568
|
+
const Reliable = ReliableRemote
|
|
569
|
+
const Unreliable = UnreliableRemote
|
|
570
|
+
|
|
571
|
+
if Reliable == nil or Unreliable == nil then
|
|
572
|
+
return
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
local SharedData = NoBytes
|
|
576
|
+
local SharedAttached: { any }? = nil
|
|
577
|
+
|
|
578
|
+
if Shared.Offset > 0 then
|
|
579
|
+
SharedData, SharedAttached = Cursor.Take(Shared)
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
for Player, Channel in Channels do
|
|
583
|
+
const SharedBytes = if Channel.Tracked then Shared.Offset - Channel.SharedFrom else 0
|
|
584
|
+
|
|
585
|
+
if SharedBytes == 0 then
|
|
586
|
+
FireSegment(Reliable, Player, Channel.Reliable)
|
|
587
|
+
elseif Channel.Reliable.Offset == 0 and Channel.SharedFrom == 0 and Channel.SharedInstancesFrom == 0 then
|
|
588
|
+
FireData(Reliable, Player, SharedData, SharedAttached)
|
|
589
|
+
else
|
|
590
|
+
const Data, Attached = Compose(Channel)
|
|
591
|
+
FireData(Reliable, Player, Data, Attached)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
ResetChannel(Channel)
|
|
595
|
+
|
|
596
|
+
for Index = 1, Channel.UnreliableCount do
|
|
597
|
+
FireSegment(Unreliable, Player, Channel.Unreliable[Index])
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
Channel.UnreliableCount = 1
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
Cursor.Reset(Shared)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
const function Expire()
|
|
607
|
+
const Now = os.clock()
|
|
608
|
+
|
|
609
|
+
for Player, Calls in PendingCalls do
|
|
610
|
+
for CallIndex, Call in Calls do
|
|
611
|
+
if Now >= Call.Deadline then
|
|
612
|
+
Calls[CallIndex] = nil
|
|
613
|
+
task.defer(Call.Thread, Wire.Unpack(Call.Definition.TimeoutValues))
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
const function Track(Player: Player)
|
|
620
|
+
if table.find(Everyone, Player) == nil then
|
|
621
|
+
table.insert(Everyone, Player)
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
const Channel = GetChannel(Player)
|
|
625
|
+
|
|
626
|
+
if not Channel.Tracked then
|
|
627
|
+
Channel.Tracked = true
|
|
628
|
+
Channel.SharedFrom = Shared.Offset
|
|
629
|
+
Channel.SharedInstancesFrom = Shared.InstancesOffset
|
|
630
|
+
Channel.LastShared = Shared.Offset
|
|
631
|
+
Channel.LastSharedInstances = Shared.InstancesOffset
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
const function Cleanup(Player: Player)
|
|
636
|
+
const Calls = PendingCalls[Player]
|
|
637
|
+
|
|
638
|
+
if Calls then
|
|
639
|
+
for CallIndex, Call in Calls do
|
|
640
|
+
task.defer(Call.Thread, Wire.Unpack(Call.Definition.TimeoutValues))
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
const Index = table.find(Everyone, Player)
|
|
645
|
+
|
|
646
|
+
if Index then
|
|
647
|
+
table.remove(Everyone, Index)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
PendingCalls[Player] = nil
|
|
651
|
+
NextCallIndex[Player] = nil
|
|
652
|
+
Channels[Player] = nil
|
|
653
|
+
Budgets[Player] = nil
|
|
654
|
+
|
|
655
|
+
for Definition, PerPlayer in Windows do
|
|
656
|
+
PerPlayer[Player] = nil
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
function Server.Start(Root: Instance)
|
|
661
|
+
if ReliableRemote then
|
|
662
|
+
return
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
const Reliable = Instance.new("RemoteEvent")
|
|
666
|
+
Reliable.Name = "Reliable"
|
|
667
|
+
Reliable.Parent = Root
|
|
668
|
+
|
|
669
|
+
const Unreliable = Instance.new("UnreliableRemoteEvent")
|
|
670
|
+
Unreliable.Name = "Unreliable"
|
|
671
|
+
Unreliable.Parent = Root
|
|
672
|
+
|
|
673
|
+
ReliableRemote = Reliable
|
|
674
|
+
UnreliableRemote = Unreliable
|
|
675
|
+
|
|
676
|
+
Reliable.OnServerEvent:Connect(Receive)
|
|
677
|
+
Unreliable.OnServerEvent:Connect(Receive)
|
|
678
|
+
Players.PlayerAdded:Connect(Track)
|
|
679
|
+
Players.PlayerRemoving:Connect(Cleanup)
|
|
680
|
+
|
|
681
|
+
for Index, Player in Players:GetPlayers() do
|
|
682
|
+
Track(Player)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
RunService.Heartbeat:Connect(function()
|
|
686
|
+
Expire()
|
|
687
|
+
task.defer(Flush)
|
|
688
|
+
end)
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
return Server
|