@rbxts/replecs 0.0.1-rc.1 → 0.1.0-alpha2

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