@rbxts/replecs 0.3.0 → 0.4.0-rc.1

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