@rbxts/replecs 0.0.1-rc.0

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,918 @@
1
+ --!optimize 2
2
+ --!native
3
+
4
+ local jecs = require(script.Parent.Parent.jecs)
5
+ local utils = require(script.Parent.utils)
6
+ local common = require(script.Parent.common)
7
+ local masking_controller = require(script.Parent.masking)
8
+
9
+ type Cursor = utils.Cursor
10
+
11
+ type World = jecs.World
12
+ type Entity<T = any> = jecs.Entity<T>
13
+
14
+ type Set<T> = { [T]: boolean }
15
+ type Map<K, V> = { [K]: V }
16
+ type Array<T> = { T }
17
+
18
+ type TrackInfo = {
19
+ entities: { [Entity]: boolean },
20
+ components: { [Entity]: { [Entity]: number } },
21
+ }
22
+
23
+ type EntityStorage = {
24
+ tags: { [Entity]: boolean },
25
+ values: { [Entity]: any },
26
+ pairs: { [Entity]: Set<Entity> },
27
+ }
28
+
29
+ type Storage = { [Entity]: EntityStorage }
30
+
31
+ export type Server = {
32
+ world: World,
33
+ inited: boolean?,
34
+
35
+ shared: common.Shared,
36
+
37
+ components: common.Components,
38
+ hooks: common.WorldHooks,
39
+ alive_tracked: { [Entity]: boolean },
40
+
41
+ track_info: TrackInfo,
42
+ additions: { [Entity]: boolean },
43
+ custom_ids: { [Entity]: Entity },
44
+ storage: Storage,
45
+ hooked: { () -> () },
46
+ masking: masking_controller.MaskingController,
47
+ connections: { RBXScriptConnection },
48
+
49
+ init: (self: Server, world: World?) -> (),
50
+ destroy: (self: Server) -> (),
51
+
52
+ get_full: (self: Server, player: Player) -> (buffer, { { any } }),
53
+ collect_updates: (self: Server) -> () -> (Player, buffer, { { any } }),
54
+ collect_unreliable: (self: Server) -> () -> (Player, buffer, { { any } }),
55
+ mark_player_ready: (self: Server, player: Player) -> (),
56
+ is_player_ready: (self: Server, player: Player) -> boolean,
57
+ }
58
+
59
+ local cursor = utils.cursor
60
+
61
+ local NIL_COMPONENT_VALUE = newproxy()
62
+
63
+ local COMPONENT_TYPES = masking_controller.COMPONENT_TYPES
64
+ local TRACK_TYPES = {
65
+ component = 1,
66
+ tag = 2,
67
+ pair = 3,
68
+ }
69
+ local ENTITY_ID_TYPES = {
70
+ id = 1,
71
+ custom = 2,
72
+ shared = 3,
73
+ }
74
+ local ECS_COMPONENT = jecs.Component
75
+
76
+ local function IS_PAIR(id: Entity): boolean
77
+ return jecs.IS_PAIR(id)
78
+ end
79
+ local function PAIR_SECOND(world: World, id: Entity): Entity
80
+ return jecs.pair_second(world, id)
81
+ end
82
+ local function ECS_ID(entity: Entity): number
83
+ return jecs.ECS_ID(entity :: any)
84
+ end
85
+
86
+ local function track_entity_lifetime(server: Server, entity: Entity)
87
+ if server.alive_tracked[entity] then
88
+ return
89
+ end
90
+ server.alive_tracked[entity] = true
91
+ server.world:add(entity, server.components.__alive_tracking__)
92
+ end
93
+
94
+ local function get_or_set_entity_storage(server: Server, entity: Entity)
95
+ local storage = server.storage[entity]
96
+
97
+ if not storage then
98
+ storage = {
99
+ tags = {},
100
+ values = {},
101
+ pairs = {},
102
+ }
103
+ server.storage[entity] = storage
104
+ end
105
+
106
+ return storage
107
+ end
108
+
109
+ local function allocate_component_change(server: Server, entity: Entity, component: Entity, value: any)
110
+ local storage = get_or_set_entity_storage(server, entity)
111
+
112
+ local non_nil = if value == nil then NIL_COMPONENT_VALUE else value
113
+
114
+ storage.values[component] = non_nil
115
+ if not server.additions[entity] and server.track_info.entities[entity] then
116
+ server.masking:allocate_component_change(entity, component, non_nil)
117
+ end
118
+ end
119
+ local function allocate_tag_addition(server: Server, entity: Entity, tag: Entity)
120
+ local storage = get_or_set_entity_storage(server, entity)
121
+ storage.tags[tag] = true
122
+
123
+ if not server.additions[entity] and server.track_info.entities[entity] then
124
+ server.masking:allocate_tag_addition(entity, tag)
125
+ end
126
+ end
127
+ local function allocate_pair_addition(server: Server, entity: Entity, relation: Entity, target: Entity)
128
+ local storage = get_or_set_entity_storage(server, entity)
129
+ local targets = storage.pairs[relation]
130
+ if targets == nil then
131
+ targets = {}
132
+ storage.pairs[relation] = targets
133
+ end
134
+ targets[target] = true
135
+
136
+ if not server.additions[entity] and server.track_info.entities[entity] then
137
+ server.masking:allocate_pair_addition(entity, relation, target)
138
+ end
139
+ end
140
+
141
+ local function allocate_component_removal(server: Server, entity: Entity, component: Entity)
142
+ local storage = get_or_set_entity_storage(server, entity)
143
+ storage.values[component] = nil
144
+
145
+ if not server.additions[entity] and server.track_info.entities[entity] then
146
+ server.masking:allocate_component_removal(entity, component)
147
+ end
148
+ end
149
+ local function allocate_tag_removal(server: Server, entity: Entity, tag: Entity)
150
+ local storage = get_or_set_entity_storage(server, entity)
151
+ storage.tags[tag] = nil
152
+
153
+ if not server.additions[entity] and server.track_info.entities[entity] then
154
+ server.masking:allocate_tag_removal(entity, tag)
155
+ end
156
+ end
157
+ local function allocate_pair_removal(server: Server, entity: Entity, relation: Entity, target: Entity)
158
+ local storage = get_or_set_entity_storage(server, entity)
159
+ local targets = storage.pairs[relation]
160
+ if targets then
161
+ targets[target] = nil
162
+ end
163
+
164
+ if not server.additions[entity] and server.track_info.entities[entity] then
165
+ server.masking:allocate_pair_removal(entity, relation, target)
166
+ end
167
+ end
168
+
169
+ local function track_component(server: Server, component: Entity<any>)
170
+ local world = server.world
171
+ local hooks = server.hooks
172
+
173
+ local info = {}
174
+ server.track_info.components[component] = info
175
+
176
+ local function hook(unhook: () -> ())
177
+ table.insert(server.hooked, unhook)
178
+ end
179
+
180
+ hook(hooks.added(world, component, function(entity, id, value)
181
+ local track_type = info[entity]
182
+ if not track_type then
183
+ return
184
+ end
185
+
186
+ if track_type == TRACK_TYPES.component then
187
+ allocate_component_change(server, entity, component, value)
188
+ elseif track_type == TRACK_TYPES.tag then
189
+ allocate_tag_addition(server, entity, component)
190
+ else
191
+ if not IS_PAIR(id) then
192
+ utils.logerror "a non-pair tag was added to a pair tracked component"
193
+ end
194
+ allocate_pair_addition(server, entity, component, PAIR_SECOND(world, id))
195
+ end
196
+ end))
197
+ hook(hooks.changed(world, component, function(entity, id, value)
198
+ local track_type = info[entity]
199
+ if not track_type then
200
+ return
201
+ end
202
+ -- no check here because only components can trigger changed (I think?)
203
+ allocate_component_change(server, entity, component, value)
204
+ end))
205
+ hook(hooks.removed(world, component, function(entity, id)
206
+ local track_type = info[entity]
207
+ if not track_type then
208
+ return
209
+ end
210
+
211
+ if track_type == TRACK_TYPES.component then
212
+ allocate_component_removal(server, entity, component)
213
+ elseif track_type == TRACK_TYPES.tag then
214
+ allocate_tag_removal(server, entity, component)
215
+ else
216
+ if not IS_PAIR(id) then
217
+ utils.logerror "a non-pair tag was removed for a pair tracked component"
218
+ end
219
+ allocate_pair_removal(server, entity, component, PAIR_SECOND(world, id))
220
+ end
221
+ end))
222
+
223
+ return info
224
+ end
225
+
226
+ local function track_entity_component(server: Server, entity: Entity, component: Entity<any>)
227
+ local world = server.world
228
+
229
+ local info = server.track_info.components[component]
230
+ if not info then
231
+ info = track_component(server, component)
232
+ end
233
+
234
+ local entity_storage = get_or_set_entity_storage(server, entity)
235
+
236
+ if world:has(component, ECS_COMPONENT) then
237
+ -- component
238
+ if world:has(entity, component) then
239
+ local value = world:get(entity, component)
240
+
241
+ if value == nil then
242
+ entity_storage.values[component] = NIL_COMPONENT_VALUE
243
+ else
244
+ entity_storage.values[component] = value
245
+ end
246
+ end
247
+
248
+ info[entity] = TRACK_TYPES.component
249
+
250
+ return COMPONENT_TYPES.component
251
+ else
252
+ -- tag
253
+ if world:has(entity, component) then
254
+ entity_storage.tags[component] = true
255
+ end
256
+ info[entity] = TRACK_TYPES.tag
257
+
258
+ return COMPONENT_TYPES.tag
259
+ end
260
+ end
261
+
262
+ local function track_entity_pair(server: Server, entity: Entity, relation: Entity)
263
+ local world = server.world
264
+ local info = server.track_info.components[relation]
265
+ if not info then
266
+ info = track_component(server, relation)
267
+ end
268
+
269
+ local entity_storage = get_or_set_entity_storage(server, entity)
270
+ local targets = {}
271
+ local index = 0
272
+
273
+ while true do
274
+ local target = world:target(entity, relation, index)
275
+ if not target then
276
+ break
277
+ end
278
+ index += 1
279
+ targets[target] = true
280
+ end
281
+ entity_storage.pairs[relation] = targets
282
+
283
+ info[entity] = TRACK_TYPES.pair
284
+ end
285
+
286
+ local function untrack_entity_component(server: Server, entity: Entity, component: Entity<any>): number?
287
+ local info = server.track_info.components[component]
288
+ if not info then
289
+ return nil
290
+ end
291
+ local listen_type = info[entity]
292
+ info[entity] = nil
293
+
294
+ local storage = server.storage[entity]
295
+ if storage then
296
+ storage.values[component] = nil
297
+ storage.tags[component] = nil
298
+ end
299
+
300
+ return listen_type
301
+ end
302
+
303
+ local function untrack_entity_pair(server: Server, entity: Entity, relation: Entity)
304
+ local info = server.track_info.components[relation]
305
+ if not info then
306
+ return
307
+ end
308
+ info[entity] = nil
309
+
310
+ local storage = server.storage[entity]
311
+ if storage then
312
+ storage.pairs[relation] = nil
313
+ end
314
+ end
315
+
316
+ local function write_component_id(server: Server, c: Cursor, component: Entity)
317
+ local encoded = server.shared.ids[component]
318
+ if not encoded then
319
+ utils.logerror(`attempted to replicate a non-shared component `, utils.logcomponent(server.world, component))
320
+ return
321
+ end
322
+ cursor.writeu8(c, encoded)
323
+ end
324
+
325
+ local function write_component_value(server: Server, c: Cursor, component: Entity, value: any, variants: { any })
326
+ local serdes = server.shared.serdes[component]
327
+ if serdes then
328
+ local output = serdes.serialize(if value == NIL_COMPONENT_VALUE then nil else value)
329
+
330
+ local bytespan = server.shared.bytespan[component]
331
+ cursor.write_buffer(c, output)
332
+ if bytespan then
333
+ if bytespan ~= buffer.len(output) then
334
+ utils.logerror(
335
+ `bytespan: {bytespan} mismatch for buffer lenght: {buffer.len(output)} in component`,
336
+ utils.logcomponent(server.world, component)
337
+ )
338
+ end
339
+ else
340
+ local len = buffer.len(output)
341
+ cursor.write_vlq(c, len)
342
+ end
343
+ else
344
+ if value == NIL_COMPONENT_VALUE then
345
+ -- this is zero for vlq
346
+ cursor.writeu8(c, 128)
347
+ else
348
+ table.insert(variants, value :: any)
349
+ cursor.write_vlq(c, #variants)
350
+ end
351
+ end
352
+
353
+ write_component_id(server, c, component)
354
+ end
355
+
356
+ local function write_entity_id(server: Server, c: Cursor, entity: Entity, variants: { any }, multiply: number?)
357
+ local custom = server.custom_ids[entity]
358
+
359
+ if custom then
360
+ local value = server.world:get(entity, custom)
361
+ write_component_value(server, c, custom, value, variants)
362
+ cursor.writei8(c, ENTITY_ID_TYPES.custom * (multiply or 1))
363
+ else
364
+ local shared_id = server.shared.ids[entity]
365
+ if shared_id then
366
+ cursor.writeu8(c, shared_id)
367
+ cursor.writei8(c, ENTITY_ID_TYPES.shared * (multiply or 1))
368
+ else
369
+ cursor.writeu24(c, ECS_ID(entity :: any))
370
+ cursor.writei8(c, ENTITY_ID_TYPES.id * (multiply or 1))
371
+ end
372
+ end
373
+ end
374
+
375
+ local function write_entity(
376
+ server: Server,
377
+ c: Cursor,
378
+ entity: Entity,
379
+ active: masking_controller.ActiveEntity,
380
+ variants: { any }
381
+ )
382
+ local storage = server.storage[entity]
383
+
384
+ local all_pairs = 0
385
+ for relation in active.components[COMPONENT_TYPES.pair] do
386
+ local targets = storage.pairs[relation]
387
+ local total_targets = 0
388
+
389
+ for target in targets do
390
+ write_entity_id(server, c, target, variants)
391
+ total_targets += 1
392
+ end
393
+ cursor.write_vlq(c, total_targets)
394
+ write_component_id(server, c, relation)
395
+ all_pairs += 1
396
+ end
397
+ cursor.write_vlq(c, all_pairs)
398
+
399
+ local components = 0
400
+ for component in active.components[COMPONENT_TYPES.component] do
401
+ local value = storage.values[component]
402
+ write_component_value(server, c, component, value, variants)
403
+ components += 1
404
+ end
405
+ cursor.write_vlq(c, components)
406
+
407
+ local tags = 0
408
+ for tag in active.components[COMPONENT_TYPES.tag] do
409
+ local has_tag = storage.tags[tag]
410
+ if has_tag then
411
+ write_component_id(server, c, tag)
412
+ tags += 1
413
+ end
414
+ end
415
+ cursor.write_vlq(c, tags)
416
+ write_entity_id(server, c, entity, variants)
417
+ end
418
+
419
+ type MemberPackets = {
420
+ buffers: { buffer },
421
+ variants: { { any } },
422
+ total_size: number,
423
+ }
424
+
425
+ local function append_packet(
426
+ packets: { [Player]: MemberPackets },
427
+ members: { any },
428
+ output: buffer,
429
+ variants: { { any } }
430
+ )
431
+ local len = buffer.len(output)
432
+ for _, member in members do
433
+ local member_packets = packets[member]
434
+
435
+ if member_packets then
436
+ table.insert(member_packets.buffers, output)
437
+ table.insert(member_packets.variants, variants)
438
+ member_packets.total_size += len
439
+ else
440
+ member_packets = {
441
+ buffers = { output },
442
+ variants = { variants },
443
+ total_size = len,
444
+ }
445
+ packets[member] = member_packets :: MemberPackets
446
+ end
447
+ end
448
+ end
449
+
450
+ local function combine_packet_outputs(outputs: { buffer }, total_size: number): buffer
451
+ local combined = buffer.create(total_size + utils.vlq_span(#outputs))
452
+ local offset = 0
453
+
454
+ for _, output in outputs do
455
+ buffer.copy(combined, offset, output)
456
+ offset += buffer.len(output)
457
+ end
458
+ cursor.write_vlq({
459
+ offset = offset,
460
+ buffer = combined,
461
+ }, #outputs)
462
+
463
+ return combined
464
+ end
465
+
466
+ local function create_packet_iterator(packets: { [Player]: MemberPackets })
467
+ local iterated: Player? = nil
468
+ local function iterator()
469
+ local player, packets = next(packets, iterated)
470
+ if not player then
471
+ return nil :: any
472
+ end
473
+ iterated = player
474
+
475
+ local combined = combine_packet_outputs(packets.buffers, packets.total_size)
476
+ return player, combined, packets.variants
477
+ end
478
+ return iterator :: () -> (Player, buffer, { { any } })
479
+ end
480
+
481
+ local function get_full(server: Server, member: Player): (buffer, { { any } })
482
+ local index = server.masking.member_indexes[member]
483
+ if index == nil then
484
+ utils.logerror "attempted to replicate for a non registered member"
485
+ end
486
+ local buffers: { buffer } = {}
487
+ local all_variants: { { any } } = {}
488
+ local total_size = 0
489
+
490
+ -- maybe edges to quickly find all relevant storages improves performance?
491
+ for _, storage in server.masking.storages do
492
+ if not storage.mask.bitmask:get(index) then
493
+ continue
494
+ end
495
+ local c = cursor.new()
496
+ local variants = {}
497
+
498
+ local total_entities = 0
499
+ for entity, active in storage.active do
500
+ write_entity(server, c, entity, active, variants)
501
+ total_entities += 1
502
+ end
503
+ cursor.write_vlq(c, total_entities)
504
+
505
+ local output = cursor.close(c)
506
+ table.insert(buffers, output)
507
+ table.insert(all_variants, variants)
508
+ total_size += buffer.len(output)
509
+ end
510
+
511
+ return combine_packet_outputs(buffers, total_size), all_variants
512
+ end
513
+
514
+ local function collect_updates(server: Server)
515
+ local packets: { [Player]: MemberPackets } = {}
516
+
517
+ for _, storage in server.masking.storages do
518
+ local c = cursor.new()
519
+ local variants = {}
520
+
521
+ local total_deleted = 0
522
+ local entity_deletions = storage.deletions.entities
523
+ for entity in entity_deletions do
524
+ write_entity_id(server, c, entity, variants)
525
+ total_deleted += 1
526
+ end
527
+ table.clear(entity_deletions)
528
+ cursor.write_vlq(c, total_deleted)
529
+
530
+ local total_component_deleted = 0
531
+ local component_deletions = storage.deletions.components
532
+ for entity, deleted in component_deletions do
533
+ local total_pairs = 0
534
+ for relation in deleted[COMPONENT_TYPES.pair] do
535
+ write_component_id(server, c, relation)
536
+ total_pairs += 1
537
+ end
538
+ cursor.write_vlq(c, total_pairs)
539
+
540
+ local total_components = 0
541
+ for component in deleted[COMPONENT_TYPES.component] do
542
+ write_component_id(server, c, component)
543
+ total_components += 1
544
+ end
545
+ cursor.write_vlq(c, total_components)
546
+
547
+ local total_tags = 0
548
+ for tag in deleted[COMPONENT_TYPES.tag] do
549
+ write_component_id(server, c, tag)
550
+ total_tags += 1
551
+ end
552
+ cursor.write_vlq(c, total_tags)
553
+
554
+ write_entity_id(server, c, entity, variants)
555
+ total_component_deleted += 1
556
+ end
557
+ table.clear(component_deletions)
558
+ cursor.write_vlq(c, total_component_deleted)
559
+
560
+ local storage_changes = storage.changes
561
+ local changed = 0
562
+ for entity, changes in storage_changes.changed do
563
+ local total_removed = 0
564
+ for component, value in changes.removed do
565
+ write_component_id(server, c, component)
566
+ total_removed += 1
567
+ end
568
+ cursor.write_vlq(c, total_removed)
569
+
570
+ local total_pairs = 0
571
+ for relation, targets in changes.pairs do
572
+ local total_targets = 0
573
+
574
+ for target, added in targets do
575
+ if added then
576
+ write_entity_id(server, c, target, variants)
577
+ else
578
+ write_entity_id(server, c, target, variants, -1)
579
+ end
580
+ total_targets += 1
581
+ end
582
+ cursor.write_vlq(c, total_targets)
583
+ write_component_id(server, c, relation)
584
+ total_pairs += 1
585
+ end
586
+ cursor.write_vlq(c, total_pairs)
587
+
588
+ local total_components = 0
589
+ for component, value in changes.component do
590
+ write_component_value(server, c, component, value, variants)
591
+ total_components += 1
592
+ end
593
+ cursor.write_vlq(c, total_components)
594
+
595
+ local total_tagged = 0
596
+ for tag in changes.tagged do
597
+ write_component_id(server, c, tag)
598
+ total_tagged += 1
599
+ end
600
+ cursor.write_vlq(c, total_tagged)
601
+
602
+ write_entity_id(server, c, entity, variants)
603
+ changed += 1
604
+ end
605
+ table.clear(storage_changes.changed)
606
+ cursor.write_vlq(c, changed)
607
+
608
+ local total_added = 0
609
+ for entity in storage_changes.added do
610
+ local active = storage.active[entity]
611
+ if not active then
612
+ continue
613
+ end
614
+
615
+ write_entity(server, c, entity, active, variants)
616
+ total_added += 1
617
+ end
618
+ table.clear(storage_changes.added)
619
+ cursor.write_vlq(c, total_added)
620
+
621
+ local output = cursor.close(c)
622
+ append_packet(packets, storage.mask.members, output, variants)
623
+ end
624
+ table.clear(server.additions)
625
+
626
+ return create_packet_iterator(packets)
627
+ end
628
+
629
+ local function collect_unreliable(server: Server)
630
+ -- todo: max packet size (for unreliables 1kb limit)
631
+ local packets: { [Player]: MemberPackets } = {}
632
+ local world = server.world
633
+
634
+ -- maybe more lookups to quickly iterate these too? ;-;
635
+ for _, mask_storage in server.masking.storages do
636
+ local c = cursor.new()
637
+ local variants = {}
638
+
639
+ local active = mask_storage.active
640
+ local total_entities = 0
641
+
642
+ for entity, actives in active do
643
+ local total_unreliable = 0
644
+ for component in actives.components[COMPONENT_TYPES.unreliable] do
645
+ local value = world:get(entity, component)
646
+ if value == nil then
647
+ continue
648
+ end
649
+
650
+ write_component_value(server, c, component, value, variants)
651
+ total_unreliable += 1
652
+ end
653
+
654
+ if total_unreliable <= 0 then
655
+ continue
656
+ end
657
+ total_entities += 1
658
+ cursor.write_vlq(c, total_unreliable)
659
+ write_entity_id(server, c, entity, variants)
660
+ end
661
+
662
+ if total_entities <= 0 then
663
+ continue
664
+ end
665
+ cursor.write_vlq(c, total_entities)
666
+
667
+ local output = cursor.close(c)
668
+ append_packet(packets, mask_storage.mask.members, output, variants)
669
+ end
670
+
671
+ return create_packet_iterator(packets)
672
+ end
673
+
674
+ local function check_created(server: Server)
675
+ if server.inited == true then
676
+ warn "attempted to init a server twice"
677
+ return false
678
+ end
679
+ if server.inited == nil then
680
+ warn "attempted to create a server twice"
681
+ return false
682
+ end
683
+ return true
684
+ end
685
+
686
+ function init(server: Server, _world: World?)
687
+ if not check_created(server) then
688
+ return
689
+ end
690
+ server.inited = true :: any
691
+
692
+ local world = _world or server.world
693
+ server.world = world
694
+
695
+ if not world then
696
+ error "Providing a world is required to start replecs"
697
+ end
698
+
699
+ local hooks = {
700
+ added = (world :: any).added,
701
+ changed = (world :: any).changed,
702
+ removed = (world :: any).removed,
703
+ }
704
+ server.hooks = hooks
705
+
706
+ local components = server.components
707
+ local masking = server.masking
708
+
709
+ server.shared = utils.create_shared_lookup(world, components)
710
+
711
+ local function hook(unhook: () -> ())
712
+ table.insert(server.hooked, unhook)
713
+ end
714
+
715
+ hook(hooks.added(world, components.networked, function(entity, id, filter)
716
+ masking:unregister_stop_entity(entity)
717
+ masking:start_entity(entity, filter)
718
+ masking:propagate_entity_addition(entity)
719
+
720
+ server.additions[entity] = true
721
+ server.track_info.entities[entity] = true
722
+ track_entity_lifetime(server, entity)
723
+ end))
724
+ hook(hooks.changed(world, components.networked, function(entity, id, filter)
725
+ masking:set_entity(entity, filter)
726
+ end))
727
+ hook(hooks.removed(world, components.networked, function(entity)
728
+ masking:register_stop_entity(entity)
729
+ masking:stop_entity(entity)
730
+
731
+ server.additions[entity] = nil
732
+ server.track_info.entities[entity] = nil
733
+ end))
734
+
735
+ hook(hooks.added(world, components.reliable, function(entity, id, filter)
736
+ if not IS_PAIR(id) then
737
+ utils.logerror "reliable should be used with a pair relationship"
738
+ end
739
+ local component = PAIR_SECOND(world, id)
740
+
741
+ local component_type = track_entity_component(server :: Server, entity, component)
742
+ masking:unregister_stop_component(entity, component, component_type)
743
+ masking:start_component(entity, component, component_type, filter)
744
+ if server.additions[entity] then
745
+ masking:allocate_entity_addition(entity, component, component_type)
746
+ end
747
+ end))
748
+ hook(hooks.changed(world, components.reliable, function(entity, id, filter)
749
+ if not IS_PAIR(id) then
750
+ utils.logerror "reliable should be used with a pair relationship"
751
+ end
752
+ local component = PAIR_SECOND(world, id)
753
+
754
+ if world:has(component, ECS_COMPONENT) then
755
+ masking:set_component(entity, component, COMPONENT_TYPES.component, filter)
756
+ else
757
+ masking:set_component(entity, component, COMPONENT_TYPES.tag, filter)
758
+ end
759
+ end))
760
+ hook(hooks.removed(world, components.reliable, function(entity, id)
761
+ if not IS_PAIR(id) then
762
+ utils.logerror "reliable should be used with a pair relationship"
763
+ end
764
+ local component = PAIR_SECOND(world, id)
765
+ local component_type = untrack_entity_component(server, entity, component)
766
+ if not component_type then
767
+ return
768
+ end
769
+ masking:register_stop_component(entity, component, component_type)
770
+ masking:stop_component(entity, component, component_type)
771
+ end))
772
+
773
+ hook(hooks.added(world, components.pair, function(entity, id, filter)
774
+ if not IS_PAIR(id) then
775
+ utils.logerror "pair should be used with a relationship"
776
+ end
777
+ local relation = PAIR_SECOND(world, id)
778
+ track_entity_pair(server, entity, relation)
779
+ masking:unregister_stop_component(entity, relation, COMPONENT_TYPES.pair)
780
+ masking:start_component(entity, relation, COMPONENT_TYPES.pair, filter)
781
+ end))
782
+ hook(hooks.changed(world, components.pair, function(entity, id, filter)
783
+ if not IS_PAIR(id) then
784
+ utils.logerror "reliable should be used with a pair relationship"
785
+ end
786
+ local relation = PAIR_SECOND(world, id)
787
+ masking:set_component(entity, relation, COMPONENT_TYPES.pair, filter)
788
+ end))
789
+ hook(hooks.removed(world, components.pair, function(entity, id)
790
+ if not IS_PAIR(id) then
791
+ utils.logerror "reliable should be used with a pair relationship"
792
+ end
793
+ local relation = PAIR_SECOND(world, id)
794
+ untrack_entity_pair(server, entity, relation)
795
+ masking:register_stop_component(entity, relation, COMPONENT_TYPES.pair)
796
+ masking:stop_component(entity, relation, COMPONENT_TYPES.pair)
797
+ end))
798
+
799
+ hook(hooks.added(world, components.unreliable, function(entity, id, filter)
800
+ if not IS_PAIR(id) then
801
+ utils.logerror "unreliable should be used with a pair relationship"
802
+ end
803
+ local component = PAIR_SECOND(world, id)
804
+ masking:unregister_stop_component(entity, component, COMPONENT_TYPES.unreliable)
805
+ masking:start_component(entity, component, COMPONENT_TYPES.unreliable, filter)
806
+ if server.additions[entity] then
807
+ masking:allocate_entity_addition(entity, component, COMPONENT_TYPES.unreliable)
808
+ end
809
+ end))
810
+ hook(hooks.changed(world, components.unreliable, function(entity, id, filter)
811
+ if not IS_PAIR(id) then
812
+ utils.logerror "unreliable should be used with a pair relationship"
813
+ end
814
+ local component = PAIR_SECOND(world, id)
815
+ masking:set_component(entity, component, COMPONENT_TYPES.unreliable, filter)
816
+ end))
817
+ hook(hooks.removed(world, components.unreliable, function(entity, id)
818
+ if not IS_PAIR(id) then
819
+ utils.logerror "unreliable should be used with a pair relationship"
820
+ end
821
+ local component = PAIR_SECOND(world, id)
822
+ untrack_entity_component(server, entity, component, COMPONENT_TYPES.unreliable)
823
+ masking:register_stop_component(entity, component, COMPONENT_TYPES.unreliable)
824
+ masking:stop_component(entity, component, COMPONENT_TYPES.unreliable)
825
+ end))
826
+
827
+ hook(hooks.added(world, components.custom_id, function(entity, id)
828
+ if not IS_PAIR(id) then
829
+ utils.logerror "custom_id should be used with a relationship in the server"
830
+ end
831
+ local target = PAIR_SECOND(world, id)
832
+ if server.custom_ids[entity] then
833
+ utils.logwarn("attempted to register a custom_id twice for the same entity", debug.traceback())
834
+ return
835
+ end
836
+
837
+ server.custom_ids[entity] = target
838
+ end))
839
+ hook(hooks.removed(world, components.custom_id, function(entity, id)
840
+ server.custom_ids[entity] = nil
841
+ end))
842
+
843
+ hook(hooks.removed(world, components.__alive_tracking__, function(entity)
844
+ -- TODO: cleanup entity garbage on deletion
845
+ server.custom_ids[entity] = nil
846
+ end))
847
+
848
+ if game and game:GetService("RunService"):IsServer() then
849
+ local added = game:GetService("Players").PlayerAdded:Connect(function(player)
850
+ server.masking:register_member(player)
851
+ end)
852
+ local removed = game:GetService("Players").PlayerRemoving:Connect(function(player)
853
+ server.masking:unregister_member(player)
854
+ end)
855
+
856
+ table.insert(server.connections, added)
857
+ table.insert(server.connections, removed)
858
+ end
859
+ end
860
+
861
+ local function mark_player_ready(server: Server, player: Player)
862
+ server.masking:activate_member(player)
863
+ end
864
+
865
+ local function is_player_ready(server: Server, player: Player): boolean
866
+ return server.masking:member_is_active(player)
867
+ end
868
+
869
+ local function destroy(server: Server)
870
+ if server.inited == nil then
871
+ return warn "attempted to destroy a server twice"
872
+ end
873
+ server.inited = nil :: any
874
+
875
+ for _, unhook in server.hooked do
876
+ unhook()
877
+ end
878
+ for _, connection in server.connections do
879
+ connection:Disconnect()
880
+ end
881
+ end
882
+
883
+ local server = {}
884
+ server.__index = server
885
+ server.init = init
886
+ server.destroy = destroy
887
+ server.get_full = get_full
888
+ server.collect_updates = collect_updates
889
+ server.collect_unreliable = collect_unreliable
890
+ server.mark_player_ready = mark_player_ready
891
+ server.is_player_ready = is_player_ready
892
+
893
+ local function create(world: World?, components: common.Components): Server
894
+ local self = {} :: Server
895
+
896
+ self.components = components
897
+
898
+ self.shared = {} :: common.Shared
899
+ self.world = world :: any
900
+ self.additions = {}
901
+ self.storage = {}
902
+ self.hooked = {}
903
+ self.track_info = {
904
+ entities = {},
905
+ components = {},
906
+ }
907
+ self.connections = {}
908
+ self.inited = false
909
+ self.masking = masking_controller.create()
910
+ self.alive_tracked = {}
911
+ self.custom_ids = {}
912
+
913
+ return setmetatable(self, server) :: any
914
+ end
915
+
916
+ server.create = create
917
+
918
+ return server :: { create: typeof(create) }