@rbxts/replecs 0.0.1-rc.0

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/out/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import "./jecs";
2
+ import replecs from "./replecs";
3
+ export = replecs;
package/out/init.luau ADDED
@@ -0,0 +1,5 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ TS.import(script, script, "jecs")
4
+ local replecs = TS.import(script, script, "replecs")
5
+ return replecs
package/out/jecs.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@rbxts/jecs";
package/out/jecs.luau ADDED
@@ -0,0 +1,7 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ for _k, _v in TS.import(script, TS.getModule(script, "@rbxts", "jecs").jecs) or {} do
5
+ exports[_k] = _v
6
+ end
7
+ return exports
@@ -0,0 +1,354 @@
1
+ --!native
2
+ --!optimize 2
3
+
4
+ local jecs = require(script.Parent.Parent.jecs)
5
+ local common = require(script.Parent.common)
6
+ local utils = require(script.Parent.utils)
7
+
8
+ local cursor = utils.cursor
9
+ type Cursor = utils.Cursor
10
+
11
+ type Entity<T = any> = jecs.Entity<T>
12
+ type World = jecs.World
13
+
14
+ export type Client = {
15
+ world: World,
16
+ inited: boolean?,
17
+ is_replicating: boolean,
18
+ after_replication_callbacks: { () -> () },
19
+
20
+ components: common.Components,
21
+ shared: common.Shared,
22
+ server_ids: { [number]: Entity },
23
+ client_ids: { [Entity]: number },
24
+ ordered_creation: boolean,
25
+ custom_ids: { [Entity]: (any) -> Entity },
26
+
27
+ init: (self: Client, world: World?) -> (),
28
+ destroy: (self: Client) -> (),
29
+ after_replication: (self: Client, callback: () -> ()) -> (),
30
+
31
+ apply_updates: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
32
+ apply_unreliable: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
33
+ apply_full: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
34
+ }
35
+
36
+ local ENTITY_ID_TYPES = {
37
+ id = 1,
38
+ custom = 2,
39
+ shared = 3,
40
+ }
41
+
42
+ local ECS_NAME = jecs.Name
43
+
44
+ local function PAIR(first: Entity, second: Entity): Entity
45
+ return jecs.pair(first, second)
46
+ end
47
+
48
+ local function get_or_create_entity(client: Client, server_id: number): Entity
49
+ local server_entity = client.server_ids[server_id]
50
+ if server_entity then
51
+ return server_entity
52
+ else
53
+ local entity = client.world:entity()
54
+ client.server_ids[server_id] = entity
55
+ client.client_ids[entity] = server_id
56
+ return entity
57
+ end
58
+ end
59
+
60
+ local function read_component_id(client: Client, c: Cursor)
61
+ local shared_id = cursor.readu8(c)
62
+ local component = client.shared.components[shared_id]
63
+ if not component then
64
+ print("NON SHARED COMPONENT", shared_id, client.shared.components)
65
+ error "received a non shared component"
66
+ end
67
+ return component
68
+ end
69
+
70
+ local function read_component_value(client: Client, c: Cursor, variants: { any }?): (Entity, any)
71
+ local component = read_component_id(client, c)
72
+ local serdes = client.shared.serdes[component]
73
+
74
+ if serdes then
75
+ local bytespan = client.shared.bytespan[component] or cursor.read_vlq(c)
76
+
77
+ local appended = cursor.read_buffer(c, bytespan)
78
+ local output = serdes.deserialize(appended)
79
+ return component, output
80
+ else
81
+ local variant_id = cursor.read_vlq(c)
82
+ if variant_id == 0 then
83
+ return component, nil
84
+ end
85
+ local value = (variants :: { any })[variant_id]
86
+ return component, value
87
+ end
88
+ end
89
+
90
+ local function read_entity_id(client: Client, c: Cursor, variants: { any }?): (Entity?, number?)
91
+ local id_type = cursor.readi8(c)
92
+
93
+ if id_type == ENTITY_ID_TYPES.id then
94
+ local server_id = cursor.readu24(c)
95
+ return client.server_ids[server_id], server_id
96
+ elseif id_type == ENTITY_ID_TYPES.custom then
97
+ local component, value = read_component_value(client, c, variants)
98
+ local custom_getter = client.custom_ids[component]
99
+ if not custom_getter then
100
+ error(
101
+ `received a custom id for a non custom component, consider adding custom_id to component: {client.world:get(
102
+ component,
103
+ ECS_NAME
104
+ ) or "(no name)"} id: {component}`
105
+ )
106
+ end
107
+ return custom_getter(value), nil
108
+ elseif id_type == ENTITY_ID_TYPES.shared then
109
+ return read_component_id(client, c), nil
110
+ end
111
+ error("malformed entity id" .. cursor.readu32(c))
112
+ end
113
+
114
+ local function process_entity_id(client: Client, c: Cursor, variants: { any }?)
115
+ local entity, server_id = read_entity_id(client, c, variants)
116
+ if entity then
117
+ return entity
118
+ else
119
+ return get_or_create_entity(client, server_id :: number)
120
+ end
121
+ end
122
+
123
+ local function process_entity(client: Client, c: Cursor, variants: { any }?)
124
+ local entity = process_entity_id(client, c, variants)
125
+
126
+ local total_tags = cursor.read_vlq(c)
127
+ for i = 1, total_tags do
128
+ local tag = read_component_id(client, c)
129
+ client.world:add(entity, tag)
130
+ end
131
+
132
+ local total_components = cursor.read_vlq(c)
133
+ for i = 1, total_components do
134
+ local component, value = read_component_value(client, c, variants)
135
+ client.world:set(entity, component, value)
136
+ end
137
+
138
+ local total_pairs = cursor.read_vlq(c)
139
+ for i = 1, total_pairs do
140
+ local relation = read_component_id(client, c)
141
+ local total_targets = cursor.read_vlq(c)
142
+
143
+ for i = 1, total_targets do
144
+ local target = process_entity_id(client, c, variants)
145
+ client.world:add(entity, PAIR(relation, target))
146
+ end
147
+ end
148
+ end
149
+
150
+ local function apply_updates(client: Client, buf: buffer, all_variants: { { any } }?)
151
+ local c = cursor.from(buf)
152
+ local total_packets = cursor.read_vlq(c)
153
+ local variant_start = (all_variants and #all_variants + 1) :: number
154
+
155
+ for a = 1, total_packets do
156
+ local variants = all_variants and all_variants[variant_start - a]
157
+ local total_added = cursor.read_vlq(c)
158
+
159
+ for i = 1, total_added do
160
+ process_entity(client, c, variants)
161
+ end
162
+
163
+ local total_changed = cursor.read_vlq(c)
164
+ for i = 1, total_changed do
165
+ local entity = process_entity_id(client, c, variants)
166
+ local total_tagged = cursor.read_vlq(c)
167
+ for i = 1, total_tagged do
168
+ local tag = read_component_id(client, c)
169
+ client.world:add(entity, tag)
170
+ end
171
+
172
+ local total_components = cursor.read_vlq(c)
173
+ for i = 1, total_components do
174
+ local component, value = read_component_value(client, c, variants)
175
+ client.world:set(entity, component, value)
176
+ end
177
+
178
+ local total_pairs = cursor.read_vlq(c)
179
+ for i = 1, total_pairs do
180
+ local relation = read_component_id(client, c)
181
+ local total_targets = cursor.read_vlq(c)
182
+
183
+ for t = 1, total_targets do
184
+ local id_type = cursor.readi8(c)
185
+ -- here I use negative numbers to indicate removals
186
+ -- but "process_entity_id" expects a positive value
187
+ -- so I just peek at it and put it back but positive
188
+ -- (surely there is a better way to do this)
189
+ cursor.writei8(c, math.abs(id_type))
190
+ local target = process_entity_id(client, c, variants)
191
+
192
+ if id_type > 0 then
193
+ client.world:add(entity, PAIR(relation, target))
194
+ elseif id_type < 0 then
195
+ client.world:remove(entity, PAIR(relation, target))
196
+ end
197
+ end
198
+ end
199
+
200
+ local total_removed = cursor.read_vlq(c)
201
+ for i = 1, total_removed do
202
+ local component = read_component_id(client, c)
203
+ client.world:remove(entity, component)
204
+ end
205
+ end
206
+
207
+ local total_component_deleted = cursor.read_vlq(c)
208
+ for i = 1, total_component_deleted do
209
+ local entity = process_entity_id(client, c, variants)
210
+
211
+ local total_tags = cursor.read_vlq(c)
212
+ for i = 1, total_tags do
213
+ local tag = read_component_id(client, c)
214
+ client.world:remove(entity, tag)
215
+ end
216
+
217
+ local total_components = cursor.read_vlq(c)
218
+ for i = 1, total_components do
219
+ local component = read_component_id(client, c)
220
+ client.world:remove(entity, component)
221
+ end
222
+
223
+ local total_pairs = cursor.read_vlq(c)
224
+ for i = 1, total_pairs do
225
+ local relation = read_component_id(client, c)
226
+ utils.remove_all_relations(client.world, entity, relation)
227
+ end
228
+ end
229
+
230
+ local total_deleted = cursor.read_vlq(c)
231
+ for i = 1, total_deleted do
232
+ local entity, server_id = read_entity_id(client, c, variants)
233
+ if entity then
234
+ client.world:delete(entity)
235
+ end
236
+ if server_id then
237
+ client.server_ids[server_id] = nil
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ local function apply_unreliable(client: Client, buf: buffer, all_variants: { { any } }?)
244
+ local c = cursor.from(buf)
245
+ local total_packets = cursor.read_vlq(c)
246
+ local variant_start = (all_variants and #all_variants + 1) :: number
247
+
248
+ for a = 1, total_packets do
249
+ local total_entities = cursor.read_vlq(c)
250
+ local variants = all_variants and all_variants[variant_start - a]
251
+
252
+ for e = 1, total_entities do
253
+ local entity = read_entity_id(client, c, variants)
254
+ local total_unreliable = cursor.read_vlq(c)
255
+
256
+ if entity then
257
+ local component_id, value = read_component_value(client, c, variants)
258
+ client.world:set(entity, component_id, value)
259
+ else
260
+ -- dont apply unreliable updates to entities that dont exist in the client yet
261
+ -- but we still need to process the values to keep the cursor in the right place
262
+ for u = 1, total_unreliable do
263
+ read_component_value(client, c, variants)
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ local function apply_full(client: Client, buf: buffer, all_variants: { { any } }?)
271
+ local c = cursor.from(buf)
272
+ local total_packets = cursor.read_vlq(c)
273
+ local variant_start = (all_variants and #all_variants + 1) :: number
274
+
275
+ for a = 1, total_packets do
276
+ local total_entities = cursor.read_vlq(c)
277
+ local variants = all_variants and all_variants[variant_start - a]
278
+
279
+ for e = 1, total_entities do
280
+ process_entity(client, c, variants)
281
+ end
282
+ end
283
+ end
284
+
285
+ local function init(client: Client, _world: World?)
286
+ if client.inited == true then
287
+ return warn "attempted to init a client twice"
288
+ end
289
+ if client.inited == nil then
290
+ return warn "attempted to re-init a destroyed client"
291
+ end
292
+ client.inited = true :: any
293
+
294
+ local world = _world or client.world
295
+ client.world = world
296
+
297
+ if not world then
298
+ error "Providing a world is required to start replecs"
299
+ end
300
+
301
+ local components = client.components
302
+
303
+ client.shared = utils.create_shared_lookup(world, components)
304
+
305
+ for component, custom_id_get in world:query(components.custom_id):with(components.shared, ECS_NAME) do
306
+ client.custom_ids[component] = custom_id_get
307
+ end
308
+ end
309
+
310
+ local function after_replication(client: Client, callback: () -> ())
311
+ if client.is_replicating then
312
+ table.insert(client.after_replication_callbacks, callback)
313
+ else
314
+ callback()
315
+ end
316
+ end
317
+
318
+ local function destroy(client: Client)
319
+ if client.inited == nil then
320
+ return warn "attempted to destroy a client twice"
321
+ end
322
+ client.inited = nil :: any
323
+ -- I'll add any logic here if I need to clean up something
324
+ -- right now this is kind of useless
325
+ end
326
+
327
+ local client = {}
328
+ client.__index = client
329
+ client.init = init
330
+ client.destroy = destroy
331
+ client.after_replication = after_replication
332
+ client.apply_updates = apply_updates
333
+ client.apply_unreliable = apply_unreliable
334
+ client.apply_full = apply_full
335
+
336
+ local function create(world: World?, components: common.Components): Client
337
+ local self = {} :: Client
338
+
339
+ self.components = components
340
+ self.ordered_creation = true
341
+ self.custom_ids = {}
342
+ self.server_ids = {}
343
+ self.client_ids = {}
344
+ self.world = world :: any
345
+ self.inited = false
346
+ self.is_replicating = false
347
+ self.after_replication_callbacks = {}
348
+
349
+ return setmetatable(self, client) :: any
350
+ end
351
+
352
+ client.create = create
353
+
354
+ return client :: { create: typeof(create) }
@@ -0,0 +1,49 @@
1
+ local jecs = require(script.Parent.Parent.jecs)
2
+
3
+ export type PlayerFilter = {
4
+ [Player]: boolean,
5
+ }
6
+
7
+ export type Serdes = {
8
+ serialize: (value: any) -> buffer,
9
+ deserialize: (buffer) -> any,
10
+ }
11
+
12
+ export type Components = {
13
+ shared: jecs.Entity,
14
+ networked: jecs.Entity<PlayerFilter?>,
15
+ reliable: jecs.Entity,
16
+ unreliable: jecs.Entity,
17
+ pair: jecs.Entity,
18
+
19
+ serdes: jecs.Entity<Serdes>,
20
+ bytespan: jecs.Entity<number>,
21
+ custom_id: jecs.Entity<(value: any) -> jecs.Entity>,
22
+ __alive_tracking__: jecs.Entity,
23
+ }
24
+
25
+ export type Shared = {
26
+ lookup: { [string]: jecs.Entity },
27
+
28
+ components: { [number]: jecs.Entity },
29
+ ids: { [jecs.Entity]: number },
30
+
31
+ bytespan: { [jecs.Entity]: number },
32
+ serdes: { [jecs.Entity]: Serdes },
33
+ }
34
+
35
+ export type WorldHooks = {
36
+ added: (
37
+ world: jecs.World,
38
+ component: jecs.Entity,
39
+ fn: (entity: jecs.Entity, id: jecs.Id, value: any) -> ()
40
+ ) -> () -> (),
41
+ changed: (
42
+ world: jecs.World,
43
+ component: jecs.Entity,
44
+ fn: (entity: jecs.Entity, id: jecs.Id, value: any) -> ()
45
+ ) -> () -> (),
46
+ removed: (world: jecs.World, component: jecs.Entity, fn: (entity: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
47
+ }
48
+
49
+ return nil
@@ -0,0 +1,96 @@
1
+ import type { Entity, Id, Tag, World } from "@rbxts/jecs";
2
+
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
+ }
26
+
27
+ type MemberFilter = Map<Player, boolean>;
28
+
29
+ export interface Components {
30
+ shared: Tag;
31
+ networked: Entity<MemberFilter | undefined>;
32
+ reliable: Entity<MemberFilter | undefined>;
33
+ unreliable: Entity<MemberFilter | undefined>;
34
+ pair: Entity<MemberFilter | undefined>;
35
+
36
+ serdes: Entity<SerdesTable>;
37
+ bytespan: Entity<number>;
38
+ custom_id: Entity<((value: any) => Entity) | undefined>;
39
+
40
+ Shared: Tag;
41
+ Networked: Entity<MemberFilter | undefined>;
42
+ Reliable: Entity<MemberFilter | undefined>;
43
+ Unreliable: Entity<MemberFilter | undefined>;
44
+ Pair: Entity<MemberFilter | undefined>;
45
+
46
+ Serdes: Entity<SerdesTable>;
47
+ Bytespan: Entity<number>;
48
+ CustomId: Entity<((value: any) => Entity) | undefined>;
49
+ }
50
+
51
+ export interface Client {
52
+ world: ObserverWorld;
53
+ inited?: boolean;
54
+
55
+ init(world?: ObserverWorld): void;
56
+ destroy(): void;
57
+ after_replication(callback: () => void): void;
58
+
59
+ apply_updates(buf: buffer, all_variants?: any[][]): void;
60
+ apply_unreliable(buf: buffer, all_variants?: any[][]): void;
61
+ apply_full(buf: buffer, all_variants?: any[][]): void;
62
+ }
63
+
64
+ export interface Server {
65
+ world: ObserverWorld;
66
+ inited?: boolean;
67
+
68
+ init(world?: ObserverWorld): void;
69
+ destroy(): void;
70
+
71
+ get_full(player: Player): LuaTuple<[buffer, any[][]]>;
72
+ collect_updates(): IterableFunction<LuaTuple<[Player, buffer, any[][]]>>;
73
+ collect_unreliable(): IterableFunction<
74
+ LuaTuple<[Player, buffer, any[][]]>
75
+ >;
76
+ mark_player_ready(player: Player): void;
77
+ is_player_ready(player: Player): boolean;
78
+ }
79
+
80
+ export interface ReplecsLib {
81
+ client: Client;
82
+ server: Server;
83
+
84
+ after_replication(world: ObserverWorld): void;
85
+ }
86
+
87
+ export interface Replecs extends Components {
88
+ create: (world?: ObserverWorld) => ReplecsLib;
89
+ create_server: (world?: ObserverWorld) => Server;
90
+ create_client: (world?: ObserverWorld) => Client;
91
+ }
92
+ }
93
+
94
+ declare const Replecs: Replecs.Replecs;
95
+
96
+ export = Replecs;
@@ -0,0 +1,83 @@
1
+ local RunService = game:GetService "RunService"
2
+ local jecs = require(script.Parent.jecs)
3
+ local common = require(script.common)
4
+
5
+ local client = require(script.client)
6
+ local server = require(script.server)
7
+
8
+ export type Server = server.Server
9
+ export type Client = client.Client
10
+
11
+ export type Replecs = {
12
+ server: Server,
13
+ client: Client,
14
+
15
+ after_replication: (self: Replecs, callback: () -> ()) -> (),
16
+ }
17
+
18
+ type MemberFilter = {
19
+ [Player]: boolean,
20
+ }
21
+
22
+ local replecs = {
23
+ shared = jecs.tag(),
24
+ networked = jecs.component() :: jecs.Entity<MemberFilter?>,
25
+ reliable = jecs.component() :: jecs.Entity<MemberFilter?>,
26
+ unreliable = jecs.component() :: jecs.Entity<MemberFilter?>,
27
+ pair = jecs.tag(),
28
+
29
+ serdes = jecs.component() :: jecs.Entity<common.Serdes>,
30
+ bytespan = jecs.component() :: jecs.Entity<number>,
31
+ custom_id = jecs.component() :: jecs.Entity<(any) -> jecs.Entity>,
32
+ __alive_tracking__ = jecs.tag(),
33
+ }
34
+ replecs.__index = replecs
35
+ replecs.Serdes = replecs.serdes
36
+ replecs.Bytespan = replecs.bytespan
37
+ replecs.CustomId = replecs.custom_id
38
+ replecs.Networked = replecs.networked
39
+ replecs.Reliable = replecs.reliable
40
+ replecs.Unreliable = replecs.unreliable
41
+ replecs.Pair = replecs.pair
42
+ replecs.Shared = replecs.shared
43
+
44
+ jecs.meta(replecs.serdes, jecs.Name, "replecs.serdes")
45
+ jecs.meta(replecs.bytespan, jecs.Name, "replecs.bytespan")
46
+ jecs.meta(replecs.custom_id, jecs.Name, "replecs.custom_id")
47
+ jecs.meta(replecs.networked, jecs.Name, "replecs.networked")
48
+ jecs.meta(replecs.reliable, jecs.Name, "replecs.reliable")
49
+ jecs.meta(replecs.unreliable, jecs.Name, "replecs.unreliable")
50
+ jecs.meta(replecs.pair, jecs.Name, "replecs.pair")
51
+ jecs.meta(replecs.shared, jecs.Name, "replecs.shared")
52
+
53
+ function replecs.create_server(world: jecs.World?): Server
54
+ return server.create(world, replecs)
55
+ end
56
+
57
+ function replecs.create_client(world: jecs.World?): Client
58
+ return client.create(world, replecs)
59
+ end
60
+
61
+ function replecs.create(world: jecs.World?): Replecs
62
+ local lib = {} :: Replecs
63
+
64
+ if RunService:IsServer() then
65
+ lib.server = server.create(world, replecs)
66
+ end
67
+ if RunService:IsClient() then
68
+ lib.client = client.create(world, replecs)
69
+ end
70
+
71
+ return setmetatable(lib, replecs) :: any
72
+ end
73
+
74
+ function replecs.after_replication(lib: Replecs, callback: () -> ())
75
+ local lib_client = lib.client
76
+ if lib_client then
77
+ lib_client:after_replication(callback)
78
+ else
79
+ callback()
80
+ end
81
+ end
82
+
83
+ return replecs