@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 +3 -0
- package/out/init.luau +5 -0
- package/out/jecs.d.ts +1 -0
- package/out/jecs.luau +7 -0
- package/out/replecs/client.luau +354 -0
- package/out/replecs/common.luau +49 -0
- package/out/replecs/index.d.ts +96 -0
- package/out/replecs/init.luau +83 -0
- package/out/replecs/masking/init.luau +1191 -0
- package/out/replecs/masking/mask_generator.luau +97 -0
- package/out/replecs/server.luau +918 -0
- package/out/replecs/utils.luau +624 -0
- package/package.json +46 -0
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
--!optimize 2
|
|
2
|
+
--!native
|
|
3
|
+
|
|
4
|
+
local jecs = require(script.Parent.Parent.jecs)
|
|
5
|
+
local utils = require(script.Parent.utils)
|
|
6
|
+
local common = require(script.Parent.common)
|
|
7
|
+
local masking_controller = require(script.Parent.masking)
|
|
8
|
+
|
|
9
|
+
type Cursor = utils.Cursor
|
|
10
|
+
|
|
11
|
+
type World = jecs.World
|
|
12
|
+
type Entity<T = any> = jecs.Entity<T>
|
|
13
|
+
|
|
14
|
+
type Set<T> = { [T]: boolean }
|
|
15
|
+
type Map<K, V> = { [K]: V }
|
|
16
|
+
type Array<T> = { T }
|
|
17
|
+
|
|
18
|
+
type TrackInfo = {
|
|
19
|
+
entities: { [Entity]: boolean },
|
|
20
|
+
components: { [Entity]: { [Entity]: number } },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type EntityStorage = {
|
|
24
|
+
tags: { [Entity]: boolean },
|
|
25
|
+
values: { [Entity]: any },
|
|
26
|
+
pairs: { [Entity]: Set<Entity> },
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Storage = { [Entity]: EntityStorage }
|
|
30
|
+
|
|
31
|
+
export type Server = {
|
|
32
|
+
world: World,
|
|
33
|
+
inited: boolean?,
|
|
34
|
+
|
|
35
|
+
shared: common.Shared,
|
|
36
|
+
|
|
37
|
+
components: common.Components,
|
|
38
|
+
hooks: common.WorldHooks,
|
|
39
|
+
alive_tracked: { [Entity]: boolean },
|
|
40
|
+
|
|
41
|
+
track_info: TrackInfo,
|
|
42
|
+
additions: { [Entity]: boolean },
|
|
43
|
+
custom_ids: { [Entity]: Entity },
|
|
44
|
+
storage: Storage,
|
|
45
|
+
hooked: { () -> () },
|
|
46
|
+
masking: masking_controller.MaskingController,
|
|
47
|
+
connections: { RBXScriptConnection },
|
|
48
|
+
|
|
49
|
+
init: (self: Server, world: World?) -> (),
|
|
50
|
+
destroy: (self: Server) -> (),
|
|
51
|
+
|
|
52
|
+
get_full: (self: Server, player: Player) -> (buffer, { { any } }),
|
|
53
|
+
collect_updates: (self: Server) -> () -> (Player, buffer, { { any } }),
|
|
54
|
+
collect_unreliable: (self: Server) -> () -> (Player, buffer, { { any } }),
|
|
55
|
+
mark_player_ready: (self: Server, player: Player) -> (),
|
|
56
|
+
is_player_ready: (self: Server, player: Player) -> boolean,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
local cursor = utils.cursor
|
|
60
|
+
|
|
61
|
+
local NIL_COMPONENT_VALUE = newproxy()
|
|
62
|
+
|
|
63
|
+
local COMPONENT_TYPES = masking_controller.COMPONENT_TYPES
|
|
64
|
+
local TRACK_TYPES = {
|
|
65
|
+
component = 1,
|
|
66
|
+
tag = 2,
|
|
67
|
+
pair = 3,
|
|
68
|
+
}
|
|
69
|
+
local ENTITY_ID_TYPES = {
|
|
70
|
+
id = 1,
|
|
71
|
+
custom = 2,
|
|
72
|
+
shared = 3,
|
|
73
|
+
}
|
|
74
|
+
local ECS_COMPONENT = jecs.Component
|
|
75
|
+
|
|
76
|
+
local function IS_PAIR(id: Entity): boolean
|
|
77
|
+
return jecs.IS_PAIR(id)
|
|
78
|
+
end
|
|
79
|
+
local function PAIR_SECOND(world: World, id: Entity): Entity
|
|
80
|
+
return jecs.pair_second(world, id)
|
|
81
|
+
end
|
|
82
|
+
local function ECS_ID(entity: Entity): number
|
|
83
|
+
return jecs.ECS_ID(entity :: any)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
local function track_entity_lifetime(server: Server, entity: Entity)
|
|
87
|
+
if server.alive_tracked[entity] then
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
server.alive_tracked[entity] = true
|
|
91
|
+
server.world:add(entity, server.components.__alive_tracking__)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
local function get_or_set_entity_storage(server: Server, entity: Entity)
|
|
95
|
+
local storage = server.storage[entity]
|
|
96
|
+
|
|
97
|
+
if not storage then
|
|
98
|
+
storage = {
|
|
99
|
+
tags = {},
|
|
100
|
+
values = {},
|
|
101
|
+
pairs = {},
|
|
102
|
+
}
|
|
103
|
+
server.storage[entity] = storage
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return storage
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
local function allocate_component_change(server: Server, entity: Entity, component: Entity, value: any)
|
|
110
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
111
|
+
|
|
112
|
+
local non_nil = if value == nil then NIL_COMPONENT_VALUE else value
|
|
113
|
+
|
|
114
|
+
storage.values[component] = non_nil
|
|
115
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
116
|
+
server.masking:allocate_component_change(entity, component, non_nil)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
local function allocate_tag_addition(server: Server, entity: Entity, tag: Entity)
|
|
120
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
121
|
+
storage.tags[tag] = true
|
|
122
|
+
|
|
123
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
124
|
+
server.masking:allocate_tag_addition(entity, tag)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
local function allocate_pair_addition(server: Server, entity: Entity, relation: Entity, target: Entity)
|
|
128
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
129
|
+
local targets = storage.pairs[relation]
|
|
130
|
+
if targets == nil then
|
|
131
|
+
targets = {}
|
|
132
|
+
storage.pairs[relation] = targets
|
|
133
|
+
end
|
|
134
|
+
targets[target] = true
|
|
135
|
+
|
|
136
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
137
|
+
server.masking:allocate_pair_addition(entity, relation, target)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
local function allocate_component_removal(server: Server, entity: Entity, component: Entity)
|
|
142
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
143
|
+
storage.values[component] = nil
|
|
144
|
+
|
|
145
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
146
|
+
server.masking:allocate_component_removal(entity, component)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
local function allocate_tag_removal(server: Server, entity: Entity, tag: Entity)
|
|
150
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
151
|
+
storage.tags[tag] = nil
|
|
152
|
+
|
|
153
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
154
|
+
server.masking:allocate_tag_removal(entity, tag)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
local function allocate_pair_removal(server: Server, entity: Entity, relation: Entity, target: Entity)
|
|
158
|
+
local storage = get_or_set_entity_storage(server, entity)
|
|
159
|
+
local targets = storage.pairs[relation]
|
|
160
|
+
if targets then
|
|
161
|
+
targets[target] = nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
if not server.additions[entity] and server.track_info.entities[entity] then
|
|
165
|
+
server.masking:allocate_pair_removal(entity, relation, target)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
local function track_component(server: Server, component: Entity<any>)
|
|
170
|
+
local world = server.world
|
|
171
|
+
local hooks = server.hooks
|
|
172
|
+
|
|
173
|
+
local info = {}
|
|
174
|
+
server.track_info.components[component] = info
|
|
175
|
+
|
|
176
|
+
local function hook(unhook: () -> ())
|
|
177
|
+
table.insert(server.hooked, unhook)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
hook(hooks.added(world, component, function(entity, id, value)
|
|
181
|
+
local track_type = info[entity]
|
|
182
|
+
if not track_type then
|
|
183
|
+
return
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
if track_type == TRACK_TYPES.component then
|
|
187
|
+
allocate_component_change(server, entity, component, value)
|
|
188
|
+
elseif track_type == TRACK_TYPES.tag then
|
|
189
|
+
allocate_tag_addition(server, entity, component)
|
|
190
|
+
else
|
|
191
|
+
if not IS_PAIR(id) then
|
|
192
|
+
utils.logerror "a non-pair tag was added to a pair tracked component"
|
|
193
|
+
end
|
|
194
|
+
allocate_pair_addition(server, entity, component, PAIR_SECOND(world, id))
|
|
195
|
+
end
|
|
196
|
+
end))
|
|
197
|
+
hook(hooks.changed(world, component, function(entity, id, value)
|
|
198
|
+
local track_type = info[entity]
|
|
199
|
+
if not track_type then
|
|
200
|
+
return
|
|
201
|
+
end
|
|
202
|
+
-- no check here because only components can trigger changed (I think?)
|
|
203
|
+
allocate_component_change(server, entity, component, value)
|
|
204
|
+
end))
|
|
205
|
+
hook(hooks.removed(world, component, function(entity, id)
|
|
206
|
+
local track_type = info[entity]
|
|
207
|
+
if not track_type then
|
|
208
|
+
return
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if track_type == TRACK_TYPES.component then
|
|
212
|
+
allocate_component_removal(server, entity, component)
|
|
213
|
+
elseif track_type == TRACK_TYPES.tag then
|
|
214
|
+
allocate_tag_removal(server, entity, component)
|
|
215
|
+
else
|
|
216
|
+
if not IS_PAIR(id) then
|
|
217
|
+
utils.logerror "a non-pair tag was removed for a pair tracked component"
|
|
218
|
+
end
|
|
219
|
+
allocate_pair_removal(server, entity, component, PAIR_SECOND(world, id))
|
|
220
|
+
end
|
|
221
|
+
end))
|
|
222
|
+
|
|
223
|
+
return info
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
local function track_entity_component(server: Server, entity: Entity, component: Entity<any>)
|
|
227
|
+
local world = server.world
|
|
228
|
+
|
|
229
|
+
local info = server.track_info.components[component]
|
|
230
|
+
if not info then
|
|
231
|
+
info = track_component(server, component)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
local entity_storage = get_or_set_entity_storage(server, entity)
|
|
235
|
+
|
|
236
|
+
if world:has(component, ECS_COMPONENT) then
|
|
237
|
+
-- component
|
|
238
|
+
if world:has(entity, component) then
|
|
239
|
+
local value = world:get(entity, component)
|
|
240
|
+
|
|
241
|
+
if value == nil then
|
|
242
|
+
entity_storage.values[component] = NIL_COMPONENT_VALUE
|
|
243
|
+
else
|
|
244
|
+
entity_storage.values[component] = value
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
info[entity] = TRACK_TYPES.component
|
|
249
|
+
|
|
250
|
+
return COMPONENT_TYPES.component
|
|
251
|
+
else
|
|
252
|
+
-- tag
|
|
253
|
+
if world:has(entity, component) then
|
|
254
|
+
entity_storage.tags[component] = true
|
|
255
|
+
end
|
|
256
|
+
info[entity] = TRACK_TYPES.tag
|
|
257
|
+
|
|
258
|
+
return COMPONENT_TYPES.tag
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
local function track_entity_pair(server: Server, entity: Entity, relation: Entity)
|
|
263
|
+
local world = server.world
|
|
264
|
+
local info = server.track_info.components[relation]
|
|
265
|
+
if not info then
|
|
266
|
+
info = track_component(server, relation)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
local entity_storage = get_or_set_entity_storage(server, entity)
|
|
270
|
+
local targets = {}
|
|
271
|
+
local index = 0
|
|
272
|
+
|
|
273
|
+
while true do
|
|
274
|
+
local target = world:target(entity, relation, index)
|
|
275
|
+
if not target then
|
|
276
|
+
break
|
|
277
|
+
end
|
|
278
|
+
index += 1
|
|
279
|
+
targets[target] = true
|
|
280
|
+
end
|
|
281
|
+
entity_storage.pairs[relation] = targets
|
|
282
|
+
|
|
283
|
+
info[entity] = TRACK_TYPES.pair
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
local function untrack_entity_component(server: Server, entity: Entity, component: Entity<any>): number?
|
|
287
|
+
local info = server.track_info.components[component]
|
|
288
|
+
if not info then
|
|
289
|
+
return nil
|
|
290
|
+
end
|
|
291
|
+
local listen_type = info[entity]
|
|
292
|
+
info[entity] = nil
|
|
293
|
+
|
|
294
|
+
local storage = server.storage[entity]
|
|
295
|
+
if storage then
|
|
296
|
+
storage.values[component] = nil
|
|
297
|
+
storage.tags[component] = nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
return listen_type
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
local function untrack_entity_pair(server: Server, entity: Entity, relation: Entity)
|
|
304
|
+
local info = server.track_info.components[relation]
|
|
305
|
+
if not info then
|
|
306
|
+
return
|
|
307
|
+
end
|
|
308
|
+
info[entity] = nil
|
|
309
|
+
|
|
310
|
+
local storage = server.storage[entity]
|
|
311
|
+
if storage then
|
|
312
|
+
storage.pairs[relation] = nil
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
local function write_component_id(server: Server, c: Cursor, component: Entity)
|
|
317
|
+
local encoded = server.shared.ids[component]
|
|
318
|
+
if not encoded then
|
|
319
|
+
utils.logerror(`attempted to replicate a non-shared component `, utils.logcomponent(server.world, component))
|
|
320
|
+
return
|
|
321
|
+
end
|
|
322
|
+
cursor.writeu8(c, encoded)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
local function write_component_value(server: Server, c: Cursor, component: Entity, value: any, variants: { any })
|
|
326
|
+
local serdes = server.shared.serdes[component]
|
|
327
|
+
if serdes then
|
|
328
|
+
local output = serdes.serialize(if value == NIL_COMPONENT_VALUE then nil else value)
|
|
329
|
+
|
|
330
|
+
local bytespan = server.shared.bytespan[component]
|
|
331
|
+
cursor.write_buffer(c, output)
|
|
332
|
+
if bytespan then
|
|
333
|
+
if bytespan ~= buffer.len(output) then
|
|
334
|
+
utils.logerror(
|
|
335
|
+
`bytespan: {bytespan} mismatch for buffer lenght: {buffer.len(output)} in component`,
|
|
336
|
+
utils.logcomponent(server.world, component)
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
else
|
|
340
|
+
local len = buffer.len(output)
|
|
341
|
+
cursor.write_vlq(c, len)
|
|
342
|
+
end
|
|
343
|
+
else
|
|
344
|
+
if value == NIL_COMPONENT_VALUE then
|
|
345
|
+
-- this is zero for vlq
|
|
346
|
+
cursor.writeu8(c, 128)
|
|
347
|
+
else
|
|
348
|
+
table.insert(variants, value :: any)
|
|
349
|
+
cursor.write_vlq(c, #variants)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
write_component_id(server, c, component)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
local function write_entity_id(server: Server, c: Cursor, entity: Entity, variants: { any }, multiply: number?)
|
|
357
|
+
local custom = server.custom_ids[entity]
|
|
358
|
+
|
|
359
|
+
if custom then
|
|
360
|
+
local value = server.world:get(entity, custom)
|
|
361
|
+
write_component_value(server, c, custom, value, variants)
|
|
362
|
+
cursor.writei8(c, ENTITY_ID_TYPES.custom * (multiply or 1))
|
|
363
|
+
else
|
|
364
|
+
local shared_id = server.shared.ids[entity]
|
|
365
|
+
if shared_id then
|
|
366
|
+
cursor.writeu8(c, shared_id)
|
|
367
|
+
cursor.writei8(c, ENTITY_ID_TYPES.shared * (multiply or 1))
|
|
368
|
+
else
|
|
369
|
+
cursor.writeu24(c, ECS_ID(entity :: any))
|
|
370
|
+
cursor.writei8(c, ENTITY_ID_TYPES.id * (multiply or 1))
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
local function write_entity(
|
|
376
|
+
server: Server,
|
|
377
|
+
c: Cursor,
|
|
378
|
+
entity: Entity,
|
|
379
|
+
active: masking_controller.ActiveEntity,
|
|
380
|
+
variants: { any }
|
|
381
|
+
)
|
|
382
|
+
local storage = server.storage[entity]
|
|
383
|
+
|
|
384
|
+
local all_pairs = 0
|
|
385
|
+
for relation in active.components[COMPONENT_TYPES.pair] do
|
|
386
|
+
local targets = storage.pairs[relation]
|
|
387
|
+
local total_targets = 0
|
|
388
|
+
|
|
389
|
+
for target in targets do
|
|
390
|
+
write_entity_id(server, c, target, variants)
|
|
391
|
+
total_targets += 1
|
|
392
|
+
end
|
|
393
|
+
cursor.write_vlq(c, total_targets)
|
|
394
|
+
write_component_id(server, c, relation)
|
|
395
|
+
all_pairs += 1
|
|
396
|
+
end
|
|
397
|
+
cursor.write_vlq(c, all_pairs)
|
|
398
|
+
|
|
399
|
+
local components = 0
|
|
400
|
+
for component in active.components[COMPONENT_TYPES.component] do
|
|
401
|
+
local value = storage.values[component]
|
|
402
|
+
write_component_value(server, c, component, value, variants)
|
|
403
|
+
components += 1
|
|
404
|
+
end
|
|
405
|
+
cursor.write_vlq(c, components)
|
|
406
|
+
|
|
407
|
+
local tags = 0
|
|
408
|
+
for tag in active.components[COMPONENT_TYPES.tag] do
|
|
409
|
+
local has_tag = storage.tags[tag]
|
|
410
|
+
if has_tag then
|
|
411
|
+
write_component_id(server, c, tag)
|
|
412
|
+
tags += 1
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
cursor.write_vlq(c, tags)
|
|
416
|
+
write_entity_id(server, c, entity, variants)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
type MemberPackets = {
|
|
420
|
+
buffers: { buffer },
|
|
421
|
+
variants: { { any } },
|
|
422
|
+
total_size: number,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
local function append_packet(
|
|
426
|
+
packets: { [Player]: MemberPackets },
|
|
427
|
+
members: { any },
|
|
428
|
+
output: buffer,
|
|
429
|
+
variants: { { any } }
|
|
430
|
+
)
|
|
431
|
+
local len = buffer.len(output)
|
|
432
|
+
for _, member in members do
|
|
433
|
+
local member_packets = packets[member]
|
|
434
|
+
|
|
435
|
+
if member_packets then
|
|
436
|
+
table.insert(member_packets.buffers, output)
|
|
437
|
+
table.insert(member_packets.variants, variants)
|
|
438
|
+
member_packets.total_size += len
|
|
439
|
+
else
|
|
440
|
+
member_packets = {
|
|
441
|
+
buffers = { output },
|
|
442
|
+
variants = { variants },
|
|
443
|
+
total_size = len,
|
|
444
|
+
}
|
|
445
|
+
packets[member] = member_packets :: MemberPackets
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
local function combine_packet_outputs(outputs: { buffer }, total_size: number): buffer
|
|
451
|
+
local combined = buffer.create(total_size + utils.vlq_span(#outputs))
|
|
452
|
+
local offset = 0
|
|
453
|
+
|
|
454
|
+
for _, output in outputs do
|
|
455
|
+
buffer.copy(combined, offset, output)
|
|
456
|
+
offset += buffer.len(output)
|
|
457
|
+
end
|
|
458
|
+
cursor.write_vlq({
|
|
459
|
+
offset = offset,
|
|
460
|
+
buffer = combined,
|
|
461
|
+
}, #outputs)
|
|
462
|
+
|
|
463
|
+
return combined
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
local function create_packet_iterator(packets: { [Player]: MemberPackets })
|
|
467
|
+
local iterated: Player? = nil
|
|
468
|
+
local function iterator()
|
|
469
|
+
local player, packets = next(packets, iterated)
|
|
470
|
+
if not player then
|
|
471
|
+
return nil :: any
|
|
472
|
+
end
|
|
473
|
+
iterated = player
|
|
474
|
+
|
|
475
|
+
local combined = combine_packet_outputs(packets.buffers, packets.total_size)
|
|
476
|
+
return player, combined, packets.variants
|
|
477
|
+
end
|
|
478
|
+
return iterator :: () -> (Player, buffer, { { any } })
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
local function get_full(server: Server, member: Player): (buffer, { { any } })
|
|
482
|
+
local index = server.masking.member_indexes[member]
|
|
483
|
+
if index == nil then
|
|
484
|
+
utils.logerror "attempted to replicate for a non registered member"
|
|
485
|
+
end
|
|
486
|
+
local buffers: { buffer } = {}
|
|
487
|
+
local all_variants: { { any } } = {}
|
|
488
|
+
local total_size = 0
|
|
489
|
+
|
|
490
|
+
-- maybe edges to quickly find all relevant storages improves performance?
|
|
491
|
+
for _, storage in server.masking.storages do
|
|
492
|
+
if not storage.mask.bitmask:get(index) then
|
|
493
|
+
continue
|
|
494
|
+
end
|
|
495
|
+
local c = cursor.new()
|
|
496
|
+
local variants = {}
|
|
497
|
+
|
|
498
|
+
local total_entities = 0
|
|
499
|
+
for entity, active in storage.active do
|
|
500
|
+
write_entity(server, c, entity, active, variants)
|
|
501
|
+
total_entities += 1
|
|
502
|
+
end
|
|
503
|
+
cursor.write_vlq(c, total_entities)
|
|
504
|
+
|
|
505
|
+
local output = cursor.close(c)
|
|
506
|
+
table.insert(buffers, output)
|
|
507
|
+
table.insert(all_variants, variants)
|
|
508
|
+
total_size += buffer.len(output)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
return combine_packet_outputs(buffers, total_size), all_variants
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
local function collect_updates(server: Server)
|
|
515
|
+
local packets: { [Player]: MemberPackets } = {}
|
|
516
|
+
|
|
517
|
+
for _, storage in server.masking.storages do
|
|
518
|
+
local c = cursor.new()
|
|
519
|
+
local variants = {}
|
|
520
|
+
|
|
521
|
+
local total_deleted = 0
|
|
522
|
+
local entity_deletions = storage.deletions.entities
|
|
523
|
+
for entity in entity_deletions do
|
|
524
|
+
write_entity_id(server, c, entity, variants)
|
|
525
|
+
total_deleted += 1
|
|
526
|
+
end
|
|
527
|
+
table.clear(entity_deletions)
|
|
528
|
+
cursor.write_vlq(c, total_deleted)
|
|
529
|
+
|
|
530
|
+
local total_component_deleted = 0
|
|
531
|
+
local component_deletions = storage.deletions.components
|
|
532
|
+
for entity, deleted in component_deletions do
|
|
533
|
+
local total_pairs = 0
|
|
534
|
+
for relation in deleted[COMPONENT_TYPES.pair] do
|
|
535
|
+
write_component_id(server, c, relation)
|
|
536
|
+
total_pairs += 1
|
|
537
|
+
end
|
|
538
|
+
cursor.write_vlq(c, total_pairs)
|
|
539
|
+
|
|
540
|
+
local total_components = 0
|
|
541
|
+
for component in deleted[COMPONENT_TYPES.component] do
|
|
542
|
+
write_component_id(server, c, component)
|
|
543
|
+
total_components += 1
|
|
544
|
+
end
|
|
545
|
+
cursor.write_vlq(c, total_components)
|
|
546
|
+
|
|
547
|
+
local total_tags = 0
|
|
548
|
+
for tag in deleted[COMPONENT_TYPES.tag] do
|
|
549
|
+
write_component_id(server, c, tag)
|
|
550
|
+
total_tags += 1
|
|
551
|
+
end
|
|
552
|
+
cursor.write_vlq(c, total_tags)
|
|
553
|
+
|
|
554
|
+
write_entity_id(server, c, entity, variants)
|
|
555
|
+
total_component_deleted += 1
|
|
556
|
+
end
|
|
557
|
+
table.clear(component_deletions)
|
|
558
|
+
cursor.write_vlq(c, total_component_deleted)
|
|
559
|
+
|
|
560
|
+
local storage_changes = storage.changes
|
|
561
|
+
local changed = 0
|
|
562
|
+
for entity, changes in storage_changes.changed do
|
|
563
|
+
local total_removed = 0
|
|
564
|
+
for component, value in changes.removed do
|
|
565
|
+
write_component_id(server, c, component)
|
|
566
|
+
total_removed += 1
|
|
567
|
+
end
|
|
568
|
+
cursor.write_vlq(c, total_removed)
|
|
569
|
+
|
|
570
|
+
local total_pairs = 0
|
|
571
|
+
for relation, targets in changes.pairs do
|
|
572
|
+
local total_targets = 0
|
|
573
|
+
|
|
574
|
+
for target, added in targets do
|
|
575
|
+
if added then
|
|
576
|
+
write_entity_id(server, c, target, variants)
|
|
577
|
+
else
|
|
578
|
+
write_entity_id(server, c, target, variants, -1)
|
|
579
|
+
end
|
|
580
|
+
total_targets += 1
|
|
581
|
+
end
|
|
582
|
+
cursor.write_vlq(c, total_targets)
|
|
583
|
+
write_component_id(server, c, relation)
|
|
584
|
+
total_pairs += 1
|
|
585
|
+
end
|
|
586
|
+
cursor.write_vlq(c, total_pairs)
|
|
587
|
+
|
|
588
|
+
local total_components = 0
|
|
589
|
+
for component, value in changes.component do
|
|
590
|
+
write_component_value(server, c, component, value, variants)
|
|
591
|
+
total_components += 1
|
|
592
|
+
end
|
|
593
|
+
cursor.write_vlq(c, total_components)
|
|
594
|
+
|
|
595
|
+
local total_tagged = 0
|
|
596
|
+
for tag in changes.tagged do
|
|
597
|
+
write_component_id(server, c, tag)
|
|
598
|
+
total_tagged += 1
|
|
599
|
+
end
|
|
600
|
+
cursor.write_vlq(c, total_tagged)
|
|
601
|
+
|
|
602
|
+
write_entity_id(server, c, entity, variants)
|
|
603
|
+
changed += 1
|
|
604
|
+
end
|
|
605
|
+
table.clear(storage_changes.changed)
|
|
606
|
+
cursor.write_vlq(c, changed)
|
|
607
|
+
|
|
608
|
+
local total_added = 0
|
|
609
|
+
for entity in storage_changes.added do
|
|
610
|
+
local active = storage.active[entity]
|
|
611
|
+
if not active then
|
|
612
|
+
continue
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
write_entity(server, c, entity, active, variants)
|
|
616
|
+
total_added += 1
|
|
617
|
+
end
|
|
618
|
+
table.clear(storage_changes.added)
|
|
619
|
+
cursor.write_vlq(c, total_added)
|
|
620
|
+
|
|
621
|
+
local output = cursor.close(c)
|
|
622
|
+
append_packet(packets, storage.mask.members, output, variants)
|
|
623
|
+
end
|
|
624
|
+
table.clear(server.additions)
|
|
625
|
+
|
|
626
|
+
return create_packet_iterator(packets)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
local function collect_unreliable(server: Server)
|
|
630
|
+
-- todo: max packet size (for unreliables 1kb limit)
|
|
631
|
+
local packets: { [Player]: MemberPackets } = {}
|
|
632
|
+
local world = server.world
|
|
633
|
+
|
|
634
|
+
-- maybe more lookups to quickly iterate these too? ;-;
|
|
635
|
+
for _, mask_storage in server.masking.storages do
|
|
636
|
+
local c = cursor.new()
|
|
637
|
+
local variants = {}
|
|
638
|
+
|
|
639
|
+
local active = mask_storage.active
|
|
640
|
+
local total_entities = 0
|
|
641
|
+
|
|
642
|
+
for entity, actives in active do
|
|
643
|
+
local total_unreliable = 0
|
|
644
|
+
for component in actives.components[COMPONENT_TYPES.unreliable] do
|
|
645
|
+
local value = world:get(entity, component)
|
|
646
|
+
if value == nil then
|
|
647
|
+
continue
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
write_component_value(server, c, component, value, variants)
|
|
651
|
+
total_unreliable += 1
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
if total_unreliable <= 0 then
|
|
655
|
+
continue
|
|
656
|
+
end
|
|
657
|
+
total_entities += 1
|
|
658
|
+
cursor.write_vlq(c, total_unreliable)
|
|
659
|
+
write_entity_id(server, c, entity, variants)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
if total_entities <= 0 then
|
|
663
|
+
continue
|
|
664
|
+
end
|
|
665
|
+
cursor.write_vlq(c, total_entities)
|
|
666
|
+
|
|
667
|
+
local output = cursor.close(c)
|
|
668
|
+
append_packet(packets, mask_storage.mask.members, output, variants)
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
return create_packet_iterator(packets)
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
local function check_created(server: Server)
|
|
675
|
+
if server.inited == true then
|
|
676
|
+
warn "attempted to init a server twice"
|
|
677
|
+
return false
|
|
678
|
+
end
|
|
679
|
+
if server.inited == nil then
|
|
680
|
+
warn "attempted to create a server twice"
|
|
681
|
+
return false
|
|
682
|
+
end
|
|
683
|
+
return true
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
function init(server: Server, _world: World?)
|
|
687
|
+
if not check_created(server) then
|
|
688
|
+
return
|
|
689
|
+
end
|
|
690
|
+
server.inited = true :: any
|
|
691
|
+
|
|
692
|
+
local world = _world or server.world
|
|
693
|
+
server.world = world
|
|
694
|
+
|
|
695
|
+
if not world then
|
|
696
|
+
error "Providing a world is required to start replecs"
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
local hooks = {
|
|
700
|
+
added = (world :: any).added,
|
|
701
|
+
changed = (world :: any).changed,
|
|
702
|
+
removed = (world :: any).removed,
|
|
703
|
+
}
|
|
704
|
+
server.hooks = hooks
|
|
705
|
+
|
|
706
|
+
local components = server.components
|
|
707
|
+
local masking = server.masking
|
|
708
|
+
|
|
709
|
+
server.shared = utils.create_shared_lookup(world, components)
|
|
710
|
+
|
|
711
|
+
local function hook(unhook: () -> ())
|
|
712
|
+
table.insert(server.hooked, unhook)
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
hook(hooks.added(world, components.networked, function(entity, id, filter)
|
|
716
|
+
masking:unregister_stop_entity(entity)
|
|
717
|
+
masking:start_entity(entity, filter)
|
|
718
|
+
masking:propagate_entity_addition(entity)
|
|
719
|
+
|
|
720
|
+
server.additions[entity] = true
|
|
721
|
+
server.track_info.entities[entity] = true
|
|
722
|
+
track_entity_lifetime(server, entity)
|
|
723
|
+
end))
|
|
724
|
+
hook(hooks.changed(world, components.networked, function(entity, id, filter)
|
|
725
|
+
masking:set_entity(entity, filter)
|
|
726
|
+
end))
|
|
727
|
+
hook(hooks.removed(world, components.networked, function(entity)
|
|
728
|
+
masking:register_stop_entity(entity)
|
|
729
|
+
masking:stop_entity(entity)
|
|
730
|
+
|
|
731
|
+
server.additions[entity] = nil
|
|
732
|
+
server.track_info.entities[entity] = nil
|
|
733
|
+
end))
|
|
734
|
+
|
|
735
|
+
hook(hooks.added(world, components.reliable, function(entity, id, filter)
|
|
736
|
+
if not IS_PAIR(id) then
|
|
737
|
+
utils.logerror "reliable should be used with a pair relationship"
|
|
738
|
+
end
|
|
739
|
+
local component = PAIR_SECOND(world, id)
|
|
740
|
+
|
|
741
|
+
local component_type = track_entity_component(server :: Server, entity, component)
|
|
742
|
+
masking:unregister_stop_component(entity, component, component_type)
|
|
743
|
+
masking:start_component(entity, component, component_type, filter)
|
|
744
|
+
if server.additions[entity] then
|
|
745
|
+
masking:allocate_entity_addition(entity, component, component_type)
|
|
746
|
+
end
|
|
747
|
+
end))
|
|
748
|
+
hook(hooks.changed(world, components.reliable, function(entity, id, filter)
|
|
749
|
+
if not IS_PAIR(id) then
|
|
750
|
+
utils.logerror "reliable should be used with a pair relationship"
|
|
751
|
+
end
|
|
752
|
+
local component = PAIR_SECOND(world, id)
|
|
753
|
+
|
|
754
|
+
if world:has(component, ECS_COMPONENT) then
|
|
755
|
+
masking:set_component(entity, component, COMPONENT_TYPES.component, filter)
|
|
756
|
+
else
|
|
757
|
+
masking:set_component(entity, component, COMPONENT_TYPES.tag, filter)
|
|
758
|
+
end
|
|
759
|
+
end))
|
|
760
|
+
hook(hooks.removed(world, components.reliable, function(entity, id)
|
|
761
|
+
if not IS_PAIR(id) then
|
|
762
|
+
utils.logerror "reliable should be used with a pair relationship"
|
|
763
|
+
end
|
|
764
|
+
local component = PAIR_SECOND(world, id)
|
|
765
|
+
local component_type = untrack_entity_component(server, entity, component)
|
|
766
|
+
if not component_type then
|
|
767
|
+
return
|
|
768
|
+
end
|
|
769
|
+
masking:register_stop_component(entity, component, component_type)
|
|
770
|
+
masking:stop_component(entity, component, component_type)
|
|
771
|
+
end))
|
|
772
|
+
|
|
773
|
+
hook(hooks.added(world, components.pair, function(entity, id, filter)
|
|
774
|
+
if not IS_PAIR(id) then
|
|
775
|
+
utils.logerror "pair should be used with a relationship"
|
|
776
|
+
end
|
|
777
|
+
local relation = PAIR_SECOND(world, id)
|
|
778
|
+
track_entity_pair(server, entity, relation)
|
|
779
|
+
masking:unregister_stop_component(entity, relation, COMPONENT_TYPES.pair)
|
|
780
|
+
masking:start_component(entity, relation, COMPONENT_TYPES.pair, filter)
|
|
781
|
+
end))
|
|
782
|
+
hook(hooks.changed(world, components.pair, function(entity, id, filter)
|
|
783
|
+
if not IS_PAIR(id) then
|
|
784
|
+
utils.logerror "reliable should be used with a pair relationship"
|
|
785
|
+
end
|
|
786
|
+
local relation = PAIR_SECOND(world, id)
|
|
787
|
+
masking:set_component(entity, relation, COMPONENT_TYPES.pair, filter)
|
|
788
|
+
end))
|
|
789
|
+
hook(hooks.removed(world, components.pair, function(entity, id)
|
|
790
|
+
if not IS_PAIR(id) then
|
|
791
|
+
utils.logerror "reliable should be used with a pair relationship"
|
|
792
|
+
end
|
|
793
|
+
local relation = PAIR_SECOND(world, id)
|
|
794
|
+
untrack_entity_pair(server, entity, relation)
|
|
795
|
+
masking:register_stop_component(entity, relation, COMPONENT_TYPES.pair)
|
|
796
|
+
masking:stop_component(entity, relation, COMPONENT_TYPES.pair)
|
|
797
|
+
end))
|
|
798
|
+
|
|
799
|
+
hook(hooks.added(world, components.unreliable, function(entity, id, filter)
|
|
800
|
+
if not IS_PAIR(id) then
|
|
801
|
+
utils.logerror "unreliable should be used with a pair relationship"
|
|
802
|
+
end
|
|
803
|
+
local component = PAIR_SECOND(world, id)
|
|
804
|
+
masking:unregister_stop_component(entity, component, COMPONENT_TYPES.unreliable)
|
|
805
|
+
masking:start_component(entity, component, COMPONENT_TYPES.unreliable, filter)
|
|
806
|
+
if server.additions[entity] then
|
|
807
|
+
masking:allocate_entity_addition(entity, component, COMPONENT_TYPES.unreliable)
|
|
808
|
+
end
|
|
809
|
+
end))
|
|
810
|
+
hook(hooks.changed(world, components.unreliable, function(entity, id, filter)
|
|
811
|
+
if not IS_PAIR(id) then
|
|
812
|
+
utils.logerror "unreliable should be used with a pair relationship"
|
|
813
|
+
end
|
|
814
|
+
local component = PAIR_SECOND(world, id)
|
|
815
|
+
masking:set_component(entity, component, COMPONENT_TYPES.unreliable, filter)
|
|
816
|
+
end))
|
|
817
|
+
hook(hooks.removed(world, components.unreliable, function(entity, id)
|
|
818
|
+
if not IS_PAIR(id) then
|
|
819
|
+
utils.logerror "unreliable should be used with a pair relationship"
|
|
820
|
+
end
|
|
821
|
+
local component = PAIR_SECOND(world, id)
|
|
822
|
+
untrack_entity_component(server, entity, component, COMPONENT_TYPES.unreliable)
|
|
823
|
+
masking:register_stop_component(entity, component, COMPONENT_TYPES.unreliable)
|
|
824
|
+
masking:stop_component(entity, component, COMPONENT_TYPES.unreliable)
|
|
825
|
+
end))
|
|
826
|
+
|
|
827
|
+
hook(hooks.added(world, components.custom_id, function(entity, id)
|
|
828
|
+
if not IS_PAIR(id) then
|
|
829
|
+
utils.logerror "custom_id should be used with a relationship in the server"
|
|
830
|
+
end
|
|
831
|
+
local target = PAIR_SECOND(world, id)
|
|
832
|
+
if server.custom_ids[entity] then
|
|
833
|
+
utils.logwarn("attempted to register a custom_id twice for the same entity", debug.traceback())
|
|
834
|
+
return
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
server.custom_ids[entity] = target
|
|
838
|
+
end))
|
|
839
|
+
hook(hooks.removed(world, components.custom_id, function(entity, id)
|
|
840
|
+
server.custom_ids[entity] = nil
|
|
841
|
+
end))
|
|
842
|
+
|
|
843
|
+
hook(hooks.removed(world, components.__alive_tracking__, function(entity)
|
|
844
|
+
-- TODO: cleanup entity garbage on deletion
|
|
845
|
+
server.custom_ids[entity] = nil
|
|
846
|
+
end))
|
|
847
|
+
|
|
848
|
+
if game and game:GetService("RunService"):IsServer() then
|
|
849
|
+
local added = game:GetService("Players").PlayerAdded:Connect(function(player)
|
|
850
|
+
server.masking:register_member(player)
|
|
851
|
+
end)
|
|
852
|
+
local removed = game:GetService("Players").PlayerRemoving:Connect(function(player)
|
|
853
|
+
server.masking:unregister_member(player)
|
|
854
|
+
end)
|
|
855
|
+
|
|
856
|
+
table.insert(server.connections, added)
|
|
857
|
+
table.insert(server.connections, removed)
|
|
858
|
+
end
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
local function mark_player_ready(server: Server, player: Player)
|
|
862
|
+
server.masking:activate_member(player)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
local function is_player_ready(server: Server, player: Player): boolean
|
|
866
|
+
return server.masking:member_is_active(player)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
local function destroy(server: Server)
|
|
870
|
+
if server.inited == nil then
|
|
871
|
+
return warn "attempted to destroy a server twice"
|
|
872
|
+
end
|
|
873
|
+
server.inited = nil :: any
|
|
874
|
+
|
|
875
|
+
for _, unhook in server.hooked do
|
|
876
|
+
unhook()
|
|
877
|
+
end
|
|
878
|
+
for _, connection in server.connections do
|
|
879
|
+
connection:Disconnect()
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
local server = {}
|
|
884
|
+
server.__index = server
|
|
885
|
+
server.init = init
|
|
886
|
+
server.destroy = destroy
|
|
887
|
+
server.get_full = get_full
|
|
888
|
+
server.collect_updates = collect_updates
|
|
889
|
+
server.collect_unreliable = collect_unreliable
|
|
890
|
+
server.mark_player_ready = mark_player_ready
|
|
891
|
+
server.is_player_ready = is_player_ready
|
|
892
|
+
|
|
893
|
+
local function create(world: World?, components: common.Components): Server
|
|
894
|
+
local self = {} :: Server
|
|
895
|
+
|
|
896
|
+
self.components = components
|
|
897
|
+
|
|
898
|
+
self.shared = {} :: common.Shared
|
|
899
|
+
self.world = world :: any
|
|
900
|
+
self.additions = {}
|
|
901
|
+
self.storage = {}
|
|
902
|
+
self.hooked = {}
|
|
903
|
+
self.track_info = {
|
|
904
|
+
entities = {},
|
|
905
|
+
components = {},
|
|
906
|
+
}
|
|
907
|
+
self.connections = {}
|
|
908
|
+
self.inited = false
|
|
909
|
+
self.masking = masking_controller.create()
|
|
910
|
+
self.alive_tracked = {}
|
|
911
|
+
self.custom_ids = {}
|
|
912
|
+
|
|
913
|
+
return setmetatable(self, server) :: any
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
server.create = create
|
|
917
|
+
|
|
918
|
+
return server :: { create: typeof(create) }
|