@rbxts/replecs 0.1.0-alpha2 → 0.1.0-alpha3

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.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # Replecs ⚡
2
+ Replecs is a fast, fine controlled JECS replication library
3
+
4
+ Replecs is a feature rich, fast, flexible, powerful, and runtime/library agnostic replication library for ECS.
5
+
6
+ - Per-entity replication
7
+ - Heavily optimized granular player filtering for entities and components
8
+ - Entity ID remapping
9
+ - Optional serdes for values
10
+ - Luau runtime agnostic
11
+ - Buffer-based replication
12
+ - Bandwidth efficient
13
+ - Supports tags and relationships
14
+ - Unreliable replication, with automatic 1kb buffer size limit
15
+
16
+ Demo place: https://www.roblox.com/games/132070456195381/
17
+ # Getting Started
18
+ ### Roblox-TS
19
+ Install `@rbxts/jecs-addons` `@rbxts/replecs` `@rbxts/jecs`.
20
+
21
+ **Make sure you install jecs as v0.8.3, not ^0.9.0** as they are not compatible. You can check the versioning by running `npm ls @rbxts/jecs`
22
+
23
+ ## 🔥Setting Up
24
+
25
+ `shared/world.ts`
26
+ ```ts
27
+ import { observer_add } from "@rbxts/jecs-addons"
28
+ import { world } from "@rbxts/jecs"
29
+
30
+ // you need to add the observer addon before passing it to the replicator!
31
+ export const world = observer_add(world())
32
+ ```
33
+
34
+ `server/runtime.server.ts`
35
+ ```ts
36
+ import { world } from "shared/world"
37
+ import { create_server } from "@rbxts/replecs"
38
+
39
+ const replicator = create_server(world)
40
+
41
+ //make sure you call replicator.init before you apply any operations
42
+ replicator.init()
43
+
44
+ //use whatever networking library you want, I am using @rbxts/remo
45
+ InitialReplecsRemotes.requestInitialPackets.connect(player => {
46
+ if (replicator.is_player_ready(player)) return;
47
+ replicator.mark_player_ready(player);
48
+
49
+ const [buf, variants] = replicator.get_full(player);
50
+
51
+ InitialReplecsRemotes.sendInitialPackets(player, buf, variants);
52
+ });
53
+ ```
54
+
55
+ `client/runtime.client.ts`
56
+ ```ts
57
+ import { world } from "shared/world"
58
+ import { create_client } from "@rbxts/replecs"
59
+
60
+ const replicator = create_server(world)
61
+
62
+ //make sure you call replicator.init before you apply any operations
63
+ replicator.init()
64
+
65
+ InitialReplecsRemotes.requestInitialPackets();
66
+
67
+ InitialReplecsRemotes.sendInitialPackets.connect((buf, variants) => {
68
+ const replicator = HANDLE.getClientReplicator();
69
+
70
+ replicator.apply_full(buf, variants);
71
+ });
72
+ ```
73
+
74
+ ## Using in action
75
+ Basic replicated component
76
+ ```ts
77
+ const component1 = world.component<string>()
78
+ //you must add shared or Shared component to components you want to replicate
79
+ world.add(component1, shared)
80
+
81
+
82
+ const replicatedEntity = world.entity()
83
+ world.set(replicatedEntity, component1, "string")
84
+
85
+ //you must add replicatedEntity as networked and add the components you want to replicate
86
+ world.add(replicatedEntity, networked)
87
+
88
+ //this makes components appear in collect_updates
89
+ world.add(replicatedEntity, pair(reliable, component1))
90
+
91
+ //this makes components appear in collect_unreliable
92
+ world.add(replicatedEntity, pair(unreliable, component1))
93
+ ```
94
+
95
+ Replication process
96
+ ```ts
97
+ RunService.Heartbeat.Connect(() => {
98
+ //this collects reliable updates
99
+ for (const [player, buf, variants] of serverReplicator.collect_updates()) {
100
+ //here I am using yetanothernet
101
+ reliableRoute.send(buf, variants).to(player)
102
+ }
103
+
104
+ //this collects unrealible updates
105
+ for (const [player, buf, variants] serverReplicator.collect_updates()) {
106
+ //here I am using yetanothernet
107
+ unreliableRoute.send(buf, variants).to(player)
108
+ }
109
+ })
110
+ ```
111
+
112
+ This is the basics of Replecs. For deeper understanding check out the demo folder files here
113
+ - [player first hydration](https://github.com/PepeElToro41/replecs/blob/main/demo/src/server/init.server.luau#L22)
114
+ - [send replication](https://github.com/PepeElToro41/replecs/blob/main/demo/src/server/systems/replecs_server.luau#L12)
115
+ - [receive replication](https://github.com/PepeElToro41/replecs/blob/main/demo/src/client/systems/replecs_client.luau)
116
+ - [setup an entity to replicate](https://github.com/PepeElToro41/replecs/blob/main/demo/src/server/controllers/spawn_cubes.luau#L10)
117
+ - custom ids: [client](https://github.com/PepeElToro41/replecs/blob/main/demo/src/shared/components/init.luau#L33) - [server](https://github.com/PepeElToro41/replecs/blob/main/demo/src/shared/players.luau#L16)
118
+ - [serdes](https://github.com/PepeElToro41/replecs/blob/main/demo/src/shared/components/init.luau#L37)
119
+ - [how you would add `shared` to all components](https://github.com/PepeElToro41/replecs/blob/main/demo/src/shared/components/init.luau#L52)
120
+ - create the replicators: [client](https://github.com/PepeElToro41/replecs/blob/main/demo/src/client/replicator.luau) - [server](https://github.com/PepeElToro41/replecs/blob/main/demo/src/server/replicator.luau)
121
+ -
122
+ ### Luau
123
+ TODO
@@ -44,6 +44,11 @@ local ENTITY_ID_TYPES = {
44
44
  shared = 3,
45
45
  global = 4,
46
46
  }
47
+ local PACKET_TYPES = {
48
+ full = 1,
49
+ unreliable = 2,
50
+ reliable = 3,
51
+ }
47
52
 
48
53
  local ECS_NAME = jecs.Name
49
54
 
@@ -132,7 +137,7 @@ local function read_entity_id(client: Client, c: Cursor, variants: { any }?): (E
132
137
  end
133
138
  end
134
139
 
135
- local function process_entity_id(client: Client, c: Cursor, variants: { any }?, v: boolean?)
140
+ local function process_entity_id(client: Client, c: Cursor, variants: { any }?)
136
141
  local entity, server_id = read_entity_id(client, c, variants)
137
142
  if entity then
138
143
  return entity
@@ -141,6 +146,16 @@ local function process_entity_id(client: Client, c: Cursor, variants: { any }?,
141
146
  end
142
147
  end
143
148
 
149
+ local function process_entity_relation(client: Client, entity: jecs.Entity, c: Cursor, variants: { any }?)
150
+ local relation = read_component_id(client, c)
151
+ local total_targets = cursor.read_vlq(c)
152
+
153
+ for _ = 1, total_targets do
154
+ local target = process_entity_id(client, c, variants)
155
+ client.world:add(entity, PAIR(relation, target))
156
+ end
157
+ end
158
+
144
159
  local function process_entity(client: Client, c: Cursor, variants: { any }?)
145
160
  local entity = process_entity_id(client, c, variants)
146
161
 
@@ -158,45 +173,91 @@ local function process_entity(client: Client, c: Cursor, variants: { any }?)
158
173
 
159
174
  local total_pairs = cursor.read_vlq(c)
160
175
  for _ = 1, total_pairs do
161
- local relation = read_component_id(client, c)
162
- local total_targets = cursor.read_vlq(c)
176
+ process_entity_relation(client, entity, c, variants)
177
+ end
178
+ end
163
179
 
164
- for _ = 1, total_targets do
165
- local target = process_entity_id(client, c, variants)
166
- client.world:add(entity, PAIR(relation, target))
180
+ local function check_packet_type(c: Cursor, type: string)
181
+ local byte_type = PACKET_TYPES[type]
182
+ local packet_type = cursor.readu8(c)
183
+ if packet_type ~= byte_type then
184
+ local got: string = nil :: any
185
+ for k, v in PACKET_TYPES do
186
+ if v == packet_type then
187
+ got = k
188
+ break
189
+ end
167
190
  end
191
+
192
+ error(`packet type mismatch, expected {type} got {got} instead`)
193
+ end
194
+ end
195
+
196
+ local function read_vlq_bitmask(c: Cursor, mask: number, bit: number): number
197
+ if utils.checkbit(mask, bit) then
198
+ return cursor.read_vlq(c)
199
+ else
200
+ return 0
168
201
  end
169
202
  end
170
203
 
171
204
  local function apply_updates(client: Client, buf: buffer, all_variants: { { any } }?)
172
205
  local c = cursor.from(buf)
206
+ check_packet_type(c, "reliable")
207
+
173
208
  local total_packets = cursor.read_vlq(c)
174
209
  local variant_start = (all_variants and #all_variants + 1) :: number
175
210
 
176
211
  for a = 1, total_packets do
177
212
  local variants = all_variants and all_variants[variant_start - a]
178
- local total_added = cursor.read_vlq(c)
213
+ local storage_mask = cursor.readu8(c)
179
214
 
180
- for i = 1, total_added do
215
+ local total_added = read_vlq_bitmask(c, storage_mask, 0)
216
+ for _ = 1, total_added do
181
217
  process_entity(client, c, variants)
182
218
  end
183
219
 
184
- local total_changed = cursor.read_vlq(c)
220
+ local total_components_added = read_vlq_bitmask(c, storage_mask, 1)
221
+ for _ = 1, total_components_added do
222
+ local entity = process_entity_id(client, c, variants)
223
+ local added_mask = cursor.readu8(c)
224
+
225
+ local total_tags = read_vlq_bitmask(c, added_mask, 0)
226
+ for _ = 1, total_tags do
227
+ local tag = read_component_id(client, c)
228
+ client.world:add(entity, tag)
229
+ end
230
+
231
+ local total_components = read_vlq_bitmask(c, added_mask, 1)
232
+ for _ = 1, total_components do
233
+ local component, value = read_component_value(client, c, variants)
234
+ client.world:set(entity, component, value)
235
+ end
236
+
237
+ local total_pairs = read_vlq_bitmask(c, added_mask, 2)
238
+ for _ = 1, total_pairs do
239
+ process_entity_relation(client, entity, c, variants)
240
+ end
241
+ end
242
+
243
+ local total_changed = read_vlq_bitmask(c, storage_mask, 2)
185
244
  for _ = 1, total_changed do
186
245
  local entity = process_entity_id(client, c, variants)
187
- local total_tagged = cursor.read_vlq(c)
246
+ local changed_mask = cursor.readu8(c)
247
+
248
+ local total_tagged = read_vlq_bitmask(c, changed_mask, 0)
188
249
  for _ = 1, total_tagged do
189
250
  local tag = read_component_id(client, c)
190
251
  client.world:add(entity, tag)
191
252
  end
192
253
 
193
- local total_components = cursor.read_vlq(c)
254
+ local total_components = read_vlq_bitmask(c, changed_mask, 1)
194
255
  for _ = 1, total_components do
195
256
  local component, value = read_component_value(client, c, variants)
196
257
  client.world:set(entity, component, value)
197
258
  end
198
259
 
199
- local total_pairs = cursor.read_vlq(c)
260
+ local total_pairs = read_vlq_bitmask(c, changed_mask, 2)
200
261
  for _ = 1, total_pairs do
201
262
  local relation = read_component_id(client, c)
202
263
  local total_targets = cursor.read_vlq(c)
@@ -218,37 +279,38 @@ local function apply_updates(client: Client, buf: buffer, all_variants: { { any
218
279
  end
219
280
  end
220
281
 
221
- local total_removed = cursor.read_vlq(c)
282
+ local total_removed = read_vlq_bitmask(c, changed_mask, 3)
222
283
  for _ = 1, total_removed do
223
284
  local component = read_component_id(client, c)
224
285
  client.world:remove(entity, component)
225
286
  end
226
287
  end
227
288
 
228
- local total_component_deleted = cursor.read_vlq(c)
289
+ local total_component_deleted = read_vlq_bitmask(c, storage_mask, 3)
229
290
  for _ = 1, total_component_deleted do
230
291
  local entity = process_entity_id(client, c, variants)
292
+ local deleted_mask = cursor.readu8(c)
231
293
 
232
- local total_tags = cursor.read_vlq(c)
294
+ local total_tags = read_vlq_bitmask(c, deleted_mask, 0)
233
295
  for _ = 1, total_tags do
234
296
  local tag = read_component_id(client, c)
235
297
  client.world:remove(entity, tag)
236
298
  end
237
299
 
238
- local total_components = cursor.read_vlq(c)
300
+ local total_components = read_vlq_bitmask(c, deleted_mask, 1)
239
301
  for _ = 1, total_components do
240
302
  local component = read_component_id(client, c)
241
303
  client.world:remove(entity, component)
242
304
  end
243
305
 
244
- local total_pairs = cursor.read_vlq(c)
306
+ local total_pairs = read_vlq_bitmask(c, deleted_mask, 2)
245
307
  for _ = 1, total_pairs do
246
308
  local relation = read_component_id(client, c)
247
309
  utils.remove_all_relations(client.world, entity, relation)
248
310
  end
249
311
  end
250
312
 
251
- local total_deleted = cursor.read_vlq(c)
313
+ local total_deleted = read_vlq_bitmask(c, storage_mask, 4)
252
314
  for _ = 1, total_deleted do
253
315
  local entity, server_id = read_entity_id(client, c, variants)
254
316
  if entity then
@@ -263,6 +325,8 @@ end
263
325
 
264
326
  local function apply_unreliable(client: Client, buf: buffer, all_variants: { { any } }?)
265
327
  local c = cursor.from(buf)
328
+ check_packet_type(c, "unreliable")
329
+
266
330
  local total_packets = cursor.read_vlq(c)
267
331
  local variant_start = (all_variants and #all_variants + 1) :: number
268
332
 
@@ -290,6 +354,8 @@ end
290
354
 
291
355
  local function apply_full(client: Client, buf: buffer, all_variants: { { any } }?)
292
356
  local c = cursor.from(buf)
357
+ check_packet_type(c, "full")
358
+
293
359
  local total_packets = cursor.read_vlq(c)
294
360
  local variant_start = (all_variants and #all_variants + 1) :: number
295
361
 
@@ -91,6 +91,7 @@ type StorageGroup = {
91
91
 
92
92
  changes: {
93
93
  added: Map<Entity, boolean>,
94
+ added_components: EntityComponentIndex<boolean>,
94
95
  changed: Map<Entity, EntityChanges>,
95
96
  },
96
97
  }
@@ -124,6 +125,18 @@ export type MaskingController = {
124
125
  component: Entity,
125
126
  component_type: number
126
127
  ) -> (),
128
+ register_component_addition: (
129
+ self: MaskingController,
130
+ entity: Entity,
131
+ component: Entity,
132
+ component_type: number
133
+ ) -> (),
134
+ unregister_component_addition: (
135
+ self: MaskingController,
136
+ entity: Entity,
137
+ component: Entity,
138
+ component_type: number
139
+ ) -> (),
127
140
  unregister_stop_entity: (self: MaskingController, entity: Entity) -> (),
128
141
  unregister_stop_component: (
129
142
  self: MaskingController,
@@ -245,6 +258,7 @@ function masking_controller.create_new_storage(
245
258
 
246
259
  changes = {
247
260
  added = {},
261
+ added_components = {},
248
262
  changed = {},
249
263
  },
250
264
 
@@ -352,6 +366,19 @@ local function get_or_set_active(storage: StorageGroup, entity: Entity, active:
352
366
  return storage_active
353
367
  end
354
368
 
369
+ local function get_or_set_component_added(
370
+ storage: StorageGroup,
371
+ entity: Entity,
372
+ added: ComponentIndex<boolean>?
373
+ ): ComponentIndex<boolean>
374
+ local storage_added = storage.changes.added_components[entity]
375
+ if storage_added == nil then
376
+ storage_added = added or create_component_indexes() :: ComponentIndex<boolean>
377
+ storage.changes.added_components[entity] = storage_added
378
+ end
379
+ return storage_added
380
+ end
381
+
355
382
  local function remove_active(storage: StorageGroup, entity: Entity): ActiveEntity
356
383
  local active = storage.active[entity]
357
384
  if active then
@@ -430,7 +457,24 @@ local function dissect_component_actives(
430
457
  end
431
458
  return new_active
432
459
  end
433
- function dissect_component_changes(changes: EntityChanges, shared_index: ComponentIndex<boolean>)
460
+
461
+ local function dissect_component_additions(added: ComponentIndex<boolean>, shared_index: ComponentIndex<boolean>)
462
+ local new_added: ComponentIndex<boolean>
463
+
464
+ for component_type, components in added do
465
+ for component in components do
466
+ local is_shared = shared_index[component_type] and shared_index[component_type][component]
467
+ if is_shared then
468
+ new_added = (new_added or create_component_indexes()) :: ComponentIndex<boolean>
469
+ new_added[component_type][component] = true
470
+ added[component_type][component] = nil
471
+ end
472
+ end
473
+ end
474
+ return new_added
475
+ end
476
+
477
+ local function dissect_component_changes(changes: EntityChanges, shared_index: ComponentIndex<boolean>)
434
478
  local new_changes: EntityChanges
435
479
 
436
480
  for component, change in changes.component do
@@ -468,6 +512,14 @@ local function merge_component_actives(current: ActiveEntity, target: ActiveEnti
468
512
  end
469
513
  end
470
514
 
515
+ local function merge_component_additions(current: ComponentIndex<boolean>, target: ComponentIndex<boolean>)
516
+ for component_type, components in current do
517
+ for component in components do
518
+ target[component_type][component] = true
519
+ end
520
+ end
521
+ end
522
+
471
523
  local function merge_component_changes(current: EntityChanges, target: EntityChanges)
472
524
  for component, change in current.component do
473
525
  target.component[component] = change
@@ -538,9 +590,27 @@ function masking_controller.move_entity_storage(
538
590
  merge_component_actives(old_active, new_active)
539
591
  end
540
592
 
593
+ local old_component_added = storage.changes.added_components[entity]
594
+ if old_component_added then
595
+ local new_component_added = get_or_set_component_added(target_storage, entity, old_component_added)
596
+
597
+ if shared_with then
598
+ local dissected_added = dissect_component_additions(old_component_added, shared_with)
599
+ if dissected_added then
600
+ storage.changes.added_components[entity] = dissected_added
601
+ else
602
+ storage.changes.added_components[entity] = nil
603
+ end
604
+ end
605
+
606
+ if old_component_added ~= new_component_added then
607
+ merge_component_additions(old_component_added, new_component_added)
608
+ end
609
+ end
610
+
541
611
  local old_changes = storage.changes.changed[entity]
542
612
  if old_changes then
543
- local new_changes = get_or_set_changed(target_storage, entity)
613
+ local new_changes = get_or_set_changed(target_storage, entity, old_changes)
544
614
 
545
615
  if shared_with then
546
616
  local dissected_changes = dissect_component_changes(old_changes, shared_with)
@@ -555,6 +625,12 @@ function masking_controller.move_entity_storage(
555
625
  merge_component_changes(old_changes, new_changes)
556
626
  end
557
627
  end
628
+
629
+ if storage.changes.added[entity] then
630
+ target_storage.changes.added[entity] = true
631
+ storage.changes.added[entity] = nil
632
+ end
633
+
558
634
  return target_storage
559
635
  end
560
636
 
@@ -579,6 +655,12 @@ function masking_controller.move_component_storage(
579
655
  set_component_index_entry(target_storage.shared_with, entity, component, component_type, true)
580
656
  remove_component_index_entry(storage.shared_with, entity, component, component_type)
581
657
 
658
+ local component_added =
659
+ get_component_index_entry(storage.changes.added_components, entity, component, component_type)
660
+ if component_added then
661
+ set_component_index_entry(target_storage.changes.added_components, entity, component, component_type, true)
662
+ end
663
+
582
664
  local changes = storage.changes.changed[entity]
583
665
  if changes then
584
666
  local target_changes = get_or_set_changed(target_storage, entity)
@@ -706,6 +788,18 @@ function masking_controller.register_stop_component(
706
788
  storage.deletions_count += 1
707
789
  end
708
790
  end
791
+
792
+ function masking_controller.register_component_addition(
793
+ masking: MaskingInternal,
794
+ entity: Entity,
795
+ component: Entity,
796
+ component_type: number
797
+ )
798
+ local storage = masking:get_component_storage_group(entity, component, component_type)
799
+ local additions = get_or_set_component_added(storage, entity)
800
+ additions[component_type][component] = true
801
+ end
802
+
709
803
  function masking_controller.unregister_stop_entity(masking: MaskingInternal, entity: Entity)
710
804
  local deletion_lookup = masking.lookups.deletions.entities[entity]
711
805
  if not deletion_lookup then
@@ -741,6 +835,19 @@ function masking_controller.unregister_stop_component(
741
835
  end
742
836
  end
743
837
 
838
+ function masking_controller.unregister_component_addition(
839
+ masking: MaskingInternal,
840
+ entity: Entity,
841
+ component: Entity,
842
+ component_type: number
843
+ )
844
+ local storage: StorageGroup = masking:get_component_storage_group(entity, component, component_type)
845
+ local additions = storage.changes.added_components[entity]
846
+ if additions then
847
+ additions[component_type][component] = nil
848
+ end
849
+ end
850
+
744
851
  function masking_controller.propagate_entity_addition(masking: MaskingInternal, entity: Entity)
745
852
  local entity_lookup = masking.lookups.entities[entity]
746
853
  if entity_lookup then
@@ -1115,6 +1222,9 @@ function masking_controller.merge_storages(
1115
1222
  old_storage: StorageGroup,
1116
1223
  new_storage: StorageGroup
1117
1224
  )
1225
+ if new_storage == old_storage then
1226
+ return
1227
+ end
1118
1228
  for entity, active in old_storage.active do
1119
1229
  local new_active = get_or_set_active(new_storage, entity)
1120
1230
  merge_component_actives(new_active, active)
@@ -1137,10 +1247,17 @@ function masking_controller.merge_storages(
1137
1247
  end
1138
1248
  end
1139
1249
  end
1250
+ for entity, added_components in old_storage.changes.added_components do
1251
+ local new_added_components = get_or_set_component_added(new_storage, entity)
1252
+ merge_component_additions(new_added_components, added_components)
1253
+ end
1140
1254
  for entity, changes in old_storage.changes.changed do
1141
1255
  local new_changes = get_or_set_changes(new_storage, entity)
1142
1256
  merge_component_changes(new_changes, changes)
1143
1257
  end
1258
+ for entity in old_storage.changes.added do
1259
+ new_storage.changes.added[entity] = true
1260
+ end
1144
1261
  for entity in old_storage.deletions.entities do
1145
1262
  new_storage.deletions.entities[entity] = true
1146
1263
  end
@@ -72,6 +72,11 @@ local TRACK_TYPES = {
72
72
  tag = 2,
73
73
  pair = 3,
74
74
  }
75
+ local PACKET_TYPES = {
76
+ full = 1,
77
+ unreliable = 2,
78
+ reliable = 3,
79
+ }
75
80
  local MAX_PACKET_SIZE = 900
76
81
  local ENTITY_ID_TYPES = {
77
82
  entity = 1,
@@ -201,7 +206,7 @@ local function track_component(server: Server, component: Entity<any>)
201
206
  allocate_tag_addition(server, entity, component)
202
207
  else
203
208
  if not IS_PAIR(id) then
204
- utils.logerror "a non-pair tag was added to a pair tracked component"
209
+ return
205
210
  end
206
211
  allocate_pair_addition(server, entity, component, PAIR_SECOND(world, id))
207
212
  end
@@ -226,7 +231,7 @@ local function track_component(server: Server, component: Entity<any>)
226
231
  allocate_tag_removal(server, entity, component)
227
232
  else
228
233
  if not IS_PAIR(id) then
229
- utils.logerror "a non-pair tag was removed for a pair tracked component"
234
+ return
230
235
  end
231
236
  allocate_pair_removal(server, entity, component, PAIR_SECOND(world, id))
232
237
  end
@@ -354,7 +359,7 @@ local function write_component_value(server: Server, c: Cursor, component: Entit
354
359
  end
355
360
  else
356
361
  if value == NIL_COMPONENT_VALUE then
357
- -- this is zero for vlq
362
+ -- 128 is zero for vlq
358
363
  cursor.writeu8(c, 128)
359
364
  else
360
365
  table.insert(variants, value :: any)
@@ -392,6 +397,24 @@ local function write_entity_id(server: Server, c: Cursor, entity: Entity, varian
392
397
  end
393
398
  end
394
399
 
400
+ local function write_entity_relations(
401
+ server: Server,
402
+ storage: EntityStorage,
403
+ c: Cursor,
404
+ relation: Entity,
405
+ variants: { any }
406
+ )
407
+ local targets = storage.pairs[relation]
408
+ local total_targets = 0
409
+
410
+ for target in targets do
411
+ write_entity_id(server, c, target, variants)
412
+ total_targets += 1
413
+ end
414
+ cursor.write_vlq(c, total_targets)
415
+ write_component_id(server, c, relation)
416
+ end
417
+
395
418
  local function write_entity(
396
419
  server: Server,
397
420
  c: Cursor,
@@ -403,15 +426,7 @@ local function write_entity(
403
426
 
404
427
  local all_pairs = 0
405
428
  for relation in active.components[COMPONENT_TYPES.pair] do
406
- local targets = storage.pairs[relation]
407
- local total_targets = 0
408
-
409
- for target in targets do
410
- write_entity_id(server, c, target, variants)
411
- total_targets += 1
412
- end
413
- cursor.write_vlq(c, total_targets)
414
- write_component_id(server, c, relation)
429
+ write_entity_relations(server, storage, c, relation, variants)
415
430
  all_pairs += 1
416
431
  end
417
432
  cursor.write_vlq(c, all_pairs)
@@ -419,6 +434,10 @@ local function write_entity(
419
434
  local components = 0
420
435
  for component in active.components[COMPONENT_TYPES.component] do
421
436
  local value = storage.values[component]
437
+ if value == nil then
438
+ continue
439
+ end
440
+
422
441
  write_component_value(server, c, component, value, variants)
423
442
  components += 1
424
443
  end
@@ -477,8 +496,8 @@ local function append_packet(
477
496
  end
478
497
  end
479
498
 
480
- local function combine_packet_outputs(outputs: Array<Packet>, total_size: number): (buffer, { { any } })
481
- local combined = buffer.create(total_size + utils.vlq_span(#outputs))
499
+ local function combine_packet_outputs(outputs: Array<Packet>, total_size: number, id: number): (buffer, { { any } })
500
+ local combined = buffer.create(total_size + utils.vlq_span(#outputs) + 1)
482
501
  local offset = 0
483
502
  local variants = table.create(#outputs) :: { { any } }
484
503
 
@@ -491,13 +510,14 @@ local function combine_packet_outputs(outputs: Array<Packet>, total_size: number
491
510
  offset = offset,
492
511
  buffer = combined,
493
512
  }, #outputs)
513
+ buffer.writeu8(combined, buffer.len(combined) - 1, id)
494
514
 
495
515
  return combined, variants
496
516
  end
497
517
 
498
518
  type PacketIterator = () -> (Player, buffer, { { any } })
499
519
 
500
- local function create_packet_iterator(packets: { [Player]: MemberPackets })
520
+ local function create_packet_iterator(packets: { [Player]: MemberPackets }, id: number)
501
521
  local iterated: Player? = nil
502
522
  local function iterator()
503
523
  local player, data = next(packets, iterated)
@@ -506,7 +526,7 @@ local function create_packet_iterator(packets: { [Player]: MemberPackets })
506
526
  end
507
527
  iterated = player
508
528
 
509
- local combined, variants = combine_packet_outputs(data.packets, data.total_size)
529
+ local combined, variants = combine_packet_outputs(data.packets, data.total_size, id)
510
530
  return player, combined, variants
511
531
  end
512
532
  return iterator :: PacketIterator
@@ -543,7 +563,7 @@ local function create_unreliable_packet_iterator(packets: { [Player]: MemberPack
543
563
  end
544
564
 
545
565
  if #sending > 0 then
546
- coroutine.yield(player, combine_packet_outputs(sending, total_size))
566
+ coroutine.yield(player, combine_packet_outputs(sending, total_size, PACKET_TYPES.unreliable))
547
567
  end
548
568
  end
549
569
  end
@@ -581,7 +601,16 @@ local function get_full(server: Server, member: Player): (buffer, { { any } })
581
601
  total_size += buffer.len(output)
582
602
  end
583
603
 
584
- return combine_packet_outputs(packets, total_size)
604
+ return combine_packet_outputs(packets, total_size, PACKET_TYPES.full)
605
+ end
606
+
607
+ local function write_vlq_bitmask(c: Cursor, value: number, mask: number, bit: number): number
608
+ if value > 0 then
609
+ cursor.write_vlq(c, value)
610
+ return utils.setbit(mask, bit)
611
+ else
612
+ return mask
613
+ end
585
614
  end
586
615
 
587
616
  local function collect_updates(server: Server)
@@ -589,7 +618,9 @@ local function collect_updates(server: Server)
589
618
 
590
619
  for _, storage in server.masking.storages do
591
620
  local c = cursor.new()
621
+
592
622
  local variants = {}
623
+ local storage_mask = 0
593
624
 
594
625
  local total_deleted = 0
595
626
  local entity_deletions = storage.deletions.entities
@@ -598,47 +629,52 @@ local function collect_updates(server: Server)
598
629
  total_deleted += 1
599
630
  end
600
631
  table.clear(entity_deletions)
601
- cursor.write_vlq(c, total_deleted)
632
+ storage_mask = write_vlq_bitmask(c, total_deleted, storage_mask, 4)
602
633
 
603
634
  local total_component_deleted = 0
604
635
  local component_deletions = storage.deletions.components
605
636
  for entity, deleted in component_deletions do
637
+ local deleted_mask = 0
638
+
606
639
  local total_pairs = 0
607
640
  for relation in deleted[COMPONENT_TYPES.pair] do
608
641
  write_component_id(server, c, relation)
609
642
  total_pairs += 1
610
643
  end
611
- cursor.write_vlq(c, total_pairs)
644
+ deleted_mask = write_vlq_bitmask(c, total_pairs, deleted_mask, 2)
612
645
 
613
646
  local total_components = 0
614
647
  for component in deleted[COMPONENT_TYPES.component] do
615
648
  write_component_id(server, c, component)
616
649
  total_components += 1
617
650
  end
618
- cursor.write_vlq(c, total_components)
651
+ deleted_mask = write_vlq_bitmask(c, total_components, deleted_mask, 1)
619
652
 
620
653
  local total_tags = 0
621
654
  for tag in deleted[COMPONENT_TYPES.tag] do
622
655
  write_component_id(server, c, tag)
623
656
  total_tags += 1
624
657
  end
625
- cursor.write_vlq(c, total_tags)
658
+ deleted_mask = write_vlq_bitmask(c, total_tags, deleted_mask, 0)
626
659
 
660
+ cursor.writeu8(c, deleted_mask)
627
661
  write_entity_id(server, c, entity, variants)
628
662
  total_component_deleted += 1
629
663
  end
630
664
  table.clear(component_deletions)
631
- cursor.write_vlq(c, total_component_deleted)
665
+ storage_mask = write_vlq_bitmask(c, total_component_deleted, storage_mask, 3)
632
666
 
633
667
  local storage_changes = storage.changes
634
668
  local changed = 0
635
669
  for entity, changes in storage_changes.changed do
670
+ local changed_mask = 0
671
+
636
672
  local total_removed = 0
637
673
  for component in changes.removed do
638
674
  write_component_id(server, c, component)
639
675
  total_removed += 1
640
676
  end
641
- cursor.write_vlq(c, total_removed)
677
+ changed_mask = write_vlq_bitmask(c, total_removed, changed_mask, 3)
642
678
 
643
679
  local total_pairs = 0
644
680
  for relation, targets in changes.pairs do
@@ -656,27 +692,68 @@ local function collect_updates(server: Server)
656
692
  write_component_id(server, c, relation)
657
693
  total_pairs += 1
658
694
  end
659
- cursor.write_vlq(c, total_pairs)
695
+ changed_mask = write_vlq_bitmask(c, total_pairs, changed_mask, 2)
660
696
 
661
697
  local total_components = 0
662
698
  for component, value in changes.component do
663
699
  write_component_value(server, c, component, value, variants)
664
700
  total_components += 1
665
701
  end
666
- cursor.write_vlq(c, total_components)
702
+ changed_mask = write_vlq_bitmask(c, total_components, changed_mask, 1)
667
703
 
668
704
  local total_tagged = 0
669
705
  for tag in changes.tagged do
670
706
  write_component_id(server, c, tag)
671
707
  total_tagged += 1
672
708
  end
673
- cursor.write_vlq(c, total_tagged)
709
+ changed_mask = write_vlq_bitmask(c, total_tagged, changed_mask, 0)
674
710
 
711
+ cursor.writeu8(c, changed_mask)
675
712
  write_entity_id(server, c, entity, variants)
676
713
  changed += 1
677
714
  end
678
715
  table.clear(storage_changes.changed)
679
- cursor.write_vlq(c, changed)
716
+ storage_mask = write_vlq_bitmask(c, changed, storage_mask, 2)
717
+
718
+ local total_components_added = 0
719
+ for entity, components in storage_changes.added_components do
720
+ local entity_storage = server.storage[entity]
721
+ local added_mask = 0
722
+
723
+ local total_pairs = 0
724
+ for relation in components[COMPONENT_TYPES.pair] do
725
+ write_entity_relations(server, entity_storage, c, relation, variants)
726
+ total_pairs += 1
727
+ end
728
+ added_mask = write_vlq_bitmask(c, total_pairs, added_mask, 2)
729
+
730
+ local total_components = 0
731
+ for component in components[COMPONENT_TYPES.component] do
732
+ local value = entity_storage.values[component]
733
+ if value == nil then
734
+ continue
735
+ end
736
+ write_component_value(server, c, component, value, variants)
737
+ total_components += 1
738
+ end
739
+ added_mask = write_vlq_bitmask(c, total_components, added_mask, 1)
740
+
741
+ local total_tags = 0
742
+ for component in components[COMPONENT_TYPES.tag] do
743
+ local has_tag = entity_storage.tags[component]
744
+ if has_tag then
745
+ write_component_id(server, c, component)
746
+ total_tags += 1
747
+ end
748
+ end
749
+ added_mask = write_vlq_bitmask(c, total_tags, added_mask, 0)
750
+
751
+ cursor.writeu8(c, added_mask)
752
+ write_entity_id(server, c, entity, variants)
753
+ total_components_added += 1
754
+ end
755
+ table.clear(storage_changes.added_components)
756
+ storage_mask = write_vlq_bitmask(c, total_components_added, storage_mask, 1)
680
757
 
681
758
  local total_added = 0
682
759
  for entity in storage_changes.added do
@@ -689,14 +766,14 @@ local function collect_updates(server: Server)
689
766
  total_added += 1
690
767
  end
691
768
  table.clear(storage_changes.added)
692
- cursor.write_vlq(c, total_added)
769
+ storage_mask = write_vlq_bitmask(c, total_added, storage_mask, 0)
693
770
 
771
+ cursor.writeu8(c, storage_mask)
694
772
  local output = cursor.close(c)
695
773
  append_packet(packets, storage.mask.members, output, variants)
696
774
  end
697
775
  table.clear(server.additions)
698
-
699
- return create_packet_iterator(packets)
776
+ return create_packet_iterator(packets, PACKET_TYPES.reliable)
700
777
  end
701
778
 
702
779
  local function collect_unreliable(server: Server)
@@ -854,6 +931,8 @@ function init(server: Server, _world: World?)
854
931
  masking:start_component(entity, component, component_type, filter)
855
932
  if server.additions[entity] then
856
933
  masking:allocate_entity_addition(entity, component, component_type)
934
+ else
935
+ masking:register_component_addition(entity, component, component_type)
857
936
  end
858
937
  end))
859
938
  hook(hooks.changed(world, components.reliable, function(entity, id, filter)
@@ -881,6 +960,7 @@ function init(server: Server, _world: World?)
881
960
  if not component_type then
882
961
  return
883
962
  end
963
+ masking:unregister_component_addition(entity, component, component_type)
884
964
  masking:register_stop_component(entity, component, component_type)
885
965
  masking:stop_component(entity, component, component_type)
886
966
  end))
@@ -895,6 +975,8 @@ function init(server: Server, _world: World?)
895
975
  masking:start_component(entity, relation, COMPONENT_TYPES.pair, filter)
896
976
  if server.additions[entity] then
897
977
  masking:allocate_entity_addition(entity, relation, COMPONENT_TYPES.pair)
978
+ else
979
+ masking:register_component_addition(entity, relation, COMPONENT_TYPES.pair)
898
980
  end
899
981
  end))
900
982
  hook(hooks.changed(world, components.pair, function(entity, id, filter)
@@ -914,6 +996,7 @@ function init(server: Server, _world: World?)
914
996
 
915
997
  local relation = PAIR_SECOND(world, id)
916
998
  untrack_entity_pair(server, entity, relation)
999
+ masking:unregister_component_addition(entity, relation, COMPONENT_TYPES.pair)
917
1000
  masking:register_stop_component(entity, relation, COMPONENT_TYPES.pair)
918
1001
  masking:stop_component(entity, relation, COMPONENT_TYPES.pair)
919
1002
  end))
@@ -965,13 +1048,14 @@ function init(server: Server, _world: World?)
965
1048
  hook(hooks.removed(world, components.custom_id, function(entity)
966
1049
  server.custom_ids[entity] = nil
967
1050
  end))
968
- hook(hooks.added(world, components.global_id, function(entity, id, value)
1051
+ hook(hooks.added(world, components.global_id, function(entity, _, value)
969
1052
  if value > 245 then
970
1053
  utils.logerror "global id size exceeded, max is 245"
971
1054
  end
972
1055
  server.global_ids[entity] = value
973
1056
  end))
974
- hook(hooks.changed(world, components.global_id, function(entity, id, value)
1057
+
1058
+ hook(hooks.changed(world, components.global_id, function(entity, _, value)
975
1059
  if value > 245 then
976
1060
  utils.logerror "global id size exceeded, max is 245"
977
1061
  end
@@ -663,6 +663,13 @@ function utils.remove_all_relations(world: World, entity: Entity, relation: Enti
663
663
  end
664
664
  end
665
665
 
666
+ function utils.setbit(mask: number, bit: number)
667
+ return bit32.bor(mask, bit32.lshift(1, bit))
668
+ end
669
+ function utils.checkbit(mask: number, bit: number)
670
+ return bit32.band(mask, bit32.lshift(1, bit)) ~= 0
671
+ end
672
+
666
673
  function utils.logerror(...: string): never
667
674
  error("REPLECS ERROR - " .. table.concat({ ... }, " "))
668
675
  end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/replecs",
3
- "version": "0.1.0-alpha2",
3
+ "version": "0.1.0-alpha3",
4
4
  "description": "Replication library for Jecs",
5
5
  "main": "out/init.lua",
6
6
  "types": "out/index.d.ts",
@@ -41,6 +41,6 @@
41
41
  "prettier": "^3.2.5"
42
42
  },
43
43
  "dependencies": {
44
- "@rbxts/jecs": "^0.8.3"
44
+ "@rbxts/jecs": "^0.9.0-rc.7"
45
45
  }
46
46
  }