@rbxts/replecs 0.1.0-alpha5 → 0.1.0-alpha8

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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # Replecs ⚡
2
- Replecs is a fast, fine controlled JECS replication library
3
2
 
4
- Replecs is a feature rich, fast, flexible, powerful, and runtime/library agnostic replication library for ECS.
3
+ Replecs is a fast, fine controlled JECS replication library
4
+
5
+ Replecs is a feature rich, fast, flexible, powerful, and runtime/library agnostic replication library for ECS.
5
6
 
6
7
  - Per-entity replication
7
8
  - Heavily optimized granular player filtering for entities and components
@@ -14,110 +15,3 @@ Replecs is a feature rich, fast, flexible, powerful, and runtime/library agnosti
14
15
  - Unreliable replication, with automatic 1kb buffer size limit
15
16
 
16
17
  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
@@ -14,12 +14,13 @@ type World = jecs.World
14
14
  export type Client = {
15
15
  world: World,
16
16
  inited: boolean?,
17
+
17
18
  is_replicating: boolean,
18
19
  after_replication_callbacks: { () -> () },
19
20
 
20
21
  components: common.Components,
21
22
  shared: common.Shared,
22
- global_id_parser: ((id: number) -> Entity)?,
23
+ global_handler: ((id: number) -> Entity)?,
23
24
  server_ids: { [number]: Entity },
24
25
  client_ids: { [Entity]: number },
25
26
  ordered_creation: boolean,
@@ -28,10 +29,13 @@ export type Client = {
28
29
  init: (self: Client, world: World?) -> (),
29
30
  destroy: (self: Client) -> (),
30
31
  after_replication: (self: Client, callback: () -> ()) -> (),
31
- parse_global_id: (self: Client, parser: (id: number) -> Entity) -> (),
32
+ handle_global: (self: Client, handler: (id: number) -> Entity) -> (),
32
33
  get_server_entity: (self: Client, client_entity: Entity) -> number?,
33
34
  get_client_entity: (self: Client, server_entity: number) -> Entity?,
34
35
 
36
+ encode_component: (self: Client, component: Entity) -> number,
37
+ decode_component: (self: Client, component: number) -> Entity,
38
+
35
39
  apply_updates: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
36
40
  apply_unreliable: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
37
41
  apply_full: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
@@ -68,6 +72,23 @@ local function get_or_create_entity(client: Client, server_id: number): Entity
68
72
  end
69
73
  end
70
74
 
75
+ local function encode_component(client: Client, component: Entity): number
76
+ local encoded = client.shared.ids[component]
77
+ if not encoded then
78
+ utils.logerror(`attempted to encode a non-shared component `, utils.logcomponent(client.world, component))
79
+ return 0
80
+ end
81
+ return encoded
82
+ end
83
+ local function decode_component(client: Client, encoded: number): Entity
84
+ local component = client.shared.components[encoded]
85
+ if not component then
86
+ print("NON SHARED COMPONENT", encoded, client.shared.components)
87
+ error "attemped to decode a non shared component"
88
+ end
89
+ return component
90
+ end
91
+
71
92
  local function read_component_id(client: Client, c: Cursor)
72
93
  local shared_id = cursor.readu8(c)
73
94
  local component = client.shared.components[shared_id]
@@ -86,7 +107,18 @@ local function read_component_value(client: Client, c: Cursor, variants: { any }
86
107
  local bytespan = client.shared.bytespan[component] or cursor.read_vlq(c)
87
108
 
88
109
  local appended = cursor.read_buffer(c, bytespan)
89
- local output = serdes.deserialize(appended)
110
+ local serdes_variants: { any }? = nil
111
+
112
+ if serdes.includes_variants then
113
+ local start_variant = cursor.read_vlq(c)
114
+
115
+ if start_variant > 0 then
116
+ local size = cursor.read_vlq(c)
117
+ serdes_variants = table.move(variants :: { any }, start_variant, start_variant + size - 1, 1, {})
118
+ end
119
+ end
120
+
121
+ local output = serdes.deserialize(appended, serdes_variants)
90
122
  return component, output
91
123
  else
92
124
  local variant_id = cursor.read_vlq(c)
@@ -98,11 +130,11 @@ local function read_component_value(client: Client, c: Cursor, variants: { any }
98
130
  end
99
131
  end
100
132
 
101
- local function resolve_global_id(client: Client, global_id: number): Entity
102
- if not client.global_id_parser then
103
- error "global id parser not set, consider using client:parse_global_id()"
133
+ local function resolve_global(client: Client, global_id: number): Entity
134
+ if not client.global_handler then
135
+ error "global id parser not set, consider using client:handle_global()"
104
136
  end
105
- local parsed = client.global_id_parser(global_id)
137
+ local parsed = client.global_handler(global_id)
106
138
  return parsed
107
139
  end
108
140
 
@@ -129,11 +161,11 @@ local function read_entity_id(client: Client, c: Cursor, variants: { any }?): (E
129
161
  return read_component_id(client, c), nil
130
162
  elseif id_type == ENTITY_ID_TYPES.global then
131
163
  local global_id = cursor.readu8(c) - GLOBAL_ID_OFFSET
132
- return resolve_global_id(client, global_id), nil
164
+ return resolve_global(client, global_id), nil
133
165
  end
134
166
  error(`malformed entity id {id_type} ` .. cursor.readu32(c))
135
167
  else
136
- return resolve_global_id(client, id_type - GLOBAL_ID_OFFSET), nil
168
+ return resolve_global(client, id_type - GLOBAL_ID_OFFSET), nil
137
169
  end
138
170
  end
139
171
 
@@ -184,7 +216,7 @@ local function check_packet_type(c: Cursor, type: string)
184
216
  local got: string = nil :: any
185
217
  for k, v in PACKET_TYPES do
186
218
  if v == packet_type then
187
- got = k
219
+ got = k :: string
188
220
  break
189
221
  end
190
222
  end
@@ -389,8 +421,8 @@ local function init(client: Client, _world: World?)
389
421
 
390
422
  client.shared = utils.create_shared_lookup(world, components)
391
423
 
392
- for component, custom_id_get in world:query(components.custom_id):with(components.shared, ECS_NAME) do
393
- client.custom_ids[component] = custom_id_get
424
+ for component, custom_get in world:query(components.custom_handler):with(components.shared, ECS_NAME) do
425
+ client.custom_ids[component] = custom_get
394
426
  end
395
427
  end
396
428
 
@@ -419,8 +451,8 @@ local function destroy(client: Client)
419
451
  -- right now this is kind of useless
420
452
  end
421
453
 
422
- local function parse_global_id(client: Client, parser: (id: number) -> Entity)
423
- client.global_id_parser = parser
454
+ local function handle_global(client: Client, handler: (id: number) -> Entity)
455
+ client.global_handler = handler
424
456
  end
425
457
 
426
458
  local client = {}
@@ -431,9 +463,11 @@ client.after_replication = after_replication
431
463
  client.apply_updates = apply_updates
432
464
  client.apply_unreliable = apply_unreliable
433
465
  client.apply_full = apply_full
466
+ client.encode_component = encode_component
467
+ client.decode_component = decode_component
434
468
  client.get_server_entity = get_server_entity
435
469
  client.get_client_entity = get_client_entity
436
- client.parse_global_id = parse_global_id
470
+ client.handle_global = handle_global
437
471
 
438
472
  local function create(world: World?, components: common.Components): Client
439
473
  local self = {} :: Client
@@ -443,7 +477,7 @@ local function create(world: World?, components: common.Components): Client
443
477
  self.custom_ids = {}
444
478
  self.server_ids = {}
445
479
  self.client_ids = {}
446
- self.global_id_parser = nil
480
+ self.global_handler = nil
447
481
  self.world = world :: any
448
482
  self.inited = false
449
483
  self.is_replicating = false
@@ -5,8 +5,9 @@ export type PlayerFilter = {
5
5
  }
6
6
 
7
7
  export type Serdes = {
8
- serialize: (value: any) -> buffer,
9
- deserialize: (buffer) -> any,
8
+ includes_variants: boolean?,
9
+ serialize: (value: any) -> (buffer, { any }?),
10
+ deserialize: (buffer, { any }?) -> any,
10
11
  }
11
12
 
12
13
  export type Components = {
@@ -18,8 +19,9 @@ export type Components = {
18
19
 
19
20
  serdes: jecs.Entity<Serdes>,
20
21
  bytespan: jecs.Entity<number>,
21
- custom_id: jecs.Entity<(value: any) -> jecs.Entity>,
22
- global_id: jecs.Entity<number>,
22
+ custom: jecs.Entity,
23
+ custom_handler: jecs.Entity<(value: any) -> jecs.Entity>,
24
+ global: jecs.Entity<number>,
23
25
  __alive_tracking__: jecs.Entity,
24
26
  }
25
27
 
@@ -1,28 +1,17 @@
1
- import type { Entity, Id, Tag, World } from "@rbxts/jecs";
1
+ import type { Entity, Tag, World } from "@rbxts/jecs";
2
2
 
3
3
  declare namespace Replecs {
4
- export interface SerdesTable {
5
- serialize: (value: any) => buffer;
6
- deserialize: (buffer: buffer) => any;
7
- }
8
-
9
- export interface ObserverWorld extends World {
10
- added<T>(
11
- this: ObserverWorld,
12
- component: Id<T>,
13
- callback: (e: Entity, id: Id<T>, value: T) => void
14
- ): () => void;
15
- removed<T>(
16
- this: ObserverWorld,
17
- component: Id<T>,
18
- callback: (e: Entity, id: Id<T>) => void
19
- ): () => void;
20
- changed<T>(
21
- this: ObserverWorld,
22
- component: Id<T>,
23
- callback: (e: Entity, id: Id<T>, value: T) => void
24
- ): () => void;
25
- }
4
+ export type SerdesTable =
5
+ | {
6
+ includes_variants?: false;
7
+ serialize: (value: any) => buffer;
8
+ deserialize: (buffer: buffer) => any;
9
+ }
10
+ | {
11
+ includes_variants: true;
12
+ serialize: (value: any) => LuaTuple<[buffer, defined[] | undefined]>;
13
+ deserialize: (buffer: buffer, blobs: defined[] | undefined) => any;
14
+ };
26
15
 
27
16
  type MemberFilterMap = Map<Player, boolean>;
28
17
  type MemberFilter = Player | MemberFilterMap | undefined;
@@ -44,8 +33,9 @@ declare namespace Replecs {
44
33
 
45
34
  serdes: Entity<SerdesTable>;
46
35
  bytespan: Entity<number>;
47
- custom_id: Entity<((value: any) => Entity) | undefined>;
48
- global_id: Entity<number>;
36
+ custom: Entity;
37
+ custom_handler: Entity<(value: any) => Entity>;
38
+ global: Entity<number>;
49
39
 
50
40
  Shared: Tag;
51
41
  Networked: Entity<MemberFilter>;
@@ -55,39 +45,50 @@ declare namespace Replecs {
55
45
 
56
46
  Serdes: Entity<SerdesTable>;
57
47
  Bytespan: Entity<number>;
58
- CustomId: Entity<(value: any) => Entity>;
59
- GlobalId: Entity<number>;
48
+ Custom: Entity;
49
+ CustomHandler: Entity<(value: any) => Entity>;
50
+ Global: Entity<number>;
60
51
  }
61
52
 
62
53
  export interface Client {
63
- world: ObserverWorld;
54
+ world: World;
64
55
  inited?: boolean;
65
56
 
66
- init(world?: ObserverWorld): void;
57
+ init(world?: World): void;
67
58
  destroy(): void;
68
59
  after_replication(callback: () => void): void;
69
60
 
70
- apply_updates(buf: buffer, all_variants?: any[][]): void;
71
- apply_unreliable(buf: buffer, all_variants?: any[][]): void;
72
- apply_full(buf: buffer, all_variants?: any[][]): void;
73
- parse_global_id(parser: (id: number) => Entity): void;
61
+ apply_updates(buf: buffer, all_variants?: unknown[][]): void;
62
+ apply_unreliable(buf: buffer, all_variants?: unknown[][]): void;
63
+ apply_full(buf: buffer, all_variants?: unknown[][]): void;
64
+
65
+ encode_component(component: Entity): number;
66
+ decode_component(encoded: number): Entity;
67
+
68
+ handle_global(handler: (id: number) => Entity): void;
74
69
 
75
70
  get_server_entity(client_entity: Entity): number | undefined;
76
71
  get_client_entity(server_entity: number): Entity | undefined;
77
72
  }
78
73
 
79
74
  export interface Server {
80
- world: ObserverWorld;
75
+ world: World;
81
76
  inited?: boolean;
82
77
 
83
- init(world?: ObserverWorld): void;
78
+ init(world?: World): void;
84
79
  destroy(): void;
85
80
 
86
- get_full(player: Player): LuaTuple<[buffer, any[][]]>;
87
- collect_updates(): IterableFunction<LuaTuple<[Player, buffer, any[][]]>>;
81
+ get_full(player: Player): LuaTuple<[buffer, unknown[][]]>;
82
+ collect_updates(): IterableFunction<
83
+ LuaTuple<[Player, buffer, unknown[][]]>
84
+ >;
88
85
  collect_unreliable(): IterableFunction<
89
- LuaTuple<[Player, buffer, any[][]]>
86
+ LuaTuple<[Player, buffer, unknown[][]]>
90
87
  >;
88
+
89
+ encode_component(component: Entity): number;
90
+ decode_component(encoded: number): Entity;
91
+
91
92
  mark_player_ready(player: Player): void;
92
93
  is_player_ready(player: Player): boolean;
93
94
 
@@ -102,9 +103,9 @@ declare namespace Replecs {
102
103
  }
103
104
 
104
105
  export interface Replecs extends Components {
105
- create: (world?: ObserverWorld) => ReplecsLib;
106
- create_server: (world?: ObserverWorld) => Server;
107
- create_client: (world?: ObserverWorld) => Client;
106
+ create: (world?: World) => ReplecsLib;
107
+ create_server: (world?: World) => Server;
108
+ create_client: (world?: World) => Client;
108
109
  }
109
110
  }
110
111
 
@@ -27,30 +27,37 @@ local replecs = {
27
27
 
28
28
  serdes = jecs.component() :: jecs.Entity<common.Serdes>,
29
29
  bytespan = jecs.component() :: jecs.Entity<number>,
30
- custom_id = jecs.component() :: jecs.Entity<(any) -> jecs.Entity>,
31
- global_id = jecs.component() :: jecs.Entity<number>,
30
+ custom = jecs.tag() :: jecs.Entity,
31
+ custom_handler = jecs.component() :: jecs.Entity<(any) -> jecs.Entity>,
32
+ global = jecs.component() :: jecs.Entity<number>,
32
33
  __alive_tracking__ = jecs.tag(),
33
34
  }
34
35
 
35
36
  replecs.__index = replecs
36
37
  replecs.Serdes = replecs.serdes
37
38
  replecs.Bytespan = replecs.bytespan
38
- replecs.CustomId = replecs.custom_id
39
+ replecs.Custom = replecs.custom
40
+ replecs.CustomHandler = replecs.custom_handler
39
41
  replecs.Networked = replecs.networked
40
42
  replecs.Reliable = replecs.reliable
41
43
  replecs.Unreliable = replecs.unreliable
42
44
  replecs.Pair = replecs.pair
43
45
  replecs.Shared = replecs.shared
44
- replecs.GlobalId = replecs.global_id
46
+ replecs.Global = replecs.global
45
47
 
46
48
  jecs.meta(replecs.serdes, jecs.Name, "replecs.serdes")
47
49
  jecs.meta(replecs.bytespan, jecs.Name, "replecs.bytespan")
48
- jecs.meta(replecs.custom_id, jecs.Name, "replecs.custom_id")
50
+ jecs.meta(replecs.custom, jecs.Name, "replecs.custom")
51
+ jecs.meta(replecs.custom_handler, jecs.Name, "replecs.custom_handler")
49
52
  jecs.meta(replecs.networked, jecs.Name, "replecs.networked")
50
53
  jecs.meta(replecs.reliable, jecs.Name, "replecs.reliable")
51
54
  jecs.meta(replecs.unreliable, jecs.Name, "replecs.unreliable")
52
55
  jecs.meta(replecs.pair, jecs.Name, "replecs.pair")
53
56
  jecs.meta(replecs.shared, jecs.Name, "replecs.shared")
57
+ jecs.meta(replecs.global, jecs.Name, "replecs.global")
58
+
59
+ jecs.meta(replecs.custom, jecs.Exclusive)
60
+ jecs.meta(replecs.global, jecs.Exclusive)
54
61
 
55
62
  function replecs.create_server(world: jecs.World?): Server
56
63
  return server.create(world, replecs)
@@ -1136,12 +1136,14 @@ function masking_controller.allocate_component_change(
1136
1136
  local storage = masking:get_component_storage_group(entity, component, COMPONENT_TYPES.component)
1137
1137
  local changes = get_or_set_changes(storage, entity)
1138
1138
  changes.component[component] = value
1139
+ changes.removed[component] = nil
1139
1140
  end
1140
1141
 
1141
1142
  function masking_controller.allocate_tag_addition(masking: MaskingInternal, entity: Entity, tag: Entity)
1142
1143
  local storage = masking:get_component_storage_group(entity, tag, COMPONENT_TYPES.tag)
1143
1144
  local changes = get_or_set_changes(storage, entity)
1144
1145
  changes.tagged[tag] = true
1146
+ changes.removed[tag] = nil
1145
1147
  end
1146
1148
 
1147
1149
  function masking_controller.allocate_pair_addition(
@@ -1164,9 +1166,7 @@ function masking_controller.allocate_component_removal(masking: MaskingInternal,
1164
1166
  local storage = masking:get_component_storage_group(entity, component, COMPONENT_TYPES.component)
1165
1167
  local changes = get_or_set_changes(storage, entity)
1166
1168
 
1167
- if changes.component[component] then
1168
- changes.component[component] = nil
1169
- end
1169
+ changes.component[component] = nil
1170
1170
  changes.removed[component] = true
1171
1171
  end
1172
1172
 
@@ -1174,9 +1174,7 @@ function masking_controller.allocate_tag_removal(masking: MaskingInternal, entit
1174
1174
  local storage = masking:get_component_storage_group(entity, tag, COMPONENT_TYPES.tag)
1175
1175
  local changes = get_or_set_changes(storage, entity)
1176
1176
 
1177
- if changes.tagged[tag] then
1178
- changes.tagged[tag] = nil
1179
- end
1177
+ changes.tagged[tag] = nil
1180
1178
  changes.removed[tag] = true
1181
1179
  end
1182
1180
 
@@ -54,6 +54,9 @@ export type Server = {
54
54
  init: (self: Server, world: World?) -> (),
55
55
  destroy: (self: Server) -> (),
56
56
 
57
+ encode_component: (self: Server, component: Entity) -> number,
58
+ decode_component: (self: Server, component: number) -> Entity,
59
+
57
60
  get_full: (self: Server, player: Player) -> (buffer, { { any } }),
58
61
  collect_updates: (self: Server) -> () -> (Player, buffer, { { any } }),
59
62
  collect_unreliable: (self: Server) -> () -> (Player, buffer, { { any } }),
@@ -330,6 +333,24 @@ local function untrack_entity_pair(server: Server, entity: Entity, relation: Ent
330
333
  end
331
334
  end
332
335
 
336
+ local function encode_component(server: Server, component: Entity): number
337
+ local encoded = server.shared.ids[component]
338
+ if not encoded then
339
+ utils.logerror(`attempted to encode a non-shared component `, utils.logcomponent(server.world, component))
340
+ return 0
341
+ end
342
+ return encoded
343
+ end
344
+
345
+ local function decode_component(server: Server, encoded: number): Entity
346
+ local component = server.shared.components[encoded]
347
+ if not component then
348
+ print("NON SHARED COMPONENTS", encoded, server.shared.components)
349
+ error "attemped to decode a non shared component"
350
+ end
351
+ return component
352
+ end
353
+
333
354
  local function write_component_id(server: Server, c: Cursor, component: Entity)
334
355
  local encoded = server.shared.ids[component]
335
356
  if not encoded then
@@ -342,7 +363,26 @@ end
342
363
  local function write_component_value(server: Server, c: Cursor, component: Entity, value: any, variants: { any })
343
364
  local serdes = server.shared.serdes[component]
344
365
  if serdes then
345
- local output = serdes.serialize(if value == NIL_COMPONENT_VALUE then nil else value)
366
+ local output, serdes_variants = serdes.serialize(if value == NIL_COMPONENT_VALUE then nil else value)
367
+
368
+ if serdes.includes_variants then
369
+ if serdes_variants == nil then
370
+ -- 128 is zero for vlq
371
+ cursor.writeu8(c, 128)
372
+ else
373
+ local start_variants = #variants
374
+ if #serdes_variants > 0 then
375
+ table.move(serdes_variants, 1, #serdes_variants, #variants + 1, variants)
376
+ end
377
+ cursor.write_vlq(c, #serdes_variants)
378
+ cursor.write_vlq(c, start_variants + 1)
379
+ end
380
+ elseif serdes_variants ~= nil then
381
+ utils.logerror(
382
+ "serdes: serializer provided variants yet includes_variants is not set to true for component",
383
+ utils.logcomponent(server.world, component)
384
+ )
385
+ end
346
386
 
347
387
  local bytespan = server.shared.bytespan[component]
348
388
  cursor.write_buffer(c, output)
@@ -1033,9 +1073,9 @@ function init(server: Server, _world: World?)
1033
1073
  masking:stop_component(entity, component, COMPONENT_TYPES.unreliable)
1034
1074
  end))
1035
1075
 
1036
- hook(hooks.added(world, components.custom_id, function(entity, id)
1076
+ hook(hooks.added(world, components.custom, function(entity, id)
1037
1077
  if not IS_PAIR(id) then
1038
- utils.logerror "custom_id should be used with a relationship in the server"
1078
+ utils.logerror "replecs.custom should be used with a relationship in the server"
1039
1079
  end
1040
1080
  local target = PAIR_SECOND(world, id)
1041
1081
  if server.custom_ids[entity] then
@@ -1045,23 +1085,23 @@ function init(server: Server, _world: World?)
1045
1085
 
1046
1086
  server.custom_ids[entity] = target
1047
1087
  end))
1048
- hook(hooks.removed(world, components.custom_id, function(entity)
1088
+ hook(hooks.removed(world, components.custom, function(entity)
1049
1089
  server.custom_ids[entity] = nil
1050
1090
  end))
1051
- hook(hooks.added(world, components.global_id, function(entity, _, value)
1091
+ hook(hooks.added(world, components.global, function(entity, _, value)
1052
1092
  if value > 245 then
1053
1093
  utils.logerror "global id size exceeded, max is 245"
1054
1094
  end
1055
1095
  server.global_ids[entity] = value
1056
1096
  end))
1057
1097
 
1058
- hook(hooks.changed(world, components.global_id, function(entity, _, value)
1098
+ hook(hooks.changed(world, components.global, function(entity, _, value)
1059
1099
  if value > 245 then
1060
1100
  utils.logerror "global id size exceeded, max is 245"
1061
1101
  end
1062
1102
  server.global_ids[entity] = value
1063
1103
  end))
1064
- hook(hooks.removed(world, components.global_id, function(entity)
1104
+ hook(hooks.removed(world, components.global, function(entity)
1065
1105
  server.global_ids[entity] = nil
1066
1106
  end))
1067
1107
 
@@ -1116,6 +1156,8 @@ server.destroy = destroy
1116
1156
  server.get_full = get_full
1117
1157
  server.collect_updates = collect_updates
1118
1158
  server.collect_unreliable = collect_unreliable
1159
+ server.encode_component = encode_component
1160
+ server.decode_component = decode_component
1119
1161
  server.mark_player_ready = mark_player_ready
1120
1162
  server.is_player_ready = is_player_ready
1121
1163
 
@@ -678,7 +678,7 @@ function utils.logwarn(...: string)
678
678
  end
679
679
 
680
680
  function utils.logcomponent(world: World, component: Entity)
681
- return ` name: {world:get(component, ECS_NAME) or "(no name)"} id: {component}`
681
+ return ` name: {world:get(component, ECS_NAME) or "(no name)"}, id: {component}`
682
682
  end
683
683
 
684
684
  return utils
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/replecs",
3
- "version": "0.1.0-alpha5",
3
+ "version": "0.1.0-alpha8",
4
4
  "description": "Replication library for Jecs",
5
5
  "main": "out/init.lua",
6
6
  "types": "out/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "typescript": "=5.5.3",
41
41
  "prettier": "^3.2.5"
42
42
  },
43
- "dependencies": {
44
- "@rbxts/jecs": "^0.9.0-rc.8"
43
+ "peerDependencies": {
44
+ "@rbxts/jecs": "^0.9.0-rc.9"
45
45
  }
46
46
  }