@rbxts/replecs 0.3.1 → 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,1760 @@
1
+ --!optimize 2
2
+
3
+ local common = require (script.Parent:WaitForChild('common'))
4
+ local cursor = require (script.Parent:WaitForChild('cursor'))
5
+ local types = require (script.Parent:WaitForChild('types'))
6
+ local customid = require (script.Parent:WaitForChild('customid'))
7
+ local shared = require (script.Parent:WaitForChild('shared'))
8
+ local handshake = require (script.Parent:WaitForChild('handshake'))
9
+ local utils = require (script.Parent:WaitForChild('utils'))
10
+
11
+ type Set<T> = { [T]: boolean }
12
+ type Map<K, V> = { [K]: V }
13
+ type Array<T> = { T }
14
+ type Disconnect = () -> ()
15
+
16
+ type Cursor = cursor.Cursor
17
+
18
+ type World = common.World
19
+ type Entity = common.Entity
20
+ type Component<T = any> = common.Component<T>
21
+
22
+ type CustomId = customid.CustomId
23
+
24
+ type CommandBuffer = {
25
+ tags: Set<Component>,
26
+ components: Map<Component, { value: any }>,
27
+ unreliable_components: Map<Component, { value: any }>,
28
+ remove: Set<Component>,
29
+
30
+ pairs_add: { [Component]: Set<Entity> },
31
+ pairs_set: { [Component]: Map<Entity, { value: any }> },
32
+ -- pair values whose target is an unresolved custom id: the serdes depends on
33
+ -- the final pair, so the value stays in the packet (cursor frozen at its
34
+ -- start) until the target resolves
35
+ pairs_unprocessed: { [Component]: Map<Entity, UnprocessedPair> },
36
+ pairs_remove: { [Component]: Set<Entity> },
37
+ relations_clear: Set<Component>,
38
+ }
39
+
40
+ type UnprocessedPair = {
41
+ cursor: Cursor,
42
+ variants: Array<any>?,
43
+ processed_value: { value: any }?,
44
+ }
45
+
46
+ type HookMethod =
47
+ & ((
48
+ self: Client,
49
+ action: "changed",
50
+ relation: Component,
51
+ callback: (entity: Entity, id: Component, value: any) -> ()
52
+ ) -> Disconnect)
53
+ & ((
54
+ self: Client,
55
+ action: "removed",
56
+ relation: Component,
57
+ callback: (entity: Entity, id: Component) -> ()
58
+ ) -> Disconnect)
59
+ & ((self: Client, action: "deleted", entity: Entity, callback: (entity: Entity) -> ()) -> Disconnect
60
+ )
61
+ type ChangedHooksEntry = {
62
+ overrides: boolean,
63
+ callbacks: Array<(entity: Entity, id: Component, value: any) -> ()>,
64
+ }
65
+ type RemovedHooksEntry = {
66
+ overrides: boolean,
67
+ callbacks: Array<(entity: Entity, id: Component) -> ()>,
68
+ }
69
+ type DeletedHookEntry = {
70
+ overrides: boolean,
71
+ callbacks: Array<(entity: Entity) -> ()>,
72
+ }
73
+
74
+ export type ClientImp = {
75
+ init: (self: Client, world: World?) -> (),
76
+ destroy: (self: Client) -> (),
77
+ handle_global: (self: Client, handler: (id: number) -> Entity) -> (),
78
+ get_server_entity: (self: Client, client_entity: Entity) -> number?,
79
+ get_client_entity: (self: Client, server_entity: number) -> Entity?,
80
+
81
+ register_entity: (self: Client, entity: Entity, server_entity: number) -> (),
82
+ unregister_entity: (self: Client, entity: Entity) -> (),
83
+
84
+ after_replication: (self: Client, callback: () -> ()) -> (),
85
+ added: (self: Client, callback: (entity: Entity) -> ()) -> Disconnect,
86
+ hook: HookMethod,
87
+ override: HookMethod,
88
+
89
+ apply_full: (self: Client, buf: buffer, variants: { any }?) -> (),
90
+ apply_entity: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
91
+ apply_updates: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
92
+ apply_unreliable: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
93
+
94
+ register_custom_id: (self: Client, custom_id: CustomId) -> (),
95
+ set_serdes: <T>(self: Client, component: Component<T>, serdes: types.Serdes<T>) -> (),
96
+ remove_serdes: (self: Client, component: Component) -> (),
97
+ set_preprocessor: <B, A>(self: Client, component: Component<B>, preprocessor: types.Preprocessor<B, A>) -> (),
98
+ remove_preprocessor: (self: Client, component: Component) -> (),
99
+ set_delta_serializer: <T, D>(self: Client, component: Component<T>, delta: types.DeltaSerializer<T, D>) -> (),
100
+ remove_delta_serializer: (self: Client, component: Component) -> (),
101
+
102
+ resolve_dirty: (self: Client) -> (),
103
+ encode_component: (self: Client, component: Component) -> number,
104
+ decode_component: (self: Client, encoded: number) -> Component?,
105
+ get_shared_count: (self: Client) -> number,
106
+
107
+ generate_handshake: (self: Client) -> types.HandshakeInfo,
108
+ verify_handshake: (self: Client, handshake: types.HandshakeInfo) -> (boolean, string?),
109
+ }
110
+
111
+ -- mirrors the server's options: the same component can carry any mix of the
112
+ -- three, resolved in one lookup at read time
113
+ type ComponentSerializationOptions = {
114
+ serdes: types.Serdes?,
115
+ preprocessor: types.Preprocessor?,
116
+ delta_serializer: types.DeltaSerializer?,
117
+ }
118
+
119
+ export type Client = ClientImp & {
120
+ world: World,
121
+ inited: boolean?,
122
+
123
+ components: types.Components,
124
+ shared: types.Shared,
125
+ registered_custom_ids: Set<CustomId>,
126
+ -- single lookup for serdes/preprocessor/delta at read time
127
+ component_serialization: Map<Component, ComponentSerializationOptions>,
128
+ -- shared components, serdes and custom ids feed the handshake; set when any changes
129
+ is_shared_dirty: boolean,
130
+
131
+ is_replicating: boolean,
132
+ after_replication_callbacks: Array<() -> ()>,
133
+
134
+ global_handler: ((id: number) -> Entity)?,
135
+ server_ids: { [number]: Entity },
136
+ client_ids: { [Entity]: number },
137
+ command_buffers: { [Entity]: CommandBuffer }?,
138
+ command_buffers_active: boolean,
139
+
140
+ addition_hooks: Array<(entity: Entity) -> ()>,
141
+ network_hooks: {
142
+ deleted: { [Entity]: DeletedHookEntry },
143
+ changed: { [Component]: ChangedHooksEntry },
144
+ removed: { [Component]: RemovedHooksEntry },
145
+ },
146
+
147
+ cleanups: { () -> () },
148
+ }
149
+
150
+ local PAIR = common.pair
151
+ local WILDCARD = common.wildcard
152
+ local ITERATE_ALL_RELATIONS = common.iterate_all_relations
153
+ local WORLD_ENTITY = common.world_entity
154
+ local WORLD_ADD = common.world_add
155
+ local WORLD_SET = common.world_set
156
+ local WORLD_REMOVE = common.world_remove
157
+ local WORLD_HAS = common.world_has
158
+ local WORLD_DELETE = common.world_delete
159
+ local WORLD_GET = common.world_get
160
+ local ECS_NAME = common.ecs_name
161
+
162
+ local HOOK_ADDED = common.hook_added
163
+ local HOOK_CHANGED = common.hook_changed
164
+ local HOOK_REMOVED = common.hook_removed
165
+
166
+ -- global ids are a single byte offset past the ENTITY_ID_TYPES tags, so any
167
+ -- id byte at or above the offset is a global id and never a tag
168
+ local GLOBAL_ID_OFFSET = 10
169
+
170
+ local ENTITY_ID_TYPES = {
171
+ entity = 1,
172
+ custom_handler = 2,
173
+ custom_id = 3,
174
+ shared = 4,
175
+ }
176
+
177
+ -- u8 the server appends last to every buffer, read first here; each apply
178
+ -- function checks it so mixed up packets error instead of desyncing
179
+ local PACKET_TYPES = {
180
+ full = 1,
181
+ entity = 2,
182
+ updates = 3,
183
+ unreliable = 4,
184
+ }
185
+
186
+ -- section bits of the u8 entity mask in a full packet; a set bit means a vlq
187
+ -- count precedes that section (mirrors the server's FULL_BITS)
188
+ local FULL_BITS = {
189
+ tags = 0,
190
+ components = 1,
191
+ pair_tags = 2,
192
+ pair_components = 3,
193
+ relation_tags = 4,
194
+ relation_components = 5,
195
+ }
196
+
197
+ -- section bits of the u16 entity mask in an update packet (mirrors the
198
+ -- server's ENTITY_BITS)
199
+ local UPDATE_BITS = {
200
+ reliable_removed = 0,
201
+ reliable_added = 1,
202
+ reliable_changed = 2,
203
+ pair_removed = 3,
204
+ pair_added = 4,
205
+ pair_changed = 5,
206
+ relations_removed = 6,
207
+ full_relations = 7,
208
+ changed_relations = 8,
209
+ }
210
+
211
+ -- trailer bits of the u16 group mask in an update packet (mirrors the
212
+ -- server's GROUP_BITS)
213
+ local UPDATE_GROUP_BITS = {
214
+ updates = 0,
215
+ added_entities = 1,
216
+ deleted_entities = 2,
217
+ }
218
+
219
+ -- server id -> the custom_handler component or CustomId that resolves it,
220
+ -- collected while reading a packet and drained once every entity is known
221
+ type CustomIdMap = { [number]: CustomId | Entity }
222
+
223
+ local client = {}
224
+ client.__index = client
225
+
226
+ -- the removal hook on this tag is how a local world:delete cleans the id maps
227
+ local function track_alive(self: Client, entity: Entity)
228
+ local alive_tracking = self.components.__alive_tracking__
229
+ if not WORLD_HAS(self.world, entity, alive_tracking) then
230
+ WORLD_ADD(self.world, entity, alive_tracking)
231
+ end
232
+ end
233
+
234
+ local function ensure_entity(self: Client, server_id: number): Entity
235
+ local client_entity = self.server_ids[server_id]
236
+ if client_entity then
237
+ return client_entity
238
+ end
239
+
240
+ local new_entity = WORLD_ENTITY(self.world)
241
+
242
+ self.server_ids[server_id] = new_entity
243
+ self.client_ids[new_entity] = server_id
244
+ track_alive(self, new_entity)
245
+
246
+ for _, callback in self.addition_hooks do
247
+ callback(new_entity)
248
+ end
249
+
250
+ return new_entity
251
+ end
252
+
253
+ -- server said the entity is gone: fire its deleted hooks, then delete it
254
+ -- unless an override claimed the deletion. the id maps are cleared either
255
+ -- way so a later packet reusing the server id creates a fresh entity
256
+ local function delete_entity(self: Client, entity: Entity, server_id: number?)
257
+ local deleted_hook = self.network_hooks.deleted[entity]
258
+ if deleted_hook then
259
+ for _, callback in deleted_hook.callbacks do
260
+ callback(entity)
261
+ end
262
+ if deleted_hook.overrides then
263
+ self.network_hooks.deleted[entity] = nil
264
+ self.client_ids[entity] = nil
265
+ else
266
+ WORLD_DELETE(self.world, entity)
267
+ end
268
+ else
269
+ WORLD_DELETE(self.world, entity)
270
+ end
271
+ if server_id then
272
+ self.server_ids[server_id] = nil
273
+ end
274
+ end
275
+
276
+ local function ensure_command_buffer(self: Client, entity: Entity): CommandBuffer
277
+ local command_buffers = self.command_buffers
278
+ if not command_buffers then
279
+ error "attempted to use a command buffer without being active. (this should never happen)"
280
+ end
281
+
282
+ local command_buffer = command_buffers[entity]
283
+ if not command_buffer then
284
+ command_buffer = {
285
+ tags = {},
286
+ components = {},
287
+ unreliable_components = {},
288
+ remove = {},
289
+ pairs_add = {},
290
+ pairs_set = {},
291
+ pairs_unprocessed = {},
292
+ pairs_remove = {},
293
+ relations_clear = {},
294
+ }
295
+ command_buffers[entity] = command_buffer
296
+ end
297
+ return command_buffer
298
+ end
299
+
300
+ local function entity_set(self: Client, entity: Entity, id: Component, value: any, changed_hooks: ChangedHooksEntry?)
301
+ if self.command_buffers_active then
302
+ local command_buffer = ensure_command_buffer(self, entity)
303
+ command_buffer.components[id] = { value = value }
304
+ else
305
+ if changed_hooks then
306
+ if not changed_hooks.overrides then
307
+ WORLD_SET(self.world, entity, id, value)
308
+ end
309
+ for _, callback in changed_hooks.callbacks do
310
+ callback(entity, id, value)
311
+ end
312
+ else
313
+ WORLD_SET(self.world, entity, id, value)
314
+ end
315
+ end
316
+ end
317
+
318
+ local function entity_add(self: Client, entity: Entity, tag: Entity, changed_hooks: ChangedHooksEntry?)
319
+ if self.command_buffers_active then
320
+ local command_buffer = ensure_command_buffer(self, entity)
321
+ command_buffer.tags[tag] = true
322
+ else
323
+ if changed_hooks then
324
+ if not changed_hooks.overrides then
325
+ WORLD_ADD(self.world, entity, tag)
326
+ end
327
+ for _, callback in changed_hooks.callbacks do
328
+ callback(entity, tag)
329
+ end
330
+ else
331
+ WORLD_ADD(self.world, entity, tag)
332
+ end
333
+ end
334
+ end
335
+
336
+ local function entity_set_unreliable(
337
+ self: Client,
338
+ entity: Entity,
339
+ id: Entity,
340
+ value: any,
341
+ changed_hooks: ChangedHooksEntry?
342
+ )
343
+ if self.command_buffers_active then
344
+ local command_buffer = ensure_command_buffer(self, entity)
345
+ command_buffer.unreliable_components[id] = { value = value }
346
+ else
347
+ if WORLD_HAS(self.world, entity, id) then
348
+ entity_set(self, entity, id, value, changed_hooks)
349
+ end
350
+ end
351
+ end
352
+
353
+ local function entity_set_pair(
354
+ self: Client,
355
+ entity: Entity,
356
+ relation: Entity,
357
+ target: Entity,
358
+ value: any,
359
+ changed_hooks: ChangedHooksEntry?
360
+ )
361
+ if self.command_buffers_active then
362
+ local command_buffer = ensure_command_buffer(self, entity)
363
+ local pair_relation = command_buffer.pairs_set[relation]
364
+
365
+ if pair_relation then
366
+ pair_relation[target] = { value = value }
367
+ else
368
+ command_buffer.pairs_set[relation] = { [target] = { value = value } }
369
+ end
370
+ else
371
+ entity_set(self, entity, PAIR(relation, target), value, changed_hooks)
372
+ end
373
+ end
374
+
375
+ local function entity_add_pair(
376
+ self: Client,
377
+ entity: Entity,
378
+ relation: Entity,
379
+ target: Entity,
380
+ changed_hooks: ChangedHooksEntry?
381
+ )
382
+ if self.command_buffers_active then
383
+ local command_buffer = ensure_command_buffer(self, entity)
384
+ local pair_relation = command_buffer.pairs_add[relation]
385
+
386
+ if pair_relation then
387
+ pair_relation[target] = true
388
+ else
389
+ command_buffer.pairs_add[relation] = { [target] = true }
390
+ end
391
+ else
392
+ entity_add(self, entity, PAIR(relation, target), changed_hooks)
393
+ end
394
+ end
395
+
396
+ local function entity_remove(self: Client, entity: Entity, id: Entity, removed_hooks: RemovedHooksEntry?)
397
+ if self.command_buffers_active then
398
+ local command_buffer = ensure_command_buffer(self, entity)
399
+ command_buffer.remove[id] = true
400
+ else
401
+ if removed_hooks then
402
+ for _, callback in removed_hooks.callbacks do
403
+ callback(entity, id)
404
+ end
405
+ if not removed_hooks.overrides then
406
+ WORLD_REMOVE(self.world, entity, id)
407
+ end
408
+ else
409
+ WORLD_REMOVE(self.world, entity, id)
410
+ end
411
+ end
412
+ end
413
+
414
+ local function entity_remove_pair(
415
+ self: Client,
416
+ entity: Entity,
417
+ relation: Entity,
418
+ target: Entity,
419
+ removed_hooks: RemovedHooksEntry?
420
+ )
421
+ if self.command_buffers_active then
422
+ local command_buffer = ensure_command_buffer(self, entity)
423
+ local pair_relation = command_buffer.pairs_remove[relation]
424
+
425
+ if pair_relation then
426
+ pair_relation[target] = true
427
+ else
428
+ command_buffer.pairs_remove[relation] = { [target] = true }
429
+ end
430
+ else
431
+ entity_remove(self, entity, PAIR(relation, target), removed_hooks)
432
+ end
433
+ end
434
+
435
+ local function entity_clear_relation(self: Client, entity: Entity, relation: Entity, removed_hooks: RemovedHooksEntry?)
436
+ if self.command_buffers_active then
437
+ local command_buffer = ensure_command_buffer(self, entity)
438
+ command_buffer.relations_clear[relation] = true
439
+ else
440
+ local world = self.world
441
+ if removed_hooks then
442
+ ITERATE_ALL_RELATIONS(world, entity, relation, function(pair)
443
+ for _, callback in removed_hooks.callbacks do
444
+ callback(entity, pair)
445
+ end
446
+ if not removed_hooks.overrides then
447
+ WORLD_REMOVE(world, entity, pair)
448
+ end
449
+ end)
450
+ else
451
+ ITERATE_ALL_RELATIONS(world, entity, relation, function(pair)
452
+ WORLD_REMOVE(world, entity, pair)
453
+ end)
454
+ end
455
+ end
456
+ end
457
+
458
+ local function resolve_global(self: Client, global_id: number): Entity
459
+ if not self.global_handler then
460
+ common.log_error "global id parser not set, consider using client:handle_global()"
461
+ end
462
+ return (self.global_handler :: (id: number) -> Entity)(global_id)
463
+ end
464
+
465
+ local function read_component_id(self: Client, c: Cursor): Entity
466
+ local shared_components = self.shared.components
467
+ local shared_id = cursor.read_span(c, #shared_components.keys)
468
+ local component = shared_components.indexes[shared_id]
469
+ if not component then
470
+ common.log_error(`received a non shared component: {shared_id}`)
471
+ end
472
+ return component
473
+ end
474
+
475
+ local function read_entity_id(self: Client, c: Cursor): (Entity?, number?, number?)
476
+ local id_type = cursor.readu8(c)
477
+
478
+ if id_type < GLOBAL_ID_OFFSET then
479
+ if
480
+ id_type == ENTITY_ID_TYPES.entity
481
+ or id_type == ENTITY_ID_TYPES.custom_id
482
+ or id_type == ENTITY_ID_TYPES.custom_handler
483
+ then
484
+ local server_id = cursor.readu40(c)
485
+ return self.server_ids[server_id], server_id, id_type
486
+ elseif id_type == ENTITY_ID_TYPES.shared then
487
+ return read_component_id(self, c), nil, id_type
488
+ end
489
+ common.log_error(`malformed entity type: {id_type}`)
490
+ end
491
+
492
+ return resolve_global(self, id_type - GLOBAL_ID_OFFSET), nil, id_type
493
+ end
494
+
495
+ local function skip_custom_payload(self: Client, c: Cursor, id_type: number?)
496
+ if id_type == ENTITY_ID_TYPES.custom_id then
497
+ read_component_id(self, c)
498
+ elseif id_type == ENTITY_ID_TYPES.custom_handler then
499
+ cursor.readu8(c)
500
+ end
501
+ end
502
+
503
+ -- lookup only: an entity the client doesn't know yet is never created.
504
+ local function find_entity(self: Client, c: Cursor): (Entity?, number?)
505
+ local entity, server_id, id_type = read_entity_id(self, c)
506
+ skip_custom_payload(self, c, id_type)
507
+ return entity, server_id
508
+ end
509
+
510
+ -- reads an entity id and hands the resolved entity to process. unknown plain
511
+ -- entities are created on the spot; unknown custom ids record their handler in
512
+ -- custom_ids and hand process a negative server id, meaning the entity is not
513
+ -- resolved yet and everything applied to it must go through a command buffer
514
+ -- until finish_custom_ids step translates it
515
+ local function process_entity(self: Client, c: Cursor, custom_ids: CustomIdMap, process: ((entity: Entity) -> ())?)
516
+ local entity, server_id, id_type = read_entity_id(self, c)
517
+
518
+ if entity then
519
+ skip_custom_payload(self, c, id_type)
520
+ if process then
521
+ process(entity)
522
+ end
523
+ return
524
+ end
525
+
526
+ local unresolved_id = server_id :: number
527
+
528
+ if id_type == ENTITY_ID_TYPES.custom_id then
529
+ custom_ids[unresolved_id] = read_component_id(self, c)
530
+ elseif id_type == ENTITY_ID_TYPES.custom_handler then
531
+ local shared_custom_id = cursor.readu8(c)
532
+ local handler = self.shared.custom_ids.indexes[shared_custom_id]
533
+
534
+ if not handler then
535
+ common.log_error "attempted to use a custom id that wasn't registered"
536
+ end
537
+ custom_ids[unresolved_id] = handler
538
+ else
539
+ local new_entity = ensure_entity(self, unresolved_id)
540
+ if process then
541
+ process(new_entity)
542
+ end
543
+ return
544
+ end
545
+
546
+ if not process then
547
+ return
548
+ end
549
+
550
+ local was_active = self.command_buffers_active
551
+ self.command_buffers_active = true
552
+ process(-unresolved_id :: any)
553
+ self.command_buffers_active = was_active
554
+ end
555
+
556
+ -- reverse of the server's write_variant_value. a serdes value is its buffer
557
+ -- (length-prefixed unless the serdes has a fixed bytespan) plus, when the
558
+ -- serdes includes variants, the slice of the packet variants it appended;
559
+ -- anything else is a variant index, 0 meaning nil. the preprocessor undoes
560
+ -- itself last since the server applied it first
561
+ local function read_variant_value(c: Cursor, options: ComponentSerializationOptions?, variants: Array<any>?): any
562
+ local serdes = options and options.serdes
563
+ local value: any
564
+
565
+ if serdes then
566
+ local bytespan = serdes.bytespan or cursor.read_vlq(c)
567
+ local appended = cursor.read_buffer(c, bytespan)
568
+ local serdes_variants: Array<any>?
569
+
570
+ if serdes.includes_variants then
571
+ local start_variant = cursor.read_vlq(c)
572
+
573
+ if start_variant > 0 then
574
+ local size = cursor.read_vlq(c)
575
+ serdes_variants = table.move(variants :: Array<any>, start_variant, start_variant + size - 1, 1, {})
576
+ end
577
+ end
578
+
579
+ value = serdes.deserialize(appended, serdes_variants :: any)
580
+ else
581
+ local variant_id = cursor.read_vlq(c)
582
+ if variant_id == 0 then
583
+ value = nil
584
+ else
585
+ value = (variants :: Array<any>)[variant_id]
586
+ end
587
+ end
588
+
589
+ local preprocessor = options and options.preprocessor
590
+ if preprocessor then
591
+ value = preprocessor.deserialize(value)
592
+ end
593
+
594
+ return value
595
+ end
596
+
597
+ local function read_component_value(self: Client, component: Component, c: Cursor, variants: Array<any>?): any
598
+ return read_variant_value(c, self.component_serialization[component], variants)
599
+ end
600
+
601
+ -- a pair value resolves its options like the server writes them: the exact
602
+ -- pair first, then the relation's wildcard pair
603
+ -- returns the pair id, the value
604
+ local function read_pair_value(
605
+ self: Client,
606
+ relation: Component,
607
+ target: Entity,
608
+ c: Cursor,
609
+ variants: Array<any>?
610
+ ): (Component, any)
611
+ local pair_id = PAIR(relation, target)
612
+ local component_serialization = self.component_serialization
613
+ local options = component_serialization[pair_id] or component_serialization[PAIR(relation, WILDCARD)]
614
+ return pair_id, read_variant_value(c, options, variants)
615
+ end
616
+
617
+ -- a section's vlq count is only written when its mask bit is set
618
+ local function read_vlq_bitmask(c: Cursor, mask: number, bit: number): number
619
+ if bit32.btest(mask, bit32.lshift(1, bit)) then
620
+ return cursor.read_vlq(c)
621
+ end
622
+ return 0
623
+ end
624
+
625
+ -- pair value whose target isn't resolved yet (negative id): keeps a cursor
626
+ -- frozen at the value so it can be read once the target's custom id resolves
627
+ -- and the pair's serdes is known. with buffers inactive the target is final
628
+ -- and the value is read right away
629
+ local function entity_set_postponed_pair(
630
+ self: Client,
631
+ entity: Entity,
632
+ relation: Component,
633
+ target: Entity,
634
+ c: Cursor,
635
+ variants: Array<any>?
636
+ ): any
637
+ if self.command_buffers_active then
638
+ local command_buffer = ensure_command_buffer(self, entity)
639
+ local pairs_unprocessed = command_buffer.pairs_unprocessed
640
+ local unprocessed: UnprocessedPair = {
641
+ cursor = c,
642
+ variants = variants,
643
+ }
644
+
645
+ local pair_relation = pairs_unprocessed[relation]
646
+ if pair_relation then
647
+ pair_relation[target] = unprocessed
648
+ else
649
+ pairs_unprocessed[relation] = { [target] = unprocessed }
650
+ end
651
+ return nil
652
+ end
653
+
654
+ local pair_id, value = read_pair_value(self, relation, target, c, variants)
655
+ entity_set(self, entity, pair_id, value, self.network_hooks.changed[PAIR(self.components.relation, relation)])
656
+ return value
657
+ end
658
+
659
+ -- pair tag: [target id, relation]
660
+ local function process_entity_pair(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
661
+ local relation = read_component_id(self, c)
662
+ local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
663
+ process_entity(self, c, custom_ids, function(target)
664
+ entity_add_pair(self, entity, relation, target, relation_hooks)
665
+ end)
666
+ end
667
+
668
+ -- pair component: [value, vlq value size, target id, relation]. the size lets
669
+ -- the value be skipped when the target is an unresolved custom id, since the
670
+ -- serdes to read it with is only known once the final pair is
671
+ local function process_entity_pair_value(
672
+ self: Client,
673
+ entity: Entity,
674
+ c: Cursor,
675
+ custom_ids: CustomIdMap,
676
+ variants: Array<any>?
677
+ )
678
+ local relation = read_component_id(self, c)
679
+ local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
680
+ process_entity(self, c, custom_ids, function(target)
681
+ local value_size = cursor.read_vlq(c)
682
+ local value_start = c.offset - value_size
683
+
684
+ if (target :: any) > 0 then
685
+ local _, value = read_pair_value(self, relation, target, c, variants)
686
+ entity_set_pair(self, entity, relation, target, value, relation_hooks)
687
+ -- the serdes must have consumed exactly the written size; pin the
688
+ -- cursor to where the server said the value starts either way
689
+ c.offset = value_start
690
+ else
691
+ local frozen_cursor = cursor.from_offset(c.buffer, c.offset)
692
+ c.offset = value_start
693
+ entity_set_postponed_pair(self, entity, relation, target, frozen_cursor, variants)
694
+ end
695
+ end)
696
+ end
697
+
698
+ -- relation tags: [target ids..., vlq count, relation]
699
+ local function process_entity_relations(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
700
+ local relation = read_component_id(self, c)
701
+ local total_targets = cursor.read_vlq(c)
702
+ local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
703
+
704
+ for _ = 1, total_targets do
705
+ process_entity(self, c, custom_ids, function(target)
706
+ entity_add_pair(self, entity, relation, target, relation_hooks)
707
+ end)
708
+ end
709
+ end
710
+
711
+ -- relation components: [(target id, value)..., vlq count, relation]; values
712
+ -- use the relation's own options, not a per-pair one
713
+ local function process_entity_relation_values(
714
+ self: Client,
715
+ entity: Entity,
716
+ c: Cursor,
717
+ custom_ids: CustomIdMap,
718
+ variants: Array<any>?
719
+ )
720
+ local relation = read_component_id(self, c)
721
+ local total_targets = cursor.read_vlq(c)
722
+ local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
723
+
724
+ for _ = 1, total_targets do
725
+ local value = read_component_value(self, relation, c, variants)
726
+ process_entity(self, c, custom_ids, function(target)
727
+ entity_set_pair(self, entity, relation, target, value, relation_hooks)
728
+ end)
729
+ end
730
+ end
731
+
732
+ -- one entity of a full snapshot, after its id: the FULL_BITS mask, then each
733
+ -- flagged section in write-reverse order (tags first, relation components
734
+ -- last)
735
+ local function process_full_entity(
736
+ self: Client,
737
+ entity: Entity,
738
+ c: Cursor,
739
+ custom_ids: CustomIdMap,
740
+ variants: Array<any>?
741
+ )
742
+ local mask = cursor.readu8(c)
743
+ local changed_hooks = self.network_hooks.changed
744
+ local components = self.components
745
+
746
+ local total_tags = read_vlq_bitmask(c, mask, FULL_BITS.tags)
747
+ for _ = 1, total_tags do
748
+ local tag = read_component_id(self, c)
749
+ entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
750
+ end
751
+
752
+ local total_components = read_vlq_bitmask(c, mask, FULL_BITS.components)
753
+ for _ = 1, total_components do
754
+ local component = read_component_id(self, c)
755
+ local value = read_component_value(self, component, c, variants)
756
+ entity_set(self, entity, component, value, changed_hooks[PAIR(components.reliable, component)])
757
+ end
758
+
759
+ local total_pair_tags = read_vlq_bitmask(c, mask, FULL_BITS.pair_tags)
760
+ for _ = 1, total_pair_tags do
761
+ process_entity_pair(self, entity, c, custom_ids)
762
+ end
763
+
764
+ local total_pair_components = read_vlq_bitmask(c, mask, FULL_BITS.pair_components)
765
+ for _ = 1, total_pair_components do
766
+ process_entity_pair_value(self, entity, c, custom_ids, variants)
767
+ end
768
+
769
+ local total_relation_tags = read_vlq_bitmask(c, mask, FULL_BITS.relation_tags)
770
+ for _ = 1, total_relation_tags do
771
+ process_entity_relations(self, entity, c, custom_ids)
772
+ end
773
+
774
+ local total_relation_components = read_vlq_bitmask(c, mask, FULL_BITS.relation_components)
775
+ for _ = 1, total_relation_components do
776
+ process_entity_relation_values(self, entity, c, custom_ids, variants)
777
+ end
778
+ end
779
+
780
+ -- pair tag removal: [target id, relation]
781
+ local function process_entity_pair_removal(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
782
+ local relation = read_component_id(self, c)
783
+ local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
784
+ process_entity(self, c, custom_ids, function(target)
785
+ entity_remove_pair(self, entity, relation, target, relation_hooks)
786
+ end)
787
+ end
788
+
789
+ -- relation target removals: [target ids..., vlq count, relation]
790
+ local function process_entity_relation_removals(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
791
+ local relation = read_component_id(self, c)
792
+ local total_targets = cursor.read_vlq(c)
793
+ local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
794
+
795
+ for _ = 1, total_targets do
796
+ process_entity(self, c, custom_ids, function(target)
797
+ entity_remove_pair(self, entity, relation, target, relation_hooks)
798
+ end)
799
+ end
800
+ end
801
+
802
+ -- one entity of an update packet, after its id: the UPDATE_BITS mask, then
803
+ -- each flagged section in write-reverse order (reliable changed first,
804
+ -- relations removed last). unreliable tracked components ride the reliable
805
+ -- changed section for their presence, so they resolve reliable hooks here
806
+ local function process_entity_updates(
807
+ self: Client,
808
+ entity: Entity,
809
+ c: Cursor,
810
+ custom_ids: CustomIdMap,
811
+ variants: Array<any>?
812
+ )
813
+ local mask = cursor.readu16(c)
814
+ local changed_hooks = self.network_hooks.changed
815
+ local removed_hooks = self.network_hooks.removed
816
+ local components = self.components
817
+
818
+ -- reliables
819
+
820
+ local total_changed = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_changed)
821
+ for _ = 1, total_changed do
822
+ local component = read_component_id(self, c)
823
+ local value = read_component_value(self, component, c, variants)
824
+ entity_set(self, entity, component, value, changed_hooks[PAIR(components.reliable, component)])
825
+ end
826
+
827
+ local total_added = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_added)
828
+ for _ = 1, total_added do
829
+ local tag = read_component_id(self, c)
830
+ entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
831
+ end
832
+
833
+ local total_removed = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_removed)
834
+ for _ = 1, total_removed do
835
+ local component = read_component_id(self, c)
836
+ entity_remove(self, entity, component, removed_hooks[PAIR(components.reliable, component)])
837
+ end
838
+
839
+ -- pairs
840
+
841
+ local total_pair_changed = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_changed)
842
+ for _ = 1, total_pair_changed do
843
+ process_entity_pair_value(self, entity, c, custom_ids, variants)
844
+ end
845
+
846
+ local total_pair_added = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_added)
847
+ for _ = 1, total_pair_added do
848
+ process_entity_pair(self, entity, c, custom_ids)
849
+ end
850
+
851
+ local total_pair_removed = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_removed)
852
+ for _ = 1, total_pair_removed do
853
+ process_entity_pair_removal(self, entity, c, custom_ids)
854
+ end
855
+
856
+ -- relations
857
+
858
+ if bit32.btest(mask, bit32.lshift(1, UPDATE_BITS.changed_relations)) then
859
+ local total_relations_removed = cursor.read_vlq(c)
860
+ for _ = 1, total_relations_removed do
861
+ process_entity_relation_removals(self, entity, c, custom_ids)
862
+ end
863
+
864
+ local total_relations_added = cursor.read_vlq(c)
865
+ for _ = 1, total_relations_added do
866
+ process_entity_relations(self, entity, c, custom_ids)
867
+ end
868
+
869
+ local total_relations_changed = cursor.read_vlq(c)
870
+ for _ = 1, total_relations_changed do
871
+ process_entity_relation_values(self, entity, c, custom_ids, variants)
872
+ end
873
+ end
874
+
875
+ if bit32.btest(mask, bit32.lshift(1, UPDATE_BITS.full_relations)) then
876
+ local total_relation_tags = cursor.read_vlq(c)
877
+ for _ = 1, total_relation_tags do
878
+ process_entity_relations(self, entity, c, custom_ids)
879
+ end
880
+
881
+ local total_relation_components = cursor.read_vlq(c)
882
+ for _ = 1, total_relation_components do
883
+ process_entity_relation_values(self, entity, c, custom_ids, variants)
884
+ end
885
+ end
886
+
887
+ local total_relations_cleared = read_vlq_bitmask(c, mask, UPDATE_BITS.relations_removed)
888
+ for _ = 1, total_relations_cleared do
889
+ local relation = read_component_id(self, c)
890
+ entity_clear_relation(self, entity, relation, removed_hooks[PAIR(components.relation, relation)])
891
+ end
892
+ end
893
+
894
+ -- one update group: [u16 group mask, deleted ids, added ids, per entity
895
+ -- blocks] in read order. deletions come first so a server id reused by an
896
+ -- addition in the same group starts from a fresh entity
897
+ local function process_update_group(self: Client, c: Cursor, custom_ids: CustomIdMap, variants: Array<any>?)
898
+ local mask = cursor.readu16(c)
899
+
900
+ local total_deleted = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.deleted_entities)
901
+ for _ = 1, total_deleted do
902
+ local entity, server_id = find_entity(self, c)
903
+ if entity then
904
+ delete_entity(self, entity, server_id)
905
+ end
906
+ end
907
+
908
+ local total_added = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.added_entities)
909
+ for _ = 1, total_added do
910
+ process_entity(self, c, custom_ids)
911
+ end
912
+
913
+ local total_entities = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.updates)
914
+ for _ = 1, total_entities do
915
+ process_entity(self, c, custom_ids, function(entity)
916
+ process_entity_updates(self, entity, c, custom_ids, variants)
917
+ end)
918
+ end
919
+ end
920
+
921
+ local function apply_command_buffer(
922
+ self: Client,
923
+ entity: Entity,
924
+ command_buffer: CommandBuffer,
925
+ request: (e: Entity,...any) -> Entity?
926
+ )
927
+ local components = self.components
928
+ local changed_hooks = self.network_hooks.changed
929
+ local removed_hooks = self.network_hooks.removed
930
+
931
+ for tag in command_buffer.tags do
932
+ entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
933
+ end
934
+ for component, value in command_buffer.components do
935
+ entity_set(self, entity, component, value.value, changed_hooks[PAIR(components.reliable, component)])
936
+ end
937
+ for component, value in command_buffer.unreliable_components do
938
+ if WORLD_HAS(self.world, entity, component) then
939
+ entity_set(self, entity, component, value.value, changed_hooks[PAIR(components.unreliable, component)])
940
+ end
941
+ end
942
+ for component in command_buffer.remove do
943
+ entity_remove(self, entity, component, removed_hooks[PAIR(components.reliable, component)])
944
+ end
945
+ for relation, targets in command_buffer.pairs_add do
946
+ local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
947
+ for target in targets do
948
+ local client_target = request(target)
949
+ if client_target then
950
+ entity_add_pair(self, entity, relation, client_target, relation_hooks)
951
+ end
952
+ end
953
+ end
954
+ for relation, targets in command_buffer.pairs_set do
955
+ local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
956
+ for target, value in targets do
957
+ local client_target = request(target)
958
+ if client_target then
959
+ entity_set_pair(self, entity, relation, client_target, value.value, relation_hooks)
960
+ end
961
+ end
962
+ end
963
+ for relation, targets in command_buffer.pairs_unprocessed do
964
+ local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
965
+ for target, unprocessed in targets do
966
+ local client_target = request(target)
967
+ if client_target then
968
+ local processed_value = unprocessed.processed_value
969
+ if processed_value then
970
+ entity_set_pair(self, entity, relation, client_target, processed_value.value, relation_hooks)
971
+ else
972
+ local value = entity_set_postponed_pair(
973
+ self,
974
+ entity,
975
+ relation,
976
+ client_target,
977
+ unprocessed.cursor,
978
+ unprocessed.variants
979
+ )
980
+ unprocessed.processed_value = { value = value }
981
+ end
982
+ end
983
+ end
984
+ end
985
+ for relation, targets in command_buffer.pairs_remove do
986
+ local relation_hooks = removed_hooks[PAIR(components.relation, relation)]
987
+ for target in targets do
988
+ local client_target = request(target)
989
+ if client_target then
990
+ entity_remove_pair(self, entity, relation, client_target, relation_hooks)
991
+ end
992
+ end
993
+ end
994
+ for relation in command_buffer.relations_clear do
995
+ entity_clear_relation(self, entity, relation, removed_hooks[PAIR(components.relation, relation)])
996
+ end
997
+ end
998
+
999
+ local function find_component_in_buffer(command_buffer: CommandBuffer, component: Entity): { value: any }?
1000
+ return command_buffer.components[component] or command_buffer.unreliable_components[component]
1001
+ end
1002
+
1003
+ local function find_has_in_buffer(command_buffer: CommandBuffer, tag: Entity): boolean
1004
+ return (command_buffer.tags[tag] ~= nil)
1005
+ or (command_buffer.components[tag] ~= nil)
1006
+ or (command_buffer.unreliable_components[tag] ~= nil)
1007
+ end
1008
+
1009
+ local function find_pair_in_buffer(
1010
+ command_buffer: CommandBuffer,
1011
+ relation: Entity,
1012
+ target: Entity,
1013
+ find: (unprocessed_entity: Entity) -> (Entity?, Entity)
1014
+ ): (boolean, any)
1015
+ local value_relations = command_buffer.pairs_set[relation]
1016
+ if value_relations then
1017
+ for buffer_target, value in value_relations do
1018
+ local found = find(buffer_target)
1019
+ if found == target then
1020
+ return true, value.value
1021
+ end
1022
+ end
1023
+ end
1024
+
1025
+ local relations = command_buffer.pairs_add[relation]
1026
+ if relations then
1027
+ for buffer_target in relations do
1028
+ local found = find(buffer_target)
1029
+ if found == target then
1030
+ return true, nil
1031
+ end
1032
+ end
1033
+ end
1034
+
1035
+ local unprocessed_relations = command_buffer.pairs_unprocessed[relation]
1036
+ if unprocessed_relations then
1037
+ for buffer_target, unprocessed in unprocessed_relations do
1038
+ local found = find(buffer_target)
1039
+ if found == target then
1040
+ local processed_value = unprocessed.processed_value
1041
+ return true, processed_value and processed_value.value
1042
+ end
1043
+ end
1044
+ end
1045
+
1046
+ return false, nil
1047
+ end
1048
+
1049
+ local function find_target_in_buffer(command_buffer: CommandBuffer, relation: Entity, index: number?): Entity?
1050
+ local i = 0
1051
+
1052
+ local pairs = command_buffer.pairs_add[relation]
1053
+ if pairs then
1054
+ for target in pairs do
1055
+ if i == (index or 0) then
1056
+ return target
1057
+ end
1058
+ i += 1
1059
+ end
1060
+ end
1061
+
1062
+ local pairs_values = command_buffer.pairs_set[relation]
1063
+ if pairs_values then
1064
+ for target in pairs_values do
1065
+ if i == (index or 0) then
1066
+ return target
1067
+ end
1068
+ i += 1
1069
+ end
1070
+ end
1071
+
1072
+ local unprocessed_pairs = command_buffer.pairs_unprocessed[relation]
1073
+ if unprocessed_pairs then
1074
+ for target in unprocessed_pairs do
1075
+ if i == (index or 0) then
1076
+ return target
1077
+ end
1078
+ i += 1
1079
+ end
1080
+ end
1081
+
1082
+ return nil
1083
+ end
1084
+
1085
+ -- resolves every custom id collected while reading a packet, then drains the
1086
+ -- command buffers. ids are negative server ids until resolved; a handler can
1087
+ -- pull other unresolved entities through its context, so resolution recurses
1088
+ -- and each server id is attempted once
1089
+ local function finish_custom_ids(self: Client, custom_ids: CustomIdMap, command_buffers: { [Entity]: CommandBuffer })
1090
+ local world = self.world
1091
+ local components = self.components
1092
+ local processed: { [number]: boolean } = {}
1093
+ local create_context: (unprocessed_entity: Entity) -> customid.HandleContext
1094
+
1095
+ -- positive ids are already client entities; negative ones look up the
1096
+ -- server id, which may have resolved earlier in this pass
1097
+ local function find(unprocessed_entity: Entity): (Entity?, Entity)
1098
+ if (unprocessed_entity :: any) > 0 then
1099
+ return unprocessed_entity, unprocessed_entity
1100
+ end
1101
+ local server_id = -(unprocessed_entity :: any)
1102
+ return self.server_ids[server_id], server_id
1103
+ end
1104
+
1105
+ local function process(unprocessed_entity: Entity, custom_handler: (CustomId | Entity)?): Entity?
1106
+ local client_entity, server_id = find(unprocessed_entity)
1107
+ if client_entity then
1108
+ return client_entity
1109
+ end
1110
+ if processed[server_id :: any] then
1111
+ return nil
1112
+ end
1113
+
1114
+ custom_handler = custom_handler or custom_ids[server_id :: any]
1115
+ if not custom_handler then
1116
+ return nil
1117
+ end
1118
+ processed[server_id :: any] = true
1119
+
1120
+ local command_buffer = command_buffers[unprocessed_entity]
1121
+ local new_entity: Entity?
1122
+
1123
+ if type(custom_handler) == "number" then
1124
+ local component: Entity = custom_handler :: any
1125
+ local handler = WORLD_GET(world, component, components.custom_handler)
1126
+ if not handler then
1127
+ common.log_error(
1128
+ "received a custom id for a component without a handler, consider adding custom_handler to component:",
1129
+ common.log_component(world, component)
1130
+ )
1131
+ end
1132
+
1133
+ local value = command_buffer and find_component_in_buffer(command_buffer, component)
1134
+ if not value then
1135
+ common.log_warn(`no component found for custom id handler: {common.log_component(world, component)}`)
1136
+ end
1137
+ new_entity = handler(value and value.value)
1138
+ else
1139
+ local custom = custom_handler :: CustomId
1140
+ local handle_callback = custom.handle_callback
1141
+ if not handle_callback then
1142
+ common.log_error("no handler callback was set for custom id:", custom.identifier)
1143
+ end
1144
+ new_entity = (handle_callback :: any)(create_context(unprocessed_entity))
1145
+ end
1146
+
1147
+ if new_entity then
1148
+ self.server_ids[server_id :: any] = new_entity
1149
+ self.client_ids[new_entity] = server_id :: any
1150
+ track_alive(self, new_entity)
1151
+
1152
+ if command_buffer then
1153
+ apply_command_buffer(self, new_entity, command_buffer, process)
1154
+ command_buffers[unprocessed_entity] = nil
1155
+ end
1156
+ end
1157
+
1158
+ return new_entity
1159
+ end
1160
+
1161
+ -- what a custom id handler sees of the entity: its buffered state, with
1162
+ -- targets and referenced entities resolved on demand
1163
+ function create_context(unprocessed_entity: Entity): customid.HandleContext
1164
+ local command_buffer = command_buffers[unprocessed_entity]
1165
+
1166
+ local function component(id: Entity): any
1167
+ if not command_buffer then
1168
+ return nil
1169
+ end
1170
+ local value = find_component_in_buffer(command_buffer, id)
1171
+ return value and value.value
1172
+ end
1173
+ local function has(tag: Entity): boolean
1174
+ if not command_buffer then
1175
+ return false
1176
+ end
1177
+ return find_has_in_buffer(command_buffer, tag)
1178
+ end
1179
+ local function target(relation: Entity, index: number?): Entity?
1180
+ if not command_buffer then
1181
+ return nil
1182
+ end
1183
+ local buffer_target = find_target_in_buffer(command_buffer, relation, index)
1184
+ if buffer_target then
1185
+ return process(buffer_target)
1186
+ end
1187
+ return nil
1188
+ end
1189
+ local function pair_value(relation: Entity, pair_target: Entity): any
1190
+ if not command_buffer then
1191
+ return nil
1192
+ end
1193
+ local _, value = find_pair_in_buffer(command_buffer, relation, pair_target, find)
1194
+ return value
1195
+ end
1196
+ local function has_pair(relation: Entity, pair_target: Entity): boolean
1197
+ if not command_buffer then
1198
+ return false
1199
+ end
1200
+ local found = find_pair_in_buffer(command_buffer, relation, pair_target, find)
1201
+ return found
1202
+ end
1203
+ local function entity(server_id: number): Entity
1204
+ return process(-server_id :: any) :: Entity
1205
+ end
1206
+
1207
+ -- the context's generic accessors are typed for callers; the buffer
1208
+ -- lookups behind them are untyped
1209
+ local context = {
1210
+ entity_id = math.abs(unprocessed_entity :: any),
1211
+ has = has,
1212
+ component = component,
1213
+ target = target,
1214
+ pair_value = pair_value,
1215
+ has_pair = has_pair,
1216
+ entity = entity,
1217
+ }
1218
+ return context :: any
1219
+ end
1220
+
1221
+ for server_id, custom_handler in custom_ids do
1222
+ process(-server_id :: any, custom_handler)
1223
+ end
1224
+
1225
+ -- known entities whose buffers reference custom ids, now resolvable
1226
+ for entity, command_buffer in command_buffers do
1227
+ if (entity :: any) < 0 then
1228
+ continue
1229
+ end
1230
+ apply_command_buffer(self, entity, command_buffer, process)
1231
+ end
1232
+ end
1233
+
1234
+ local function finish_replication(self: Client)
1235
+ self.is_replicating = false
1236
+ for _, callback in self.after_replication_callbacks do
1237
+ callback()
1238
+ end
1239
+ table.clear(self.after_replication_callbacks)
1240
+ end
1241
+
1242
+ -- a full snapshot from get_full: [vlq entity count, per entity: id + sections].
1243
+ -- entities whose id is a custom id are buffered until finish_custom_ids can
1244
+ -- resolve them
1245
+ local function check_packet_type(c: Cursor, expected_byte: number)
1246
+ local got_byte = cursor.readu8(c)
1247
+ if got_byte == expected_byte then
1248
+ return
1249
+ end
1250
+
1251
+ local got_type: string?
1252
+ local expected_type: string?
1253
+ for packet_type, byte in PACKET_TYPES :: { [string]: number } do
1254
+ if byte == got_byte then
1255
+ got_type = packet_type
1256
+ elseif byte == expected_byte then
1257
+ expected_type = packet_type
1258
+ end
1259
+ end
1260
+ common.log_error(
1261
+ `packet type mismatch, expected: {expected_type or "(unknown)"} got: {got_type or "(unknown)"} instead`
1262
+ )
1263
+ end
1264
+
1265
+ function client.apply_full(self: Client, buf: buffer, variants: { any }?)
1266
+ self:resolve_dirty()
1267
+ self.is_replicating = true
1268
+
1269
+ local c = cursor.from(buf)
1270
+ check_packet_type(c, PACKET_TYPES.full)
1271
+ local command_buffers: { [Entity]: CommandBuffer } = {}
1272
+ local custom_ids: CustomIdMap = {}
1273
+ self.command_buffers = command_buffers
1274
+
1275
+ local total_entities = cursor.read_vlq(c)
1276
+ for _ = 1, total_entities do
1277
+ process_entity(self, c, custom_ids, function(entity)
1278
+ process_full_entity(self, entity, c, custom_ids, variants)
1279
+ end)
1280
+ end
1281
+
1282
+ finish_custom_ids(self, custom_ids, command_buffers)
1283
+ self.command_buffers = nil
1284
+ finish_replication(self)
1285
+ end
1286
+
1287
+ -- a combined packet from collect_entity: [inner packets..., vlq packet count],
1288
+ -- each inner packet a full snapshot of the same entity holding the members of
1289
+ -- one filter group. the cursor reads from the end, so packets come out last
1290
+ -- to first and pick their variants list by index
1291
+ function client.apply_entity(self: Client, buf: buffer, all_variants: { { any } }?)
1292
+ self:resolve_dirty()
1293
+ self.is_replicating = true
1294
+
1295
+ local c = cursor.from(buf)
1296
+ check_packet_type(c, PACKET_TYPES.entity)
1297
+ local command_buffers: { [Entity]: CommandBuffer } = {}
1298
+ local custom_ids: CustomIdMap = {}
1299
+ self.command_buffers = command_buffers
1300
+
1301
+ local total_packets = cursor.read_vlq(c)
1302
+ for packet_index = total_packets, 1, -1 do
1303
+ local variants = if all_variants then all_variants[packet_index] else nil
1304
+ local total_entities = cursor.read_vlq(c)
1305
+ for _ = 1, total_entities do
1306
+ process_entity(self, c, custom_ids, function(entity)
1307
+ process_full_entity(self, entity, c, custom_ids, variants)
1308
+ end)
1309
+ end
1310
+ end
1311
+
1312
+ finish_custom_ids(self, custom_ids, command_buffers)
1313
+ self.command_buffers = nil
1314
+ finish_replication(self)
1315
+ end
1316
+
1317
+ -- a combined packet from collect_updates: [group packets..., vlq packet
1318
+ -- count], one inner packet per visibility group the player belongs to. read
1319
+ -- like apply_entity: last group first, variants picked by index
1320
+ function client.apply_updates(self: Client, buf: buffer, all_variants: { { any } }?)
1321
+ self:resolve_dirty()
1322
+ self.is_replicating = true
1323
+
1324
+ local c = cursor.from(buf)
1325
+ check_packet_type(c, PACKET_TYPES.updates)
1326
+ local command_buffers: { [Entity]: CommandBuffer } = {}
1327
+ local custom_ids: CustomIdMap = {}
1328
+ self.command_buffers = command_buffers
1329
+
1330
+ local total_packets = cursor.read_vlq(c)
1331
+ for packet_index = total_packets, 1, -1 do
1332
+ local variants = if all_variants then all_variants[packet_index] else nil
1333
+ process_update_group(self, c, custom_ids, variants)
1334
+ end
1335
+
1336
+ finish_custom_ids(self, custom_ids, command_buffers)
1337
+ self.command_buffers = nil
1338
+ finish_replication(self)
1339
+ end
1340
+
1341
+ -- one entity of an unreliable packet, after its id: [vlq pair count, pairs...,
1342
+ -- vlq component count, components...]. the packet carries current values
1343
+ -- only, never presence: an entity or member the client doesn't have yet is
1344
+ -- skipped (its adds travel reliably), but every value is still read so the
1345
+ -- cursor stays aligned. `entity` is nil when the entity is unknown
1346
+ local function process_unreliable_entity(self: Client, entity: Entity?, c: Cursor, variants: Array<any>?)
1347
+ local world = self.world
1348
+ local changed_hooks = self.network_hooks.changed
1349
+ local components = self.components
1350
+
1351
+ local total_pairs = cursor.read_vlq(c)
1352
+ for _ = 1, total_pairs do
1353
+ local relation = read_component_id(self, c)
1354
+ local target = find_entity(self, c)
1355
+ local value_size = cursor.read_vlq(c)
1356
+ local value_start = c.offset - value_size
1357
+
1358
+ if entity and target then
1359
+ local pair_id, value = read_pair_value(self, relation, target, c, variants)
1360
+ if WORLD_HAS(world, entity, pair_id) then
1361
+ entity_set(self, entity, pair_id, value, changed_hooks[PAIR(components.relation, relation)])
1362
+ end
1363
+ end
1364
+ -- the size skips the value when nothing can receive it
1365
+ c.offset = value_start
1366
+ end
1367
+
1368
+ local total_components = cursor.read_vlq(c)
1369
+ for _ = 1, total_components do
1370
+ local component = read_component_id(self, c)
1371
+ local value = read_component_value(self, component, c, variants)
1372
+ if entity then
1373
+ entity_set_unreliable(self, entity, component, value, changed_hooks[PAIR(components.unreliable, component)])
1374
+ end
1375
+ end
1376
+ end
1377
+
1378
+ -- a combined packet from collect_unreliable: [inner packets..., vlq packet
1379
+ -- count], each inner packet [per entity: id + values, vlq entity count].
1380
+ -- entities are only looked up, never created: the channel is lossy and
1381
+ -- carries no presence, so no command buffers or custom id resolution here
1382
+ function client.apply_unreliable(self: Client, buf: buffer, all_variants: { { any } }?)
1383
+ self:resolve_dirty()
1384
+ self.is_replicating = true
1385
+
1386
+ local c = cursor.from(buf)
1387
+ check_packet_type(c, PACKET_TYPES.unreliable)
1388
+
1389
+ local total_packets = cursor.read_vlq(c)
1390
+ for packet_index = total_packets, 1, -1 do
1391
+ local variants = if all_variants then all_variants[packet_index] else nil
1392
+ local total_entities = cursor.read_vlq(c)
1393
+ for _ = 1, total_entities do
1394
+ local entity = find_entity(self, c)
1395
+ process_unreliable_entity(self, entity, c, variants)
1396
+ end
1397
+ end
1398
+
1399
+ finish_replication(self)
1400
+ end
1401
+
1402
+ function client.after_replication(self: Client, callback: () -> ())
1403
+ if self.is_replicating then
1404
+ table.insert(self.after_replication_callbacks, callback)
1405
+ else
1406
+ callback()
1407
+ end
1408
+ end
1409
+
1410
+ function client.added(self: Client, callback: (entity: Entity) -> ()): Disconnect
1411
+ table.insert(self.addition_hooks, callback)
1412
+ return function()
1413
+ local index = table.find(self.addition_hooks, callback)
1414
+ if index then
1415
+ table.remove(self.addition_hooks, index)
1416
+ end
1417
+ end
1418
+ end
1419
+
1420
+ local function add_hook_entry(self: Client, action: string, ...): ({ overrides: boolean }, Disconnect)
1421
+ if action == "changed" or action == "removed" then
1422
+ local relation = select(1, ...) :: Entity
1423
+ local callback = select(2, ...) :: (entity: Entity, id: Component, value: any) -> ()
1424
+ local hooks = action == "changed" and self.network_hooks.changed or self.network_hooks.removed
1425
+ local entries = hooks[relation] :: ChangedHooksEntry
1426
+
1427
+ if not entries then
1428
+ entries = {
1429
+ overrides = false,
1430
+ callbacks = {},
1431
+ }
1432
+ hooks[relation] = entries :: any
1433
+ end
1434
+ table.insert(entries.callbacks, callback)
1435
+
1436
+ local function disconnect()
1437
+ local callbacks = entries.callbacks
1438
+ local index = table.find(callbacks, callback)
1439
+ if index then
1440
+ table.remove(callbacks, index)
1441
+ if #callbacks == 0 then
1442
+ hooks[relation] = nil :: any
1443
+ end
1444
+ end
1445
+ end
1446
+ return entries, disconnect
1447
+ elseif action == "deleted" then
1448
+ local entity = select(1, ...) :: Entity
1449
+ local callback = select(2, ...) :: (entity: Entity) -> ()
1450
+ local hooks = self.network_hooks.deleted
1451
+ local entries = hooks[entity]
1452
+
1453
+ if not entries then
1454
+ entries = {
1455
+ overrides = false,
1456
+ callbacks = {},
1457
+ }
1458
+ hooks[entity] = entries
1459
+ end
1460
+ table.insert(entries.callbacks, callback)
1461
+
1462
+ local function disconnect()
1463
+ local callbacks = entries.callbacks
1464
+ local index = table.find(callbacks, callback)
1465
+ if index then
1466
+ table.remove(callbacks, index)
1467
+ if #callbacks == 0 then
1468
+ hooks[entity] = nil
1469
+ end
1470
+ end
1471
+ end
1472
+ return entries, disconnect
1473
+ else
1474
+ error("invalid hook action: " .. action)
1475
+ end
1476
+ end
1477
+
1478
+ function client.hook(self: Client, action: string, ...)
1479
+ local entry, disconnect = add_hook_entry(self, action, ...)
1480
+ entry.overrides = false
1481
+ return disconnect
1482
+ end
1483
+
1484
+ function client.override(self: Client, action: string, ...)
1485
+ local entry, disconnect = add_hook_entry(self, action, ...)
1486
+ entry.overrides = true
1487
+ return disconnect
1488
+ end
1489
+
1490
+ function client.get_server_entity(self: Client, client_entity: Entity): number?
1491
+ return self.client_ids[client_entity]
1492
+ end
1493
+
1494
+ function client.get_client_entity(self: Client, server_entity: number): Entity?
1495
+ return self.server_ids[server_entity]
1496
+ end
1497
+
1498
+ function client.register_entity(self: Client, entity: Entity, server_entity: number)
1499
+ self.server_ids[server_entity] = entity
1500
+ self.client_ids[entity] = server_entity
1501
+ track_alive(self, entity)
1502
+ end
1503
+
1504
+ function client.unregister_entity(self: Client, entity: Entity)
1505
+ local server_id = self.client_ids[entity]
1506
+ if server_id then
1507
+ self.server_ids[server_id] = nil
1508
+ self.client_ids[entity] = nil
1509
+ end
1510
+ end
1511
+
1512
+ function client.handle_global(self: Client, handler: (id: number) -> Entity)
1513
+ self.global_handler = handler
1514
+ end
1515
+
1516
+ function client.register_custom_id(self: Client, custom_id: CustomId)
1517
+ self.registered_custom_ids[custom_id] = true
1518
+ self.is_shared_dirty = true
1519
+ end
1520
+
1521
+ local function ensure_component_serialization(self: Client, component: Component): ComponentSerializationOptions
1522
+ local options = self.component_serialization[component]
1523
+ if not options then
1524
+ options = {} :: ComponentSerializationOptions
1525
+ self.component_serialization[component] = options
1526
+ end
1527
+ return options
1528
+ end
1529
+
1530
+ local function prune_component_serialization(self: Client, component: Component, options: ComponentSerializationOptions)
1531
+ if options.serdes == nil and options.preprocessor == nil and options.delta_serializer == nil then
1532
+ self.component_serialization[component] = nil
1533
+ end
1534
+ end
1535
+
1536
+ -- serdes take part in the handshake, preprocessors and delta serializers are
1537
+ -- local to each side and never mark shared dirty
1538
+ function client.set_serdes(self: Client, component: Component, serdes: types.Serdes)
1539
+ ensure_component_serialization(self, component).serdes = serdes
1540
+ self.is_shared_dirty = true
1541
+ end
1542
+
1543
+ function client.remove_serdes(self: Client, component: Component)
1544
+ local options = self.component_serialization[component]
1545
+ if not options then
1546
+ return
1547
+ end
1548
+ options.serdes = nil
1549
+ prune_component_serialization(self, component, options)
1550
+ self.is_shared_dirty = true
1551
+ end
1552
+
1553
+ function client.set_preprocessor(self: Client, component: Component, preprocessor: types.Preprocessor)
1554
+ ensure_component_serialization(self, component).preprocessor = preprocessor
1555
+ end
1556
+
1557
+ function client.remove_preprocessor(self: Client, component: Component)
1558
+ local options = self.component_serialization[component]
1559
+ if not options then
1560
+ return
1561
+ end
1562
+ options.preprocessor = nil
1563
+ prune_component_serialization(self, component, options)
1564
+ end
1565
+
1566
+ function client.set_delta_serializer(self: Client, component: Component, delta_serializer: types.DeltaSerializer)
1567
+ ensure_component_serialization(self, component).delta_serializer = delta_serializer
1568
+ end
1569
+
1570
+ function client.remove_delta_serializer(self: Client, component: Component)
1571
+ local options = self.component_serialization[component]
1572
+ if not options then
1573
+ return
1574
+ end
1575
+ options.delta_serializer = nil
1576
+ prune_component_serialization(self, component, options)
1577
+ end
1578
+
1579
+ -- rebuilds the shared lookups (component ids, custom ids, serdes) when
1580
+ -- flagged; cheap no-op otherwise, so every deserialize entry point calls it
1581
+ function client.resolve_dirty(self: Client)
1582
+ if not self.is_shared_dirty then
1583
+ return
1584
+ end
1585
+
1586
+ local serdes: { [Component]: types.Serdes } = {}
1587
+ for component, options in self.component_serialization do
1588
+ if options.serdes then
1589
+ serdes[component] = options.serdes
1590
+ end
1591
+ end
1592
+
1593
+ self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
1594
+ self.is_shared_dirty = false
1595
+ end
1596
+
1597
+ -- snapshot of the shared lookups for the server to verify against its own;
1598
+ -- resolves pending shared changes first so the handshake never describes a
1599
+ -- stale state
1600
+ -- the wire id of a shared component, stable across server and client once the
1601
+ -- handshake passes; 0 and an error for anything not shared
1602
+ function client.encode_component(self: Client, component: Component): number
1603
+ self:resolve_dirty()
1604
+ local encoded = self.shared.components.members[component]
1605
+ if not encoded then
1606
+ common.log_error(`attempted to encode a non-shared component `, common.log_component(self.world, component))
1607
+ return 0
1608
+ end
1609
+ return encoded
1610
+ end
1611
+
1612
+ function client.decode_component(self: Client, encoded: number): Component?
1613
+ self:resolve_dirty()
1614
+ return self.shared.components.indexes[encoded]
1615
+ end
1616
+
1617
+ function client.get_shared_count(self: Client): number
1618
+ self:resolve_dirty()
1619
+ return #self.shared.components.keys
1620
+ end
1621
+
1622
+ function client.generate_handshake(self: Client): types.HandshakeInfo
1623
+ self:resolve_dirty()
1624
+ return handshake.generate_handshake(self.shared)
1625
+ end
1626
+
1627
+ function client.verify_handshake(self: Client, server_handshake: types.HandshakeInfo): (boolean, string?)
1628
+ self:resolve_dirty()
1629
+ return handshake.verify_handshake(self.shared, server_handshake, "server", "client")
1630
+ end
1631
+
1632
+ function client.init(self: Client, wrd: World?)
1633
+ if self.inited == true then
1634
+ return common.log_warn "attempted to init a client twice"
1635
+ end
1636
+ if self.inited == nil then
1637
+ return common.log_warn "attempted to re-init a destroyed client"
1638
+ end
1639
+ self.inited = true
1640
+
1641
+ local world = wrd or self.world
1642
+ self.world = world
1643
+
1644
+ if not world then
1645
+ common.log_error "Providing a world is required to start replecs"
1646
+ end
1647
+
1648
+ if not self.components then
1649
+ self.components = utils.create_world_components(world)
1650
+ end
1651
+ local components = self.components
1652
+
1653
+ local function hook(unhook: () -> ())
1654
+ table.insert(self.cleanups, unhook)
1655
+ end
1656
+
1657
+ -- shared components and their names feed the handshake lookups; any change
1658
+ -- rebuilds them at the next resolve_dirty
1659
+ local function mark_shared_dirty()
1660
+ self.is_shared_dirty = true
1661
+ end
1662
+
1663
+ for _, component in { components.shared, ECS_NAME } do
1664
+ hook(HOOK_ADDED(world, component, mark_shared_dirty))
1665
+ hook(HOOK_CHANGED(world, component, mark_shared_dirty))
1666
+ hook(HOOK_REMOVED(world, component, mark_shared_dirty))
1667
+ end
1668
+
1669
+ hook(HOOK_ADDED(world, components.serdes, function(component, _, serdes)
1670
+ self:set_serdes(component, serdes)
1671
+ end))
1672
+ hook(HOOK_CHANGED(world, components.serdes, function(component, _, serdes)
1673
+ self:set_serdes(component, serdes)
1674
+ end))
1675
+ hook(HOOK_REMOVED(world, components.serdes, function(component)
1676
+ self:remove_serdes(component)
1677
+ end))
1678
+
1679
+ -- a replicated entity deleted locally (or by delete_entity) drops out of
1680
+ -- the id maps, so the server id can be bound to a fresh entity later
1681
+ hook(HOOK_REMOVED(world, components.__alive_tracking__, function(entity)
1682
+ local server_id = self.client_ids[entity]
1683
+ if server_id then
1684
+ self.server_ids[server_id] = nil
1685
+ self.client_ids[entity] = nil
1686
+ end
1687
+ self.network_hooks.deleted[entity] = nil
1688
+ end))
1689
+
1690
+ for component, serdes in world:query(components.serdes):iter() do
1691
+ self:set_serdes(component, serdes)
1692
+ end
1693
+
1694
+ self.is_shared_dirty = true
1695
+ end
1696
+
1697
+ function client.destroy(self: Client)
1698
+ if self.inited == nil then
1699
+ return common.log_warn "attempted to destroy a client twice"
1700
+ end
1701
+ self.inited = nil
1702
+ for _, cleanup in self.cleanups do
1703
+ cleanup()
1704
+ end
1705
+ table.clear(self.cleanups)
1706
+ end
1707
+
1708
+ -- components may be nil without preregistration: they are created from the
1709
+ -- world here when one is given, otherwise at init
1710
+ local function create(world: World?, components: types.Components?): Client
1711
+ local self = {} :: Client
1712
+
1713
+ self.world = world :: any
1714
+ self.components = components or (if world then utils.create_world_components(world) else nil) :: any
1715
+ self.inited = false
1716
+
1717
+ self.shared = {} :: types.Shared
1718
+ self.registered_custom_ids = {}
1719
+ self.component_serialization = {}
1720
+ -- starts dirty so the first resolve_dirty builds the lookups
1721
+ self.is_shared_dirty = true
1722
+
1723
+ self.is_replicating = false
1724
+ self.after_replication_callbacks = {}
1725
+
1726
+ self.global_handler = nil
1727
+ self.server_ids = {}
1728
+ self.client_ids = {}
1729
+ self.command_buffers = nil
1730
+ self.command_buffers_active = false
1731
+
1732
+ self.addition_hooks = {}
1733
+ self.network_hooks = {
1734
+ deleted = {},
1735
+ changed = {},
1736
+ removed = {},
1737
+ }
1738
+
1739
+ self.cleanups = {}
1740
+ return setmetatable(self, client) :: any
1741
+ end
1742
+
1743
+ return {
1744
+ create = create,
1745
+
1746
+ read_component_id = read_component_id,
1747
+ read_entity_id = read_entity_id,
1748
+ find_entity = find_entity,
1749
+ process_entity = process_entity,
1750
+
1751
+ read_component_value = read_component_value,
1752
+ read_pair_value = read_pair_value,
1753
+
1754
+ process_entity_pair = process_entity_pair,
1755
+ process_entity_pair_value = process_entity_pair_value,
1756
+ process_entity_relations = process_entity_relations,
1757
+ process_entity_relation_values = process_entity_relation_values,
1758
+ process_full_entity = process_full_entity,
1759
+ delete_entity = delete_entity,
1760
+ }