@rbxts/replecs 0.1.0-alpha9 → 0.2.0-cdtest.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.
@@ -3,10 +3,13 @@
3
3
 
4
4
  local jecs = require "../jecs"
5
5
  local common = require "./common"
6
+ local customid = require "./customid"
6
7
  local utils = require "./utils"
7
8
 
8
9
  local cursor = utils.cursor
10
+
9
11
  type Cursor = utils.Cursor
12
+ type CustomId = customid.CustomId
10
13
 
11
14
  type Entity<T = any> = jecs.Entity<T>
12
15
  type Id<T = any> = jecs.Id<T>
@@ -14,8 +17,20 @@ type World = jecs.World
14
17
 
15
18
  type Array<T> = { T }
16
19
  type Map<K, V> = { [K]: V }
20
+ type Set<T> = { [T]: boolean }
17
21
  type Disconnect = () -> ()
18
22
 
23
+ type CommandBuffer = {
24
+ tags: { [Entity]: boolean },
25
+ components: { [Entity]: { value: any } },
26
+ unreliable_components: { [Entity]: { value: any } },
27
+ remove: { [Entity]: boolean },
28
+ pairs: { [Entity]: Set<Entity> },
29
+ pairs_values: { [Entity]: { [Entity]: { value: any } } },
30
+ pairs_remove: { [Entity]: Set<Entity> },
31
+ pairs_clear: Set<Entity>,
32
+ }
33
+
19
34
  type HookMethod =
