@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.
@@ -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