@rbxts/replecs 0.3.1 → 0.4.0-rc.2

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,3937 @@
1
+ --!optimize 2
2
+
3
+ local common = require (script.Parent:WaitForChild('common'))
4
+ local cursor = require (script.Parent:WaitForChild('cursor'))
5
+ local registry = require (script:WaitForChild('registry'))
6
+ local bitmasks = require (script:WaitForChild('bitmasks'))
7
+ local filter_groups = require (script:WaitForChild('filter_group'))
8
+ local types = require (script.Parent:WaitForChild('types'))
9
+ local customid = require (script.Parent:WaitForChild('customid'))
10
+ local shared = require (script.Parent:WaitForChild('shared'))
11
+ local handshake = require (script.Parent:WaitForChild('handshake'))
12
+ local utils = require (script.Parent:WaitForChild('utils'))
13
+
14
+ type Set<T> = { [T]: boolean }
15
+ type Map<K, V> = { [K]: V }
16
+ type Array<T> = { T }
17
+
18
+ type Player = any
19
+
20
+ type FilterAny = types.FilterAny
21
+ type FilterSet = types.FilterSet
22
+ type FilterPredicator = types.FilterPredicator
23
+ type FilterPlayer = types.FilterPlayer
24
+ type FilterGroup = filter_groups.FilterGroup
25
+
26
+ -- what the public set_* API accepts as a filter
27
+ type FilterInput = FilterAny | FilterGroup
28
+
29
+ type MaskHash = string
30
+
31
+ type World = common.World
32
+ type Entity = common.Entity
33
+ type Component<T = any> = common.Component<T>
34
+ type Bitmask = buffer
35
+ type Bitset = number
36
+ type TrackType = number
37
+
38
+ type BitmaskDeltas = bitmasks.BitmaskDeltas
39
+ type Cursor = cursor.Cursor
40
+
41
+ export type ServerImp = {
42
+ set_networked: (self: Server, entity: Entity, filter: FilterInput?) -> (),
43
+ set_reliable: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
44
+ set_unreliable: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
45
+ set_once: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
46
+ set_pair: (self: Server, entity: Entity, id: Component, filter: FilterInput?) -> (),
47
+ set_unreliable_pair: (self: Server, entity: Entity, id: Component, filter: FilterInput?) -> (),
48
+ set_relation: (self: Server, entity: Entity, relation: Component, filter: FilterInput?) -> (),
49
+
50
+ stop_networked: (self: Server, entity: Entity) -> (),
51
+ stop_reliable: (self: Server, entity: Entity, component: Component) -> (),
52
+ stop_unreliable: (self: Server, entity: Entity, component: Component) -> (),
53
+ stop_once: (self: Server, entity: Entity, component: Component) -> (),
54
+ stop_pair: (self: Server, entity: Entity, id: Component) -> (),
55
+ stop_unreliable_pair: (self: Server, entity: Entity, id: Component) -> (),
56
+ stop_relation: (self: Server, entity: Entity, relation: Component) -> (),
57
+
58
+ force_change: (self: Server, entity: Entity, component: Component) -> (),
59
+
60
+ set_global: (self: Server, entity: Entity, global_id: number) -> (),
61
+ remove_global: (self: Server, entity: Entity) -> (),
62
+ set_custom: (self: Server, entity: Entity, custom: Component | CustomId) -> (),
63
+ remove_custom: (self: Server, entity: Entity) -> (),
64
+ register_custom_id: (self: Server, custom_id: CustomId) -> (),
65
+
66
+ set_serdes: <T>(self: Server, component: Component<T>, serdes: types.Serdes<T>) -> (),
67
+ remove_serdes: (self: Server, component: Component) -> (),
68
+ set_preprocessor: <B, A>(self: Server, component: Component<B>, preprocessor: types.Preprocessor<B, A>) -> (),
69
+ remove_preprocessor: (self: Server, component: Component) -> (),
70
+ set_delta_serializer: <T, D>(self: Server, component: Component<T>, delta: types.DeltaSerializer<T, D>) -> (),
71
+ remove_delta_serializer: (self: Server, component: Component) -> (),
72
+ }
73
+
74
+ -- filter == nil means "replicate to all active clients", resolved at collect time
75
+ type StorageBase = {
76
+ filter: Bitmask?,
77
+ last_replicated: Bitmask?,
78
+
79
+ -- set while a filter group drives this storage's filter; the listener is
80
+ -- kept so it can be disconnected when the filter changes or the storage dies
81
+ filter_group: FilterGroup?,
82
+ filter_listener: ((Bitmask) -> ())?,
83
+ filter_input: FilterInput?,
84
+ }
85
+
86
+ type TrackingStorageBase = StorageBase & {
87
+ track_type: TrackType,
88
+
89
+ dirty_flags: Bitset,
90
+ is_component: boolean,
91
+ -- once storages replicate on add, removal and visibility gain only; world
92
+ -- value changes are ignored unless force_change is called
93
+ is_once: boolean?,
94
+ -- set by stop_*: the filter is forced empty so collect sends the removal
95
+ -- delta, then the storage is discarded; a start_* before that revives it
96
+ is_dead: boolean,
97
+ }
98
+
99
+ type ReliableStorage = TrackingStorageBase & {}
100
+ type PairStorage = TrackingStorageBase & {}
101
+ type UnreliableStorage = TrackingStorageBase & {}
102
+ type UnreliablePairStorage = TrackingStorageBase & {}
103
+
104
+ type RelationStorage = TrackingStorageBase & {
105
+ dirty_targets: Set<Entity>,
106
+ }
107
+
108
+ type NetworkedStorage = StorageBase & {
109
+ dirty_flags: Bitset,
110
+ is_dead: boolean,
111
+ -- live (not stopped) unreliable + unreliable_pair storages; keeps
112
+ -- unreliable_networked membership in sync without scanning storages
113
+ unreliable_count: number,
114
+ -- set by cleanup_entity: unlike a stopped storage, a cleaned one cannot be
115
+ -- revived; set_* calls error until collect_updates discards it
116
+ cleaned: boolean?,
117
+
118
+ dirty_reliable: Array<Component>,
119
+ dirty_unreliable: Array<Component>,
120
+ dirty_pairs: Array<Component>,
121
+ dirty_unreliable_pairs: Array<Component>,
122
+ dirty_relations: Array<Component>,
123
+
124
+ replicate_exclude: Set<Component>?,
125
+
126
+ reliable_storages: { [Component]: ReliableStorage },
127
+ unreliable_storages: { [Component]: UnreliableStorage },
128
+ pair_storages: { [Component]: PairStorage },
129
+ unreliable_pair_storages: { [Component]: UnreliablePairStorage },
130
+ relation_storages: { [Component]: RelationStorage },
131
+ }
132
+
133
+ type PostponedComponent = {
134
+ filter: FilterInput?,
135
+ once: boolean?,
136
+ }
137
+
138
+ -- a predicate-driven filter, re-evaluated before every collect_updates cycle
139
+ type FunctionFilterEntry = {
140
+ predicator: registry.MemberPredicator,
141
+ entity: Entity,
142
+ networked: NetworkedStorage,
143
+
144
+ -- set for reliable component filters, nil for entity-level ones
145
+ tracking: Component?,
146
+ tracking_storage: TrackingStorageBase?,
147
+ }
148
+
149
+ type NetworkedReferencesInfo = {
150
+ networked: NetworkedStorage,
151
+ tracking_storage: TrackingStorageBase,
152
+ }
153
+
154
+ type HookedInfo = {
155
+ networked_references: Map<Entity, NetworkedReferencesInfo>,
156
+ }
157
+
158
+ type PostponedComponentsList = {
159
+ [TrackType]: Map<Component, PostponedComponent>,
160
+ }
161
+
162
+ type ComponentSerializationOptions = {
163
+ serdes: types.Serdes?,
164
+ preprocessor: types.Preprocessor?,
165
+ delta_serializer: types.DeltaSerializer?,
166
+ }
167
+
168
+ type CustomId = customid.CustomId
169
+
170
+ -- how an entity id is written on the wire; absent means plain entity / shared
171
+ type EntitySerializationOptions = {
172
+ global_id: number?,
173
+ custom: (Component | CustomId)?,
174
+ }
175
+
176
+ export type Server = ServerImp & {
177
+ world: World,
178
+ inited: boolean?,
179
+
180
+ registry: registry.Registry,
181
+ -- byte size of every bitmask, fixed by the client cap given to create
182
+ bitmask_size: number,
183
+ ready_players_bitmask: Bitmask,
184
+ components: types.Components,
185
+ shared: types.Shared,
186
+
187
+ networked: Map<Entity, NetworkedStorage>,
188
+ -- live networked entities with at least one live unreliable storage; the
189
+ -- unreliable collect iterates this instead of every networked entity
190
+ unreliable_networked: Set<Entity>,
191
+ postponed_components: Map<Entity, PostponedComponentsList>,
192
+ -- single lookup for global/custom id resolution at write time
193
+ entity_serialization: Map<Entity, EntitySerializationOptions>,
194
+ registered_custom_ids: Set<CustomId>,
195
+ -- single lookup for serdes/preprocessor/delta at write time
196
+ component_serialization: Map<Component, ComponentSerializationOptions>,
197
+ -- serdes and custom ids feed the handshake; set when either changes
198
+ is_shared_dirty: boolean,
199
+
200
+ hooked_components: Map<Component, HookedInfo>,
201
+ hooked_unreliable: Map<Component, HookedInfo>,
202
+ hooked_pairs: Map<Component, HookedInfo>,
203
+ hooked_unreliable_pairs: Map<Component, HookedInfo>,
204
+ hooked_relations: Map<Component, HookedInfo>,
205
+
206
+ dirty_networked: Array<Entity>,
207
+ function_filters: Map<StorageBase, FunctionFilterEntry>,
208
+
209
+ empty_bitmask: Bitmask,
210
+ draft_bitmask: Bitmask,
211
+ -- scratch for the entity target of the networked entity being collected
212
+ target_bitmask: Bitmask,
213
+ filled_bitmask: Bitmask,
214
+
215
+ client_active_bitmask: Bitmask,
216
+ last_active_bitmask: Bitmask,
217
+ newly_active_bitmask: Bitmask,
218
+ newly_unactive_bitmask: Bitmask,
219
+
220
+ entity_bitmask_deltas: BitmaskDeltas,
221
+ component_bitmask_deltas: BitmaskDeltas,
222
+
223
+ cleanups: { () -> () },
224
+
225
+ resolve_dirty: (self: Server) -> (),
226
+ get_full: (self: Server, player: Player) -> (buffer, Array<any>),
227
+ collect_entity: (self: Server, entity: Entity) -> PacketIterator,
228
+ collect_updates: (self: Server) -> PacketIterator,
229
+ collect_unreliable: (self: Server) -> PacketIterator,
230
+
231
+ register_player: (self: Server, player: Player) -> (),
232
+ add_player_alias: (self: Server, player: Player, alias: any) -> (),
233
+ remove_player_alias: (self: Server, alias: any) -> (),
234
+ mark_player_ready: (self: Server, player: Player) -> (),
235
+ is_player_ready: (self: Server, player: Player) -> boolean,
236
+ remove_player: (self: Server, player: Player) -> (),
237
+
238
+ encode_component: (self: Server, component: Component) -> number,
239
+ decode_component: (self: Server, encoded: number) -> Component?,
240
+ get_shared_count: (self: Server) -> number,
241
+
242
+ generate_handshake: (self: Server) -> types.HandshakeInfo,
243
+ verify_handshake: (self: Server, handshake: types.HandshakeInfo) -> (boolean, string?),
244
+
245
+ cleanup_entity: (self: Server, entity: Entity) -> (),
246
+
247
+ init: (self: Server, world: World?) -> (),
248
+ destroy: (self: Server) -> (),
249
+ }
250
+
251
+ -- matches replecs.MAX_CLIENTS; only reached when server.create
252
+ -- is called directly without a cap
253
+ local DEFAULT_MAX_CLIENTS = 128
254
+
255
+ local RELATIONSHIPS = common.relationships
256
+ local WILDCARD = common.wildcard
257
+ local ECS_NAME = common.ecs_name
258
+
259
+ local IS_PAIR = common.is_pair
260
+ local PAIR = common.pair
261
+ local PAIR_FIRST = common.pair_first
262
+ local PAIR_SECOND = common.pair_second
263
+ local WORLD_ADD = common.world_add
264
+ local WORLD_HAS = common.world_has
265
+ local WORLD_GET = common.world_get
266
+ local WORLD_TARGET = common.world_target
267
+ local IS_COMPONENT = common.is_component
268
+
269
+ local LISTEN_RELATION = common.listen_relation
270
+
271
+ local HOOK_ADDED = common.hook_added
272
+ local HOOK_CHANGED = common.hook_changed
273
+ local HOOK_REMOVED = common.hook_removed
274
+
275
+ local LOG_ERROR = common.log_error
276
+
277
+ local function HOOK_PAIR_ADDED(world: World, pair_id: Entity, callback: (Entity, Component, any) -> ())
278
+ local relation = PAIR_FIRST(world, pair_id)
279
+
280
+ return HOOK_ADDED(world, relation, function(entity, id, value)
281
+ if id ~= pair_id then
282
+ return
283
+ end
284
+ callback(entity, id, value)
285
+ end)
286
+ end
287
+
288
+ local function HOOK_PAIR_CHANGED(world: World, pair_id: Entity, callback: (Entity, Component, any) -> ())
289
+ local relation = PAIR_FIRST(world, pair_id)
290
+
291
+ return HOOK_CHANGED(world, relation, function(entity, id, value)
292
+ if id ~= pair_id then
293
+ return
294
+ end
295
+ callback(entity, id, value)
296
+ end)
297
+ end
298
+
299
+ local function HOOK_PAIR_REMOVED(world: World, pair_id: Entity, callback: (Entity, Component) -> ())
300
+ local relation = PAIR_FIRST(world, pair_id)
301
+
302
+ return HOOK_REMOVED(world, relation, function(entity, id)
303
+ if id ~= pair_id then
304
+ return
305
+ end
306
+ callback(entity, id)
307
+ end)
308
+ end
309
+
310
+ local DIRTY_CHANGED_FLAG = 0b001
311
+ local DIRTY_FILTER_FLAG = 0b010
312
+ local DIRTY_COMPONENT_FLAG = 0b100
313
+
314
+ local VLQ_ZERO = 128
315
+
316
+ -- global ids are written as a single byte; values below the offset are
317
+ -- reserved for the ENTITY_ID_TYPES tags that trail every other id form
318
+ local GLOBAL_ID_OFFSET = 10
319
+
320
+ local MAX_GLOBAL_ID = 255 - GLOBAL_ID_OFFSET
321
+
322
+ -- unreliable remotes drop packets over the transport budget, so combined
323
+ -- batches stay under the ~1kB roblox limit
324
+ local MAX_UNRELIABLE_PACKET_SIZE = 900
325
+
326
+ local TRACK_TYPES = {
327
+ reliable = 1,
328
+ pair = 2,
329
+ relation = 3,
330
+ unreliable = 4,
331
+ unreliable_pair = 5,
332
+ }
333
+
334
+ -- sentinel for nil component values (tags), keeps value arrays hole-free
335
+ local NIL_VALUE = newproxy()
336
+
337
+ -- wraps a world value before it enters a value array
338
+ local function SET_VALUE(value: any): any
339
+ return if value == nil then NIL_VALUE else value
340
+ end
341
+
342
+ -- unwraps a value array entry back into the world value
343
+ local function READ_VALUE(value: any): any
344
+ return if value == NIL_VALUE then nil else value
345
+ end
346
+
347
+ local ENTITY_ID_TYPES = {
348
+ entity = 1,
349
+ custom_handler = 2,
350
+ custom_id = 3,
351
+ shared = 4,
352
+ }
353
+
354
+ -- u8 trailing every outgoing buffer (the client reads it first) so a packet
355
+ -- fed to the wrong apply function fails loudly instead of desyncing
356
+ local PACKET_TYPES = {
357
+ full = 1,
358
+ entity = 2,
359
+ updates = 3,
360
+ unreliable = 4,
361
+ }
362
+ local PACKET_TYPE_SPAN = 1
363
+
364
+ local server = {}
365
+ server.__index = server
366
+
367
+
368
+ local function flag_test(bitset: Bitset, flags: Bitset): boolean
369
+ return bit32.band(bitset, flags) == flags
370
+ end
371
+
372
+
373
+ function mark_networked_dirty(self: Server, entity: Entity, networked: NetworkedStorage, flag: Bitset)
374
+ if networked.dirty_flags == 0 then
375
+ table.insert(self.dirty_networked, entity)
376
+ end
377
+ networked.dirty_flags = bit32.bor(networked.dirty_flags, flag)
378
+ end
379
+
380
+
381
+ local function get_dirty_list(networked: NetworkedStorage, track_type: TrackType): Array<Component>
382
+ if track_type == TRACK_TYPES.reliable then
383
+ return networked.dirty_reliable
384
+ elseif track_type == TRACK_TYPES.unreliable then
385
+ return networked.dirty_unreliable
386
+ elseif track_type == TRACK_TYPES.pair then
387
+ return networked.dirty_pairs
388
+ elseif track_type == TRACK_TYPES.unreliable_pair then
389
+ return networked.dirty_unreliable_pairs
390
+ else
391
+ return networked.dirty_relations
392
+ end
393
+ end
394
+
395
+
396
+ function mark_tracking_storage_dirty(
397
+ self: Server,
398
+ entity: Entity,
399
+ component: Component,
400
+ networked: NetworkedStorage,
401
+ storage: TrackingStorageBase,
402
+ flag: Bitset
403
+ )
404
+ if storage.dirty_flags == 0 then
405
+ table.insert(get_dirty_list(networked, storage.track_type), component)
406
+ end
407
+ storage.dirty_flags = bit32.bor(storage.dirty_flags, flag)
408
+ mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
409
+ end
410
+
411
+ local function increment_unreliable(self: Server, entity: Entity, networked: NetworkedStorage)
412
+ networked.unreliable_count += 1
413
+ if not networked.is_dead then
414
+ self.unreliable_networked[entity] = true
415
+ end
416
+ end
417
+
418
+ local function decrement_unreliable(self: Server, entity: Entity, networked: NetworkedStorage)
419
+ networked.unreliable_count -= 1
420
+ if networked.unreliable_count <= 0 then
421
+ self.unreliable_networked[entity] = nil
422
+ end
423
+ end
424
+
425
+
426
+ local function edit_storage_filter(self: Server, storage: StorageBase, filter: Bitmask?)
427
+ if not storage.last_replicated then
428
+ storage.last_replicated = bitmasks.clone(storage.filter or self.last_active_bitmask)
429
+ end
430
+ storage.filter = filter
431
+ end
432
+
433
+
434
+ local function resolve_filter_bitmask(self: Server, filter: FilterAny): Bitmask
435
+ local registry = self.registry
436
+ local type = typeof(filter)
437
+ if type == "table" then
438
+ return registry:create_bitmask_from_set(filter :: FilterSet)
439
+ elseif type == "function" then
440
+ return registry:create_bitmask_from_function(filter :: registry.MemberPredicator)
441
+ else
442
+ return registry:create_bitmask_from_member(filter :: Player)
443
+ end
444
+ end
445
+
446
+ local function disconnect_filter_group(storage: StorageBase)
447
+ local group = storage.filter_group
448
+ local listener = storage.filter_listener
449
+ if group and listener then
450
+ group:disconnect(listener)
451
+ end
452
+ storage.filter_group = nil
453
+ storage.filter_listener = nil
454
+ end
455
+
456
+ -- releases whatever was driving this storage's filter (group or predicate)
457
+ local function clear_filter_source(self: Server, storage: StorageBase)
458
+ disconnect_filter_group(storage)
459
+ self.function_filters[storage] = nil
460
+ end
461
+
462
+ -- a nil-filter storage inherits entity visibility, so its replicated set is
463
+ -- exactly the entity's; clients entering the entity filter then get the
464
+ -- storage through its own added delta
465
+ local function snapshot_tracking_replicated(storage: TrackingStorageBase, entity_last: Bitmask)
466
+ if storage.last_replicated then
467
+ return
468
+ end
469
+
470
+ local filter = storage.filter
471
+ if not filter then
472
+ storage.last_replicated = bitmasks.clone(entity_last)
473
+ return
474
+ end
475
+
476
+ local snapshot = bitmasks.clone(filter)
477
+ bitmasks.band(snapshot, entity_last, snapshot)
478
+ storage.last_replicated = snapshot
479
+ end
480
+
481
+ local function edit_networked_filter(self: Server, entity: Entity, networked: NetworkedStorage, bitmask: Bitmask?)
482
+ edit_storage_filter(self, networked, bitmask)
483
+ mark_networked_dirty(self, entity, networked, DIRTY_FILTER_FLAG)
484
+ mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
485
+
486
+ local entity_last = networked.last_replicated :: Bitmask
487
+
488
+ for component, reliable_storage in networked.reliable_storages do
489
+ snapshot_tracking_replicated(reliable_storage, entity_last)
490
+ mark_tracking_storage_dirty(self, entity, component, networked, reliable_storage, DIRTY_FILTER_FLAG)
491
+ end
492
+ for component, unreliable_storage in networked.unreliable_storages do
493
+ snapshot_tracking_replicated(unreliable_storage, entity_last)
494
+ mark_tracking_storage_dirty(self, entity, component, networked, unreliable_storage, DIRTY_FILTER_FLAG)
495
+ end
496
+ for id, pair_storage in networked.pair_storages do
497
+ snapshot_tracking_replicated(pair_storage, entity_last)
498
+ mark_tracking_storage_dirty(self, entity, id, networked, pair_storage, DIRTY_FILTER_FLAG)
499
+ end
500
+ for id, unreliable_pair_storage in networked.unreliable_pair_storages do
501
+ snapshot_tracking_replicated(unreliable_pair_storage, entity_last)
502
+ mark_tracking_storage_dirty(self, entity, id, networked, unreliable_pair_storage, DIRTY_FILTER_FLAG)
503
+ end
504
+ for rel, relation_storage in networked.relation_storages do
505
+ snapshot_tracking_replicated(relation_storage, entity_last)
506
+ mark_tracking_storage_dirty(self, entity, rel, networked, relation_storage, DIRTY_FILTER_FLAG)
507
+ end
508
+ end
509
+
510
+ local function edit_tracking_filter(
511
+ self: Server,
512
+ entity: Entity,
513
+ component: Component,
514
+ networked: NetworkedStorage,
515
+ storage: TrackingStorageBase,
516
+ bitmask: Bitmask?
517
+ )
518
+ if not storage.last_replicated then
519
+ -- only components in entity visibiblity were ever replicated, so the snapshot
520
+ -- must not claim clients that were outside the entity filter
521
+ local last_active = self.last_active_bitmask
522
+ local snapshot = bitmasks.clone(storage.filter or last_active)
523
+ local entity_last = networked.last_replicated or networked.filter or last_active
524
+ bitmasks.band(snapshot, entity_last, snapshot)
525
+ storage.last_replicated = snapshot
526
+ end
527
+ storage.filter = bitmask
528
+ mark_tracking_storage_dirty(self, entity, component, networked, storage, DIRTY_FILTER_FLAG)
529
+ end
530
+
531
+ local function connect_networked_group(self: Server, entity: Entity, networked: NetworkedStorage, group: FilterGroup)
532
+ local function listener(bitmask: Bitmask)
533
+ edit_networked_filter(self, entity, networked, bitmask)
534
+ end
535
+ networked.filter_group = group
536
+ networked.filter_listener = listener
537
+ group:connect(listener)
538
+ end
539
+
540
+ local function connect_tracking_group(
541
+ self: Server,
542
+ entity: Entity,
543
+ component: Component,
544
+ networked: NetworkedStorage,
545
+ storage: TrackingStorageBase,
546
+ group: FilterGroup
547
+ )
548
+ local function listener(bitmask: Bitmask)
549
+ edit_tracking_filter(self, entity, component, networked, storage, bitmask)
550
+ end
551
+ storage.filter_group = group
552
+ storage.filter_listener = listener
553
+ group:connect(listener)
554
+ end
555
+
556
+ function ensure_hooked_component(self: Server, component: Component): HookedInfo
557
+ local info = self.hooked_components[component]
558
+ if info then
559
+ return info
560
+ end
561
+
562
+ local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
563
+ local new_info: HookedInfo = {
564
+ networked_references = networked_references,
565
+ }
566
+ self.hooked_components[component] = new_info
567
+
568
+ local function hook(unhook: () -> ())
569
+ table.insert(self.cleanups, unhook)
570
+ end
571
+ local world = self.world
572
+
573
+ local function mark_component_dirty(entity: Entity)
574
+ local reference = networked_references[entity]
575
+ if not reference then
576
+ return
577
+ end
578
+ mark_tracking_storage_dirty(
579
+ self,
580
+ entity,
581
+ component,
582
+ reference.networked,
583
+ reference.tracking_storage,
584
+ DIRTY_CHANGED_FLAG
585
+ )
586
+ end
587
+
588
+ -- once storages only replicate the value on add, removal and visibility
589
+ -- gain; in-place world changes are ignored (force_change bypasses this)
590
+ local function mark_component_changed(entity: Entity)
591
+ local reference = networked_references[entity]
592
+ if not reference or reference.tracking_storage.is_once then
593
+ return
594
+ end
595
+ mark_tracking_storage_dirty(
596
+ self,
597
+ entity,
598
+ component,
599
+ reference.networked,
600
+ reference.tracking_storage,
601
+ DIRTY_CHANGED_FLAG
602
+ )
603
+ end
604
+
605
+ hook(HOOK_ADDED(world, component, mark_component_dirty))
606
+ hook(HOOK_CHANGED(world, component, mark_component_changed))
607
+ hook(HOOK_REMOVED(world, component, mark_component_dirty))
608
+
609
+ return new_info
610
+ end
611
+
612
+ local function ensure_hooked_relation(self: Server, rel: Component): HookedInfo
613
+ local info = self.hooked_relations[rel]
614
+ if info then
615
+ return info
616
+ end
617
+
618
+ local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
619
+ local new_info: HookedInfo = {
620
+ networked_references = networked_references,
621
+ }
622
+ self.hooked_relations[rel] = new_info
623
+
624
+ local function hook(unhook: () -> ())
625
+ table.insert(self.cleanups, unhook)
626
+ end
627
+ local world = self.world
628
+
629
+ local function mark_relation_dirty(entity: Entity, id: Entity)
630
+ if not IS_PAIR(id) then
631
+ return
632
+ end
633
+
634
+ local reference = networked_references[entity]
635
+ if not reference then
636
+ return
637
+ end
638
+ local affected = PAIR_SECOND(world, id)
639
+ local relation_storage = reference.tracking_storage :: RelationStorage
640
+
641
+ mark_tracking_storage_dirty(self, entity, rel, reference.networked, relation_storage, DIRTY_CHANGED_FLAG)
642
+ relation_storage.dirty_targets[affected] = true
643
+ end
644
+
645
+ hook(HOOK_ADDED(world, LISTEN_RELATION(rel), mark_relation_dirty))
646
+ hook(HOOK_CHANGED(world, LISTEN_RELATION(rel), mark_relation_dirty))
647
+ hook(HOOK_REMOVED(world, LISTEN_RELATION(rel), mark_relation_dirty))
648
+
649
+ return new_info
650
+ end
651
+
652
+ local function ensure_hooked_pair(self: Server, id: Entity)
653
+ local info = self.hooked_pairs[id]
654
+ if info then
655
+ return info
656
+ end
657
+
658
+ local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
659
+ local new_info: HookedInfo = {
660
+ networked_references = networked_references,
661
+ }
662
+ self.hooked_pairs[id] = new_info
663
+
664
+ local function hook(unhook: () -> ())
665
+ table.insert(self.cleanups, unhook)
666
+ end
667
+ local world = self.world
668
+
669
+ local function mark_pair_dirty(entity: Entity)
670
+ local reference = networked_references[entity]
671
+ if not reference then
672
+ return
673
+ end
674
+ mark_tracking_storage_dirty(self, entity, id, reference.networked, reference.tracking_storage, DIRTY_CHANGED_FLAG)
675
+ end
676
+
677
+ hook(HOOK_PAIR_ADDED(world, id, mark_pair_dirty))
678
+ hook(HOOK_PAIR_CHANGED(world, id, mark_pair_dirty))
679
+ hook(HOOK_PAIR_REMOVED(world, id, mark_pair_dirty))
680
+
681
+ return new_info
682
+ end
683
+
684
+ local function ensure_hooked_unreliable(self: Server, component: Component): HookedInfo
685
+ local info = self.hooked_unreliable[component]
686
+ if info then
687
+ return info
688
+ end
689
+
690
+ local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
691
+ local new_info: HookedInfo = {
692
+ networked_references = networked_references,
693
+ }
694
+ self.hooked_unreliable[component] = new_info
695
+
696
+ local function hook(unhook: () -> ())
697
+ table.insert(self.cleanups, unhook)
698
+ end
699
+ local world = self.world
700
+
701
+ local function mark_unreliable_dirty(entity: Entity)
702
+ local reference = networked_references[entity]
703
+ if not reference then
704
+ return
705
+ end
706
+ mark_tracking_storage_dirty(
707
+ self,
708
+ entity,
709
+ component,
710
+ reference.networked,
711
+ reference.tracking_storage,
712
+ DIRTY_CHANGED_FLAG
713
+ )
714
+ end
715
+
716
+ hook(HOOK_ADDED(world, component, mark_unreliable_dirty))
717
+ hook(HOOK_REMOVED(world, component, mark_unreliable_dirty))
718
+
719
+ return new_info
720
+ end
721
+
722
+ local function ensure_hooked_unreliable_pair(self: Server, id: Entity): HookedInfo
723
+ local info = self.hooked_unreliable_pairs[id]
724
+ if info then
725
+ return info
726
+ end
727
+
728
+ local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
729
+ local new_info: HookedInfo = {
730
+ networked_references = networked_references,
731
+ }
732
+ self.hooked_unreliable_pairs[id] = new_info
733
+
734
+ local function hook(unhook: () -> ())
735
+ table.insert(self.cleanups, unhook)
736
+ end
737
+ local world = self.world
738
+
739
+ local function mark_unreliable_pair_dirty(entity: Entity)
740
+ local reference = networked_references[entity]
741
+ if not reference then
742
+ return
743
+ end
744
+ mark_tracking_storage_dirty(self, entity, id, reference.networked, reference.tracking_storage, DIRTY_CHANGED_FLAG)
745
+ end
746
+
747
+ hook(HOOK_PAIR_ADDED(world, id, mark_unreliable_pair_dirty))
748
+ hook(HOOK_PAIR_REMOVED(world, id, mark_unreliable_pair_dirty))
749
+
750
+ return new_info
751
+ end
752
+
753
+ local function get_hooked_map(self: Server, track_type: TrackType): Map<Component, HookedInfo>
754
+ if track_type == TRACK_TYPES.reliable then
755
+ return self.hooked_components
756
+ elseif track_type == TRACK_TYPES.unreliable then
757
+ return self.hooked_unreliable
758
+ elseif track_type == TRACK_TYPES.pair then
759
+ return self.hooked_pairs
760
+ elseif track_type == TRACK_TYPES.unreliable_pair then
761
+ return self.hooked_unreliable_pairs
762
+ else
763
+ return self.hooked_relations
764
+ end
765
+ end
766
+
767
+ -- hook references live as long as their storage: a stopped storage keeps
768
+ -- receiving world changes so a same-frame revive still replicates them. the
769
+ -- reference goes away only when collect discards the storage
770
+ local function clear_hook_reference(self: Server, entity: Entity, component: Component, storage: TrackingStorageBase)
771
+ local info = get_hooked_map(self, storage.track_type)[component]
772
+ if not info then
773
+ return
774
+ end
775
+ local reference = info.networked_references[entity]
776
+ if reference and reference.tracking_storage == storage then
777
+ info.networked_references[entity] = nil
778
+ end
779
+ end
780
+
781
+ local function clear_networked_references(self: Server, entity: Entity, networked: NetworkedStorage)
782
+ for component, storage in networked.reliable_storages do
783
+ clear_hook_reference(self, entity, component, storage)
784
+ end
785
+ for component, storage in networked.unreliable_storages do
786
+ clear_hook_reference(self, entity, component, storage)
787
+ end
788
+ for id, storage in networked.pair_storages do
789
+ clear_hook_reference(self, entity, id, storage)
790
+ end
791
+ for id, storage in networked.unreliable_pair_storages do
792
+ clear_hook_reference(self, entity, id, storage)
793
+ end
794
+ for rel, storage in networked.relation_storages do
795
+ clear_hook_reference(self, entity, rel, storage)
796
+ end
797
+ end
798
+
799
+ -- resolves a filter input into a bitmask and attaches its driving source
800
+ -- (group listener or predicate entry) to the storage; nil filter means
801
+ -- "replicate to all active clients", resolved at collect
802
+ local function apply_tracking_filter(
803
+ self: Server,
804
+ entity: Entity,
805
+ component: Component,
806
+ networked: NetworkedStorage,
807
+ storage: TrackingStorageBase,
808
+ filter: FilterInput?
809
+ ): Bitmask?
810
+ local bitmask: Bitmask?
811
+
812
+ if filter then
813
+ if filter_groups.is_filter_group(filter) then
814
+ local group = filter :: FilterGroup
815
+ bitmask = group:calculate_up()
816
+ connect_tracking_group(self, entity, component, networked, storage, group)
817
+ elseif typeof(filter) == "function" then
818
+ local predicator = filter :: registry.MemberPredicator
819
+ bitmask = self.registry:create_bitmask_from_function(predicator)
820
+
821
+ self.function_filters[storage] = {
822
+ predicator = predicator,
823
+ entity = entity,
824
+ networked = networked,
825
+
826
+ tracking = component,
827
+ tracking_storage = storage,
828
+ }
829
+ else
830
+ bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
831
+ end
832
+ end
833
+
834
+ return bitmask
835
+ end
836
+
837
+ local function change_tracking(
838
+ self: Server,
839
+ entity: Entity,
840
+ component: Component,
841
+ networked: NetworkedStorage,
842
+ storage: TrackingStorageBase,
843
+ filter: FilterInput?
844
+ )
845
+ -- old filter source stops driving this storage before the new one takes over
846
+ clear_filter_source(self, storage)
847
+
848
+ local bitmask = apply_tracking_filter(self, entity, component, networked, storage, filter)
849
+
850
+ -- a stopped storage keeps its last_replicated (forced at stop), so reviving
851
+ -- here sends only the delta between the pre-stop and the new filter
852
+ storage.is_dead = false
853
+ storage.filter_input = filter
854
+ edit_tracking_filter(self, entity, component, networked, storage, bitmask)
855
+ end
856
+
857
+ -- the last filter is forced into last_replicated and the current one becomes
858
+ -- the empty bitmask: collect emits the removal to everyone who had it, then
859
+ -- drops the storage. hooks stop feeding it right away
860
+ local function stop_tracking(
861
+ self: Server,
862
+ entity: Entity,
863
+ component: Component,
864
+ networked: NetworkedStorage,
865
+ storage: TrackingStorageBase
866
+ )
867
+ storage.is_dead = true
868
+ storage.filter_input = nil
869
+ clear_filter_source(self, storage)
870
+ edit_tracking_filter(self, entity, component, networked, storage, self.empty_bitmask)
871
+ end
872
+
873
+ local function remove_postponed_component(self: Server, entity: Entity, track_type: TrackType, component: Component)
874
+ local postponed = self.postponed_components[entity]
875
+ if not postponed then
876
+ return
877
+ end
878
+ local postponed_list = postponed[track_type]
879
+ if postponed_list then
880
+ postponed_list[component] = nil
881
+ end
882
+ end
883
+
884
+ local function create_tracking_storage(
885
+ self: Server,
886
+ component: Component,
887
+ track_type: TrackType,
888
+ filter: FilterInput?
889
+ ): TrackingStorageBase
890
+ return {
891
+ filter = nil :: Bitmask?,
892
+ last_replicated = buffer.create(self.bitmask_size),
893
+
894
+ track_type = track_type,
895
+ is_component = IS_COMPONENT(self.world, component),
896
+
897
+ dirty_flags = 0,
898
+ is_dead = false,
899
+ filter_input = filter,
900
+ }
901
+ end
902
+
903
+ local function start_reliable(
904
+ self: Server,
905
+ entity: Entity,
906
+ component: Component,
907
+ networked: NetworkedStorage,
908
+ filter: FilterInput?,
909
+ once: boolean?
910
+ )
911
+ local reliable_storage = networked.reliable_storages[component]
912
+
913
+ if reliable_storage then
914
+ -- revived storage keeps its last_replicated, collect sends only the delta
915
+ change_tracking(self, entity, component, networked, reliable_storage, filter)
916
+ -- set_once / set_reliable on an existing storage switches its mode
917
+ reliable_storage.is_once = once
918
+ else
919
+ local new_storage = create_tracking_storage(self, component, TRACK_TYPES.reliable, filter) :: ReliableStorage
920
+ new_storage.is_once = once
921
+ networked.reliable_storages[component] = new_storage
922
+
923
+ new_storage.filter = apply_tracking_filter(self, entity, component, networked, new_storage, filter)
924
+ mark_tracking_storage_dirty(self, entity, component, networked, new_storage, DIRTY_FILTER_FLAG)
925
+ reliable_storage = new_storage
926
+ end
927
+
928
+ local hook_info = ensure_hooked_component(self, component)
929
+ hook_info.networked_references[entity] = {
930
+ tracking_storage = reliable_storage,
931
+ networked = networked,
932
+ }
933
+ end
934
+
935
+ local function start_unreliable(
936
+ self: Server,
937
+ entity: Entity,
938
+ component: Component,
939
+ networked: NetworkedStorage,
940
+ filter: FilterInput?
941
+ )
942
+ local unreliable_storage = networked.unreliable_storages[component]
943
+
944
+ if unreliable_storage then
945
+ -- reviving a stopped storage regains the count its stop gave up
946
+ if unreliable_storage.is_dead then
947
+ increment_unreliable(self, entity, networked)
948
+ end
949
+ change_tracking(self, entity, component, networked, unreliable_storage, filter)
950
+ else
951
+ local new_storage = create_tracking_storage(self, component, TRACK_TYPES.unreliable, filter)
952
+ networked.unreliable_storages[component] = new_storage
953
+ increment_unreliable(self, entity, networked)
954
+
955
+ new_storage.filter = apply_tracking_filter(self, entity, component, networked, new_storage, filter)
956
+ mark_tracking_storage_dirty(self, entity, component, networked, new_storage, DIRTY_FILTER_FLAG)
957
+ unreliable_storage = new_storage
958
+ end
959
+
960
+ local hook_info = ensure_hooked_unreliable(self, component)
961
+ hook_info.networked_references[entity] = {
962
+ tracking_storage = unreliable_storage,
963
+ networked = networked,
964
+ }
965
+ end
966
+
967
+ local function start_pair(
968
+ self: Server,
969
+ entity: Entity,
970
+ id: Component,
971
+ networked: NetworkedStorage,
972
+ filter: FilterInput?
973
+ )
974
+ local pair_storage = networked.pair_storages[id]
975
+
976
+ if pair_storage then
977
+ change_tracking(self, entity, id, networked, pair_storage, filter)
978
+ else
979
+ local new_storage = create_tracking_storage(self, id, TRACK_TYPES.pair, filter) :: PairStorage
980
+ networked.pair_storages[id] = new_storage
981
+
982
+ new_storage.filter = apply_tracking_filter(self, entity, id, networked, new_storage, filter)
983
+ mark_tracking_storage_dirty(self, entity, id, networked, new_storage, DIRTY_FILTER_FLAG)
984
+ pair_storage = new_storage
985
+ end
986
+
987
+ local hook_info = ensure_hooked_pair(self, id)
988
+ hook_info.networked_references[entity] = {
989
+ tracking_storage = pair_storage,
990
+ networked = networked,
991
+ }
992
+ end
993
+
994
+ local function start_unreliable_pair(
995
+ self: Server,
996
+ entity: Entity,
997
+ id: Component,
998
+ networked: NetworkedStorage,
999
+ filter: FilterInput?
1000
+ )
1001
+ local unreliable_pair_storage = networked.unreliable_pair_storages[id]
1002
+
1003
+ if unreliable_pair_storage then
1004
+ -- reviving a stopped storage regains the count its stop gave up
1005
+ if unreliable_pair_storage.is_dead then
1006
+ increment_unreliable(self, entity, networked)
1007
+ end
1008
+ -- revived storage keeps its last_replicated, collect sends only the delta
1009
+ change_tracking(self, entity, id, networked, unreliable_pair_storage, filter)
1010
+ else
1011
+ local new_storage =
1012
+ create_tracking_storage(self, id, TRACK_TYPES.unreliable_pair, filter) :: UnreliablePairStorage
1013
+ networked.unreliable_pair_storages[id] = new_storage
1014
+ increment_unreliable(self, entity, networked)
1015
+
1016
+ new_storage.filter = apply_tracking_filter(self, entity, id, networked, new_storage, filter)
1017
+ mark_tracking_storage_dirty(self, entity, id, networked, new_storage, DIRTY_FILTER_FLAG)
1018
+ unreliable_pair_storage = new_storage
1019
+ end
1020
+
1021
+ local hook_info = ensure_hooked_unreliable_pair(self, id)
1022
+ hook_info.networked_references[entity] = {
1023
+ tracking_storage = unreliable_pair_storage,
1024
+ networked = networked,
1025
+ }
1026
+ end
1027
+
1028
+ local function start_relation(
1029
+ self: Server,
1030
+ entity: Entity,
1031
+ rel: Component,
1032
+ networked: NetworkedStorage,
1033
+ filter: FilterInput?
1034
+ )
1035
+ local relation_storage = networked.relation_storages[rel]
1036
+
1037
+ if relation_storage then
1038
+ change_tracking(self, entity, rel, networked, relation_storage, filter)
1039
+ else
1040
+ local new_storage = create_tracking_storage(self, rel, TRACK_TYPES.relation, filter) :: RelationStorage
1041
+ new_storage.dirty_targets = {}
1042
+ networked.relation_storages[rel] = new_storage
1043
+
1044
+ new_storage.filter = apply_tracking_filter(self, entity, rel, networked, new_storage, filter)
1045
+ mark_tracking_storage_dirty(self, entity, rel, networked, new_storage, DIRTY_FILTER_FLAG)
1046
+ relation_storage = new_storage
1047
+ end
1048
+
1049
+ local hook_info = ensure_hooked_relation(self, rel)
1050
+ hook_info.networked_references[entity] = {
1051
+ tracking_storage = relation_storage,
1052
+ networked = networked,
1053
+ }
1054
+ end
1055
+
1056
+ local function ensure_networked(self: Server, entity: Entity): (NetworkedStorage, boolean)
1057
+ local existing = self.networked[entity]
1058
+ if existing then
1059
+ existing.is_dead = false
1060
+ return existing, true
1061
+ end
1062
+
1063
+ local new_networked: NetworkedStorage = {
1064
+ filter = nil :: any,
1065
+ last_replicated = buffer.create(self.bitmask_size),
1066
+ dirty_flags = 0,
1067
+ is_dead = false,
1068
+ unreliable_count = 0,
1069
+
1070
+ dirty_reliable = {},
1071
+ dirty_unreliable = {},
1072
+ dirty_pairs = {},
1073
+ dirty_unreliable_pairs = {},
1074
+ dirty_relations = {},
1075
+
1076
+ reliable_storages = {},
1077
+ unreliable_storages = {},
1078
+ pair_storages = {},
1079
+ unreliable_pair_storages = {},
1080
+ relation_storages = {},
1081
+ }
1082
+ self.networked[entity] = new_networked
1083
+ return new_networked, false
1084
+ end
1085
+
1086
+ function start_networked(self: Server, entity: Entity, filter: FilterInput?)
1087
+ local networked, revived = ensure_networked(self, entity)
1088
+
1089
+ -- the removal hook on this tag is how world:delete reaches the server
1090
+ local alive_tracking = self.components.__alive_tracking__
1091
+ if not WORLD_HAS(self.world, entity, alive_tracking) then
1092
+ WORLD_ADD(self.world, entity, alive_tracking)
1093
+ end
1094
+
1095
+ if revived then
1096
+ -- old filter snapshots into last_replicated, clients leaving the filter
1097
+ -- get a removal delta instead of the pending deletion
1098
+ change_networked(self, entity, networked, filter)
1099
+ else
1100
+ local bitmask: Bitmask?
1101
+
1102
+ if filter then
1103
+ if filter_groups.is_filter_group(filter) then
1104
+ local group = filter :: FilterGroup
1105
+ bitmask = group:calculate_up()
1106
+ connect_networked_group(self, entity, networked, group)
1107
+ elseif typeof(filter) == "function" then
1108
+ local predicator = filter :: registry.MemberPredicator
1109
+ bitmask = self.registry:create_bitmask_from_function(predicator)
1110
+
1111
+ self.function_filters[networked] = {
1112
+ predicator = predicator,
1113
+ entity = entity,
1114
+ networked = networked,
1115
+ }
1116
+ else
1117
+ bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
1118
+ end
1119
+ -- bitmask stays nil: replicate to all active clients, resolved at collect
1120
+
1121
+ end
1122
+
1123
+ networked.filter = bitmask
1124
+ mark_networked_dirty(self, entity, networked, DIRTY_FILTER_FLAG)
1125
+ end
1126
+
1127
+ local postponed = self.postponed_components[entity]
1128
+ if postponed then
1129
+ self.postponed_components[entity] = nil
1130
+
1131
+ for track_type, components_list in postponed do
1132
+ if track_type == TRACK_TYPES.reliable then
1133
+ for component, info in components_list do
1134
+ start_reliable(self, entity, component, networked, info.filter, info.once)
1135
+ end
1136
+ elseif track_type == TRACK_TYPES.unreliable then
1137
+ for component, info in components_list do
1138
+ start_unreliable(self, entity, component, networked, info.filter)
1139
+ end
1140
+ elseif track_type == TRACK_TYPES.pair then
1141
+ for id, info in components_list do
1142
+ start_pair(self, entity, id, networked, info.filter)
1143
+ end
1144
+ elseif track_type == TRACK_TYPES.unreliable_pair then
1145
+ for id, info in components_list do
1146
+ start_unreliable_pair(self, entity, id, networked, info.filter)
1147
+ end
1148
+ elseif track_type == TRACK_TYPES.relation then
1149
+ for rel, info in components_list do
1150
+ start_relation(self, entity, rel, networked, info.filter)
1151
+ end
1152
+ end
1153
+ end
1154
+ end
1155
+
1156
+ -- a revived entity keeps its live unreliable storages (their count never
1157
+ -- dropped), so membership lost at stop_networked comes back here
1158
+ if networked.unreliable_count > 0 then
1159
+ self.unreliable_networked[entity] = true
1160
+ end
1161
+ end
1162
+
1163
+ function change_networked(self: Server, entity: Entity, networked: NetworkedStorage, filter: FilterInput?)
1164
+ -- old filter source stops driving this storage before the new one takes over
1165
+ clear_filter_source(self, networked)
1166
+
1167
+ local bitmask: Bitmask?
1168
+ if filter then
1169
+ if filter_groups.is_filter_group(filter) then
1170
+ local group = filter :: FilterGroup
1171
+ bitmask = group:calculate_up()
1172
+ connect_networked_group(self, entity, networked, group)
1173
+ elseif typeof(filter) == "function" then
1174
+ local predicator = filter :: registry.MemberPredicator
1175
+ bitmask = self.registry:create_bitmask_from_function(predicator)
1176
+ self.function_filters[networked] = {
1177
+ predicator = predicator,
1178
+ entity = entity,
1179
+ networked = networked,
1180
+ }
1181
+ else
1182
+ bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
1183
+ end
1184
+ end
1185
+
1186
+ edit_networked_filter(self, entity, networked, bitmask)
1187
+ end
1188
+
1189
+ function server.set_networked(self: Server, entity: Entity, filter: FilterInput?)
1190
+ local networked = self.networked[entity]
1191
+
1192
+ if networked and networked.cleaned then
1193
+ LOG_ERROR "attempted to network a dead entity"
1194
+ end
1195
+
1196
+ if networked and not networked.is_dead then
1197
+ change_networked(self, entity, networked, filter)
1198
+ else
1199
+ start_networked(self, entity, filter)
1200
+ end
1201
+ end
1202
+
1203
+ function push_postponed_component(
1204
+ postponed: PostponedComponentsList,
1205
+ track_type: TrackType,
1206
+ component: Component,
1207
+ filter: any,
1208
+ once: boolean?
1209
+ )
1210
+ local postponed_list = postponed[track_type]
1211
+ if not postponed_list then
1212
+ postponed_list = {}
1213
+ postponed[track_type] = postponed_list
1214
+ end
1215
+ postponed_list[component] = { filter = filter, once = once }
1216
+ end
1217
+
1218
+ function ensure_postponed(self: Server, entity: Entity): PostponedComponentsList
1219
+ local postponed = self.postponed_components[entity]
1220
+ if not postponed then
1221
+ postponed = {}
1222
+ self.postponed_components[entity] = postponed
1223
+ end
1224
+
1225
+ return postponed
1226
+ end
1227
+
1228
+ function server.stop_networked(self: Server, entity: Entity)
1229
+ local networked = self.networked[entity]
1230
+ -- already dead: postponing again would resurrect storages a stop_* dropped
1231
+ if not networked or networked.cleaned or networked.is_dead then
1232
+ return
1233
+ end
1234
+
1235
+ networked.is_dead = true
1236
+ clear_filter_source(self, networked)
1237
+ mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
1238
+ -- count stays: live storages get postponed and come back on revive
1239
+ self.unreliable_networked[entity] = nil
1240
+
1241
+ local postponed = ensure_postponed(self, entity)
1242
+
1243
+ -- storages stopped with stop_* must not come back with the entity, so only
1244
+ -- live ones get postponed. hook references stay: world changes during the
1245
+ -- dead window keep marking the storage so a revive replicates them
1246
+ for component, reliable_storage in networked.reliable_storages do
1247
+ if reliable_storage.is_dead then
1248
+ continue
1249
+ end
1250
+ clear_filter_source(self, reliable_storage)
1251
+ push_postponed_component(
1252
+ postponed,
1253
+ TRACK_TYPES.reliable,
1254
+ component,
1255
+ reliable_storage.filter_input,
1256
+ reliable_storage.is_once
1257
+ )
1258
+ end
1259
+ for component, unreliable_storage in networked.unreliable_storages do
1260
+ if unreliable_storage.is_dead then
1261
+ continue
1262
+ end
1263
+ clear_filter_source(self, unreliable_storage)
1264
+ push_postponed_component(postponed, TRACK_TYPES.unreliable, component, unreliable_storage.filter_input)
1265
+ end
1266
+ for id, pair_storage in networked.pair_storages do
1267
+ if pair_storage.is_dead then
1268
+ continue
1269
+ end
1270
+ clear_filter_source(self, pair_storage)
1271
+ push_postponed_component(postponed, TRACK_TYPES.pair, id, pair_storage.filter_input)
1272
+ end
1273
+ for id, unreliable_pair_storage in networked.unreliable_pair_storages do
1274
+ if unreliable_pair_storage.is_dead then
1275
+ continue
1276
+ end
1277
+ -- filter_input keeps the group or predicate itself, so a later start reattaches it
1278
+ clear_filter_source(self, unreliable_pair_storage)
1279
+ push_postponed_component(postponed, TRACK_TYPES.unreliable_pair, id, unreliable_pair_storage.filter_input)
1280
+ end
1281
+ for rel, relation_storage in networked.relation_storages do
1282
+ if relation_storage.is_dead then
1283
+ continue
1284
+ end
1285
+ clear_filter_source(self, relation_storage)
1286
+ push_postponed_component(postponed, TRACK_TYPES.relation, rel, relation_storage.filter_input)
1287
+ end
1288
+ end
1289
+
1290
+ function server.cleanup_entity(self: Server, entity: Entity)
1291
+ local networked = self.networked[entity]
1292
+
1293
+ if networked then
1294
+ if not networked.is_dead then
1295
+ self:stop_networked(entity)
1296
+ end
1297
+ networked.cleaned = true
1298
+ end
1299
+
1300
+ -- stop_networked postpones components for a revive; the entity is gone, drop them
1301
+ self.postponed_components[entity] = nil
1302
+ self.unreliable_networked[entity] = nil
1303
+
1304
+ -- custom ids travel with the server id, so the client resolves the
1305
+ -- deletion without them; a global id is the only address the deletion
1306
+ -- delta has, so it outlives the storage until collect_updates discards it
1307
+ local options = self.entity_serialization[entity]
1308
+ if options then
1309
+ options.custom = nil
1310
+ if not networked or options.global_id == nil then
1311
+ self.entity_serialization[entity] = nil
1312
+ end
1313
+ end
1314
+ end
1315
+
1316
+ function server.set_reliable(self: Server, entity: Entity, component: Component, filter: FilterInput?)
1317
+ local networked = self.networked[entity]
1318
+
1319
+ if networked and networked.cleaned then
1320
+ LOG_ERROR "attempted to network a dead entity"
1321
+ end
1322
+
1323
+ if networked and not networked.is_dead then
1324
+ start_reliable(self, entity, component, networked, filter, false)
1325
+ else
1326
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.reliable, component, filter, false)
1327
+ end
1328
+ end
1329
+
1330
+ -- the unreliable channel only carries values, so tags cannot be tracked
1331
+ -- unreliably; use set_reliable for presence-only replication
1332
+ function server.set_unreliable(self: Server, entity: Entity, component: Component, filter: FilterInput?)
1333
+ local networked = self.networked[entity]
1334
+
1335
+ if networked and networked.cleaned then
1336
+ LOG_ERROR "attempted to network a dead entity"
1337
+ end
1338
+ if not IS_COMPONENT(self.world, component) then
1339
+ LOG_ERROR(
1340
+ "attempted to track a tag unreliably, unreliables only send values: ",
1341
+ common.log_component(self.world, component)
1342
+ )
1343
+ end
1344
+
1345
+ if networked and not networked.is_dead then
1346
+ start_unreliable(self, entity, component, networked, filter)
1347
+ else
1348
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.unreliable, component, filter)
1349
+ end
1350
+ end
1351
+
1352
+ function server.set_once(self: Server, entity: Entity, component: Component, filter: FilterInput?)
1353
+ local networked = self.networked[entity]
1354
+
1355
+ if networked and networked.cleaned then
1356
+ LOG_ERROR "attempted to network a dead entity"
1357
+ end
1358
+
1359
+ if networked and not networked.is_dead then
1360
+ start_reliable(self, entity, component, networked, filter, true)
1361
+ else
1362
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.reliable, component, filter, true)
1363
+ end
1364
+ end
1365
+
1366
+ -- a dead storage or entity still takes the mark: it is harmless if collect
1367
+ -- discards it and needed if a revive lands before then
1368
+ function server.force_change(self: Server, entity: Entity, component: Component)
1369
+ local networked = self.networked[entity]
1370
+ if not networked or networked.cleaned then
1371
+ return
1372
+ end
1373
+
1374
+ local reliable_storage = networked.reliable_storages[component]
1375
+ if not reliable_storage then
1376
+ return
1377
+ end
1378
+ mark_tracking_storage_dirty(self, entity, component, networked, reliable_storage, DIRTY_CHANGED_FLAG)
1379
+ end
1380
+
1381
+ function server.set_pair(self: Server, entity: Entity, id: Component, filter: FilterInput?)
1382
+ local networked = self.networked[entity]
1383
+
1384
+ if networked and networked.cleaned then
1385
+ LOG_ERROR "attempted to network a dead entity"
1386
+ end
1387
+
1388
+ if networked and not networked.is_dead then
1389
+ start_pair(self, entity, id, networked, filter)
1390
+ else
1391
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.pair, id, filter)
1392
+ end
1393
+ end
1394
+
1395
+ -- unreliable pairs replicate their value reliably on add and remove; later
1396
+ -- value changes are left to the unreliable channel. pair tags are rejected,
1397
+ -- the channel has nothing to send for them
1398
+ function server.set_unreliable_pair(self: Server, entity: Entity, id: Component, filter: FilterInput?)
1399
+ local networked = self.networked[entity]
1400
+
1401
+ if networked and networked.cleaned then
1402
+ LOG_ERROR "attempted to network a dead entity"
1403
+ end
1404
+ if not IS_COMPONENT(self.world, id) then
1405
+ LOG_ERROR(
1406
+ "attempted to track a pair tag unreliably, unreliables only send values: ",
1407
+ common.log_component(self.world, id)
1408
+ )
1409
+ end
1410
+
1411
+ if networked and not networked.is_dead then
1412
+ start_unreliable_pair(self, entity, id, networked, filter)
1413
+ else
1414
+ -- dead entities postpone too; a revive replays this alongside the rest
1415
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.unreliable_pair, id, filter)
1416
+ end
1417
+ end
1418
+
1419
+ function server.set_relation(self: Server, entity: Entity, relation: Component, filter: FilterInput?)
1420
+ local networked = self.networked[entity]
1421
+
1422
+ if networked and networked.cleaned then
1423
+ LOG_ERROR "attempted to network a dead entity"
1424
+ end
1425
+
1426
+ if networked and not networked.is_dead then
1427
+ start_relation(self, entity, relation, networked, filter)
1428
+ else
1429
+ push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.relation, relation, filter)
1430
+ end
1431
+ end
1432
+
1433
+ -- stop_* marks the storage dead (removal goes out at the next collect) and
1434
+ -- drops its postponed entry so a later start_networked does not bring it
1435
+ -- back. a dead entity keeps its live storages until collect, so both happen:
1436
+ -- otherwise a same-frame revive would keep the storage alive. a set_* call
1437
+ -- before the collect revives the storage in place
1438
+
1439
+ -- resolves the networked storage a stop_* acts on; nil means only the
1440
+ -- postponed entry needs dropping
1441
+ local function stop_target(self: Server, entity: Entity, track_type: TrackType, component: Component): NetworkedStorage?
1442
+ local networked = self.networked[entity]
1443
+ if networked and networked.cleaned then
1444
+ return nil
1445
+ end
1446
+
1447
+ if not networked or networked.is_dead then
1448
+ remove_postponed_component(self, entity, track_type, component)
1449
+ end
1450
+ return networked
1451
+ end
1452
+
1453
+ function server.stop_reliable(self: Server, entity: Entity, component: Component)
1454
+ local networked = stop_target(self, entity, TRACK_TYPES.reliable, component)
1455
+ if not networked then
1456
+ return
1457
+ end
1458
+
1459
+ local reliable_storage = networked.reliable_storages[component]
1460
+ if reliable_storage and not reliable_storage.is_dead then
1461
+ stop_tracking(self, entity, component, networked, reliable_storage)
1462
+ end
1463
+ end
1464
+
1465
+ -- once storages live in the reliable storages, same stop path
1466
+ function server.stop_once(self: Server, entity: Entity, component: Component)
1467
+ self:stop_reliable(entity, component)
1468
+ end
1469
+
1470
+ function server.stop_unreliable(self: Server, entity: Entity, component: Component)
1471
+ local networked = stop_target(self, entity, TRACK_TYPES.unreliable, component)
1472
+ if not networked then
1473
+ return
1474
+ end
1475
+
1476
+ local unreliable_storage = networked.unreliable_storages[component]
1477
+ if unreliable_storage and not unreliable_storage.is_dead then
1478
+ stop_tracking(self, entity, component, networked, unreliable_storage)
1479
+ decrement_unreliable(self, entity, networked)
1480
+ end
1481
+ end
1482
+
1483
+ function server.stop_pair(self: Server, entity: Entity, id: Component)
1484
+ local networked = stop_target(self, entity, TRACK_TYPES.pair, id)
1485
+ if not networked then
1486
+ return
1487
+ end
1488
+
1489
+ local pair_storage = networked.pair_storages[id]
1490
+ if pair_storage and not pair_storage.is_dead then
1491
+ stop_tracking(self, entity, id, networked, pair_storage)
1492
+ end
1493
+ end
1494
+
1495
+ function server.stop_unreliable_pair(self: Server, entity: Entity, id: Component)
1496
+ local networked = stop_target(self, entity, TRACK_TYPES.unreliable_pair, id)
1497
+ if not networked then
1498
+ return
1499
+ end
1500
+
1501
+ local unreliable_pair_storage = networked.unreliable_pair_storages[id]
1502
+ if unreliable_pair_storage and not unreliable_pair_storage.is_dead then
1503
+ stop_tracking(self, entity, id, networked, unreliable_pair_storage)
1504
+ decrement_unreliable(self, entity, networked)
1505
+ end
1506
+ end
1507
+
1508
+ function server.stop_relation(self: Server, entity: Entity, relation: Component)
1509
+ local networked = stop_target(self, entity, TRACK_TYPES.relation, relation)
1510
+ if not networked then
1511
+ return
1512
+ end
1513
+
1514
+ local relation_storage = networked.relation_storages[relation]
1515
+ if relation_storage and not relation_storage.is_dead then
1516
+ stop_tracking(self, entity, relation, networked, relation_storage)
1517
+ end
1518
+ end
1519
+
1520
+ local function ensure_entity_serialization(self: Server, entity: Entity): EntitySerializationOptions
1521
+ local options = self.entity_serialization[entity]
1522
+ if not options then
1523
+ options = {} :: EntitySerializationOptions
1524
+ self.entity_serialization[entity] = options
1525
+ end
1526
+ return options
1527
+ end
1528
+
1529
+ local function prune_entity_serialization(self: Server, entity: Entity, options: EntitySerializationOptions)
1530
+ if options.global_id == nil and options.custom == nil then
1531
+ self.entity_serialization[entity] = nil
1532
+ end
1533
+ end
1534
+
1535
+ function server.set_global(self: Server, entity: Entity, global_id: number)
1536
+ if global_id > MAX_GLOBAL_ID then
1537
+ LOG_ERROR(`global id size exceeded, max is {MAX_GLOBAL_ID}`)
1538
+ end
1539
+ ensure_entity_serialization(self, entity).global_id = global_id
1540
+ end
1541
+
1542
+ function server.remove_global(self: Server, entity: Entity)
1543
+ local options = self.entity_serialization[entity]
1544
+ if not options then
1545
+ return
1546
+ end
1547
+ options.global_id = nil
1548
+ prune_entity_serialization(self, entity, options)
1549
+ end
1550
+
1551
+ function server.set_custom(self: Server, entity: Entity, custom: Component | CustomId)
1552
+ local options = ensure_entity_serialization(self, entity)
1553
+ if options.custom then
1554
+ common.log_warn("attempted to register a custom_id twice for the same entity", debug.traceback())
1555
+ return
1556
+ end
1557
+
1558
+ if type(custom) ~= "number" and not self.registered_custom_ids[custom :: CustomId] then
1559
+ LOG_ERROR "attempted to use a custom id that wasn't registered, use register_custom_id first"
1560
+ end
1561
+ options.custom = custom
1562
+ end
1563
+
1564
+ function server.remove_custom(self: Server, entity: Entity)
1565
+ local options = self.entity_serialization[entity]
1566
+ if not options then
1567
+ return
1568
+ end
1569
+ options.custom = nil
1570
+ prune_entity_serialization(self, entity, options)
1571
+ end
1572
+
1573
+ function server.register_custom_id(self: Server, custom_id: CustomId)
1574
+ self.registered_custom_ids[custom_id] = true
1575
+ self.is_shared_dirty = true
1576
+ end
1577
+
1578
+ local function ensure_component_serialization(self: Server, component: Component): ComponentSerializationOptions
1579
+ local options = self.component_serialization[component]
1580
+ if not options then
1581
+ options = {} :: ComponentSerializationOptions
1582
+ self.component_serialization[component] = options
1583
+ end
1584
+ return options
1585
+ end
1586
+
1587
+ local function prune_component_serialization(self: Server, component: Component, options: ComponentSerializationOptions)
1588
+ if options.serdes == nil and options.preprocessor == nil and options.delta_serializer == nil then
1589
+ self.component_serialization[component] = nil
1590
+ end
1591
+ end
1592
+
1593
+ function server.set_serdes(self: Server, component: Component, serdes: types.Serdes)
1594
+ ensure_component_serialization(self, component).serdes = serdes
1595
+ self.is_shared_dirty = true
1596
+ end
1597
+
1598
+ function server.remove_serdes(self: Server, component: Component)
1599
+ local options = self.component_serialization[component]
1600
+ if not options then
1601
+ return
1602
+ end
1603
+ options.serdes = nil
1604
+ prune_component_serialization(self, component, options)
1605
+ self.is_shared_dirty = true
1606
+ end
1607
+
1608
+ function server.set_preprocessor(self: Server, component: Component, preprocessor: types.Preprocessor)
1609
+ ensure_component_serialization(self, component).preprocessor = preprocessor
1610
+ end
1611
+
1612
+ function server.remove_preprocessor(self: Server, component: Component)
1613
+ local options = self.component_serialization[component]
1614
+ if not options then
1615
+ return
1616
+ end
1617
+ options.preprocessor = nil
1618
+ prune_component_serialization(self, component, options)
1619
+ end
1620
+
1621
+ function server.set_delta_serializer(self: Server, component: Component, delta_serializer: types.DeltaSerializer)
1622
+ ensure_component_serialization(self, component).delta_serializer = delta_serializer
1623
+ end
1624
+
1625
+ function server.remove_delta_serializer(self: Server, component: Component)
1626
+ local options = self.component_serialization[component]
1627
+ if not options then
1628
+ return
1629
+ end
1630
+ options.delta_serializer = nil
1631
+ prune_component_serialization(self, component, options)
1632
+ end
1633
+
1634
+ type ChangedSerializeEntry = {
1635
+ components: Array<Component>,
1636
+ values: Array<any>,
1637
+ }
1638
+
1639
+ type FullRelationEntry = {
1640
+ components: {
1641
+ [Component]: {
1642
+ targets: { Entity },
1643
+ values: { any },
1644
+ },
1645
+ },
1646
+ tags: {
1647
+ [Component]: { Entity },
1648
+ },
1649
+ }
1650
+ type ChangedRelationEntry = {
1651
+ added: {
1652
+ [Component]: { Entity },
1653
+ },
1654
+ removed: {
1655
+ [Component]: { Entity },
1656
+ },
1657
+ changed: {
1658
+ [Component]: {
1659
+ targets: { Entity },
1660
+ values: { any },
1661
+ },
1662
+ },
1663
+ }
1664
+
1665
+ -- every bucket is created on first push, so an entity only pays for the
1666
+ -- categories it actually touched this collect
1667
+ export type EntityUpdates = {
1668
+ changed: ChangedSerializeEntry?,
1669
+ added: Array<Component>?,
1670
+ removed: Array<Component>?,
1671
+
1672
+ pair_changed: ChangedSerializeEntry?,
1673
+ pair_added: Array<Component>?,
1674
+ pair_removed: Array<Component>?,
1675
+
1676
+ full_relations: FullRelationEntry?,
1677
+ changed_relations: ChangedRelationEntry?,
1678
+ removed_relations: Array<Component>?,
1679
+ }
1680
+
1681
+ type GroupedKey = "added" | "removed" | "pair_added" | "pair_removed" | "removed_relations"
1682
+
1683
+ type MemberPacket = {
1684
+ buffer: buffer,
1685
+ variants: Array<any>,
1686
+ }
1687
+
1688
+ export type MemberUpdatePackets = {
1689
+ -- combined byte size of every buffer in `updates`
1690
+ total_size: number,
1691
+ updates: Array<MemberPacket>,
1692
+ }
1693
+
1694
+ type PacketIterator = () -> (Player, buffer, { { any } })
1695
+
1696
+ type SerializeGroupOutput = {
1697
+ buffer: buffer,
1698
+ variants: Array<any>,
1699
+ }
1700
+
1701
+ export type SerializeGroup = {
1702
+ bitmask: Bitmask,
1703
+ deleted_entities: Array<Entity>,
1704
+ added_entities: Array<Entity>,
1705
+
1706
+ updates: Map<Entity, EntityUpdates>,
1707
+
1708
+ output: SerializeGroupOutput,
1709
+ }
1710
+
1711
+ -- unreliable packets carry only current values: presence, filters and
1712
+ -- relations all travel on the reliable channel
1713
+ type UnreliableSerializeEntry = {
1714
+ components: ChangedSerializeEntry?,
1715
+ pairs: ChangedSerializeEntry?,
1716
+ }
1717
+
1718
+ export type UnreliableSerializeGroup = {
1719
+ bitmask: Bitmask,
1720
+ updates: Map<Entity, UnreliableSerializeEntry>,
1721
+
1722
+ outputs: Array<SerializeGroupOutput>,
1723
+ }
1724
+
1725
+ function calculate_component_deltas(
1726
+ last_replicated: Bitmask,
1727
+ component_target: Bitmask,
1728
+ entity_target: Bitmask,
1729
+ deltas: BitmaskDeltas
1730
+ )
1731
+ bitmasks.load_bitmask_deltas(deltas)
1732
+ bitmasks.calculate_bitmask_deltas(last_replicated, component_target, deltas)
1733
+
1734
+ -- clients outside entity visibility get entity add/delete instead
1735
+ bitmasks.band(deltas.added, entity_target, deltas.added)
1736
+ bitmasks.band(deltas.removed, entity_target, deltas.removed)
1737
+ end
1738
+
1739
+ function exclude_active_deltas(deltas: BitmaskDeltas, newly_active: Bitmask, newly_unactive: Bitmask)
1740
+ bitmasks.exclude(deltas.added, newly_active, deltas.added)
1741
+ bitmasks.exclude(deltas.removed, newly_active, deltas.removed)
1742
+ bitmasks.exclude(deltas.removed, newly_unactive, deltas.removed)
1743
+ end
1744
+
1745
+ local function entity_updates(group: SerializeGroup, entity: Entity): EntityUpdates
1746
+ local entry = group.updates[entity]
1747
+ if not entry then
1748
+ entry = {} :: EntityUpdates
1749
+ group.updates[entity] = entry
1750
+ end
1751
+ return entry
1752
+ end
1753
+
1754
+ local function push_grouped_component(entry: EntityUpdates, key: GroupedKey, component: Component)
1755
+ local list = (entry :: any)[key] :: Array<Component>?
1756
+ if not list then
1757
+ list = {}
1758
+ (entry :: any)[key] = list
1759
+ end
1760
+ table.insert(list :: Array<Component>, component)
1761
+ end
1762
+
1763
+ local function push_changed_component(entry: EntityUpdates, is_pair: boolean, component: Component, value: any)
1764
+ local changed = if is_pair then entry.pair_changed else entry.changed
1765
+ if not changed then
1766
+ changed = { components = {}, values = {} }
1767
+ if is_pair then
1768
+ entry.pair_changed = changed
1769
+ else
1770
+ entry.changed = changed
1771
+ end
1772
+ end
1773
+ table.insert((changed :: ChangedSerializeEntry).components, component)
1774
+ table.insert((changed :: ChangedSerializeEntry).values, SET_VALUE(value))
1775
+ end
1776
+
1777
+ local function push_unreliable_update(
1778
+ updates: Map<Entity, UnreliableSerializeEntry>,
1779
+ entity: Entity,
1780
+ is_pair: boolean,
1781
+ component: Component,
1782
+ value: any
1783
+ )
1784
+ local entry = updates[entity]
1785
+ if not entry then
1786
+ entry = {} :: UnreliableSerializeEntry
1787
+ updates[entity] = entry
1788
+ end
1789
+
1790
+ local changed = if is_pair then entry.pairs else entry.components
1791
+ if not changed then
1792
+ changed = { components = {}, values = {} }
1793
+ if is_pair then
1794
+ entry.pairs = changed
1795
+ else
1796
+ entry.components = changed
1797
+ end
1798
+ end
1799
+
1800
+ table.insert((changed :: ChangedSerializeEntry).components, component)
1801
+ table.insert((changed :: ChangedSerializeEntry).values, SET_VALUE(value))
1802
+ end
1803
+
1804
+ function push_storage_updates(
1805
+ storage: TrackingStorageBase,
1806
+ entry: EntityUpdates,
1807
+ is_pair: boolean,
1808
+ component: Component,
1809
+ has_component: boolean,
1810
+ value: any
1811
+ )
1812
+ if storage.is_component then
1813
+ if has_component then
1814
+ push_changed_component(entry, is_pair, component, value)
1815
+ else
1816
+ push_grouped_component(entry, if is_pair then "pair_removed" else "removed", component)
1817
+ end
1818
+ else
1819
+ if has_component then
1820
+ push_grouped_component(entry, if is_pair then "pair_added" else "added", component)
1821
+ else
1822
+ push_grouped_component(entry, if is_pair then "pair_removed" else "removed", component)
1823
+ end
1824
+ end
1825
+ end
1826
+
1827
+ function push_relation_full(
1828
+ world: World,
1829
+ storage: RelationStorage,
1830
+ entry: EntityUpdates,
1831
+ entity: Entity,
1832
+ rel: Component
1833
+ )
1834
+ local bucket: FullRelationEntry = entry.full_relations or { components = {}, tags = {} }
1835
+ entry.full_relations = bucket
1836
+
1837
+ if storage.is_component then
1838
+ local targets: Array<Entity> = {}
1839
+ local values: Array<any> = {}
1840
+
1841
+ local index = 0
1842
+ while true do
1843
+ local target = WORLD_TARGET(world, entity, rel, index)
1844
+ if not target then
1845
+ break
1846
+ end
1847
+ index += 1
1848
+
1849
+ local value = WORLD_GET(world, entity, PAIR(rel, target))
1850
+ table.insert(targets, target)
1851
+ table.insert(values, SET_VALUE(value))
1852
+ end
1853
+
1854
+ bucket.components[rel] = { targets = targets, values = values }
1855
+ else
1856
+ local targets: Array<Entity> = {}
1857
+
1858
+ local index = 0
1859
+ while true do
1860
+ local target = WORLD_TARGET(world, entity, rel, index)
1861
+ if not target then
1862
+ break
1863
+ end
1864
+ index += 1
1865
+ table.insert(targets, target)
1866
+ end
1867
+
1868
+ bucket.tags[rel] = targets
1869
+ end
1870
+ end
1871
+
1872
+ function push_relation_updates(
1873
+ world: World,
1874
+ storage: RelationStorage,
1875
+ entry: EntityUpdates,
1876
+ entity: Entity,
1877
+ rel: Component
1878
+ )
1879
+ local bucket: ChangedRelationEntry = entry.changed_relations or { added = {}, removed = {}, changed = {} }
1880
+ entry.changed_relations = bucket
1881
+
1882
+ local is_component = storage.is_component
1883
+
1884
+ for target in storage.dirty_targets do
1885
+ if WORLD_HAS(world, entity, PAIR(rel, target)) then
1886
+ if is_component then
1887
+ local changed = bucket.changed[rel]
1888
+ if not changed then
1889
+ changed = { targets = {}, values = {} }
1890
+ bucket.changed[rel] = changed
1891
+ end
1892
+
1893
+ local value = WORLD_GET(world, entity, PAIR(rel, target))
1894
+ table.insert(changed.targets, target)
1895
+ table.insert(changed.values, SET_VALUE(value))
1896
+ else
1897
+ local added = bucket.added[rel]
1898
+ if not added then
1899
+ added = {}
1900
+ bucket.added[rel] = added
1901
+ end
1902
+ table.insert(added, target)
1903
+ end
1904
+ else
1905
+ local removed = bucket.removed[rel]
1906
+ if not removed then
1907
+ removed = {}
1908
+ bucket.removed[rel] = removed
1909
+ end
1910
+ table.insert(removed, target)
1911
+ end
1912
+ end
1913
+ end
1914
+
1915
+ local function setbit(mask: number, bit: number)
1916
+ return bit32.bor(mask, bit32.lshift(1, bit))
1917
+ end
1918
+
1919
+ local function vlq_span(length: number): number
1920
+ if length < 0x80 then
1921
+ return 1
1922
+ elseif length < 0x4000 then
1923
+ return 2
1924
+ elseif length < 0x200000 then
1925
+ return 3
1926
+ else
1927
+ return 4
1928
+ end
1929
+ end
1930
+
1931
+ local function write_vlq_bitmask(c: Cursor, value: number, mask: number, bit: number): number
1932
+ if value > 0 then
1933
+ cursor.write_vlq(c, value)
1934
+ return setbit(mask, bit)
1935
+ else
1936
+ return mask
1937
+ end
1938
+ end
1939
+
1940
+ local function write_variant_value(
1941
+ self: Server,
1942
+ c: Cursor,
1943
+ log_component: Component,
1944
+ options: ComponentSerializationOptions?,
1945
+ value: any,
1946
+ variants: Array<any>
1947
+ )
1948
+ local serialize_value = READ_VALUE(value)
1949
+
1950
+ if options then
1951
+ local serdes = options.serdes
1952
+ local preprocessor = options.preprocessor
1953
+
1954
+ if preprocessor then
1955
+ serialize_value = preprocessor.serialize(serialize_value)
1956
+ end
1957
+
1958
+ if serdes then
1959
+ local output, serdes_variants = serdes.serialize(serialize_value)
1960
+
1961
+ if serdes.includes_variants then
1962
+ if serdes_variants == nil then
1963
+ cursor.writeu8(c, VLQ_ZERO)
1964
+ else
1965
+ local start_variants = #variants
1966
+ if #serdes_variants > 0 then
1967
+ table.move(serdes_variants, 1, #serdes_variants, #variants + 1, variants)
1968
+ end
1969
+ cursor.write_vlq(c, #serdes_variants)
1970
+ cursor.write_vlq(c, start_variants + 1)
1971
+ end
1972
+ elseif serdes_variants ~= nil then
1973
+ common.log_error(
1974
+ "serdes: serializer provided variants yet includes_variants is not set to true for component: ",
1975
+ common.log_component(self.world, log_component)
1976
+ )
1977
+ end
1978
+
1979
+ local bytespan = serdes.bytespan
1980
+ cursor.write_buffer(c, output)
1981
+ if bytespan then
1982
+ if bytespan ~= buffer.len(output) then
1983
+ common.log_error(
1984
+ `bytespan: {bytespan} mismatch for buffer length: {buffer.len(output)} in component: `,
1985
+ common.log_component(self.world, log_component)
1986
+ )
1987
+ end
1988
+ else
1989
+ cursor.write_vlq(c, buffer.len(output))
1990
+ end
1991
+ return
1992
+ end
1993
+ end
1994
+
1995
+ if serialize_value == nil then
1996
+ cursor.writeu8(c, VLQ_ZERO)
1997
+ else
1998
+ table.insert(variants, serialize_value)
1999
+ cursor.write_vlq(c, #variants)
2000
+ end
2001
+ end
2002
+
2003
+ local function write_component_value(self: Server, c: Cursor, component: Component, value: any, variants: Array<any>)
2004
+ local options = self.component_serialization[component]
2005
+ write_variant_value(self, c, component, options, value, variants)
2006
+ end
2007
+
2008
+ local function write_pair_value(
2009
+ self: Server,
2010
+ c: Cursor,
2011
+ relation: Component,
2012
+ id: Component,
2013
+ value: any,
2014
+ variants: Array<any>
2015
+ )
2016
+ local options = self.component_serialization[id] or self.component_serialization[PAIR(relation, WILDCARD)]
2017
+ write_variant_value(self, c, relation, options, value, variants)
2018
+ end
2019
+
2020
+ local function write_component_id(self: Server, c: Cursor, component: Entity)
2021
+ local shared_components = self.shared.components
2022
+ local encoded = shared_components.members[component]
2023
+ if not encoded then
2024
+ LOG_ERROR(`attempted to replicate a non-shared component `, common.log_component(self.world, component))
2025
+ return
2026
+ end
2027
+ cursor.write_span(c, encoded, #shared_components.keys)
2028
+ end
2029
+
2030
+ -- ids are written back-to-front like the rest of the wire format: payload
2031
+ -- first, then the ENTITY_ID_TYPES tag the client reads first. global ids are
2032
+ -- the exception, a single byte with no tag.
2033
+ local function write_entity_id(self: Server, c: Cursor, entity: Entity)
2034
+ local options = self.entity_serialization[entity]
2035
+
2036
+ if options then
2037
+ local global_id = options.global_id
2038
+ if global_id then
2039
+ cursor.writeu8(c, global_id + GLOBAL_ID_OFFSET)
2040
+ return
2041
+ end
2042
+
2043
+ local custom = options.custom
2044
+ if custom then
2045
+ if type(custom) == "number" then
2046
+ write_component_id(self, c, custom :: any)
2047
+ cursor.writeu40(c, entity :: any)
2048
+ cursor.writeu8(c, ENTITY_ID_TYPES.custom_id)
2049
+ else
2050
+ cursor.writeu8(c, self.shared.custom_ids.members[custom :: CustomId])
2051
+ cursor.writeu40(c, entity :: any)
2052
+ cursor.writeu8(c, ENTITY_ID_TYPES.custom_handler)
2053
+ end
2054
+ return
2055
+ end
2056
+ end
2057
+
2058
+ if self.shared.components.members[entity] then
2059
+ write_component_id(self, c, entity)
2060
+ cursor.writeu8(c, ENTITY_ID_TYPES.shared)
2061
+ else
2062
+ cursor.writeu40(c, entity :: any)
2063
+ cursor.writeu8(c, ENTITY_ID_TYPES.entity)
2064
+ end
2065
+ end
2066
+
2067
+ local function write_entity_pair_value(
2068
+ self: Server,
2069
+ world: World,
2070
+ c: Cursor,
2071
+ id: Component,
2072
+ value: any,
2073
+ variants: Array<any>
2074
+ )
2075
+ local first, second = PAIR_FIRST(world, id), PAIR_SECOND(world, id)
2076
+ local pointer_start = c.offset
2077
+
2078
+ write_pair_value(self, c, first, id, value, variants)
2079
+
2080
+ -- if the entity is a custom id, we dont know if this has a serdes in the client, as it needs to be processed first to know the final pair
2081
+ -- we write a vlq so the client can jump in the cursor to skip this value entirely and process it later
2082
+ local pointer_offset = c.offset - pointer_start
2083
+ cursor.write_vlq(c, pointer_offset)
2084
+
2085
+ write_entity_id(self, c, second)
2086
+ write_component_id(self, c, first)
2087
+ end
2088
+
2089
+ local function write_entity_pair(self: Server, world: World, c: Cursor, id: Component)
2090
+ local first, second = PAIR_FIRST(world, id), PAIR_SECOND(world, id)
2091
+ write_entity_id(self, c, second)
2092
+ write_component_id(self, c, first)
2093
+ end
2094
+
2095
+ local ENTITY_BITS = {
2096
+ reliable_removed = 0,
2097
+ reliable_added = 1,
2098
+ reliable_changed = 2,
2099
+ pair_removed = 3,
2100
+ pair_added = 4,
2101
+ pair_changed = 5,
2102
+ relations_removed = 6,
2103
+ full_relations = 7,
2104
+ changed_relations = 8,
2105
+ }
2106
+
2107
+ -- per group trailer mask
2108
+ local GROUP_BITS = {
2109
+ updates = 0,
2110
+ added_entities = 1,
2111
+ deleted_entities = 2,
2112
+ }
2113
+
2114
+ local function write_relation_targets(self: Server, c: Cursor, relation: Component, targets: Array<Entity>)
2115
+ for _, target in targets do
2116
+ write_entity_id(self, c, target)
2117
+ end
2118
+ cursor.write_vlq(c, #targets)
2119
+ write_component_id(self, c, relation)
2120
+ end
2121
+
2122
+ local function write_relation_targets_values(
2123
+ self: Server,
2124
+ c: Cursor,
2125
+ relation: Component,
2126
+ targets: Array<Entity>,
2127
+ values: Array<any>,
2128
+ variants: Array<any>
2129
+ )
2130
+ for index, target in targets do
2131
+ write_entity_id(self, c, target)
2132
+ write_component_value(self, c, relation, values[index], variants)
2133
+ end
2134
+ cursor.write_vlq(c, #targets)
2135
+ write_component_id(self, c, relation)
2136
+ end
2137
+
2138
+ -- component ids only (tags added, anything removed): ids..., returns count
2139
+ local function write_grouped_components(self: Server, c: Cursor, components: Array<Component>?): number
2140
+ if not components then
2141
+ return 0
2142
+ end
2143
+ for _, component in components do
2144
+ write_component_id(self, c, component)
2145
+ end
2146
+ return #components
2147
+ end
2148
+
2149
+ -- pair ids only (tags added, anything removed): pairs..., returns count
2150
+ local function write_grouped_pairs(self: Server, c: Cursor, ids: Array<Component>?): number
2151
+ if not ids then
2152
+ return 0
2153
+ end
2154
+ local world = self.world
2155
+ for _, id in ids do
2156
+ write_entity_pair(self, world, c, id)
2157
+ end
2158
+ return #ids
2159
+ end
2160
+
2161
+ -- one entity block. every section is written only when non empty, its bit set
2162
+ -- in the entity mask and its count vlq written right after the payload
2163
+ local function serialize_entity_updates(
2164
+ self: Server,
2165
+ c: Cursor,
2166
+ entity: Entity,
2167
+ entry: EntityUpdates,
2168
+ variants: Array<any>
2169
+ )
2170
+ local world = self.world
2171
+ local mask = 0
2172
+
2173
+ -- relations
2174
+
2175
+ local total = write_grouped_components(self, c, entry.removed_relations)
2176
+ mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.relations_removed)
2177
+
2178
+ local full = entry.full_relations
2179
+ if full then
2180
+ local total_components = 0
2181
+ for relation, targets in full.components do
2182
+ write_relation_targets_values(self, c, relation, targets.targets, targets.values, variants)
2183
+ total_components += 1
2184
+ end
2185
+ cursor.write_vlq(c, total_components)
2186
+
2187
+ local total_tags = 0
2188
+ for relation, targets in full.tags do
2189
+ write_relation_targets(self, c, relation, targets)
2190
+ total_tags += 1
2191
+ end
2192
+ cursor.write_vlq(c, total_tags)
2193
+
2194
+ mask = setbit(mask, ENTITY_BITS.full_relations)
2195
+ end
2196
+
2197
+ local changed_relations = entry.changed_relations
2198
+ if changed_relations then
2199
+ local total_changed = 0
2200
+ for relation, targets in changed_relations.changed do
2201
+ write_relation_targets_values(self, c, relation, targets.targets, targets.values, variants)
2202
+ total_changed += 1
2203
+ end
2204
+ cursor.write_vlq(c, total_changed)
2205
+
2206
+ local total_added = 0
2207
+ for relation, targets in changed_relations.added do
2208
+ write_relation_targets(self, c, relation, targets)
2209
+ total_added += 1
2210
+ end
2211
+ cursor.write_vlq(c, total_added)
2212
+
2213
+ local total_removed = 0
2214
+ for relation, targets in changed_relations.removed do
2215
+ write_relation_targets(self, c, relation, targets)
2216
+ total_removed += 1
2217
+ end
2218
+ cursor.write_vlq(c, total_removed)
2219
+
2220
+ mask = setbit(mask, ENTITY_BITS.changed_relations)
2221
+ end
2222
+
2223
+ -- pairs
2224
+
2225
+ total = write_grouped_pairs(self, c, entry.pair_removed)
2226
+ mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.pair_removed)
2227
+
2228
+ total = write_grouped_pairs(self, c, entry.pair_added)
2229
+ mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.pair_added)
2230
+
2231
+ local pair_changed = entry.pair_changed
2232
+ if pair_changed then
2233
+ local ids = pair_changed.components
2234
+ local values = pair_changed.values
2235
+ for index, id in ids do
2236
+ write_entity_pair_value(self, world, c, id, values[index], variants)
2237
+ end
2238
+ mask = write_vlq_bitmask(c, #ids, mask, ENTITY_BITS.pair_changed)
2239
+ end
2240
+
2241
+ -- reliables
2242
+
2243
+ total = write_grouped_components(self, c, entry.removed)
2244
+ mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.reliable_removed)
2245
+
2246
+ total = write_grouped_components(self, c, entry.added)
2247
+ mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.reliable_added)
2248
+
2249
+ local changed = entry.changed
2250
+ if changed then
2251
+ local components = changed.components
2252
+ local values = changed.values
2253
+ for index, component in components do
2254
+ write_component_value(self, c, component, values[index], variants)
2255
+ write_component_id(self, c, component)
2256
+ end
2257
+ mask = write_vlq_bitmask(c, #components, mask, ENTITY_BITS.reliable_changed)
2258
+ end
2259
+
2260
+ cursor.writeu16(c, mask)
2261
+ write_entity_id(self, c, entity)
2262
+ end
2263
+
2264
+ -- layout (read order is the reverse of write order):
2265
+ -- u16 group mask GROUP_BITS, vlq count per set bit
2266
+ -- deleted entities entity ids
2267
+ -- added entities entity ids
2268
+ -- updates per entity:
2269
+ -- entity id
2270
+ -- u16 entity mask ENTITY_BITS, vlq count per set bit
2271
+ -- reliable changed value + component id, vlq
2272
+ -- reliable added / removed component ids, vlq
2273
+ -- pair changed pair values, vlq
2274
+ -- pair added / removed pairs, vlq
2275
+ -- changed relations [targets..., vlq, relation] for
2276
+ -- changed / added / removed, each vlq counted
2277
+ -- full relations components then tags, each vlq counted
2278
+ -- relations removed relation ids, vlq
2279
+ local function serialize_update_group(self: Server, group: SerializeGroup): (buffer, Array<any>)
2280
+ local c = cursor.new()
2281
+ local variants: Array<any> = {}
2282
+ local group_mask = 0
2283
+
2284
+ local total = 0
2285
+ for entity, entry in group.updates do
2286
+ serialize_entity_updates(self, c, entity, entry, variants)
2287
+ total += 1
2288
+ end
2289
+ group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.updates)
2290
+
2291
+ total = 0
2292
+ for _, entity in group.added_entities do
2293
+ write_entity_id(self, c, entity)
2294
+ total += 1
2295
+ end
2296
+ group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.added_entities)
2297
+
2298
+ total = 0
2299
+ for _, entity in group.deleted_entities do
2300
+ write_entity_id(self, c, entity)
2301
+ total += 1
2302
+ end
2303
+ group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.deleted_entities)
2304
+
2305
+ cursor.writeu16(c, group_mask)
2306
+
2307
+ return cursor.close(c), variants
2308
+ end
2309
+
2310
+ -- layout (read order is the reverse of write order). packets split at
2311
+ -- MAX_UNRELIABLE_PACKET_SIZE, each one self-contained.
2312
+ -- vlq entity count
2313
+ -- per entity: component values + ids, vlq component count,
2314
+ -- pair values, vlq pair count, entity id
2315
+ local function serialize_unreliable_group(
2316
+ self: Server,
2317
+ group: UnreliableSerializeGroup,
2318
+ outputs: Array<SerializeGroupOutput>
2319
+ )
2320
+ local c = cursor.new()
2321
+ local world = self.world
2322
+ local variants: Array<any> = {}
2323
+ local total_entities = 0
2324
+
2325
+ local checkpoint_offset = 0
2326
+ local checkpoint_variants_count = 0
2327
+ local checkpoint_total_entities = 0
2328
+
2329
+ local function create_checkpoint()
2330
+ checkpoint_offset = c.offset
2331
+ checkpoint_variants_count = #variants
2332
+ checkpoint_total_entities = total_entities
2333
+ end
2334
+
2335
+ local function rollback_checkpoint()
2336
+ c.offset = checkpoint_offset
2337
+ for i = #variants, checkpoint_variants_count + 1, -1 do
2338
+ variants[i] = nil
2339
+ end
2340
+ total_entities = checkpoint_total_entities
2341
+ end
2342
+
2343
+ local function commit()
2344
+ if total_entities <= 0 then
2345
+ return
2346
+ end
2347
+
2348
+ cursor.write_vlq(c, total_entities)
2349
+ table.insert(outputs, {
2350
+ buffer = cursor.close(c),
2351
+ variants = variants,
2352
+ })
2353
+
2354
+ c = cursor.new()
2355
+ variants = {}
2356
+ total_entities = 0
2357
+ end
2358
+
2359
+ local function serialize_entity(entity: Entity, entry: UnreliableSerializeEntry)
2360
+ local components = entry.components
2361
+ if components then
2362
+ local values = components.values
2363
+ for index, component in components.components do
2364
+ write_component_value(self, c, component, values[index], variants)
2365
+ write_component_id(self, c, component)
2366
+ end
2367
+ cursor.write_vlq(c, #components.components)
2368
+ else
2369
+ cursor.write_vlq(c, 0)
2370
+ end
2371
+
2372
+ local pairs = entry.pairs
2373
+ if pairs then
2374
+ local values = pairs.values
2375
+ for index, id in pairs.components do
2376
+ write_entity_pair_value(self, world, c, id, values[index], variants)
2377
+ end
2378
+ cursor.write_vlq(c, #pairs.components)
2379
+ else
2380
+ cursor.write_vlq(c, 0)
2381
+ end
2382
+
2383
+ write_entity_id(self, c, entity)
2384
+ total_entities += 1
2385
+ end
2386
+
2387
+ for entity, entry in group.updates do
2388
+ create_checkpoint()
2389
+ serialize_entity(entity, entry)
2390
+
2391
+ if c.offset + vlq_span(total_entities) + vlq_span(1) + PACKET_TYPE_SPAN > MAX_UNRELIABLE_PACKET_SIZE then
2392
+ rollback_checkpoint()
2393
+ commit()
2394
+ serialize_entity(entity, entry)
2395
+ end
2396
+ end
2397
+
2398
+ commit()
2399
+ end
2400
+
2401
+ local function collect_updates_groups(self: Server): Map<MaskHash, SerializeGroup>
2402
+ local client_registry = self.registry
2403
+ for storage, entry in self.function_filters do
2404
+ local bitmask = client_registry:create_bitmask_from_function(entry.predicator)
2405
+ local current = storage.filter
2406
+ if current and bitmasks.equals(current, bitmask) then
2407
+ continue
2408
+ end
2409
+
2410
+ local tracking_component = entry.tracking
2411
+
2412
+ if tracking_component then
2413
+ -- routes to the right dirty list via the storage's track_type
2414
+ edit_tracking_filter(
2415
+ self,
2416
+ entry.entity,
2417
+ tracking_component,
2418
+ entry.networked,
2419
+ entry.tracking_storage :: TrackingStorageBase,
2420
+ bitmask
2421
+ )
2422
+ else
2423
+ edit_networked_filter(self, entry.entity, entry.networked, bitmask)
2424
+ end
2425
+ end
2426
+
2427
+ local serialize_groups: Map<MaskHash, SerializeGroup> = {}
2428
+ local networked_entities = self.networked
2429
+ local world = self.world
2430
+
2431
+ local function emit(bitmask: Bitmask): SerializeGroup
2432
+ local hash = bitmasks.hash(bitmask)
2433
+ local serialize = serialize_groups[hash]
2434
+ if not serialize then
2435
+ local new_serialize: SerializeGroup = {
2436
+ -- deltas are reused scratch buffers, groups need a stable copy
2437
+ bitmask = bitmasks.clone(bitmask),
2438
+ deleted_entities = {},
2439
+ added_entities = {},
2440
+
2441
+ updates = {},
2442
+
2443
+ variants = nil :: any,
2444
+ output = nil :: any,
2445
+ }
2446
+ serialize_groups[hash] = new_serialize
2447
+ return new_serialize
2448
+ end
2449
+
2450
+ return serialize
2451
+ end
2452
+
2453
+ local active = self.client_active_bitmask
2454
+ local last_active = self.last_active_bitmask
2455
+ local newly_active = self.newly_active_bitmask
2456
+ local newly_unactive = self.newly_unactive_bitmask
2457
+
2458
+ bitmasks.exclude(active, last_active, newly_active)
2459
+ bitmasks.exclude(last_active, active, newly_unactive)
2460
+
2461
+ local is_active_dirty = not bitmasks.is_empty(newly_active) or not bitmasks.is_empty(newly_unactive)
2462
+
2463
+ local entity_deltas = self.entity_bitmask_deltas
2464
+ local component_deltas = self.component_bitmask_deltas
2465
+ local draft = self.draft_bitmask
2466
+ local entity_target = self.target_bitmask
2467
+
2468
+ for _, entity in self.dirty_networked do
2469
+ local networked = networked_entities[entity]
2470
+
2471
+ if networked.is_dead then
2472
+ -- deletion goes to everyone who last had the entity and is still here
2473
+ local last = networked.last_replicated or networked.filter or last_active
2474
+ bitmasks.load(last, draft)
2475
+ bitmasks.band(draft, active, draft)
2476
+ bitmasks.exclude(draft, newly_active, draft)
2477
+ if not bitmasks.is_empty(draft) then
2478
+ table.insert(emit(draft).deleted_entities, entity)
2479
+ end
2480
+ clear_networked_references(self, entity, networked)
2481
+ networked_entities[entity] = nil
2482
+ if networked.cleaned then
2483
+ self.entity_serialization[entity] = nil
2484
+ end
2485
+ continue
2486
+ end
2487
+
2488
+ -- filter sources (groups especially) can carry freed or never active
2489
+ -- indexes; only active clients ever receive anything. every storage
2490
+ -- target below is clipped by this one
2491
+ local target: Bitmask = active
2492
+ local entity_filter = networked.filter
2493
+ if entity_filter then
2494
+ bitmasks.load(entity_filter, entity_target)
2495
+ bitmasks.band(entity_target, active, entity_target)
2496
+ target = entity_target
2497
+ end
2498
+ local is_entity_filter_dirty = flag_test(networked.dirty_flags, DIRTY_FILTER_FLAG)
2499
+
2500
+ if is_entity_filter_dirty then
2501
+ local last = networked.last_replicated or networked.filter or last_active
2502
+ bitmasks.load_bitmask_deltas(entity_deltas)
2503
+ bitmasks.calculate_bitmask_deltas(last, target, entity_deltas)
2504
+ -- newly active clients get a full snapshot at this same boundary instead;
2505
+ -- newly unactive clients are gone, nobody to send removals to
2506
+ if is_active_dirty then
2507
+ exclude_active_deltas(entity_deltas, newly_active, newly_unactive)
2508
+ end
2509
+
2510
+ if not bitmasks.is_empty(entity_deltas.added) then
2511
+ table.insert(emit(entity_deltas.added).added_entities, entity)
2512
+ end
2513
+ if not bitmasks.is_empty(entity_deltas.removed) then
2514
+ table.insert(emit(entity_deltas.removed).deleted_entities, entity)
2515
+ end
2516
+ networked.last_replicated = nil
2517
+ end
2518
+
2519
+ for _, component in networked.dirty_reliable do
2520
+ local reliable_storage = networked.reliable_storages[component]
2521
+ local is_component_filter_dirty = flag_test(reliable_storage.dirty_flags, DIRTY_FILTER_FLAG)
2522
+
2523
+ local has_delta_source = reliable_storage.filter ~= nil or reliable_storage.last_replicated ~= nil
2524
+ local component_target = reliable_storage.filter or target
2525
+ local is_component_added = false
2526
+ local has_component = WORLD_HAS(world, entity, component)
2527
+ local has_value = has_component and reliable_storage.is_component
2528
+ -- `and/or` would turn a false value into nil
2529
+ local value = if has_value then WORLD_GET(world, entity, component) else nil
2530
+
2531
+ if has_delta_source and is_component_filter_dirty then
2532
+ local last = reliable_storage.last_replicated or reliable_storage.filter :: Bitmask
2533
+
2534
+ calculate_component_deltas(last, component_target, target, component_deltas)
2535
+ if is_active_dirty then
2536
+ exclude_active_deltas(component_deltas, newly_active, newly_unactive)
2537
+ end
2538
+
2539
+ is_component_added = not bitmasks.is_empty(component_deltas.added)
2540
+
2541
+ if is_component_added then
2542
+ local entry = entity_updates(emit(component_deltas.added), entity)
2543
+ push_storage_updates(reliable_storage, entry, false, component, has_component, value)
2544
+ end
2545
+
2546
+ if not bitmasks.is_empty(component_deltas.removed) then
2547
+ local entry = entity_updates(emit(component_deltas.removed), entity)
2548
+ push_grouped_component(entry, "removed", component)
2549
+ end
2550
+ reliable_storage.last_replicated = nil
2551
+ end
2552
+
2553
+ if flag_test(reliable_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
2554
+ bitmasks.load(component_target, draft)
2555
+ bitmasks.band(draft, target, draft)
2556
+ if is_active_dirty then
2557
+ bitmasks.exclude(draft, newly_active, draft)
2558
+ bitmasks.exclude(draft, newly_unactive, draft)
2559
+ end
2560
+
2561
+ if is_component_added then
2562
+ bitmasks.exclude(draft, component_deltas.added, draft)
2563
+ end
2564
+ if is_entity_filter_dirty then
2565
+ bitmasks.exclude(draft, entity_deltas.added, draft)
2566
+ end
2567
+
2568
+ if not bitmasks.is_empty(draft) then
2569
+ local entry = entity_updates(emit(draft), entity)
2570
+ push_storage_updates(reliable_storage, entry, false, component, has_component, value)
2571
+ end
2572
+ end
2573
+ reliable_storage.dirty_flags = 0
2574
+ -- the removal delta above was its last word
2575
+ if reliable_storage.is_dead then
2576
+ clear_hook_reference(self, entity, component, reliable_storage)
2577
+ networked.reliable_storages[component] = nil
2578
+ end
2579
+ end
2580
+
2581
+ for _, component in networked.dirty_unreliable do
2582
+ local unreliable_storage = networked.unreliable_storages[component]
2583
+ local is_unreliable_filter_dirty = flag_test(unreliable_storage.dirty_flags, DIRTY_FILTER_FLAG)
2584
+
2585
+ local has_delta_source = unreliable_storage.filter ~= nil or unreliable_storage.last_replicated ~= nil
2586
+ local component_target = unreliable_storage.filter or target
2587
+ local is_unreliable_added = false
2588
+ local has_value = WORLD_HAS(world, entity, component)
2589
+ local value = if has_value then WORLD_GET(world, entity, component) else nil
2590
+
2591
+ if has_delta_source and is_unreliable_filter_dirty then
2592
+ local last = unreliable_storage.last_replicated or unreliable_storage.filter :: Bitmask
2593
+
2594
+ calculate_component_deltas(last, component_target, target, component_deltas)
2595
+ if is_active_dirty then
2596
+ exclude_active_deltas(component_deltas, newly_active, newly_unactive)
2597
+ end
2598
+
2599
+ is_unreliable_added = not bitmasks.is_empty(component_deltas.added)
2600
+
2601
+ if is_unreliable_added then
2602
+ local entry = entity_updates(emit(component_deltas.added), entity)
2603
+ if has_value then
2604
+ push_changed_component(entry, false, component, value)
2605
+ else
2606
+ push_grouped_component(entry, "removed", component)
2607
+ end
2608
+ end
2609
+
2610
+ if not bitmasks.is_empty(component_deltas.removed) then
2611
+ local entry = entity_updates(emit(component_deltas.removed), entity)
2612
+ push_grouped_component(entry, "removed", component)
2613
+ end
2614
+ unreliable_storage.last_replicated = nil
2615
+ end
2616
+
2617
+ if flag_test(unreliable_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
2618
+ bitmasks.load(component_target, draft)
2619
+ bitmasks.band(draft, target, draft)
2620
+ if is_active_dirty then
2621
+ bitmasks.exclude(draft, newly_active, draft)
2622
+ bitmasks.exclude(draft, newly_unactive, draft)
2623
+ end
2624
+
2625
+ if is_unreliable_added then
2626
+ bitmasks.exclude(draft, component_deltas.added, draft)
2627
+ end
2628
+ if is_entity_filter_dirty then
2629
+ bitmasks.exclude(draft, entity_deltas.added, draft)
2630
+ end
2631
+
2632
+ if not bitmasks.is_empty(draft) then
2633
+ local entry = entity_updates(emit(draft), entity)
2634
+ if has_value then
2635
+ push_changed_component(entry, false, component, value)
2636
+ else
2637
+ push_grouped_component(entry, "removed", component)
2638
+ end
2639
+ end
2640
+ end
2641
+ unreliable_storage.dirty_flags = 0
2642
+ if unreliable_storage.is_dead then
2643
+ clear_hook_reference(self, entity, component, unreliable_storage)
2644
+ networked.unreliable_storages[component] = nil
2645
+ end
2646
+ end
2647
+
2648
+ for _, id in networked.dirty_pairs do
2649
+ local pair_storage = networked.pair_storages[id]
2650
+ local is_pair_filter_dirty = flag_test(pair_storage.dirty_flags, DIRTY_FILTER_FLAG)
2651
+
2652
+ local has_delta_source = pair_storage.filter ~= nil or pair_storage.last_replicated ~= nil
2653
+ local component_target = pair_storage.filter or target
2654
+ local is_pair_added = false
2655
+ local has_pair = WORLD_HAS(world, entity, id)
2656
+ local has_value = (has_pair and pair_storage.is_component)
2657
+ local value = if has_value then WORLD_GET(world, entity, id) else nil
2658
+
2659
+ if has_delta_source and is_pair_filter_dirty then
2660
+ local last = pair_storage.last_replicated or pair_storage.filter :: Bitmask
2661
+
2662
+ calculate_component_deltas(last, component_target, target, component_deltas)
2663
+ if is_active_dirty then
2664
+ exclude_active_deltas(component_deltas, newly_active, newly_unactive)
2665
+ end
2666
+
2667
+ is_pair_added = not bitmasks.is_empty(component_deltas.added)
2668
+
2669
+ if is_pair_added then
2670
+ local entry = entity_updates(emit(component_deltas.added), entity)
2671
+ push_storage_updates(pair_storage, entry, true, id, has_pair, value)
2672
+ end
2673
+
2674
+ if not bitmasks.is_empty(component_deltas.removed) then
2675
+ local entry = entity_updates(emit(component_deltas.removed), entity)
2676
+ push_grouped_component(entry, "pair_removed", id)
2677
+ end
2678
+ pair_storage.last_replicated = nil
2679
+ end
2680
+
2681
+ if flag_test(pair_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
2682
+ bitmasks.load(component_target, draft)
2683
+ bitmasks.band(draft, target, draft)
2684
+ if is_active_dirty then
2685
+ bitmasks.exclude(draft, newly_active, draft)
2686
+ bitmasks.exclude(draft, newly_unactive, draft)
2687
+ end
2688
+
2689
+ if is_pair_added then
2690
+ bitmasks.exclude(draft, component_deltas.added, draft)
2691
+ end
2692
+ if is_entity_filter_dirty then
2693
+ bitmasks.exclude(draft, entity_deltas.added, draft)
2694
+ end
2695
+
2696
+ if not bitmasks.is_empty(draft) then
2697
+ local entry = entity_updates(emit(draft), entity)
2698
+ push_storage_updates(pair_storage, entry, true, id, has_pair, value)
2699
+ end
2700
+ end
2701
+ pair_storage.dirty_flags = 0
2702
+ if pair_storage.is_dead then
2703
+ clear_hook_reference(self, entity, id, pair_storage)
2704
+ networked.pair_storages[id] = nil
2705
+ end
2706
+ end
2707
+
2708
+ for _, id in networked.dirty_unreliable_pairs do
2709
+ local unreliable_pair_storage = networked.unreliable_pair_storages[id]
2710
+ local is_pair_filter_dirty = flag_test(unreliable_pair_storage.dirty_flags, DIRTY_FILTER_FLAG)
2711
+
2712
+ local has_delta_source = unreliable_pair_storage.filter ~= nil
2713
+ or unreliable_pair_storage.last_replicated ~= nil
2714
+ local component_target = unreliable_pair_storage.filter or target
2715
+ local is_pair_added = false
2716
+ local has_pair = WORLD_HAS(world, entity, id)
2717
+ local has_value = (has_pair and unreliable_pair_storage.is_component)
2718
+ local value = has_value and WORLD_GET(world, entity, id) or nil
2719
+
2720
+ if has_delta_source and is_pair_filter_dirty then
2721
+ local last = unreliable_pair_storage.last_replicated or unreliable_pair_storage.filter :: Bitmask
2722
+
2723
+ calculate_component_deltas(last, component_target, target, component_deltas)
2724
+ if is_active_dirty then
2725
+ exclude_active_deltas(component_deltas, newly_active, newly_unactive)
2726
+ end
2727
+
2728
+ is_pair_added = not bitmasks.is_empty(component_deltas.added)
2729
+
2730
+ if is_pair_added then
2731
+ local entry = entity_updates(emit(component_deltas.added), entity)
2732
+ push_storage_updates(unreliable_pair_storage, entry, true, id, has_pair, value)
2733
+ end
2734
+
2735
+ if not bitmasks.is_empty(component_deltas.removed) then
2736
+ local entry = entity_updates(emit(component_deltas.removed), entity)
2737
+ push_grouped_component(entry, "pair_removed", id)
2738
+ end
2739
+ unreliable_pair_storage.last_replicated = nil
2740
+ end
2741
+
2742
+ if flag_test(unreliable_pair_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
2743
+ bitmasks.load(component_target, draft)
2744
+ bitmasks.band(draft, target, draft)
2745
+ if is_active_dirty then
2746
+ bitmasks.exclude(draft, newly_active, draft)
2747
+ bitmasks.exclude(draft, newly_unactive, draft)
2748
+ end
2749
+
2750
+ if is_pair_added then
2751
+ bitmasks.exclude(draft, component_deltas.added, draft)
2752
+ end
2753
+ if is_entity_filter_dirty then
2754
+ bitmasks.exclude(draft, entity_deltas.added, draft)
2755
+ end
2756
+
2757
+ if not bitmasks.is_empty(draft) then
2758
+ local entry = entity_updates(emit(draft), entity)
2759
+ push_storage_updates(unreliable_pair_storage, entry, true, id, has_pair, value)
2760
+ end
2761
+ end
2762
+ unreliable_pair_storage.dirty_flags = 0
2763
+ if unreliable_pair_storage.is_dead then
2764
+ clear_hook_reference(self, entity, id, unreliable_pair_storage)
2765
+ networked.unreliable_pair_storages[id] = nil
2766
+ end
2767
+ end
2768
+
2769
+ for _, rel in networked.dirty_relations do
2770
+ local relation_storage = networked.relation_storages[rel]
2771
+ local is_rel_filter_dirty = flag_test(relation_storage.dirty_flags, DIRTY_FILTER_FLAG)
2772
+
2773
+ local has_delta_source = relation_storage.filter ~= nil or relation_storage.last_replicated ~= nil
2774
+ local component_target = relation_storage.filter or target
2775
+ local is_rel_added = false
2776
+
2777
+ if has_delta_source and is_rel_filter_dirty then
2778
+ local last = relation_storage.last_replicated or relation_storage.filter :: Bitmask
2779
+
2780
+ calculate_component_deltas(last, component_target, target, component_deltas)
2781
+ if is_active_dirty then
2782
+ exclude_active_deltas(component_deltas, newly_active, newly_unactive)
2783
+ end
2784
+
2785
+ is_rel_added = not bitmasks.is_empty(component_deltas.added)
2786
+
2787
+ if is_rel_added then
2788
+ local entry = entity_updates(emit(component_deltas.added), entity)
2789
+ push_relation_full(world, relation_storage, entry, entity, rel)
2790
+ end
2791
+
2792
+ if not bitmasks.is_empty(component_deltas.removed) then
2793
+ local entry = entity_updates(emit(component_deltas.removed), entity)
2794
+ push_grouped_component(entry, "removed_relations", rel)
2795
+ end
2796
+ relation_storage.last_replicated = nil
2797
+ end
2798
+
2799
+ if flag_test(relation_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
2800
+ bitmasks.load(component_target, draft)
2801
+ bitmasks.band(draft, target, draft)
2802
+ if is_active_dirty then
2803
+ bitmasks.exclude(draft, newly_active, draft)
2804
+ bitmasks.exclude(draft, newly_unactive, draft)
2805
+ end
2806
+
2807
+ if is_rel_added then
2808
+ bitmasks.exclude(draft, component_deltas.added, draft)
2809
+ end
2810
+ if is_entity_filter_dirty then
2811
+ bitmasks.exclude(draft, entity_deltas.added, draft)
2812
+ end
2813
+
2814
+ if not bitmasks.is_empty(draft) then
2815
+ local entry = entity_updates(emit(draft), entity)
2816
+ push_relation_updates(world, relation_storage, entry, entity, rel)
2817
+ end
2818
+ end
2819
+ relation_storage.dirty_flags = 0
2820
+ table.clear(relation_storage.dirty_targets)
2821
+ if relation_storage.is_dead then
2822
+ clear_hook_reference(self, entity, rel, relation_storage)
2823
+ networked.relation_storages[rel] = nil
2824
+ end
2825
+ end
2826
+
2827
+ table.clear(networked.dirty_reliable)
2828
+ table.clear(networked.dirty_unreliable)
2829
+ table.clear(networked.dirty_pairs)
2830
+ table.clear(networked.dirty_unreliable_pairs)
2831
+ table.clear(networked.dirty_relations)
2832
+ networked.dirty_flags = 0
2833
+ end
2834
+ table.clear(self.dirty_networked)
2835
+
2836
+ bitmasks.load(active, last_active)
2837
+ bitmasks.clear(newly_active)
2838
+ bitmasks.clear(newly_unactive)
2839
+
2840
+ return serialize_groups
2841
+ end
2842
+
2843
+ -- fills every group's output buffer and variants in place
2844
+ local function serialize_update_groups(self: Server, groups: Map<MaskHash, SerializeGroup>)
2845
+ for _, group in groups do
2846
+ local buf, variants = serialize_update_group(self, group)
2847
+ group.output = {
2848
+ buffer = buf,
2849
+ variants = variants,
2850
+ }
2851
+ end
2852
+ end
2853
+
2854
+ local function collect_unreliable_groups(self: Server): Map<MaskHash, UnreliableSerializeGroup>
2855
+ local unreliable_groups: Map<MaskHash, UnreliableSerializeGroup> = {}
2856
+ local world = self.world
2857
+ local active = self.client_active_bitmask
2858
+ local draft = self.draft_bitmask
2859
+
2860
+ local function emit(bitmask: Bitmask): UnreliableSerializeGroup
2861
+ local hash = bitmasks.hash(bitmask)
2862
+ local group = unreliable_groups[hash]
2863
+ if not group then
2864
+ local new_group: UnreliableSerializeGroup = {
2865
+ bitmask = bitmasks.clone(bitmask),
2866
+ updates = {},
2867
+
2868
+ outputs = {},
2869
+ }
2870
+ unreliable_groups[hash] = new_group
2871
+ return new_group
2872
+ end
2873
+ return group
2874
+ end
2875
+
2876
+ for entity in self.unreliable_networked do
2877
+ local networked = self.networked[entity]
2878
+ local entity_filter = networked.filter
2879
+
2880
+ for component, storage in networked.unreliable_storages do
2881
+ if storage.is_dead or not WORLD_HAS(world, entity, component) then
2882
+ continue
2883
+ end
2884
+
2885
+ -- visibility right now: active clients inside both the entity and
2886
+ -- storage filters. no delta correction exists here, so inactive
2887
+ -- clients are always clipped out
2888
+ bitmasks.load(active, draft)
2889
+ if entity_filter then
2890
+ bitmasks.band(draft, entity_filter, draft)
2891
+ end
2892
+ local filter = storage.filter
2893
+ if filter then
2894
+ bitmasks.band(draft, filter, draft)
2895
+ end
2896
+ if bitmasks.is_empty(draft) then
2897
+ continue
2898
+ end
2899
+
2900
+ push_unreliable_update(emit(draft).updates, entity, false, component, WORLD_GET(world, entity, component))
2901
+ end
2902
+
2903
+ for id, storage in networked.unreliable_pair_storages do
2904
+ -- pair tags have no value to resend, their presence is reliable
2905
+ if storage.is_dead or not storage.is_component or not WORLD_HAS(world, entity, id) then
2906
+ continue
2907
+ end
2908
+
2909
+ bitmasks.load(active, draft)
2910
+ if entity_filter then
2911
+ bitmasks.band(draft, entity_filter, draft)
2912
+ end
2913
+ local filter = storage.filter
2914
+ if filter then
2915
+ bitmasks.band(draft, filter, draft)
2916
+ end
2917
+ if bitmasks.is_empty(draft) then
2918
+ continue
2919
+ end
2920
+
2921
+ push_unreliable_update(emit(draft).updates, entity, true, id, WORLD_GET(world, entity, id))
2922
+ end
2923
+ end
2924
+
2925
+ return unreliable_groups
2926
+ end
2927
+
2928
+ local function serialize_unreliable_groups(self: Server, groups: Map<MaskHash, UnreliableSerializeGroup>)
2929
+ for _, group in groups do
2930
+ serialize_unreliable_group(self, group, group.outputs)
2931
+ end
2932
+ end
2933
+
2934
+ -- rebuilds the shared lookups (component ids, custom ids, serdes) when
2935
+ -- flagged; no-op otherwise.
2936
+ function server.resolve_dirty(self: Server)
2937
+ if not self.is_shared_dirty then
2938
+ return
2939
+ end
2940
+
2941
+ local serdes: { [Component]: types.Serdes } = {}
2942
+ for component, options in self.component_serialization do
2943
+ if options.serdes then
2944
+ serdes[component] = options.serdes
2945
+ end
2946
+ end
2947
+
2948
+ self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
2949
+ self.is_shared_dirty = false
2950
+ end
2951
+
2952
+ local FULL_BITS = {
2953
+ tags = 0,
2954
+ components = 1,
2955
+ pair_tags = 2,
2956
+ pair_components = 3,
2957
+ relation_tags = 4,
2958
+ relation_components = 5,
2959
+ }
2960
+
2961
+ -- full snapshot for a single player, serialized straight into one buffer.
2962
+ --
2963
+ -- layout in read order (sections are written in the reverse):
2964
+ -- vlq entity count
2965
+ -- per entity:
2966
+ -- entity id
2967
+ -- u8 entity mask FULL_BITS, vlq count per set bit
2968
+ -- tags component ids (tags and unreliable presence)
2969
+ -- components value + component id
2970
+ -- pair tags pairs
2971
+ -- pair components pair values
2972
+ -- relation tags per relation: [targets..., vlq, relation]
2973
+ -- relation components per relation: [target + value..., vlq, relation]
2974
+ local function write_full_entity(
2975
+ self: Server,
2976
+ c: Cursor,
2977
+ entity: Entity,
2978
+ networked: NetworkedStorage,
2979
+ index: number,
2980
+ variants: Array<any>
2981
+ )
2982
+ local world = self.world
2983
+ local mask = 0
2984
+
2985
+ -- relation components: every current target with its value
2986
+ local total = 0
2987
+ for rel, relation_storage in networked.relation_storages do
2988
+ if not relation_storage.is_component then
2989
+ continue
2990
+ end
2991
+ local filter = relation_storage.filter
2992
+ if filter and not bitmasks.test(filter, index) then
2993
+ continue
2994
+ end
2995
+
2996
+ local targets: Array<Entity> = {}
2997
+ local values: Array<any> = {}
2998
+ local target_index = 0
2999
+ while true do
3000
+ local target = WORLD_TARGET(world, entity, rel, target_index)
3001
+ if not target then
3002
+ break
3003
+ end
3004
+ target_index += 1
3005
+ local value = WORLD_GET(world, entity, PAIR(rel, target))
3006
+ table.insert(targets, target)
3007
+ table.insert(values, SET_VALUE(value))
3008
+ end
3009
+ write_relation_targets_values(self, c, rel, targets, values, variants)
3010
+ total += 1
3011
+ end
3012
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.relation_components)
3013
+
3014
+ -- relation tags: every current target
3015
+ total = 0
3016
+ for rel, relation_storage in networked.relation_storages do
3017
+ if relation_storage.is_component then
3018
+ continue
3019
+ end
3020
+ local filter = relation_storage.filter
3021
+ if filter and not bitmasks.test(filter, index) then
3022
+ continue
3023
+ end
3024
+
3025
+ local targets: Array<Entity> = {}
3026
+ local target_index = 0
3027
+ while true do
3028
+ local target = WORLD_TARGET(world, entity, rel, target_index)
3029
+ if not target then
3030
+ break
3031
+ end
3032
+ target_index += 1
3033
+ table.insert(targets, target)
3034
+ end
3035
+ write_relation_targets(self, c, rel, targets)
3036
+ total += 1
3037
+ end
3038
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.relation_tags)
3039
+
3040
+ -- pair components, unreliable values included
3041
+ total = 0
3042
+ for id, storage in networked.pair_storages do
3043
+ if not storage.is_component then
3044
+ continue
3045
+ end
3046
+ local filter = storage.filter
3047
+ if filter and not bitmasks.test(filter, index) then
3048
+ continue
3049
+ end
3050
+ if WORLD_HAS(world, entity, id) then
3051
+ write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
3052
+ total += 1
3053
+ end
3054
+ end
3055
+ for id, storage in networked.unreliable_pair_storages do
3056
+ if not storage.is_component then
3057
+ continue
3058
+ end
3059
+ local filter = storage.filter
3060
+ if filter and not bitmasks.test(filter, index) then
3061
+ continue
3062
+ end
3063
+ if WORLD_HAS(world, entity, id) then
3064
+ write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
3065
+ total += 1
3066
+ end
3067
+ end
3068
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.pair_components)
3069
+
3070
+ -- pair tags: reliable without a value
3071
+ total = 0
3072
+ for id, storage in networked.pair_storages do
3073
+ if storage.is_component then
3074
+ continue
3075
+ end
3076
+ local filter = storage.filter
3077
+ if filter and not bitmasks.test(filter, index) then
3078
+ continue
3079
+ end
3080
+ if WORLD_HAS(world, entity, id) then
3081
+ write_entity_pair(self, world, c, id)
3082
+ total += 1
3083
+ end
3084
+ end
3085
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.pair_tags)
3086
+
3087
+ -- components: unreliable values ride along too, a snapshot is the only
3088
+ -- reliable chance the client gets to see them
3089
+ total = 0
3090
+ for component, storage in networked.reliable_storages do
3091
+ if not storage.is_component then
3092
+ continue
3093
+ end
3094
+ local filter = storage.filter
3095
+ if filter and not bitmasks.test(filter, index) then
3096
+ continue
3097
+ end
3098
+ if WORLD_HAS(world, entity, component) then
3099
+ write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
3100
+ write_component_id(self, c, component)
3101
+ total += 1
3102
+ end
3103
+ end
3104
+ for component, storage in networked.unreliable_storages do
3105
+ local filter = storage.filter
3106
+ if filter and not bitmasks.test(filter, index) then
3107
+ continue
3108
+ end
3109
+ if WORLD_HAS(world, entity, component) then
3110
+ write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
3111
+ write_component_id(self, c, component)
3112
+ total += 1
3113
+ end
3114
+ end
3115
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.components)
3116
+
3117
+ -- tags: reliable without a value
3118
+ total = 0
3119
+ for component, storage in networked.reliable_storages do
3120
+ if storage.is_component then
3121
+ continue
3122
+ end
3123
+ local filter = storage.filter
3124
+ if filter and not bitmasks.test(filter, index) then
3125
+ continue
3126
+ end
3127
+ if WORLD_HAS(world, entity, component) then
3128
+ write_component_id(self, c, component)
3129
+ total += 1
3130
+ end
3131
+ end
3132
+ mask = write_vlq_bitmask(c, total, mask, FULL_BITS.tags)
3133
+
3134
+ cursor.writeu8(c, mask)
3135
+ write_entity_id(self, c, entity)
3136
+ end
3137
+
3138
+ function server.get_full(self: Server, player: Player): (buffer, Array<any>)
3139
+ self:resolve_dirty()
3140
+
3141
+ local index = self.registry.client_indexes[player]
3142
+ if index == nil then
3143
+ LOG_ERROR "attempted to get full of a non registered client"
3144
+ end
3145
+
3146
+ local is_active = bitmasks.test(self.client_active_bitmask, index)
3147
+ if not is_active then
3148
+ common.log_warn(
3149
+ "attempted to get full of a client that is not active, the packet may be incomplete.",
3150
+ "mark the player as ready first with mark_player_ready and run collect_updates"
3151
+ )
3152
+ end
3153
+
3154
+ local c = cursor.new()
3155
+ local variants: Array<any> = {}
3156
+ local total_entities = 0
3157
+
3158
+ for entity, networked in self.networked do
3159
+ if networked.is_dead then
3160
+ continue
3161
+ end
3162
+
3163
+ local entity_filter = networked.filter
3164
+ local visible = if entity_filter then bitmasks.test(entity_filter, index) else is_active
3165
+ if not visible then
3166
+ continue
3167
+ end
3168
+
3169
+ write_full_entity(self, c, entity, networked, index, variants)
3170
+ total_entities += 1
3171
+ end
3172
+
3173
+ cursor.write_vlq(c, total_entities)
3174
+ cursor.writeu8(c, PACKET_TYPES.full)
3175
+ return cursor.close(c), variants
3176
+ end
3177
+
3178
+ local function distribute_update_groups(groups: Map<MaskHash, SerializeGroup>): Map<number, MemberUpdatePackets>
3179
+ local member_updates: Map<number, MemberUpdatePackets> = {}
3180
+ for _, group in groups do
3181
+ local output = group.output
3182
+
3183
+ local buf = output.buffer
3184
+ local size = buffer.len(buf)
3185
+ local update: MemberPacket = {
3186
+ buffer = buf,
3187
+ variants = output.variants :: Array<any>,
3188
+ }
3189
+
3190
+ bitmasks.iter_set(group.bitmask, function(index)
3191
+ local member = member_updates[index]
3192
+ if not member then
3193
+ member = { total_size = 0, updates = {} }
3194
+ member_updates[index] = member
3195
+ end
3196
+ member.total_size += size
3197
+ table.insert(member.updates, update)
3198
+ end)
3199
+ end
3200
+ return member_updates
3201
+ end
3202
+
3203
+ local function distribute_unreliable_groups(
3204
+ groups: Map<MaskHash, UnreliableSerializeGroup>
3205
+ ): Map<number, MemberUpdatePackets>
3206
+ local member_updates: Map<number, MemberUpdatePackets> = {}
3207
+
3208
+ for _, group in groups do
3209
+ local outputs = group.outputs
3210
+ local updates: Array<MemberPacket> = {}
3211
+
3212
+ for _, output in outputs do
3213
+ local buf = output.buffer
3214
+ local update: MemberPacket = {
3215
+ buffer = buf,
3216
+ variants = output.variants :: Array<any>,
3217
+ }
3218
+ table.insert(updates, update)
3219
+ end
3220
+
3221
+ bitmasks.iter_set(group.bitmask, function(index)
3222
+ for _, update in updates do
3223
+ local buf = update.buffer
3224
+ local size = buffer.len(buf)
3225
+
3226
+ local member = member_updates[index]
3227
+ if not member then
3228
+ member = { total_size = 0, updates = {} }
3229
+ member_updates[index] = member
3230
+ end
3231
+ member.total_size += size
3232
+ table.insert(member.updates, update)
3233
+ end
3234
+ end)
3235
+ end
3236
+
3237
+ return member_updates
3238
+ end
3239
+
3240
+ local function combine_packet_outputs(
3241
+ outputs: Array<MemberPacket>,
3242
+ total_size: number,
3243
+ packet_type: number
3244
+ ): (buffer, Array<Array<any>>)
3245
+ local count = #outputs
3246
+ local combined = buffer.create(total_size + vlq_span(count) + PACKET_TYPE_SPAN)
3247
+ local variants = table.create(count) :: Array<Array<any>>
3248
+
3249
+ local offset = 0
3250
+ for _, update in outputs do
3251
+ local buf = update.buffer
3252
+ buffer.copy(combined, offset, buf)
3253
+ offset += buffer.len(buf)
3254
+ table.insert(variants, update.variants)
3255
+ end
3256
+
3257
+ local tail = cursor.from_offset(combined, offset)
3258
+ cursor.write_vlq(tail, count)
3259
+ cursor.writeu8(tail, packet_type)
3260
+ return combined, variants
3261
+ end
3262
+
3263
+ local function create_packet_iterator(
3264
+ self: Server,
3265
+ member_updates: Map<number, MemberUpdatePackets>,
3266
+ packet_type: number
3267
+ ): PacketIterator
3268
+ local index_clients = self.registry.index_clients
3269
+ local iterated: number?
3270
+ local function iterator()
3271
+ local index, member = next(member_updates, iterated)
3272
+ if index == nil then
3273
+ return nil :: any
3274
+ end
3275
+ iterated = index
3276
+
3277
+ local player = index_clients[index]
3278
+ if player == nil then
3279
+ LOG_ERROR "packet addressed to a client that is no longer registered, send packets right after collecting"
3280
+ end
3281
+
3282
+ local combined, variants = combine_packet_outputs(member.updates, member.total_size, packet_type)
3283
+ return player, combined, variants
3284
+ end
3285
+ return iterator :: PacketIterator
3286
+ end
3287
+
3288
+ export type EntitySerializeGroup = {
3289
+ bitmask: Bitmask,
3290
+ relation_components: Array<Component>,
3291
+ relation_tags: Array<Component>,
3292
+ pair_components: Array<Component>,
3293
+ pair_tags: Array<Component>,
3294
+ components: Array<Component>,
3295
+ tags: Array<Component>,
3296
+ }
3297
+
3298
+ local function serialize_entity_group(
3299
+ self: Server,
3300
+ c: Cursor,
3301
+ entity: Entity,
3302
+ group: EntitySerializeGroup,
3303
+ variants: Array<any>
3304
+ )
3305
+ local world = self.world
3306
+ local mask = 0
3307
+
3308
+ local relation_components = group.relation_components
3309
+ for _, rel in relation_components do
3310
+ local targets: Array<Entity> = {}
3311
+ local values: Array<any> = {}
3312
+ local target_index = 0
3313
+ while true do
3314
+ local target = WORLD_TARGET(world, entity, rel, target_index)
3315
+ if not target then
3316
+ break
3317
+ end
3318
+ target_index += 1
3319
+ local value = WORLD_GET(world, entity, PAIR(rel, target))
3320
+ table.insert(targets, target)
3321
+ table.insert(values, SET_VALUE(value))
3322
+ end
3323
+ write_relation_targets_values(self, c, rel, targets, values, variants)
3324
+ end
3325
+ mask = write_vlq_bitmask(c, #relation_components, mask, FULL_BITS.relation_components)
3326
+
3327
+ local relation_tags = group.relation_tags
3328
+ for _, rel in relation_tags do
3329
+ local targets: Array<Entity> = {}
3330
+ local target_index = 0
3331
+ while true do
3332
+ local target = WORLD_TARGET(world, entity, rel, target_index)
3333
+ if not target then
3334
+ break
3335
+ end
3336
+ target_index += 1
3337
+ table.insert(targets, target)
3338
+ end
3339
+ write_relation_targets(self, c, rel, targets)
3340
+ end
3341
+ mask = write_vlq_bitmask(c, #relation_tags, mask, FULL_BITS.relation_tags)
3342
+
3343
+ local pair_components = group.pair_components
3344
+ for _, id in pair_components do
3345
+ write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
3346
+ end
3347
+ mask = write_vlq_bitmask(c, #pair_components, mask, FULL_BITS.pair_components)
3348
+
3349
+ local pair_tags = group.pair_tags
3350
+ for _, id in pair_tags do
3351
+ write_entity_pair(self, world, c, id)
3352
+ end
3353
+ mask = write_vlq_bitmask(c, #pair_tags, mask, FULL_BITS.pair_tags)
3354
+
3355
+ local components = group.components
3356
+ for _, component in components do
3357
+ write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
3358
+ write_component_id(self, c, component)
3359
+ end
3360
+ mask = write_vlq_bitmask(c, #components, mask, FULL_BITS.components)
3361
+
3362
+ local tags = group.tags
3363
+ for _, component in tags do
3364
+ write_component_id(self, c, component)
3365
+ end
3366
+ mask = write_vlq_bitmask(c, #tags, mask, FULL_BITS.tags)
3367
+
3368
+ cursor.writeu8(c, mask)
3369
+ write_entity_id(self, c, entity)
3370
+ end
3371
+
3372
+ local function collect_entity_groups(self: Server, entity: Entity): Map<MaskHash, EntitySerializeGroup>
3373
+ local networked = self.networked[entity]
3374
+ if not networked or networked.is_dead then
3375
+ LOG_ERROR "attempted to collect an entity that is not networked"
3376
+ end
3377
+
3378
+ local scratch = self.draft_bitmask
3379
+ local groups: Map<MaskHash, EntitySerializeGroup> = {}
3380
+
3381
+ local function emit(bitmask: Bitmask): EntitySerializeGroup
3382
+ local hash = bitmasks.hash(bitmask)
3383
+ local group = groups[hash]
3384
+ if not group then
3385
+ group = {
3386
+ bitmask = bitmasks.clone(bitmask),
3387
+ relation_components = {},
3388
+ relation_tags = {},
3389
+ pair_components = {},
3390
+ pair_tags = {},
3391
+ components = {},
3392
+ tags = {},
3393
+ }
3394
+ groups[hash] = group
3395
+ end
3396
+ return group
3397
+ end
3398
+
3399
+ -- everyone who sees the entity at all; storage filters clip within it
3400
+ local entity_visible = bitmasks.clone(self.client_active_bitmask)
3401
+ local entity_filter = networked.filter
3402
+ if entity_filter then
3403
+ bitmasks.band(entity_visible, entity_filter, entity_visible)
3404
+ end
3405
+ if bitmasks.is_empty(entity_visible) then
3406
+ return groups
3407
+ end
3408
+ emit(entity_visible)
3409
+
3410
+ -- nil storage filters resolve to the entity's own visibility
3411
+ local function group_for(storage: StorageBase): EntitySerializeGroup?
3412
+ local filter = storage.filter
3413
+ if not filter then
3414
+ return emit(entity_visible)
3415
+ end
3416
+ bitmasks.band(filter, entity_visible, scratch)
3417
+ if bitmasks.is_empty(scratch) then
3418
+ return nil
3419
+ end
3420
+ return emit(scratch)
3421
+ end
3422
+
3423
+ local world = self.world
3424
+ for rel, storage in networked.relation_storages do
3425
+ local group = group_for(storage)
3426
+ if not group then
3427
+ continue
3428
+ end
3429
+ table.insert(if storage.is_component then group.relation_components else group.relation_tags, rel)
3430
+ end
3431
+ for id, storage in networked.pair_storages do
3432
+ if not WORLD_HAS(world, entity, id) then
3433
+ continue
3434
+ end
3435
+ local group = group_for(storage)
3436
+ if not group then
3437
+ continue
3438
+ end
3439
+ table.insert(if storage.is_component then group.pair_components else group.pair_tags, id)
3440
+ end
3441
+
3442
+ for id, storage in networked.unreliable_pair_storages do
3443
+ if not WORLD_HAS(world, entity, id) then
3444
+ continue
3445
+ end
3446
+ local group = group_for(storage)
3447
+ if group then
3448
+ table.insert(group.pair_components, id)
3449
+ end
3450
+ end
3451
+ for component, storage in networked.reliable_storages do
3452
+ if not WORLD_HAS(world, entity, component) then
3453
+ continue
3454
+ end
3455
+ local group = group_for(storage)
3456
+ if not group then
3457
+ continue
3458
+ end
3459
+ table.insert(if storage.is_component then group.components else group.tags, component)
3460
+ end
3461
+ for component, storage in networked.unreliable_storages do
3462
+ if not WORLD_HAS(world, entity, component) then
3463
+ continue
3464
+ end
3465
+ local group = group_for(storage)
3466
+ if group then
3467
+ table.insert(group.components, component)
3468
+ end
3469
+ end
3470
+
3471
+ return groups
3472
+ end
3473
+
3474
+ function server.collect_entity(self: Server, entity: Entity): PacketIterator
3475
+ self:resolve_dirty()
3476
+ local groups = collect_entity_groups(self, entity)
3477
+
3478
+ local member_updates: Map<number, MemberUpdatePackets> = {}
3479
+ for _, group in groups do
3480
+ local c = cursor.new()
3481
+ local variants: Array<any> = {}
3482
+ serialize_entity_group(self, c, entity, group, variants)
3483
+ cursor.write_vlq(c, 1)
3484
+
3485
+ local buf = cursor.close(c)
3486
+ local size = buffer.len(buf)
3487
+ local update: MemberPacket = { buffer = buf, variants = variants }
3488
+
3489
+ bitmasks.iter_set(group.bitmask, function(index)
3490
+ local member = member_updates[index]
3491
+ if not member then
3492
+ member = { total_size = 0, updates = {} }
3493
+ member_updates[index] = member
3494
+ end
3495
+ member.total_size += size
3496
+ table.insert(member.updates, update)
3497
+ end)
3498
+ end
3499
+
3500
+ return create_packet_iterator(self, member_updates, PACKET_TYPES.entity)
3501
+ end
3502
+
3503
+ function server.collect_updates(self: Server): PacketIterator
3504
+ self:resolve_dirty()
3505
+ local groups = collect_updates_groups(self)
3506
+ serialize_update_groups(self, groups)
3507
+
3508
+ return create_packet_iterator(self, distribute_update_groups(groups), PACKET_TYPES.updates)
3509
+ end
3510
+
3511
+ local function create_unreliable_packet_iterator(
3512
+ self: Server,
3513
+ member_updates: Map<number, MemberUpdatePackets>
3514
+ ): PacketIterator
3515
+ local index_clients = self.registry.index_clients
3516
+
3517
+ return coroutine.wrap(function()
3518
+ for index, member in member_updates do
3519
+ local player = index_clients[index]
3520
+ if player == nil then
3521
+ LOG_ERROR "packet addressed to a client that is no longer registered, send packets right after collecting"
3522
+ end
3523
+ local updates = member.updates
3524
+
3525
+ table.sort(updates, function(a, b)
3526
+ return buffer.len(a.buffer) < buffer.len(b.buffer)
3527
+ end)
3528
+ local total_packets = #updates
3529
+
3530
+ local current = 1
3531
+ while current <= total_packets do
3532
+ local sending: Array<MemberPacket> = {}
3533
+ local batch_size = 0
3534
+
3535
+ while current <= total_packets do
3536
+ local packet = updates[current]
3537
+ local packet_size = buffer.len(packet.buffer)
3538
+
3539
+ -- combine appends the packet count and type byte, so they
3540
+ -- count toward the budget
3541
+ local next_count = #sending + 1
3542
+ local tail = vlq_span(next_count) + PACKET_TYPE_SPAN
3543
+ if batch_size + packet_size + tail > MAX_UNRELIABLE_PACKET_SIZE and #sending > 0 then
3544
+ break
3545
+ end
3546
+
3547
+ table.insert(sending, packet)
3548
+ batch_size += packet_size
3549
+ current += 1
3550
+
3551
+ if batch_size + tail >= MAX_UNRELIABLE_PACKET_SIZE then
3552
+ break
3553
+ end
3554
+ end
3555
+
3556
+ if #sending > 0 then
3557
+ local combined, variants = combine_packet_outputs(sending, batch_size, PACKET_TYPES.unreliable)
3558
+ coroutine.yield(player, combined, variants)
3559
+ end
3560
+ end
3561
+ end
3562
+ end)
3563
+ end
3564
+
3565
+ function server.collect_unreliable(self: Server): PacketIterator
3566
+ self:resolve_dirty()
3567
+ local groups = collect_unreliable_groups(self)
3568
+ serialize_unreliable_groups(self, groups)
3569
+ return create_unreliable_packet_iterator(self, distribute_unreliable_groups(groups))
3570
+ end
3571
+
3572
+ function server.register_player(self: Server, player: Player)
3573
+ self.registry:register_client(player)
3574
+ end
3575
+
3576
+ function server.add_player_alias(self: Server, player: Player, alias: any)
3577
+ self.registry:add_alias(player, alias)
3578
+ end
3579
+
3580
+ function server.remove_player_alias(self: Server, alias: any)
3581
+ self.registry:remove_alias(alias)
3582
+ end
3583
+
3584
+ function server.mark_player_ready(self: Server, player: Player)
3585
+ local registry = self.registry
3586
+ registry:register_client(player)
3587
+
3588
+ local index = registry.client_indexes[player]
3589
+ if not index then
3590
+ return
3591
+ end
3592
+ bitmasks.set(self.client_active_bitmask, index)
3593
+ end
3594
+
3595
+ function server.is_player_ready(self: Server, player: Player): boolean
3596
+ local index = self.registry.client_indexes[player]
3597
+ if not index then
3598
+ return false
3599
+ end
3600
+ return bitmasks.test(self.client_active_bitmask, index)
3601
+ end
3602
+
3603
+ function server.encode_component(self: Server, component: Component): number
3604
+ self:resolve_dirty()
3605
+ local encoded = self.shared.components.members[component]
3606
+ if not encoded then
3607
+ LOG_ERROR(`attempted to encode a non-shared component `, common.log_component(self.world, component))
3608
+ return 0
3609
+ end
3610
+ return encoded
3611
+ end
3612
+
3613
+ function server.decode_component(self: Server, encoded: number): Component?
3614
+ self:resolve_dirty()
3615
+ return self.shared.components.indexes[encoded]
3616
+ end
3617
+
3618
+ function server.get_shared_count(self: Server): number
3619
+ self:resolve_dirty()
3620
+ return #self.shared.components.keys
3621
+ end
3622
+
3623
+ function server.generate_handshake(self: Server): types.HandshakeInfo
3624
+ self:resolve_dirty()
3625
+ return handshake.generate_handshake(self.shared)
3626
+ end
3627
+
3628
+ function server.verify_handshake(self: Server, client_handshake: types.HandshakeInfo): (boolean, string?)
3629
+ self:resolve_dirty()
3630
+ return handshake.verify_handshake(self.shared, client_handshake, "client", "server")
3631
+ end
3632
+
3633
+
3634
+ local function invalidate_client_index(storage: StorageBase, hole_mask: number, index: number)
3635
+ local filter = storage.filter
3636
+ if filter then
3637
+ bitmasks.punch_hole_mask(filter, hole_mask, index)
3638
+ end
3639
+ local last_replicated = storage.last_replicated
3640
+ if last_replicated then
3641
+ bitmasks.punch_hole_mask(last_replicated, hole_mask, index)
3642
+ end
3643
+ end
3644
+
3645
+ function server.remove_player(self: Server, player: Player)
3646
+ local registry = self.registry
3647
+ local index = registry.client_indexes[player]
3648
+
3649
+ if index then
3650
+ bitmasks.reset(self.client_active_bitmask, index)
3651
+ bitmasks.reset(self.last_active_bitmask, index)
3652
+
3653
+ local hole_mask = bitmasks.get_hole_mask(index)
3654
+ for _, networked in self.networked do
3655
+ invalidate_client_index(networked, hole_mask, index)
3656
+ for _, reliable_storage in networked.reliable_storages do
3657
+ invalidate_client_index(reliable_storage, hole_mask, index)
3658
+ end
3659
+ for _, unreliable_storage in networked.unreliable_storages do
3660
+ invalidate_client_index(unreliable_storage, hole_mask, index)
3661
+ end
3662
+ for _, pair_storage in networked.pair_storages do
3663
+ invalidate_client_index(pair_storage, hole_mask, index)
3664
+ end
3665
+ for _, unreliable_pair_storage in networked.unreliable_pair_storages do
3666
+ invalidate_client_index(unreliable_pair_storage, hole_mask, index)
3667
+ end
3668
+ for _, relation_storage in networked.relation_storages do
3669
+ invalidate_client_index(relation_storage, hole_mask, index)
3670
+ end
3671
+ end
3672
+ end
3673
+
3674
+ registry:remove_client_aliases(player)
3675
+ registry:unregister_client(player)
3676
+ end
3677
+
3678
+ local function hook_tracking_relation(
3679
+ self: Server,
3680
+ hook: (() -> ()) -> (),
3681
+ track_type: Component,
3682
+ set: (
3683
+ self: Server,
3684
+ entity: Entity,
3685
+ component: Component,
3686
+ filter: FilterInput?
3687
+ ) -> (),
3688
+ stop: (self: Server, entity: Entity, component: Component) -> ()
3689
+ )
3690
+ local world = self.world
3691
+ hook(HOOK_ADDED(world, LISTEN_RELATION(track_type), function(entity, id, filter)
3692
+ set(self, entity, PAIR_SECOND(world, id), filter)
3693
+ end))
3694
+ hook(HOOK_CHANGED(world, LISTEN_RELATION(track_type), function(entity, id, filter)
3695
+ set(self, entity, PAIR_SECOND(world, id), filter)
3696
+ end))
3697
+ hook(HOOK_REMOVED(world, LISTEN_RELATION(track_type), function(entity, id)
3698
+ stop(self, entity, PAIR_SECOND(world, id))
3699
+ end))
3700
+ end
3701
+
3702
+ local function discover_tracking_relation(
3703
+ self: Server,
3704
+ track_type: Component,
3705
+ set: (
3706
+ self: Server,
3707
+ entity: Entity,
3708
+ component: Component,
3709
+ filter: FilterInput?
3710
+ ) -> ()
3711
+ )
3712
+ local world = self.world
3713
+ for entity in world:query(PAIR(track_type, WILDCARD)):iter() do
3714
+ local index = 0
3715
+ while true do
3716
+ local component = WORLD_TARGET(world, entity, track_type, index)
3717
+ if not component then
3718
+ break
3719
+ end
3720
+ index += 1
3721
+ set(self, entity, component, WORLD_GET(world, entity, PAIR(track_type, component)))
3722
+ end
3723
+ end
3724
+ end
3725
+
3726
+ local function discover_current(self: Server)
3727
+ local world = self.world
3728
+ local components = self.components
3729
+
3730
+ for component, serdes in world:query(components.serdes):iter() do
3731
+ self:set_serdes(component, serdes)
3732
+ end
3733
+
3734
+ for entity, filter in world:query(components.networked):iter() do
3735
+ self:set_networked(entity, filter)
3736
+ end
3737
+
3738
+ if RELATIONSHIPS then
3739
+ discover_tracking_relation(self, components.reliable, self.set_reliable)
3740
+ discover_tracking_relation(self, components.once, self.set_once)
3741
+ discover_tracking_relation(self, components.unreliable, self.set_unreliable)
3742
+ discover_tracking_relation(self, components.relation, self.set_relation)
3743
+
3744
+ for entity in world:query(PAIR(components.custom, WILDCARD)):iter() do
3745
+ local handler = WORLD_TARGET(world, entity, components.custom, 0)
3746
+ if handler then
3747
+ self:set_custom(entity, handler)
3748
+ end
3749
+ end
3750
+ end
3751
+ end
3752
+
3753
+ function server.init(self: Server, wrd: World?)
3754
+ if self.inited == true then
3755
+ return common.log_warn "attempted to init a server twice"
3756
+ end
3757
+ if self.inited == nil then
3758
+ return common.log_warn "attempted to re-init a destroyed server"
3759
+ end
3760
+ self.inited = true
3761
+
3762
+ local world = wrd or self.world
3763
+ self.world = world
3764
+
3765
+ if not world then
3766
+ common.log_error "Providing a world is required to start replecs"
3767
+ end
3768
+
3769
+ if not self.components then
3770
+ self.components = utils.create_world_components(world)
3771
+ end
3772
+ local components = self.components
3773
+
3774
+ local function hook(unhook: () -> ())
3775
+ table.insert(self.cleanups, unhook)
3776
+ end
3777
+
3778
+ -- shared components and their names feed the handshake lookups; any change
3779
+ -- rebuilds them at the next resolve_dirty
3780
+ local function mark_shared_dirty()
3781
+ self.is_shared_dirty = true
3782
+ end
3783
+
3784
+ for _, component in { components.shared, ECS_NAME } do
3785
+ hook(HOOK_ADDED(world, component, mark_shared_dirty))
3786
+ hook(HOOK_CHANGED(world, component, mark_shared_dirty))
3787
+ hook(HOOK_REMOVED(world, component, mark_shared_dirty))
3788
+ end
3789
+
3790
+ hook(HOOK_ADDED(world, components.networked, function(entity, _, filter)
3791
+ self:set_networked(entity, filter)
3792
+ end))
3793
+ hook(HOOK_CHANGED(world, components.networked, function(entity, _, filter)
3794
+ self:set_networked(entity, filter)
3795
+ end))
3796
+ hook(HOOK_REMOVED(world, components.networked, function(entity)
3797
+ self:stop_networked(entity)
3798
+ end))
3799
+
3800
+ if RELATIONSHIPS then
3801
+ hook_tracking_relation(self, hook, components.reliable, self.set_reliable, self.stop_reliable)
3802
+ hook_tracking_relation(self, hook, components.once, self.set_once, self.stop_once)
3803
+ hook_tracking_relation(self, hook, components.unreliable, self.set_unreliable, self.stop_unreliable)
3804
+ hook_tracking_relation(self, hook, components.relation, self.set_relation, self.stop_relation)
3805
+
3806
+ hook(HOOK_ADDED(world, LISTEN_RELATION(components.custom), function(entity, id)
3807
+ if not IS_PAIR(id) then
3808
+ LOG_ERROR "replecs.custom should be used as a pair in the server: pair(custom, handler)"
3809
+ end
3810
+ self:set_custom(entity, PAIR_SECOND(world, id))
3811
+ end))
3812
+ hook(HOOK_REMOVED(world, LISTEN_RELATION(components.custom), function(entity)
3813
+ self:remove_custom(entity)
3814
+ end))
3815
+ end
3816
+
3817
+ hook(HOOK_ADDED(world, components.serdes, function(component, _, serdes)
3818
+ self:set_serdes(component, serdes)
3819
+ end))
3820
+ hook(HOOK_CHANGED(world, components.serdes, function(component, _, serdes)
3821
+ self:set_serdes(component, serdes)
3822
+ end))
3823
+ hook(HOOK_REMOVED(world, components.serdes, function(component)
3824
+ self:remove_serdes(component)
3825
+ end))
3826
+
3827
+ hook(HOOK_REMOVED(world, components.__alive_tracking__, function(entity)
3828
+ self:cleanup_entity(entity)
3829
+ end))
3830
+
3831
+ discover_current(self)
3832
+
3833
+ if game and game:GetService("RunService"):IsServer() then
3834
+ local Players = game:GetService "Players"
3835
+
3836
+ local added = Players.PlayerAdded:Connect(function(player)
3837
+ self:register_player(player)
3838
+ end)
3839
+ local removing = Players.PlayerRemoving:Connect(function(player)
3840
+ self:remove_player(player)
3841
+ end)
3842
+ for _, player in Players:GetPlayers() do
3843
+ self:register_player(player)
3844
+ end
3845
+
3846
+ hook(function()
3847
+ added:Disconnect()
3848
+ removing:Disconnect()
3849
+ end)
3850
+ end
3851
+
3852
+ self.is_shared_dirty = true
3853
+ end
3854
+
3855
+ function server.destroy(self: Server)
3856
+ if self.inited == nil then
3857
+ return common.log_warn "attempted to destroy a server twice"
3858
+ end
3859
+ self.inited = nil
3860
+ for _, cleanup in self.cleanups do
3861
+ cleanup()
3862
+ end
3863
+ table.clear(self.cleanups)
3864
+ end
3865
+
3866
+ function server.create(world: World?, components: types.Components?, max_clients: number?): Server
3867
+ local self = {} :: Server
3868
+
3869
+ self.world = world :: any
3870
+ self.components = components or (if world then utils.create_world_components(world) else nil) :: any
3871
+ self.inited = false
3872
+
3873
+ self.registry = registry.create(max_clients or DEFAULT_MAX_CLIENTS)
3874
+ self.bitmask_size = self.registry.bitmask_size
3875
+ self.ready_players_bitmask = buffer.create(self.bitmask_size)
3876
+ self.shared = {} :: types.Shared
3877
+
3878
+ self.networked = {}
3879
+ self.unreliable_networked = {}
3880
+ self.postponed_components = {}
3881
+ self.entity_serialization = {}
3882
+ self.registered_custom_ids = {}
3883
+ self.component_serialization = {}
3884
+ -- starts dirty so the first resolve_dirty builds the lookups
3885
+ self.is_shared_dirty = true
3886
+
3887
+ self.hooked_components = {}
3888
+ self.hooked_unreliable = {}
3889
+ self.hooked_relations = {}
3890
+ self.hooked_pairs = {}
3891
+ self.hooked_unreliable_pairs = {}
3892
+
3893
+ self.dirty_networked = {}
3894
+ self.function_filters = {}
3895
+
3896
+ self.empty_bitmask = buffer.create(self.bitmask_size)
3897
+ self.draft_bitmask = buffer.create(self.bitmask_size)
3898
+ self.target_bitmask = buffer.create(self.bitmask_size)
3899
+ self.filled_bitmask = buffer.create(self.bitmask_size)
3900
+ buffer.fill(self.filled_bitmask, 0, 0xFF, self.bitmask_size)
3901
+
3902
+ self.client_active_bitmask = buffer.create(self.bitmask_size)
3903
+ self.last_active_bitmask = buffer.create(self.bitmask_size)
3904
+ self.newly_active_bitmask = buffer.create(self.bitmask_size)
3905
+ self.newly_unactive_bitmask = buffer.create(self.bitmask_size)
3906
+
3907
+ self.entity_bitmask_deltas = {
3908
+ added = buffer.create(self.bitmask_size),
3909
+ removed = buffer.create(self.bitmask_size),
3910
+ }
3911
+ self.component_bitmask_deltas = {
3912
+ added = buffer.create(self.bitmask_size),
3913
+ removed = buffer.create(self.bitmask_size),
3914
+ }
3915
+
3916
+ self.cleanups = {}
3917
+ return setmetatable(self, server) :: any
3918
+ end
3919
+
3920
+ return {
3921
+ create = server.create,
3922
+
3923
+ collect_updates_groups = function(self: Server): Map<MaskHash, SerializeGroup>
3924
+ self:resolve_dirty()
3925
+ return collect_updates_groups(self)
3926
+ end,
3927
+ collect_unreliable_groups = function(self: Server): Map<MaskHash, UnreliableSerializeGroup>
3928
+ self:resolve_dirty()
3929
+ return collect_unreliable_groups(self)
3930
+ end,
3931
+ serialize_update_groups = serialize_update_groups,
3932
+ serialize_unreliable_groups = serialize_unreliable_groups,
3933
+ collect_entity_groups = function(self: Server, entity: Entity): Map<MaskHash, EntitySerializeGroup>
3934
+ self:resolve_dirty()
3935
+ return collect_entity_groups(self, entity)
3936
+ end,
3937
+ }