20
35
  & ((
21
36
  self: Client,
@@ -39,6 +54,8 @@ type DeletedHookEntry = {
39
54
  callbacks: Array<(entity: Entity) -> ()>,
40
55
  }
41
56
 
57
+ type CustomIdMap = { [Entity]: CustomId | Entity }
58
+
42
59
  export type Client = {
43
60
  world: World,
44
61
  inited: boolean?,
@@ -46,7 +63,7 @@ export type Client = {
46
63
  is_replicating: boolean,
47
64
  after_replication_callbacks: { () -> () },
48
65
 
49
- is_dirty: boolean,
66
+ is_shared_dirty: boolean,
50
67
  requires_shared_lookup: { Entity },
51
68
  components: common.Components,
52
69
  shared: common.Shared,
@@ -54,7 +71,9 @@ export type Client = {
54
71
  server_ids: { [number]: Entity },
55
72
  client_ids: { [Entity]: number },
56
73
  ordered_creation: boolean,
57
- custom_ids: { [Entity]: (any) -> Entity },
74
+ registered_custom_ids: { [CustomId]: boolean },
75
+ command_buffers: { [Entity]: CommandBuffer }?,
76
+ command_buffers_active: boolean,
58
77
 
59
78
  init: (self: Client, world: World?) -> (),
60
79
  destroy: (self: Client) -> (),
@@ -62,6 +81,9 @@ export type Client = {
62
81
  get_server_entity: (self: Client, client_entity: Entity) -> number?,
63
82
  get_client_entity: (self: Client, server_entity: number) -> Entity?,
64
83
 
84
+ register_entity: (self: Client, entity: Entity, server_entity: number) -> (),
85
+ unregister_entity: (self: Client, entity: Entity) -> (),
86
+
65
87
  hooks: common.WorldHooks,
66
88
  hooked: Array<() -> ()>,
67
89
 
@@ -78,17 +100,26 @@ export type Client = {
78
100
 
79
101
  encode_component: (self: Client, component: Entity) -> number,
80
102
  decode_component: (self: Client, component: number) -> Entity,
103
+ register_custom_id: (self: Client, custom_id: CustomId) -> (),
81
104
 
82
105
  apply_updates: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
83
106
  apply_unreliable: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
84
107
  apply_full: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
108
+
109
+ generate_handshake: (self: Client) -> common.HandshakeInfo,
110
+ verify_handshake: (self: Client, handshake: common.HandshakeInfo) -> (boolean, string?),
85
111
  }
86
112
 
113
+ local client_replicator = {}
114
+ client_replicator.__index = client_replicator
115
+
87
116
  local GLOBAL_ID_OFFSET = 10
117
+
88
118
  local ENTITY_ID_TYPES = {
89
119
  entity = 1,
90
- custom = 2,
91
- shared = 3,
120
+ custom_handler = 2,
121
+ custom_id = 3,
122
+ shared = 4,
92
123
  }
93
124
  local PACKET_TYPES = {
94
125
  full = 1,
@@ -96,7 +127,6 @@ local PACKET_TYPES = {
96
127
  reliable = 3,
97
128
  }
98
129
 
99
- local ECS_NAME = jecs.Name
100
130
  local WILDCARD = jecs.Wildcard
101
131
 
102
132
  local function PAIR(first: Entity, second: Entity): Entity
@@ -122,20 +152,97 @@ local function get_or_create_entity(client: Client, server_id: number): Entity
122
152
  end
123
153
  end
124
154
 
125
- local function network_entity_set(client: Client, entity: Entity, id: Id, value: any, changed_hooks: ChangedHooksEntry?)
126
- if changed_hooks then
127
- if not changed_hooks.overrides then
155
+ local function get_command_buffer(client: Client, entity: Entity): CommandBuffer
156
+ local command_buffers = client.command_buffers
157
+
158
+ if not command_buffers then
159
+ error "attempted to use a command buffer without having one. (this should never happen)"
160
+ end
161
+
162
+ local command_buffer = command_buffers[entity :: any]
163
+ if not command_buffer then
164
+ command_buffer = {
165
+ tags = {},
166
+ components = {},
167
+ unreliable_components = {},
168
+ remove = {},
169
+ pairs = {},
170
+ pairs_values = {},
171
+ pairs_remove = {},
172
+ pairs_clear = {},
173
+ }
174
+ command_buffers[entity :: any] = command_buffer
175
+ end
176
+ return command_buffer
177
+ end
178
+
179
+ function client_replicator.entity_set(
180
+ client: Client,
181
+ entity: Entity,
182
+ id: Id,
183
+ value: any,
184
+ changed_hooks: ChangedHooksEntry?
185
+ )
186
+ if client.command_buffers_active then
187
+ local command_buffer = get_command_buffer(client, entity)
188
+ command_buffer.components[id] = { value = value }
189
+ else
190
+ if changed_hooks then
191
+ if not changed_hooks.overrides then
192
+ client.world:set(entity, id, value)
193
+ end
194
+ for _, callback in changed_hooks.callbacks do
195
+ callback(entity, id, value)
196
+ end
197
+ else
128
198
  client.world:set(entity, id, value)
129
199
  end
130
- for _, callback in changed_hooks.callbacks do
131
- callback(entity, id, value)
200
+ end
201
+ end
202
+
203
+ function client_replicator.entity_set_unreliable(
204
+ client: Client,
205
+ entity: Entity,
206
+ component: Entity,
207
+ value: any,
208
+ changed_hooks: ChangedHooksEntry?
209
+ )
210
+ if client.command_buffers_active then
211
+ local command_buffer = get_command_buffer(client, entity)
212
+ command_buffer.unreliable_components[component] = value
213
+ else
214
+ client_replicator.entity_set(client, entity, component, value, changed_hooks)
215
+ end
216
+ end
217
+
218
+ function client_replicator.entity_set_pair(
219
+ client: Client,
220
+ entity: Entity,
221
+ relation: Entity,
222
+ target: Entity,
223
+ value: any,
224
+ changed_hooks: ChangedHooksEntry?
225
+ )
226
+ if client.command_buffers_active then
227
+ local command_buffer = get_command_buffer(client, entity)
228
+ local pair_relation = command_buffer.pairs_values[relation]
229
+
230
+ if pair_relation then
231
+ pair_relation[target] = { value = value }
232
+ else
233
+ command_buffer.pairs_values[relation] = { [target] = value }
132
234
  end
133
235
  else
134
- client.world:set(entity, id, value)
236
+ client_replicator.entity_set(client, entity, PAIR(relation, target), value, changed_hooks)
135
237
  end
136
238
  end
137
239
 
138
- local function network_entity_add(client: Client, entity: Entity, component: Entity, changed_hooks: ChangedHooksEntry?)
240
+ function client_replicator.entity_add(
241
+ client: Client,
242
+ entity: Entity,
243
+ component: Entity,
244
+ changed_hooks: ChangedHooksEntry?
245
+ )
139
246
  if changed_hooks then
140
247
  if not changed_hooks.overrides then
141
248
  client.world:add(entity, component)
@@ -148,77 +255,129 @@ local function network_entity_add(client: Client, entity: Entity, component: Ent
148
255
  end
149
256
  end
150
257
 
151
- local function network_entity_remove(client: Client, entity: Entity, id: Entity, removed_hooks: RemovedHooksEntry?)
152
- if removed_hooks then
153
- for _, callback in removed_hooks.callbacks do
154
- callback(entity, id)
258
+ function client_replicator.entity_add_pair(
259
+ client: Client,
260
+ entity: Entity,
261
+ relation: Entity,
262
+ target: Entity,
263
+ changed_hooks: ChangedHooksEntry?
264
+ )
265
+ if client.command_buffers_active then
266
+ local command_buffer = get_command_buffer(client, entity)
267
+ local pair_relation = command_buffer.pairs[relation]
268
+
269
+ if pair_relation then
270
+ pair_relation[target] = true
271
+ else
272
+ command_buffer.pairs[relation] = { [target] = true }
155
273
  end
156
- if not removed_hooks.overrides then
274
+ else
275
+ client_replicator.entity_add(client, entity, PAIR(relation, target), changed_hooks)
276
+ end
277
+ end
278
+
279
+ function client_replicator.entity_remove(client: Client, entity: Entity, id: Entity, removed_hooks: RemovedHooksEntry?)
280
+ if client.command_buffers_active then
281
+ local command_buffer = get_command_buffer(client, entity)
282
+ command_buffer.remove[id] = true
283
+ else
284
+ if removed_hooks then
285
+ for _, callback in removed_hooks.callbacks do
286
+ callback(entity, id)
287
+ end
288
+ if not removed_hooks.overrides then
289
+ client.world:remove(entity, id)
290
+ end
291
+ else
157
292
  client.world:remove(entity, id)
158
293
  end
159
- else
160
- client.world:remove(entity, id)
161
294
  end
162
295
  end
163
296
 
164
- local function network_entity_remove_relations(
297
+ function client_replicator.entity_remove_pair(
165
298
  client: Client,
166
299
  entity: Entity,
167
300
  relation: Entity,
301
+ target: Entity,
168
302
  removed_hooks: RemovedHooksEntry?
169
303
  )
170
- local world = client.world
171
- local entity_index = jecs.entity_index_try_get_fast(world.entity_index :: any, entity :: any)
172
- if not entity_index then
173
- return
304
+ if client.command_buffers_active then
305
+ local command_buffer = get_command_buffer(client, entity)
306
+ local pair_relation = command_buffer.pairs_remove[relation]
307
+
308
+ if pair_relation then
309
+ pair_relation[target] = true
310
+ else
311
+ command_buffer.pairs_remove[relation] = { [target] = true }
312
+ end
313
+ else
314
+ client_replicator.entity_remove(client, entity, PAIR(relation, target), removed_hooks)
174
315
  end
175
- local archetype = entity_index.archetype
316
+ end
176
317
 
177
- local wildcard = PAIR(relation, WILDCARD) :: any
178
- local idr = world.component_index[wildcard]
179
- if not idr then
180
- return
181
- end
318
+ function client_replicator.entity_remove_relations(
319
+ client: Client,
320
+ entity: Entity,
321
+ relation: Entity,
322
+ removed_hooks: RemovedHooksEntry?
323
+ )
324
+ if client.command_buffers_active then
325
+ local command_buffer = get_command_buffer(client, entity)
326
+ command_buffer.pairs_clear[relation] = true
327
+ else
328
+ local world = client.world
329
+ local entity_index = jecs.entity_index_try_get_fast(world.entity_index :: any, entity :: any)
330
+ if not entity_index then
331
+ return
332
+ end
333
+ local archetype = entity_index.archetype
182
334
 
183
- local archetype_id = archetype.id
184
- local count = idr.counts[archetype_id]
185
- if not count then
186
- return
187
- end
335
+ local wildcard = PAIR(relation, WILDCARD) :: any
336
+ local idr = world.component_index[wildcard]
337
+ if not idr then
338
+ return
339
+ end
188
340
 
189
- local start = idr.records[archetype_id]
190
- if not start then
191
- return
192
- end
341
+ local archetype_id = archetype.id
342
+ local count = idr.counts[archetype_id]
343
+ if not count then
344
+ return
345
+ end
193
346
 
194
- if removed_hooks then
195
- for i = start, start + count - 1 do
196
- local pair = archetype.types[i]
197
- for _, callback in removed_hooks.callbacks do
198
- callback(entity, pair)
347
+ local start = idr.records[archetype_id]
348
+ if not start then
349
+ return
350
+ end
351
+
352
+ if removed_hooks then
353
+ for i = start, start + count - 1 do
354
+ local pair = archetype.types[i]
355
+ for _, callback in removed_hooks.callbacks do
356
+ callback(entity, pair)
357
+ end
358
+ if not removed_hooks.overrides then
359
+ world:remove(entity, pair :: any)
360
+ end
199
361
  end
200
- if not removed_hooks.overrides then
362
+ else
363
+ for i = start, start + count - 1 do
364
+ local pair = archetype.types[i]
201
365
  world:remove(entity, pair :: any)
202
366
  end
203
367
  end
204
- else
205
- for i = start, start + count - 1 do
206
- local pair = archetype.types[i]
207
- world:remove(entity, pair :: any)
208
- end
209
368
  end
210
369
  end
211
370
 
212
- local function encode_component(client: Client, component: Entity): number
213
- local encoded = client.shared.ids[component]
371
+ function client_replicator.encode_component(client: Client, component: Entity): number
372
+ local encoded = client.shared.components.members[component]
214
373
  if not encoded then
215
374
  utils.logerror(`attempted to encode a non-shared component `, utils.logcomponent(client.world, component))
216
375
  return 0
217
376
  end
218
377
  return encoded
219
378
  end
220
- local function decode_component(client: Client, encoded: number): Entity
221
- local component = client.shared.components[encoded]
379
+ function client_replicator.decode_component(client: Client, encoded: number): Entity
380
+ local component = client.shared.components.indexes[encoded]
222
381
  if not component then
223
382
  print("NON SHARED COMPONENT", encoded, client.shared.components)
224
383
  error "attemped to decode a non shared component"
@@ -226,9 +385,10 @@ local function decode_component(client: Client, encoded: number): Entity
226
385
  return component
227
386
  end
228
387
 
229
- local function read_component_id(client: Client, c: Cursor): Entity
230
- local shared_id = cursor.readu8(c)
231
- local component = client.shared.components[shared_id]
388
+ function client_replicator.read_component_id(client: Client, c: Cursor): Entity
389
+ local shared_components = client.shared.components
390
+ local shared_id = cursor.read_span(c, #shared_components.keys)
391
+ local component = shared_components.indexes[shared_id]
232
392
  if not component then
233
393
  print("NON SHARED COMPONENT", shared_id, client.shared.components)
234
394
  error "received a non shared component"
@@ -236,11 +396,11 @@ local function read_component_id(client: Client, c: Cursor): Entity
236
396
  return component
237
397
  end
238
398
 
239
- local function read_component_value(client: Client, component: Entity, c: Cursor, variants: { any }?): any
399
+ function client_replicator.read_component_value(client: Client, component: Entity, c: Cursor, variants: { any }?): any
240
400
  local serdes = client.shared.serdes[component]
241
401
 
242
402
  if serdes then
243
- local bytespan = client.shared.bytespan[component] or cursor.read_vlq(c)
403
+ local bytespan = serdes.bytespan or cursor.read_vlq(c)
244
404
 
245
405
  local appended = cursor.read_buffer(c, bytespan)
246
406
  local serdes_variants: { any }? = nil
@@ -266,13 +426,13 @@ local function read_component_value(client: Client, component: Entity, c: Cursor
266
426
  end
267
427
  end
268
428
 
269
- local function read_component(client: Client, c: Cursor, variants: { any }?): (Entity, any)
270
- local component = read_component_id(client, c)
271
- local value = read_component_value(client, component, c, variants)
429
+ function client_replicator.read_component(client: Client, c: Cursor, variants: { any }?): (Entity, any)
430
+ local component = client_replicator.read_component_id(client, c)
431
+ local value = client_replicator.read_component_value(client, component, c, variants)
272
432
  return component, value
273
433
  end
274
434
 
275
- local function resolve_global(client: Client, global_id: number): Entity
435
+ function client_replicator.resolve_global(client: Client, global_id: number): Entity
276
436
  if not client.global_handler then
277
437
  error "global id parser not set, consider using client:handle_global()"
278
438
  end
@@ -280,99 +440,145 @@ local function resolve_global(client: Client, global_id: number): Entity
280
440
  return parsed
281
441
  end
282
442
 
283
- local function read_entity_id(client: Client, c: Cursor, variants: { any }?): (Entity?, number?)
443
+ -- returns entity, server_id, id_type
444
+ function client_replicator.read_entity_id(client: Client, c: Cursor): (Entity?, number?, number?)
284
445
  local id_type = cursor.readu8(c)
285
446
 
286
447
  if id_type <= GLOBAL_ID_OFFSET then
287
- if id_type == ENTITY_ID_TYPES.entity then
448
+ if
449
+ id_type == ENTITY_ID_TYPES.entity
450
+ or id_type == ENTITY_ID_TYPES.custom_id
451
+ or id_type == ENTITY_ID_TYPES.custom_handler
452
+ then
288
453
  local server_id = cursor.readu40(c)
289
- return client.server_ids[server_id], server_id
290
- elseif id_type == ENTITY_ID_TYPES.custom then
291
- local component, value = read_component(client, c, variants)
292
- local custom_getter = client.custom_ids[component]
293
- if not custom_getter then
294
- error(
295
- `received a custom id for a non custom component, consider adding custom_id to component: {client.world:get(
296
- component,
297
- ECS_NAME
298
- ) or "(no name)"} id: {component}`
299
- )
300
- end
301
- return custom_getter(value), nil
454
+ return client.server_ids[server_id], server_id, id_type
302
455
  elseif id_type == ENTITY_ID_TYPES.shared then
303
- return read_component_id(client, c), nil
456
+ return client_replicator.read_component_id(client, c), nil, id_type
304
457
  end
305
- error(`malformed entity id {id_type} ` .. cursor.readu32(c))
458
+ error(`malformed entity type {id_type} ` .. cursor.readu32(c))
306
459
  else
307
- return resolve_global(client, id_type - GLOBAL_ID_OFFSET), nil
460
+ return client_replicator.resolve_global(client, id_type - GLOBAL_ID_OFFSET), nil, id_type
308
461
  end
309
462
  end
310
463
 
311
- local function process_entity_id(client: Client, c: Cursor, variants: { any }?)
312
- local entity, server_id = read_entity_id(client, c, variants)
464
+ function client_replicator.process_entity_id(
465
+ client: Client,
466
+ c: Cursor,
467
+ custom_ids: CustomIdMap,
468
+ process: (entity: Entity) -> ()
469
+ )
470
+ local entity, server_id, id_type = client_replicator.read_entity_id(client, c)
471
+
313
472
  if entity then
314
- return entity
473
+ -- skip customid, keep cursor offset
474
+ if id_type == ENTITY_ID_TYPES.custom_id then
475
+ client_replicator.read_component_id(client, c)
476
+ elseif id_type == ENTITY_ID_TYPES.custom_handler then
477
+ cursor.readu8(c)
478
+ end
479
+
480
+ return process(entity)
315
481
  else
316
- return get_or_create_entity(client, server_id :: number)
482
+ if id_type == ENTITY_ID_TYPES.custom_id or id_type == ENTITY_ID_TYPES.custom_handler then
483
+ if id_type == ENTITY_ID_TYPES.custom_id then
484
+ local component_id = client_replicator.read_component_id(client, c)
485
+ custom_ids[server_id :: any] = component_id
486
+ elseif id_type == ENTITY_ID_TYPES.custom_handler then
487
+ local shared_custom_id = cursor.readu8(c)
488
+ local handler = client.shared.custom_ids.indexes[shared_custom_id]
489
+
490
+ if not handler then
491
+ utils.logerror "attempted to use a custom id that wasn't registered"
492
+ end
493
+ custom_ids[server_id :: any] = handler
494
+ end
495
+
496
+ if client.command_buffers_active then
497
+ -- negative id means it's a custom id,
498
+ -- it needs to be translated first
499
+ process(-server_id :: any)
500
+ else
501
+ client.command_buffers_active = true
502
+ process(-server_id :: any)
503
+ client.command_buffers_active = false
504
+ end
505
+ else
506
+ local new_entity = get_or_create_entity(client, server_id :: number)
507
+ process(new_entity)
508
+ end
317
509
  end
318
510
  end
319
511
 
320
- local function process_entity_relations(client: Client, entity: jecs.Entity, c: Cursor, variants: { any }?)
512
+ function client_replicator.process_entity_relations(
513
+ client: Client,
514
+ entity: jecs.Entity,
515
+ c: Cursor,
516
+ custom_ids: CustomIdMap
517
+ )
321
518
  local components = client.components
322
- local relation = read_component_id(client, c)
519
+ local relation = client_replicator.read_component_id(client, c)
323
520
  local total_targets = cursor.read_vlq(c)
324
521
  local pair_hooks = client.network_hooks.changed[PAIR(components.pair, relation)]
325
522
 
326
523
  for _ = 1, total_targets do
327
- local target = process_entity_id(client, c, variants)
328
- network_entity_add(client, entity, PAIR(relation, target), pair_hooks)
524
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
525
+ client_replicator.entity_add_pair(client, entity, relation, target, pair_hooks)
526
+ end)
329
527
  end
330
528
  end
331
529
 
332
- local function process_entity_relation_values(client: Client, entity: jecs.Entity, c: Cursor, variants: { any }?)
333
- local relation = read_component_id(client, c)
530
+ function client_replicator.process_entity_relation_values(
531
+ client: Client,
532
+ entity: jecs.Entity,
533
+ c: Cursor,
534
+ custom_ids: CustomIdMap,
535
+ variants: { any }?
536
+ )
537
+ local relation = client_replicator.read_component_id(client, c)
334
538
  local total_targets = cursor.read_vlq(c)
335
539
  local components = client.components
336
540
  local pair_hooks = client.network_hooks.changed[PAIR(components.pair, relation)]
337
541
 
338
542
  for _ = 1, total_targets do
339
- local value = read_component_value(client, relation, c, variants)
340
- local target = process_entity_id(client, c, variants)
341
- network_entity_set(client, entity, PAIR(relation, target), value, pair_hooks)
543
+ local value = client_replicator.read_component_value(client, relation, c, variants)
544
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
545
+ client_replicator.entity_set_pair(client, entity, relation, target, value, pair_hooks)
546
+ end)
342
547
  end
343
548
  end
344
549
 
345
- local function process_entity(client: Client, c: Cursor, variants: { any }?)
346
- local entity = process_entity_id(client, c, variants)
347
- local changed_hooks = client.network_hooks.changed
348
- local components = client.components
550
+ function client_replicator.process_entity(client: Client, c: Cursor, custom_ids: CustomIdMap, variants: { any }?)
551
+ client_replicator.process_entity_id(client, c, custom_ids, function(entity)
552
+ local changed_hooks = client.network_hooks.changed
553
+ local components = client.components
349
554
 
350
- local total_tags = cursor.read_vlq(c)
351
- for _ = 1, total_tags do
352
- local tag = read_component_id(client, c)
353
- local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
354
- network_entity_add(client, entity, tag, tag_hooks)
355
- end
555
+ local total_tags = cursor.read_vlq(c)
556
+ for _ = 1, total_tags do
557
+ local tag = client_replicator.read_component_id(client, c)
558
+ local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
559
+ client_replicator.entity_add(client, entity, tag, tag_hooks)
560
+ end
356
561
 
357
- local total_components = cursor.read_vlq(c)
358
- for _ = 1, total_components do
359
- local component, value = read_component(client, c, variants)
360
- local component_hooks = changed_hooks[PAIR(components.reliable, component)]
361
- network_entity_set(client, entity, component, value, component_hooks)
362
- end
562
+ local total_components = cursor.read_vlq(c)
563
+ for _ = 1, total_components do
564
+ local component, value = client_replicator.read_component(client, c, variants)
565
+ local component_hooks = changed_hooks[PAIR(components.reliable, component)]
566
+ client_replicator.entity_set(client, entity, component, value, component_hooks)
567
+ end
363
568
 
364
- local total_pairs = cursor.read_vlq(c)
365
- for _ = 1, total_pairs do
366
- process_entity_relations(client, entity, c, variants)
367
- end
569
+ local total_pairs = cursor.read_vlq(c)
570
+ for _ = 1, total_pairs do
571
+ client_replicator.process_entity_relations(client, entity, c, custom_ids)
572
+ end
368
573
 
369
- local total_pairs_values = cursor.read_vlq(c)
370
- for _ = 1, total_pairs_values do
371
- process_entity_relation_values(client, entity, c, variants)
372
- end
574
+ local total_pairs_values = cursor.read_vlq(c)
575
+ for _ = 1, total_pairs_values do
576
+ client_replicator.process_entity_relation_values(client, entity, c, custom_ids, variants)
577
+ end
578
+ end)
373
579
  end
374
580
 
375
- local function finish_replication(client: Client)
581
+ function client_replicator.finish_replication(client: Client)
376
582
  client.is_replicating = false
377
583
  for _, callback in client.after_replication_callbacks do
378
584
  callback()
@@ -380,10 +586,10 @@ local function finish_replication(client: Client)
380
586
  table.clear(client.after_replication_callbacks)
381
587
  end
382
588
 
383
- local function resolve_dirty(client: Client)
384
- if client.is_dirty then
385
- client.shared = utils.create_shared_lookup(client.world, client.components)
386
- client.is_dirty = false :: true -- wtf
589
+ function client_replicator.resolve_dirty(client: Client)
590
+ if client.is_shared_dirty then
591
+ client.shared = utils.resolved_shared(client.world, client.components, client.registered_custom_ids)
592
+ client.is_shared_dirty = false :: true -- wtf
387
593
  end
388
594
  end
389
595
 
@@ -411,10 +617,287 @@ local function read_vlq_bitmask(c: Cursor, mask: number, bit: number): number
411
617
  end
412
618
  end
413
619
 
414
- local function apply_updates(client: Client, buf: buffer, all_variants: { { any } }?)
620
+ function client_replicator.apply_command_buffer(
621
+ client: Client,
622
+ entity: Entity,
623
+ command_buffer: CommandBuffer,
624
+ request: (e: Entity, ...any) -> Entity?
625
+ )
626
+ local components = client.components
627
+
628
+ for tag in command_buffer.tags do
629
+ local tag_hooks = client.network_hooks.changed[PAIR(components.reliable, tag)]
630
+ client_replicator.entity_add(client, entity, tag, tag_hooks)
631
+ end
632
+ for component, value in command_buffer.components do
633
+ local component_hooks = client.network_hooks.changed[PAIR(components.reliable, component)]
634
+ client_replicator.entity_set(client, entity, component, value, component_hooks)
635
+ end
636
+ for component, value in command_buffer.unreliable_components do
637
+ local component_hooks = client.network_hooks.changed[PAIR(components.unreliable, component)]
638
+ client_replicator.entity_set(client, entity, component, value, component_hooks)
639
+ end
640
+ for component in command_buffer.remove do
641
+ local component_hooks = client.network_hooks.removed[PAIR(components.reliable, component)]
642
+ client_replicator.entity_remove(client, entity, component, component_hooks)
643
+ end
644
+ for relation, targets in command_buffer.pairs do
645
+ local pair_hooks = client.network_hooks.changed[PAIR(components.pair, relation)]
646
+ for target in targets do
647
+ local client_target = request(target)
648
+ if client_target then
649
+ client_replicator.entity_add_pair(client, entity, relation, client_target, pair_hooks)
650
+ end
651
+ end
652
+ end
653
+ for relation, targets in command_buffer.pairs_values do
654
+ local pair_hooks = client.network_hooks.changed[PAIR(components.pair, relation)]
655
+ for target, value in targets do
656
+ local client_target = request(target)
657
+ if client_target then
658
+ client_replicator.entity_set_pair(client, entity, relation, client_target, value.value, pair_hooks)
659
+ end
660
+ end
661
+ end
662
+ for relation, targets in command_buffer.pairs_remove do
663
+ local pair_hooks = client.network_hooks.removed[PAIR(components.pair, relation)]
664
+ for target in targets do
665
+ local client_target = request(target)
666
+ if client_target then
667
+ client_replicator.entity_remove_pair(client, entity, relation, client_target, pair_hooks)
668
+ end
669
+ end
670
+ end
671
+ for relation in command_buffer.pairs_clear do
672
+ local pair_hooks = client.network_hooks.removed[PAIR(components.pair, relation)]
673
+ client_replicator.entity_remove_relations(client, entity, relation, pair_hooks)
674
+ end
675
+ end
676
+
677
+ function client_replicator.find_component_in_buffer(command_buffer: CommandBuffer, component: Entity): { value: any }?
678
+ return command_buffer.components[component] or command_buffer.unreliable_components[component]
679
+ end
680
+
681
+ function client_replicator.find_has_in_buffer(command_buffer: CommandBuffer, tag: Entity): boolean
682
+ return (command_buffer.tags[tag] ~= nil)
683
+ or (command_buffer.components[tag] ~= nil)
684
+ or (command_buffer.unreliable_components[tag] ~= nil)
685
+ end
686
+ function client_replicator.find_pair_in_buffer(
687
+ command_buffer: CommandBuffer,
688
+ relation: Entity,
689
+ target: Entity,
690
+ find: (unprocessed_entity: Entity) -> (Entity?, Entity)
691
+ ): (boolean, any)
692
+ local value_relations = command_buffer.pairs_values[relation]
693
+
694
+ if value_relations then
695
+ for unprocessed_target, value in value_relations do
696
+ local found = find(unprocessed_target)
697
+ if found == target then
698
+ return true, value.value
699
+ end
700
+ end
701
+ end
702
+
703
+ local relations = command_buffer.pairs[relation]
704
+ if relations then
705
+ for unprocessed_target in relations do
706
+ local found = find(unprocessed_target)
707
+ if found == target then
708
+ return true, nil
709
+ end
710
+ end
711
+ end
712
+ return false, nil
713
+ end
714
+
715
+ function client_replicator.find_target_in_buffer(
716
+ command_buffer: CommandBuffer,
717
+ relation: Entity,
718
+ index: number?
719
+ ): Entity?
720
+ local i = 0
721
+
722
+ local pairs = command_buffer.pairs[relation]
723
+ if pairs then
724
+ for target in pairs do
725
+ if i == (index or 0) then
726
+ return target
727
+ end
728
+ i += 1
729
+ end
730
+ end
731
+ local pairs_values = command_buffer.pairs_values[relation]
732
+ if pairs_values then
733
+ for target in pairs_values do
734
+ if i == (index or 0) then
735
+ return target
736
+ end
737
+ i += 1
738
+ end
739
+ end
740
+ return nil
741
+ end
742
+
743
+ function client_replicator.finish_custom_ids(
744
+ client: Client,
745
+ custom_ids: CustomIdMap,
746
+ command_buffers: { [Entity]: CommandBuffer }
747
+ )
748
+ local funcs = {} -- this is so process can use create_context even if its defined after
749
+ local processed: { [Entity]: boolean } = {}
750
+
751
+ local function find(unprocessed_entity: Entity): (Entity?, Entity)
752
+ -- negative entities means they are still server ids that haven't been processed yet
753
+ if (unprocessed_entity :: any) > 0 then
754
+ return unprocessed_entity, unprocessed_entity
755
+ end
756
+ local server_entity = -(unprocessed_entity :: any)
757
+ local client_entity = client.server_ids[server_entity]
758
+ if client_entity then
759
+ return client_entity, server_entity
760
+ end
761
+ return nil, server_entity
762
+ end
763
+
764
+ local function process(unprocessed_entity: Entity, custom_handler: (CustomId | Entity)?): Entity?
765
+ local client_entity, server_entity = find(unprocessed_entity)
766
+ if client_entity then
767
+ return client_entity
768
+ end
769
+
770
+ if processed[server_entity] then
771
+ return nil
772
+ end
773
+
774
+ local new_entity: Entity? = nil
775
+ local command_buffer = command_buffers[unprocessed_entity :: any]
776
+ custom_handler = custom_handler or custom_ids[server_entity]
777
+
778
+ if type(custom_handler) == "number" then
779
+ local component: Entity = custom_handler :: any
780
+
781
+ local handler = client.world:get(component, client.components.custom_handler)
782
+ if not handler then
783
+ return utils.logerror(
784
+ `received a custom id for a non custom component, consider adding custom_handler to component: {utils.logcomponent(
785
+ client.world,
786
+ component
787
+ )}`
788
+ )
789
+ end
790
+ if command_buffer then
791
+ local value = client_replicator.find_component_in_buffer(command_buffer, component)
792
+ if value then
793
+ new_entity = handler(value.value)
794
+ else
795
+ utils.logwarn(
796
+ `No component found for custom id handler: {utils.logcomponent(client.world, component)}`
797
+ )
798
+ new_entity = handler(nil)
799
+ end
800
+ else
801
+ utils.logwarn(`No component found for custom id handler: {utils.logcomponent(client.world, component)}`)
802
+ new_entity = handler(nil)
803
+ end
804
+ else
805
+ local handle_callback = (custom_handler :: CustomId).handle_callback
806
+ if not handle_callback then
807
+ return utils.logerror(
808
+ "no handler callback was set for custom id:",
809
+ (custom_handler :: CustomId).identifier
810
+ )
811
+ end
812
+ new_entity = handle_callback(funcs.create_context(unprocessed_entity))
813
+ end
814
+
815
+ if new_entity then
816
+ client.server_ids[server_entity :: any] = new_entity
817
+ if command_buffer then
818
+ client_replicator.apply_command_buffer(client, new_entity, command_buffer, process)
819
+ command_buffers[unprocessed_entity] = nil
820
+ end
821
+ end
822
+
823
+ processed[server_entity] = true
824
+ return new_entity
825
+ end
826
+
827
+ local function create_context(unprocessed_entity: Entity)
828
+ local command_buffer = command_buffers[unprocessed_entity]
829
+
830
+ local function component(component: Entity): any
831
+ if not command_buffer then
832
+ return nil
833
+ end
834
+ local val = client_replicator.find_component_in_buffer(command_buffer, component)
835
+ return val and val.value
836
+ end
837
+ local function target(relation: Entity, index: number?): Entity
838
+ if not command_buffer then
839
+ return nil :: any
840
+ end
841
+ local server_target = client_replicator.find_target_in_buffer(command_buffer, relation, index)
842
+ if server_target then
843
+ return process(server_target :: any) :: Entity
844
+ end
845
+ return nil :: any
846
+ end
847
+ local function pair_value(relation: Entity, target: Entity): any
848
+ if not command_buffer then
849
+ return nil :: any
850
+ end
851
+ local _, value = client_replicator.find_pair_in_buffer(command_buffer, relation, target, find)
852
+ return value
853
+ end
854
+ local function has_pair(relation: Entity, target: Entity): boolean
855
+ if not command_buffer then
856
+ return nil :: any
857
+ end
858
+ local has = client_replicator.find_pair_in_buffer(command_buffer, relation, target, find)
859
+ return has
860
+ end
861
+ local function has(tag: Entity): boolean
862
+ if not command_buffer then
863
+ return nil :: any
864
+ end
865
+ return client_replicator.find_has_in_buffer(command_buffer, tag)
866
+ end
867
+ local function entity(server_entity: number): Entity
868
+ return process(-server_entity :: any) :: Entity
869
+ end
870
+
871
+ local context: customid.HandleContext = {
872
+ entity_id = math.abs(unprocessed_entity :: any),
873
+ has = has,
874
+ component = component,
875
+ target = target,
876
+ pair_value = pair_value,
877
+ has_pair = has_pair,
878
+ entity = entity,
879
+ }
880
+
881
+ return context
882
+ end
883
+ funcs.create_context = create_context
884
+
885
+ for entity, custom_handler in custom_ids do
886
+ process(-(entity :: any), custom_handler)
887
+ end
888
+
889
+ for entity, command_buffer in command_buffers do
890
+ if (entity :: any) < 0 then
891
+ continue
892
+ end
893
+ client_replicator.apply_command_buffer(client, entity, command_buffer, process)
894
+ end
895
+ end
896
+
897
+ function client_replicator.apply_updates(client: Client, buf: buffer, all_variants: { { any } }?)
415
898
  local c = cursor.from(buf)
416
899
  check_packet_type(c, "reliable")
417
- resolve_dirty(client)
900
+ client_replicator.resolve_dirty(client)
418
901
  client.is_replicating = true
419
902
 
420
903
  local components = client.components
@@ -424,143 +907,156 @@ local function apply_updates(client: Client, buf: buffer, all_variants: { { any
424
907
  local removed_hooks = client.network_hooks.removed
425
908
  local deleted_hooks = client.network_hooks.deleted
426
909
 
910
+ local command_buffers: { [Entity]: CommandBuffer } = {}
911
+ local custom_ids: CustomIdMap = {}
912
+
913
+ client.command_buffers = command_buffers
914
+
427
915
  for a = 1, total_packets do
428
916
  local variants = all_variants and all_variants[variant_start - a]
429
917
  local storage_mask = cursor.readu8(c)
430
918
 
431
919
  local total_added = read_vlq_bitmask(c, storage_mask, 0)
432
920
  for _ = 1, total_added do
433
- process_entity(client, c, variants)
921
+ client_replicator.process_entity(client, c, custom_ids, variants)
434
922
  end
435
923
 
436
924
  local total_components_added = read_vlq_bitmask(c, storage_mask, 1)
437
925
  for _ = 1, total_components_added do
438
- local entity = process_entity_id(client, c, variants)
439
- local added_mask = cursor.readu8(c)
440
-
441
- local total_tags = read_vlq_bitmask(c, added_mask, 0)
442
- for _ = 1, total_tags do
443
- local tag = read_component_id(client, c)
444
- local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
445
- network_entity_add(client, entity, tag, tag_hooks)
446
- end
926
+ client_replicator.process_entity_id(client, c, custom_ids, function(entity)
927
+ local added_mask = cursor.readu8(c)
928
+
929
+ local total_tags = read_vlq_bitmask(c, added_mask, 0)
930
+ for _ = 1, total_tags do
931
+ local tag = client_replicator.read_component_id(client, c)
932
+ local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
933
+ client_replicator.entity_add(client, entity, tag, tag_hooks)
934
+ end
447
935
 
448
- local total_components = read_vlq_bitmask(c, added_mask, 1)
449
- for _ = 1, total_components do
450
- local component, value = read_component(client, c, variants)
451
- local component_hooks = changed_hooks[PAIR(components.reliable, component)]
452
- network_entity_set(client, entity, component, value, component_hooks)
453
- end
936
+ local total_components = read_vlq_bitmask(c, added_mask, 1)
937
+ for _ = 1, total_components do
938
+ local component, value = client_replicator.read_component(client, c, variants)
939
+ local component_hooks = changed_hooks[PAIR(components.reliable, component)]
940
+ client_replicator.entity_set(client, entity, component, value, component_hooks)
941
+ end
454
942
 
455
- local total_pairs = read_vlq_bitmask(c, added_mask, 2)
456
- for _ = 1, total_pairs do
457
- process_entity_relations(client, entity, c, variants)
458
- end
943
+ local total_pairs = read_vlq_bitmask(c, added_mask, 2)
944
+ for _ = 1, total_pairs do
945
+ client_replicator.process_entity_relations(client, entity, c, custom_ids)
946
+ end
459
947
 
460
- local total_pairs_values = read_vlq_bitmask(c, added_mask, 3)
461
- for _ = 1, total_pairs_values do
462
- process_entity_relation_values(client, entity, c, variants)
463
- end
948
+ local total_pairs_values = read_vlq_bitmask(c, added_mask, 3)
949
+ for _ = 1, total_pairs_values do
950
+ client_replicator.process_entity_relation_values(client, entity, c, custom_ids, variants)
951
+ end
952
+ end)
464
953
  end
465
954
 
466
955
  local total_changed = read_vlq_bitmask(c, storage_mask, 2)
467
956
  for _ = 1, total_changed do
468
- local entity = process_entity_id(client, c, variants)
469
- local changed_mask = cursor.readu8(c)
470
-
471
- local total_tagged = read_vlq_bitmask(c, changed_mask, 0)
472
- for _ = 1, total_tagged do
473
- local tag = read_component_id(client, c)
474
- local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
475
- network_entity_add(client, entity, tag, tag_hooks)
476
- end
477
-
478
- local total_components = read_vlq_bitmask(c, changed_mask, 1)
479
- for _ = 1, total_components do
480
- local component, value = read_component(client, c, variants)
481
- local component_hooks = changed_hooks[PAIR(components.reliable, component)]
482
- network_entity_set(client, entity, component, value, component_hooks)
483
- end
484
-
485
- local total_removed = read_vlq_bitmask(c, changed_mask, 2)
486
- for _ = 1, total_removed do
487
- local component = read_component_id(client, c)
488
- local component_hooks = removed_hooks[PAIR(components.reliable, component)]
489
- network_entity_remove(client, entity, component, component_hooks)
490
- end
491
-
492
- local total_pairs = read_vlq_bitmask(c, changed_mask, 3)
493
- for _ = 1, total_pairs do
494
- local relation = read_component_id(client, c)
495
- local pair_changed_hooks = changed_hooks[PAIR(components.pair, relation)]
496
- local pair_removed_hooks = removed_hooks[PAIR(components.pair, relation)]
497
- local total_targets = cursor.read_vlq(c)
498
-
499
- for _ = 1, total_targets do
500
- local action_type = cursor.readi8(c)
501
-
502
- if action_type == 1 then -- add pair
503
- local target = process_entity_id(client, c, variants)
504
- network_entity_add(client, entity, PAIR(relation, target), pair_changed_hooks)
505
- elseif action_type == -1 then -- remove pair
506
- local target = read_entity_id(client, c, variants)
507
- if target then
508
- network_entity_remove(client, entity, PAIR(relation, target), pair_removed_hooks)
957
+ client_replicator.process_entity_id(client, c, custom_ids, function(entity)
958
+ local changed_mask = cursor.readu8(c)
959
+
960
+ local total_tagged = read_vlq_bitmask(c, changed_mask, 0)
961
+ for _ = 1, total_tagged do
962
+ local tag = client_replicator.read_component_id(client, c)
963
+ local tag_hooks = changed_hooks[PAIR(components.reliable, tag)]
964
+ client_replicator.entity_add(client, entity, tag, tag_hooks)
965
+ end
966
+
967
+ local total_components = read_vlq_bitmask(c, changed_mask, 1)
968
+ for _ = 1, total_components do
969
+ local component, value = client_replicator.read_component(client, c, variants)
970
+ local component_hooks = changed_hooks[PAIR(components.reliable, component)]
971
+ client_replicator.entity_set(client, entity, component, value, component_hooks)
972
+ end
973
+
974
+ local total_removed = read_vlq_bitmask(c, changed_mask, 2)
975
+ for _ = 1, total_removed do
976
+ local component = client_replicator.read_component_id(client, c)
977
+ local component_hooks = removed_hooks[PAIR(components.reliable, component)]
978
+ client_replicator.entity_remove(client, entity, component, component_hooks)
979
+ end
980
+
981
+ local total_pairs = read_vlq_bitmask(c, changed_mask, 3)
982
+ for _ = 1, total_pairs do
983
+ local relation = client_replicator.read_component_id(client, c)
984
+ local pair_changed_hooks = changed_hooks[PAIR(components.pair, relation)]
985
+ local pair_removed_hooks = removed_hooks[PAIR(components.pair, relation)]
986
+ local total_targets = cursor.read_vlq(c)
987
+
988
+ for _ = 1, total_targets do
989
+ local action_type = cursor.readi8(c)
990
+
991
+ if action_type == 1 then -- add pair
992
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
993
+ client_replicator.entity_add_pair(client, entity, relation, target, pair_changed_hooks)
994
+ end)
995
+ elseif action_type == -1 then -- remove pair
996
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
997
+ client_replicator.entity_remove_pair(
998
+ client,
999
+ entity,
1000
+ relation,
1001
+ target,
1002
+ pair_removed_hooks
1003
+ )
1004
+ end)
509
1005
  end
510
1006
  end
511
1007
  end
512
- end
513
1008
 
514
- local total_pairs_values_removed = read_vlq_bitmask(c, changed_mask, 4)
515
- for _ = 1, total_pairs_values_removed do
516
- local relation = read_component_id(client, c)
517
- local pair_hooks = removed_hooks[PAIR(components.pair, relation)]
518
- local total_targets = cursor.read_vlq(c)
1009
+ local total_pairs_values_removed = read_vlq_bitmask(c, changed_mask, 4)
1010
+ for _ = 1, total_pairs_values_removed do
1011
+ local relation = client_replicator.read_component_id(client, c)
1012
+ local pair_hooks = removed_hooks[PAIR(components.pair, relation)]
1013
+ local total_targets = cursor.read_vlq(c)
519
1014
 
520
- for _ = 1, total_targets do
521
- local target = read_entity_id(client, c, variants)
522
- if target then
523
- network_entity_remove(client, entity, PAIR(relation, target), pair_hooks)
1015
+ for _ = 1, total_targets do
1016
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
1017
+ client_replicator.entity_remove_pair(client, entity, relation, target, pair_hooks)
1018
+ end)
524
1019
  end
525
1020
  end
526
- end
527
1021
 
528
- local total_pairs_values = read_vlq_bitmask(c, changed_mask, 5)
529
- for _ = 1, total_pairs_values do
530
- local relation = read_component_id(client, c)
531
- local pair_hooks = changed_hooks[PAIR(components.pair, relation)]
532
- local total_targets = cursor.read_vlq(c)
533
-
534
- for _ = 1, total_targets do
535
- local value = read_component_value(client, relation, c, variants)
536
- local target = process_entity_id(client, c, variants)
537
- network_entity_set(client, entity, PAIR(relation, target), value, pair_hooks)
1022
+ local total_pairs_values = read_vlq_bitmask(c, changed_mask, 5)
1023
+ for _ = 1, total_pairs_values do
1024
+ local relation = client_replicator.read_component_id(client, c)
1025
+ local pair_hooks = changed_hooks[PAIR(components.pair, relation)]
1026
+ local total_targets = cursor.read_vlq(c)
1027
+
1028
+ for _ = 1, total_targets do
1029
+ local value = client_replicator.read_component_value(client, relation, c, variants)
1030
+ client_replicator.process_entity_id(client, c, custom_ids, function(target)
1031
+ client_replicator.entity_set_pair(client, entity, relation, target, value, pair_hooks)
1032
+ end)
1033
+ end
538
1034
  end
539
- end
1035
+ end)
540
1036
  end
541
1037
 
542
1038
  local total_component_deletions = read_vlq_bitmask(c, storage_mask, 3)
543
1039
  for _ = 1, total_component_deletions do
544
- local entity = process_entity_id(client, c, variants)
545
-
546
- local total_removed = cursor.read_vlq(c)
547
- for _ = 1, total_removed do
548
- local component = read_component_id(client, c)
549
- local component_hooks = removed_hooks[PAIR(components.reliable, component)]
550
- network_entity_remove(client, entity, component, component_hooks)
551
- end
1040
+ client_replicator.process_entity_id(client, c, custom_ids, function(entity)
1041
+ local total_removed = cursor.read_vlq(c)
1042
+ for _ = 1, total_removed do
1043
+ local component = client_replicator.read_component_id(client, c)
1044
+ local component_hooks = removed_hooks[PAIR(components.reliable, component)]
1045
+ client_replicator.entity_remove(client, entity, component, component_hooks)
1046
+ end
552
1047
 
553
- local total_pairs = cursor.read_vlq(c)
554
- for _ = 1, total_pairs do
555
- local relation = read_component_id(client, c)
556
- local pair_hooks = removed_hooks[PAIR(components.pair, relation)]
557
- network_entity_remove_relations(client, entity, relation, pair_hooks)
558
- end
1048
+ local total_pairs = cursor.read_vlq(c)
1049
+ for _ = 1, total_pairs do
1050
+ local relation = client_replicator.read_component_id(client, c)
1051
+ local pair_hooks = removed_hooks[PAIR(components.pair, relation)]
1052
+ client_replicator.entity_remove_relations(client, entity, relation, pair_hooks)
1053
+ end
1054
+ end)
559
1055
  end
560
1056
 
561
1057
  local total_deleted = read_vlq_bitmask(c, storage_mask, 4)
562
1058
  for _ = 1, total_deleted do
563
- local entity, server_id = read_entity_id(client, c, variants)
1059
+ local entity, server_id = client_replicator.read_entity_id(client, c)
564
1060
  if entity then
565
1061
  local deleted_hook = deleted_hooks[entity]
566
1062
  if deleted_hook then
@@ -580,13 +1076,14 @@ local function apply_updates(client: Client, buf: buffer, all_variants: { { any
580
1076
  end
581
1077
  end
582
1078
 
583
- finish_replication(client)
1079
+ client_replicator.finish_custom_ids(client, custom_ids, command_buffers)
1080
+ client_replicator.finish_replication(client)
584
1081
  end
585
1082
 
586
- local function apply_unreliable(client: Client, buf: buffer, all_variants: { { any } }?)
1083
+ function client_replicator.apply_unreliable(client: Client, buf: buffer, all_variants: { { any } }?)
587
1084
  local c = cursor.from(buf)
588
1085
  check_packet_type(c, "unreliable")
589
- resolve_dirty(client)
1086
+ client_replicator.resolve_dirty(client)
590
1087
  client.is_replicating = true
591
1088
 
592
1089
  local components = client.components
@@ -599,51 +1096,56 @@ local function apply_unreliable(client: Client, buf: buffer, all_variants: { { a
599
1096
  local variants = all_variants and all_variants[variant_start - a]
600
1097
 
601
1098
  for _ = 1, total_entities do
602
- local entity = read_entity_id(client, c, variants)
603
-
1099
+ local entity = client_replicator.read_entity_id(client, c)
604
1100
  local total_unreliable = cursor.read_vlq(c)
605
1101
 
606
1102
  if entity then
607
1103
  for _ = 1, total_unreliable do
608
- local component_id, value = read_component(client, c, variants)
1104
+ local component_id, value = client_replicator.read_component(client, c, variants)
609
1105
  local unreliable_hooks = changed_hooks[PAIR(components.unreliable, component_id)]
610
- network_entity_set(client, entity, component_id, value, unreliable_hooks)
1106
+ client_replicator.entity_set_unreliable(client, entity, component_id, value, unreliable_hooks)
611
1107
  end
612
1108
  else
613
1109
  -- dont apply unreliable updates to entities that dont exist in the client yet
614
1110
  -- but we still need to process the values to keep the cursor in the right place
615
1111
  for _ = 1, total_unreliable do
616
- read_component(client, c, variants)
1112
+ client_replicator.read_component(client, c, variants)
617
1113
  end
618
1114
  end
619
1115
  end
620
1116
  end
621
1117
 
622
- finish_replication(client)
1118
+ client_replicator.finish_replication(client)
623
1119
  end
624
1120
 
625
- local function apply_full(client: Client, buf: buffer, all_variants: { { any } }?)
1121
+ function client_replicator.apply_full(client: Client, buf: buffer, all_variants: { { any } }?)
626
1122
  local c = cursor.from(buf)
627
1123
  check_packet_type(c, "full")
628
- resolve_dirty(client)
1124
+ client_replicator.resolve_dirty(client)
629
1125
  client.is_replicating = true
630
1126
 
631
1127
  local total_packets = cursor.read_vlq(c)
632
1128
  local variant_start = (all_variants and #all_variants + 1) :: number
633
1129
 
1130
+ local command_buffers: { [Entity]: CommandBuffer } = {}
1131
+ local custom_ids: CustomIdMap = {}
1132
+
1133
+ client.command_buffers = command_buffers
1134
+
634
1135
  for a = 1, total_packets do
635
1136
  local total_entities = cursor.read_vlq(c)
636
1137
  local variants = all_variants and all_variants[variant_start - a]
637
1138
 
638
1139
  for _ = 1, total_entities do
639
- process_entity(client, c, variants)
1140
+ client_replicator.process_entity(client, c, custom_ids, variants)
640
1141
  end
641
1142
  end
642
1143
 
643
- finish_replication(client)
1144
+ client_replicator.finish_custom_ids(client, custom_ids, command_buffers)
1145
+ client_replicator.finish_replication(client)
644
1146
  end
645
1147
 
646
- local function init(client: Client, _world: World?)
1148
+ function client_replicator.init(client: Client, _world: World?)
647
1149
  if client.inited == true then
648
1150
  return warn "attempted to init a client twice"
649
1151
  end
@@ -668,11 +1170,7 @@ local function init(client: Client, _world: World?)
668
1170
 
669
1171
  local components = client.components
670
1172
 
671
- client.shared = utils.create_shared_lookup(world, components)
672
-
673
- for component, custom_get in world:query(components.custom_handler):with(components.shared, ECS_NAME) do
674
- client.custom_ids[component] = custom_get
675
- end
1173
+ client.shared = utils.resolved_shared(world, components, client.registered_custom_ids)
676
1174
 
677
1175
  local alive_unhook = hooks.removed(world, components.__alive_tracking__, function(entity)
678
1176
  local server_id = client.client_ids[entity]
@@ -686,17 +1184,17 @@ local function init(client: Client, _world: World?)
686
1184
 
687
1185
  for _, component in client.requires_shared_lookup do
688
1186
  local added_hook = hooks.added(world, component, function()
689
- client.is_dirty = true
1187
+ client.is_shared_dirty = true
690
1188
  end)
691
1189
  local removed_hook = hooks.removed(world, component, function()
692
- client.is_dirty = true
1190
+ client.is_shared_dirty = true
693
1191
  end)
694
1192
  table.insert(client.hooked, added_hook)
695
1193
  table.insert(client.hooked, removed_hook)
696
1194
  end
697
1195
  end
698
1196
 
699
- local function after_replication(client: Client, callback: () -> ())
1197
+ function client_replicator.after_replication(client: Client, callback: () -> ())
700
1198
  if client.is_replicating then
701
1199
  table.insert(client.after_replication_callbacks, callback)
702
1200
  else
@@ -704,7 +1202,7 @@ local function after_replication(client: Client, callback: () -> ())
704
1202
  end
705
1203
  end
706
1204
 
707
- local function added(client: Client, callback: (entity: Entity) -> ())
1205
+ function client_replicator.added(client: Client, callback: (entity: Entity) -> ())
708
1206
  table.insert(client.addition_hooks, callback)
709
1207
  return function()
710
1208
  local index = table.find(client.addition_hooks, callback)
@@ -772,26 +1270,55 @@ local function add_hook_entry(client: Client, action: string, ...): ({ overrides
772
1270
  end
773
1271
  end
774
1272
 
775
- local function hook(client: Client, action: string, ...)
1273
+ function client_replicator.hook(client: Client, action: string, ...)
776
1274
  local entry, disconnect = add_hook_entry(client, action, ...)
777
1275
  entry.overrides = false
778
1276
  return disconnect
779
1277
  end
780
- local function override(client: Client, action: string, ...)
1278
+ function client_replicator.override(client: Client, action: string, ...)
781
1279
  local entry, disconnect = add_hook_entry(client, action, ...)
782
1280
  entry.overrides = true
783
1281
  return disconnect
784
1282
  end
785
1283
 
786
- local function get_server_entity(client: Client, client_entity: Entity): number?
1284
+ function client_replicator.get_server_entity(client: Client, client_entity: Entity): number?
787
1285
  return client.client_ids[client_entity]
788
1286
  end
789
1287
 
790
- local function get_client_entity(client: Client, client_entity: number): Entity?
1288
+ function client_replicator.get_client_entity(client: Client, client_entity: number): Entity?
791
1289
  return client.server_ids[client_entity]
792
1290
  end
793
1291
 
794
- local function destroy(client: Client)
1292
+ function client_replicator.register_entity(client: Client, entity: Entity, server_entity: number)
1293
+ client.server_ids[server_entity] = entity
1294
+ client.client_ids[entity] = server_entity
1295
+ client.world:add(entity, client.components.__alive_tracking__)
1296
+ end
1297
+
1298
+ function client_replicator.unregister_entity(client: Client, entity: Entity)
1299
+ local server_id = client.client_ids[entity]
1300
+ if server_id then
1301
+ client.server_ids[server_id] = nil
1302
+ client.client_ids[entity] = nil
1303
+ end
1304
+ end
1305
+
1306
+ function client_replicator.register_custom_id(client: Client, custom_id: CustomId)
1307
+ client.registered_custom_ids[custom_id] = true
1308
+ client.is_shared_dirty = true
1309
+ end
1310
+
1311
+ function client_replicator.generate_handshake(client: Client): common.HandshakeInfo
1312
+ client_replicator.resolve_dirty(client)
1313
+ return utils.generate_handshake(client.world, client.shared)
1314
+ end
1315
+
1316
+ function client_replicator.verify_handshake(client: Client, handshake: common.HandshakeInfo): (boolean, string?)
1317
+ client_replicator.resolve_dirty(client)
1318
+ return utils.verify_handshake(client.world, client.shared, handshake, "server", "client")
1319
+ end
1320
+
1321
+ function client_replicator.destroy(client: Client)
795
1322
  if client.inited == nil then
796
1323
  return warn "attempted to destroy a client twice"
797
1324
  end
@@ -801,40 +1328,21 @@ local function destroy(client: Client)
801
1328
  end
802
1329
  end
803
1330
 
804
- local function handle_global(client: Client, handler: (id: number) -> Entity)
1331
+ function client_replicator.handle_global(client: Client, handler: (id: number) -> Entity)
805
1332
  client.global_handler = handler
806
1333
  end
807
1334
 
808
- local client = {}
809
- client.__index = client
810
- client.init = init
811
- client.destroy = destroy
812
- client.apply_updates = apply_updates
813
- client.apply_unreliable = apply_unreliable
814
- client.apply_full = apply_full
815
- client.encode_component = encode_component
816
- client.decode_component = decode_component
817
- client.get_server_entity = get_server_entity
818
- client.get_client_entity = get_client_entity
819
- client.handle_global = handle_global
820
- client.after_replication = after_replication
821
- client.hook = hook
822
- client.override = override
823
- client.added = added
824
-
825
1335
  local function create(world: World?, components: common.Components): Client
826
1336
  local self = {} :: Client
827
1337
 
828
1338
  self.components = components
829
1339
  self.ordered_creation = true
830
- self.custom_ids = {}
831
1340
  self.server_ids = {}
832
1341
  self.client_ids = {}
833
- self.is_dirty = false
1342
+ self.is_shared_dirty = false
834
1343
  self.requires_shared_lookup = {
835
1344
  self.components.shared,
836
1345
  self.components.serdes,
837
- self.components.bytespan,
838
1346
  jecs.Name,
839
1347
  }
840
1348
  self.global_handler = nil
@@ -842,6 +1350,7 @@ local function create(world: World?, components: common.Components): Client
842
1350
  self.inited = false
843
1351
  self.is_replicating = false
844
1352
  self.hooked = {}
1353
+ self.registered_custom_ids = {}
845
1354
 
846
1355
  self.addition_hooks = {}
847
1356
  self.network_hooks = {
@@ -851,9 +1360,10 @@ local function create(world: World?, components: common.Components): Client
851
1360
  }
852
1361
  self.after_replication_callbacks = {}
853
1362
 
854
- return setmetatable(self, client) :: any
1363
+ return setmetatable(self, client_replicator) :: any
855
1364
  end
856
1365
 
857
- client.create = create
858
-
859
- return client :: { create: typeof(create) }
1366
+ return {
1367
+ create = create,
1368
+ client_replicator = client_replicator,
1369
+ }