@rbxts/replecs 0.3.1 → 0.4.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replecs/client/init.luau +1760 -0
- package/out/replecs/common.luau +17 -3
- package/out/replecs/cursor.luau +268 -0
- package/out/replecs/handshake.luau +108 -0
- package/out/replecs/init.luau +59 -15
- package/out/replecs/server/bitmasks.luau +322 -0
- package/out/replecs/server/filter_group.luau +196 -0
- package/out/replecs/server/init.luau +3949 -0
- package/out/replecs/server/registry.luau +178 -0
- package/out/replecs/server/utils.luau +20 -0
- package/out/replecs/shared.luau +70 -0
- package/out/replecs/types.luau +31 -16
- package/out/replecs/utils.luau +14 -784
- package/out/replecs/ver.luau +1 -1
- package/package.json +5 -3
- package/out/replecs/client.luau +0 -1673
- package/out/replecs/index.d.ts +0 -257
- package/out/replecs/masking/init.luau +0 -1695
- package/out/replecs/masking/mask_generator.luau +0 -106
- package/out/replecs/server.luau +0 -2216
- package/out/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,1760 @@
|
|
|
1
|
+
--!optimize 2
|
|
2
|
+
|
|
3
|
+
local common = require (script.Parent:WaitForChild('common'))
|
|
4
|
+
local cursor = require (script.Parent:WaitForChild('cursor'))
|
|
5
|
+
local types = require (script.Parent:WaitForChild('types'))
|
|
6
|
+
local customid = require (script.Parent:WaitForChild('customid'))
|
|
7
|
+
local shared = require (script.Parent:WaitForChild('shared'))
|
|
8
|
+
local handshake = require (script.Parent:WaitForChild('handshake'))
|
|
9
|
+
local utils = require (script.Parent:WaitForChild('utils'))
|
|
10
|
+
|
|
11
|
+
type Set<T> = { [T]: boolean }
|
|
12
|
+
type Map<K, V> = { [K]: V }
|
|
13
|
+
type Array<T> = { T }
|
|
14
|
+
type Disconnect = () -> ()
|
|
15
|
+
|
|
16
|
+
type Cursor = cursor.Cursor
|
|
17
|
+
|
|
18
|
+
type World = common.World
|
|
19
|
+
type Entity = common.Entity
|
|
20
|
+
type Component<T = any> = common.Component<T>
|
|
21
|
+
|
|
22
|
+
type CustomId = customid.CustomId
|
|
23
|
+
|
|
24
|
+
type CommandBuffer = {
|
|
25
|
+
tags: Set<Component>,
|
|
26
|
+
components: Map<Component, { value: any }>,
|
|
27
|
+
unreliable_components: Map<Component, { value: any }>,
|
|
28
|
+
remove: Set<Component>,
|
|
29
|
+
|
|
30
|
+
pairs_add: { [Component]: Set<Entity> },
|
|
31
|
+
pairs_set: { [Component]: Map<Entity, { value: any }> },
|
|
32
|
+
-- pair values whose target is an unresolved custom id: the serdes depends on
|
|
33
|
+
-- the final pair, so the value stays in the packet (cursor frozen at its
|
|
34
|
+
-- start) until the target resolves
|
|
35
|
+
pairs_unprocessed: { [Component]: Map<Entity, UnprocessedPair> },
|
|
36
|
+
pairs_remove: { [Component]: Set<Entity> },
|
|
37
|
+
relations_clear: Set<Component>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type UnprocessedPair = {
|
|
41
|
+
cursor: Cursor,
|
|
42
|
+
variants: Array<any>?,
|
|
43
|
+
processed_value: { value: any }?,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type HookMethod =
|
|
47
|
+
& ((
|
|
48
|
+
self: Client,
|
|
49
|
+
action: "changed",
|
|
50
|
+
relation: Component,
|
|
51
|
+
callback: (entity: Entity, id: Component, value: any) -> ()
|
|
52
|
+
) -> Disconnect)
|
|
53
|
+
& ((
|
|
54
|
+
self: Client,
|
|
55
|
+
action: "removed",
|
|
56
|
+
relation: Component,
|
|
57
|
+
callback: (entity: Entity, id: Component) -> ()
|
|
58
|
+
) -> Disconnect)
|
|
59
|
+
& ((self: Client, action: "deleted", entity: Entity, callback: (entity: Entity) -> ()) -> Disconnect
|
|
60
|
+
)
|
|
61
|
+
type ChangedHooksEntry = {
|
|
62
|
+
overrides: boolean,
|
|
63
|
+
callbacks: Array<(entity: Entity, id: Component, value: any) -> ()>,
|
|
64
|
+
}
|
|
65
|
+
type RemovedHooksEntry = {
|
|
66
|
+
overrides: boolean,
|
|
67
|
+
callbacks: Array<(entity: Entity, id: Component) -> ()>,
|
|
68
|
+
}
|
|
69
|
+
type DeletedHookEntry = {
|
|
70
|
+
overrides: boolean,
|
|
71
|
+
callbacks: Array<(entity: Entity) -> ()>,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type ClientImp = {
|
|
75
|
+
init: (self: Client, world: World?) -> (),
|
|
76
|
+
destroy: (self: Client) -> (),
|
|
77
|
+
handle_global: (self: Client, handler: (id: number) -> Entity) -> (),
|
|
78
|
+
get_server_entity: (self: Client, client_entity: Entity) -> number?,
|
|
79
|
+
get_client_entity: (self: Client, server_entity: number) -> Entity?,
|
|
80
|
+
|
|
81
|
+
register_entity: (self: Client, entity: Entity, server_entity: number) -> (),
|
|
82
|
+
unregister_entity: (self: Client, entity: Entity) -> (),
|
|
83
|
+
|
|
84
|
+
after_replication: (self: Client, callback: () -> ()) -> (),
|
|
85
|
+
added: (self: Client, callback: (entity: Entity) -> ()) -> Disconnect,
|
|
86
|
+
hook: HookMethod,
|
|
87
|
+
override: HookMethod,
|
|
88
|
+
|
|
89
|
+
apply_full: (self: Client, buf: buffer, variants: { any }?) -> (),
|
|
90
|
+
apply_entity: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
|
|
91
|
+
apply_updates: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
|
|
92
|
+
apply_unreliable: (self: Client, buf: buffer, all_variants: { { any } }?) -> (),
|
|
93
|
+
|
|
94
|
+
register_custom_id: (self: Client, custom_id: CustomId) -> (),
|
|
95
|
+
set_serdes: <T>(self: Client, component: Component<T>, serdes: types.Serdes<T>) -> (),
|
|
96
|
+
remove_serdes: (self: Client, component: Component) -> (),
|
|
97
|
+
set_preprocessor: <B, A>(self: Client, component: Component<B>, preprocessor: types.Preprocessor<B, A>) -> (),
|
|
98
|
+
remove_preprocessor: (self: Client, component: Component) -> (),
|
|
99
|
+
set_delta_serializer: <T, D>(self: Client, component: Component<T>, delta: types.DeltaSerializer<T, D>) -> (),
|
|
100
|
+
remove_delta_serializer: (self: Client, component: Component) -> (),
|
|
101
|
+
|
|
102
|
+
resolve_dirty: (self: Client) -> (),
|
|
103
|
+
encode_component: (self: Client, component: Component) -> number,
|
|
104
|
+
decode_component: (self: Client, encoded: number) -> Component?,
|
|
105
|
+
get_shared_count: (self: Client) -> number,
|
|
106
|
+
|
|
107
|
+
generate_handshake: (self: Client) -> types.HandshakeInfo,
|
|
108
|
+
verify_handshake: (self: Client, handshake: types.HandshakeInfo) -> (boolean, string?),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
-- mirrors the server's options: the same component can carry any mix of the
|
|
112
|
+
-- three, resolved in one lookup at read time
|
|
113
|
+
type ComponentSerializationOptions = {
|
|
114
|
+
serdes: types.Serdes?,
|
|
115
|
+
preprocessor: types.Preprocessor?,
|
|
116
|
+
delta_serializer: types.DeltaSerializer?,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type Client = ClientImp & {
|
|
120
|
+
world: World,
|
|
121
|
+
inited: boolean?,
|
|
122
|
+
|
|
123
|
+
components: types.Components,
|
|
124
|
+
shared: types.Shared,
|
|
125
|
+
registered_custom_ids: Set<CustomId>,
|
|
126
|
+
-- single lookup for serdes/preprocessor/delta at read time
|
|
127
|
+
component_serialization: Map<Component, ComponentSerializationOptions>,
|
|
128
|
+
-- shared components, serdes and custom ids feed the handshake; set when any changes
|
|
129
|
+
is_shared_dirty: boolean,
|
|
130
|
+
|
|
131
|
+
is_replicating: boolean,
|
|
132
|
+
after_replication_callbacks: Array<() -> ()>,
|
|
133
|
+
|
|
134
|
+
global_handler: ((id: number) -> Entity)?,
|
|
135
|
+
server_ids: { [number]: Entity },
|
|
136
|
+
client_ids: { [Entity]: number },
|
|
137
|
+
command_buffers: { [Entity]: CommandBuffer }?,
|
|
138
|
+
command_buffers_active: boolean,
|
|
139
|
+
|
|
140
|
+
addition_hooks: Array<(entity: Entity) -> ()>,
|
|
141
|
+
network_hooks: {
|
|
142
|
+
deleted: { [Entity]: DeletedHookEntry },
|
|
143
|
+
changed: { [Component]: ChangedHooksEntry },
|
|
144
|
+
removed: { [Component]: RemovedHooksEntry },
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
cleanups: { () -> () },
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
local PAIR = common.pair
|
|
151
|
+
local WILDCARD = common.wildcard
|
|
152
|
+
local ITERATE_ALL_RELATIONS = common.iterate_all_relations
|
|
153
|
+
local WORLD_ENTITY = common.world_entity
|
|
154
|
+
local WORLD_ADD = common.world_add
|
|
155
|
+
local WORLD_SET = common.world_set
|
|
156
|
+
local WORLD_REMOVE = common.world_remove
|
|
157
|
+
local WORLD_HAS = common.world_has
|
|
158
|
+
local WORLD_DELETE = common.world_delete
|
|
159
|
+
local WORLD_GET = common.world_get
|
|
160
|
+
local ECS_NAME = common.ecs_name
|
|
161
|
+
|
|
162
|
+
local HOOK_ADDED = common.hook_added
|
|
163
|
+
local HOOK_CHANGED = common.hook_changed
|
|
164
|
+
local HOOK_REMOVED = common.hook_removed
|
|
165
|
+
|
|
166
|
+
-- global ids are a single byte offset past the ENTITY_ID_TYPES tags, so any
|
|
167
|
+
-- id byte at or above the offset is a global id and never a tag
|
|
168
|
+
local GLOBAL_ID_OFFSET = 10
|
|
169
|
+
|
|
170
|
+
local ENTITY_ID_TYPES = {
|
|
171
|
+
entity = 1,
|
|
172
|
+
custom_handler = 2,
|
|
173
|
+
custom_id = 3,
|
|
174
|
+
shared = 4,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
-- u8 the server appends last to every buffer, read first here; each apply
|
|
178
|
+
-- function checks it so mixed up packets error instead of desyncing
|
|
179
|
+
local PACKET_TYPES = {
|
|
180
|
+
full = 1,
|
|
181
|
+
entity = 2,
|
|
182
|
+
updates = 3,
|
|
183
|
+
unreliable = 4,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
-- section bits of the u8 entity mask in a full packet; a set bit means a vlq
|
|
187
|
+
-- count precedes that section (mirrors the server's FULL_BITS)
|
|
188
|
+
local FULL_BITS = {
|
|
189
|
+
tags = 0,
|
|
190
|
+
components = 1,
|
|
191
|
+
pair_tags = 2,
|
|
192
|
+
pair_components = 3,
|
|
193
|
+
relation_tags = 4,
|
|
194
|
+
relation_components = 5,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
-- section bits of the u16 entity mask in an update packet (mirrors the
|
|
198
|
+
-- server's ENTITY_BITS)
|
|
199
|
+
local UPDATE_BITS = {
|
|
200
|
+
reliable_removed = 0,
|
|
201
|
+
reliable_added = 1,
|
|
202
|
+
reliable_changed = 2,
|
|
203
|
+
pair_removed = 3,
|
|
204
|
+
pair_added = 4,
|
|
205
|
+
pair_changed = 5,
|
|
206
|
+
relations_removed = 6,
|
|
207
|
+
full_relations = 7,
|
|
208
|
+
changed_relations = 8,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
-- trailer bits of the u16 group mask in an update packet (mirrors the
|
|
212
|
+
-- server's GROUP_BITS)
|
|
213
|
+
local UPDATE_GROUP_BITS = {
|
|
214
|
+
updates = 0,
|
|
215
|
+
added_entities = 1,
|
|
216
|
+
deleted_entities = 2,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
-- server id -> the custom_handler component or CustomId that resolves it,
|
|
220
|
+
-- collected while reading a packet and drained once every entity is known
|
|
221
|
+
type CustomIdMap = { [number]: CustomId | Entity }
|
|
222
|
+
|
|
223
|
+
local client = {}
|
|
224
|
+
client.__index = client
|
|
225
|
+
|
|
226
|
+
-- the removal hook on this tag is how a local world:delete cleans the id maps
|
|
227
|
+
local function track_alive(self: Client, entity: Entity)
|
|
228
|
+
local alive_tracking = self.components.__alive_tracking__
|
|
229
|
+
if not WORLD_HAS(self.world, entity, alive_tracking) then
|
|
230
|
+
WORLD_ADD(self.world, entity, alive_tracking)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
local function ensure_entity(self: Client, server_id: number): Entity
|
|
235
|
+
local client_entity = self.server_ids[server_id]
|
|
236
|
+
if client_entity then
|
|
237
|
+
return client_entity
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
local new_entity = WORLD_ENTITY(self.world)
|
|
241
|
+
|
|
242
|
+
self.server_ids[server_id] = new_entity
|
|
243
|
+
self.client_ids[new_entity] = server_id
|
|
244
|
+
track_alive(self, new_entity)
|
|
245
|
+
|
|
246
|
+
for _, callback in self.addition_hooks do
|
|
247
|
+
callback(new_entity)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
return new_entity
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
-- server said the entity is gone: fire its deleted hooks, then delete it
|
|
254
|
+
-- unless an override claimed the deletion. the id maps are cleared either
|
|
255
|
+
-- way so a later packet reusing the server id creates a fresh entity
|
|
256
|
+
local function delete_entity(self: Client, entity: Entity, server_id: number?)
|
|
257
|
+
local deleted_hook = self.network_hooks.deleted[entity]
|
|
258
|
+
if deleted_hook then
|
|
259
|
+
for _, callback in deleted_hook.callbacks do
|
|
260
|
+
callback(entity)
|
|
261
|
+
end
|
|
262
|
+
if deleted_hook.overrides then
|
|
263
|
+
self.network_hooks.deleted[entity] = nil
|
|
264
|
+
self.client_ids[entity] = nil
|
|
265
|
+
else
|
|
266
|
+
WORLD_DELETE(self.world, entity)
|
|
267
|
+
end
|
|
268
|
+
else
|
|
269
|
+
WORLD_DELETE(self.world, entity)
|
|
270
|
+
end
|
|
271
|
+
if server_id then
|
|
272
|
+
self.server_ids[server_id] = nil
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
local function ensure_command_buffer(self: Client, entity: Entity): CommandBuffer
|
|
277
|
+
local command_buffers = self.command_buffers
|
|
278
|
+
if not command_buffers then
|
|
279
|
+
error "attempted to use a command buffer without being active. (this should never happen)"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
local command_buffer = command_buffers[entity]
|
|
283
|
+
if not command_buffer then
|
|
284
|
+
command_buffer = {
|
|
285
|
+
tags = {},
|
|
286
|
+
components = {},
|
|
287
|
+
unreliable_components = {},
|
|
288
|
+
remove = {},
|
|
289
|
+
pairs_add = {},
|
|
290
|
+
pairs_set = {},
|
|
291
|
+
pairs_unprocessed = {},
|
|
292
|
+
pairs_remove = {},
|
|
293
|
+
relations_clear = {},
|
|
294
|
+
}
|
|
295
|
+
command_buffers[entity] = command_buffer
|
|
296
|
+
end
|
|
297
|
+
return command_buffer
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
local function entity_set(self: Client, entity: Entity, id: Component, value: any, changed_hooks: ChangedHooksEntry?)
|
|
301
|
+
if self.command_buffers_active then
|
|
302
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
303
|
+
command_buffer.components[id] = { value = value }
|
|
304
|
+
else
|
|
305
|
+
if changed_hooks then
|
|
306
|
+
if not changed_hooks.overrides then
|
|
307
|
+
WORLD_SET(self.world, entity, id, value)
|
|
308
|
+
end
|
|
309
|
+
for _, callback in changed_hooks.callbacks do
|
|
310
|
+
callback(entity, id, value)
|
|
311
|
+
end
|
|
312
|
+
else
|
|
313
|
+
WORLD_SET(self.world, entity, id, value)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
local function entity_add(self: Client, entity: Entity, tag: Entity, changed_hooks: ChangedHooksEntry?)
|
|
319
|
+
if self.command_buffers_active then
|
|
320
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
321
|
+
command_buffer.tags[tag] = true
|
|
322
|
+
else
|
|
323
|
+
if changed_hooks then
|
|
324
|
+
if not changed_hooks.overrides then
|
|
325
|
+
WORLD_ADD(self.world, entity, tag)
|
|
326
|
+
end
|
|
327
|
+
for _, callback in changed_hooks.callbacks do
|
|
328
|
+
callback(entity, tag)
|
|
329
|
+
end
|
|
330
|
+
else
|
|
331
|
+
WORLD_ADD(self.world, entity, tag)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
local function entity_set_unreliable(
|
|
337
|
+
self: Client,
|
|
338
|
+
entity: Entity,
|
|
339
|
+
id: Entity,
|
|
340
|
+
value: any,
|
|
341
|
+
changed_hooks: ChangedHooksEntry?
|
|
342
|
+
)
|
|
343
|
+
if self.command_buffers_active then
|
|
344
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
345
|
+
command_buffer.unreliable_components[id] = { value = value }
|
|
346
|
+
else
|
|
347
|
+
if WORLD_HAS(self.world, entity, id) then
|
|
348
|
+
entity_set(self, entity, id, value, changed_hooks)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
local function entity_set_pair(
|
|
354
|
+
self: Client,
|
|
355
|
+
entity: Entity,
|
|
356
|
+
relation: Entity,
|
|
357
|
+
target: Entity,
|
|
358
|
+
value: any,
|
|
359
|
+
changed_hooks: ChangedHooksEntry?
|
|
360
|
+
)
|
|
361
|
+
if self.command_buffers_active then
|
|
362
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
363
|
+
local pair_relation = command_buffer.pairs_set[relation]
|
|
364
|
+
|
|
365
|
+
if pair_relation then
|
|
366
|
+
pair_relation[target] = { value = value }
|
|
367
|
+
else
|
|
368
|
+
command_buffer.pairs_set[relation] = { [target] = { value = value } }
|
|
369
|
+
end
|
|
370
|
+
else
|
|
371
|
+
entity_set(self, entity, PAIR(relation, target), value, changed_hooks)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
local function entity_add_pair(
|
|
376
|
+
self: Client,
|
|
377
|
+
entity: Entity,
|
|
378
|
+
relation: Entity,
|
|
379
|
+
target: Entity,
|
|
380
|
+
changed_hooks: ChangedHooksEntry?
|
|
381
|
+
)
|
|
382
|
+
if self.command_buffers_active then
|
|
383
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
384
|
+
local pair_relation = command_buffer.pairs_add[relation]
|
|
385
|
+
|
|
386
|
+
if pair_relation then
|
|
387
|
+
pair_relation[target] = true
|
|
388
|
+
else
|
|
389
|
+
command_buffer.pairs_add[relation] = { [target] = true }
|
|
390
|
+
end
|
|
391
|
+
else
|
|
392
|
+
entity_add(self, entity, PAIR(relation, target), changed_hooks)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
local function entity_remove(self: Client, entity: Entity, id: Entity, removed_hooks: RemovedHooksEntry?)
|
|
397
|
+
if self.command_buffers_active then
|
|
398
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
399
|
+
command_buffer.remove[id] = true
|
|
400
|
+
else
|
|
401
|
+
if removed_hooks then
|
|
402
|
+
for _, callback in removed_hooks.callbacks do
|
|
403
|
+
callback(entity, id)
|
|
404
|
+
end
|
|
405
|
+
if not removed_hooks.overrides then
|
|
406
|
+
WORLD_REMOVE(self.world, entity, id)
|
|
407
|
+
end
|
|
408
|
+
else
|
|
409
|
+
WORLD_REMOVE(self.world, entity, id)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
local function entity_remove_pair(
|
|
415
|
+
self: Client,
|
|
416
|
+
entity: Entity,
|
|
417
|
+
relation: Entity,
|
|
418
|
+
target: Entity,
|
|
419
|
+
removed_hooks: RemovedHooksEntry?
|
|
420
|
+
)
|
|
421
|
+
if self.command_buffers_active then
|
|
422
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
423
|
+
local pair_relation = command_buffer.pairs_remove[relation]
|
|
424
|
+
|
|
425
|
+
if pair_relation then
|
|
426
|
+
pair_relation[target] = true
|
|
427
|
+
else
|
|
428
|
+
command_buffer.pairs_remove[relation] = { [target] = true }
|
|
429
|
+
end
|
|
430
|
+
else
|
|
431
|
+
entity_remove(self, entity, PAIR(relation, target), removed_hooks)
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
local function entity_clear_relation(self: Client, entity: Entity, relation: Entity, removed_hooks: RemovedHooksEntry?)
|
|
436
|
+
if self.command_buffers_active then
|
|
437
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
438
|
+
command_buffer.relations_clear[relation] = true
|
|
439
|
+
else
|
|
440
|
+
local world = self.world
|
|
441
|
+
if removed_hooks then
|
|
442
|
+
ITERATE_ALL_RELATIONS(world, entity, relation, function(pair)
|
|
443
|
+
for _, callback in removed_hooks.callbacks do
|
|
444
|
+
callback(entity, pair)
|
|
445
|
+
end
|
|
446
|
+
if not removed_hooks.overrides then
|
|
447
|
+
WORLD_REMOVE(world, entity, pair)
|
|
448
|
+
end
|
|
449
|
+
end)
|
|
450
|
+
else
|
|
451
|
+
ITERATE_ALL_RELATIONS(world, entity, relation, function(pair)
|
|
452
|
+
WORLD_REMOVE(world, entity, pair)
|
|
453
|
+
end)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
local function resolve_global(self: Client, global_id: number): Entity
|
|
459
|
+
if not self.global_handler then
|
|
460
|
+
common.log_error "global id parser not set, consider using client:handle_global()"
|
|
461
|
+
end
|
|
462
|
+
return (self.global_handler :: (id: number) -> Entity)(global_id)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
local function read_component_id(self: Client, c: Cursor): Entity
|
|
466
|
+
local shared_components = self.shared.components
|
|
467
|
+
local shared_id = cursor.read_span(c, #shared_components.keys)
|
|
468
|
+
local component = shared_components.indexes[shared_id]
|
|
469
|
+
if not component then
|
|
470
|
+
common.log_error(`received a non shared component: {shared_id}`)
|
|
471
|
+
end
|
|
472
|
+
return component
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
local function read_entity_id(self: Client, c: Cursor): (Entity?, number?, number?)
|
|
476
|
+
local id_type = cursor.readu8(c)
|
|
477
|
+
|
|
478
|
+
if id_type < GLOBAL_ID_OFFSET then
|
|
479
|
+
if
|
|
480
|
+
id_type == ENTITY_ID_TYPES.entity
|
|
481
|
+
or id_type == ENTITY_ID_TYPES.custom_id
|
|
482
|
+
or id_type == ENTITY_ID_TYPES.custom_handler
|
|
483
|
+
then
|
|
484
|
+
local server_id = cursor.readu40(c)
|
|
485
|
+
return self.server_ids[server_id], server_id, id_type
|
|
486
|
+
elseif id_type == ENTITY_ID_TYPES.shared then
|
|
487
|
+
return read_component_id(self, c), nil, id_type
|
|
488
|
+
end
|
|
489
|
+
common.log_error(`malformed entity type: {id_type}`)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
return resolve_global(self, id_type - GLOBAL_ID_OFFSET), nil, id_type
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
local function skip_custom_payload(self: Client, c: Cursor, id_type: number?)
|
|
496
|
+
if id_type == ENTITY_ID_TYPES.custom_id then
|
|
497
|
+
read_component_id(self, c)
|
|
498
|
+
elseif id_type == ENTITY_ID_TYPES.custom_handler then
|
|
499
|
+
cursor.readu8(c)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
-- lookup only: an entity the client doesn't know yet is never created.
|
|
504
|
+
local function find_entity(self: Client, c: Cursor): (Entity?, number?)
|
|
505
|
+
local entity, server_id, id_type = read_entity_id(self, c)
|
|
506
|
+
skip_custom_payload(self, c, id_type)
|
|
507
|
+
return entity, server_id
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
-- reads an entity id and hands the resolved entity to process. unknown plain
|
|
511
|
+
-- entities are created on the spot; unknown custom ids record their handler in
|
|
512
|
+
-- custom_ids and hand process a negative server id, meaning the entity is not
|
|
513
|
+
-- resolved yet and everything applied to it must go through a command buffer
|
|
514
|
+
-- until finish_custom_ids step translates it
|
|
515
|
+
local function process_entity(self: Client, c: Cursor, custom_ids: CustomIdMap, process: ((entity: Entity) -> ())?)
|
|
516
|
+
local entity, server_id, id_type = read_entity_id(self, c)
|
|
517
|
+
|
|
518
|
+
if entity then
|
|
519
|
+
skip_custom_payload(self, c, id_type)
|
|
520
|
+
if process then
|
|
521
|
+
process(entity)
|
|
522
|
+
end
|
|
523
|
+
return
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
local unresolved_id = server_id :: number
|
|
527
|
+
|
|
528
|
+
if id_type == ENTITY_ID_TYPES.custom_id then
|
|
529
|
+
custom_ids[unresolved_id] = read_component_id(self, c)
|
|
530
|
+
elseif id_type == ENTITY_ID_TYPES.custom_handler then
|
|
531
|
+
local shared_custom_id = cursor.readu8(c)
|
|
532
|
+
local handler = self.shared.custom_ids.indexes[shared_custom_id]
|
|
533
|
+
|
|
534
|
+
if not handler then
|
|
535
|
+
common.log_error "attempted to use a custom id that wasn't registered"
|
|
536
|
+
end
|
|
537
|
+
custom_ids[unresolved_id] = handler
|
|
538
|
+
else
|
|
539
|
+
local new_entity = ensure_entity(self, unresolved_id)
|
|
540
|
+
if process then
|
|
541
|
+
process(new_entity)
|
|
542
|
+
end
|
|
543
|
+
return
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
if not process then
|
|
547
|
+
return
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
local was_active = self.command_buffers_active
|
|
551
|
+
self.command_buffers_active = true
|
|
552
|
+
process(-unresolved_id :: any)
|
|
553
|
+
self.command_buffers_active = was_active
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
-- reverse of the server's write_variant_value. a serdes value is its buffer
|
|
557
|
+
-- (length-prefixed unless the serdes has a fixed bytespan) plus, when the
|
|
558
|
+
-- serdes includes variants, the slice of the packet variants it appended;
|
|
559
|
+
-- anything else is a variant index, 0 meaning nil. the preprocessor undoes
|
|
560
|
+
-- itself last since the server applied it first
|
|
561
|
+
local function read_variant_value(c: Cursor, options: ComponentSerializationOptions?, variants: Array<any>?): any
|
|
562
|
+
local serdes = options and options.serdes
|
|
563
|
+
local value: any
|
|
564
|
+
|
|
565
|
+
if serdes then
|
|
566
|
+
local bytespan = serdes.bytespan or cursor.read_vlq(c)
|
|
567
|
+
local appended = cursor.read_buffer(c, bytespan)
|
|
568
|
+
local serdes_variants: Array<any>?
|
|
569
|
+
|
|
570
|
+
if serdes.includes_variants then
|
|
571
|
+
local start_variant = cursor.read_vlq(c)
|
|
572
|
+
|
|
573
|
+
if start_variant > 0 then
|
|
574
|
+
local size = cursor.read_vlq(c)
|
|
575
|
+
serdes_variants = table.move(variants :: Array<any>, start_variant, start_variant + size - 1, 1, {})
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
value = serdes.deserialize(appended, serdes_variants :: any)
|
|
580
|
+
else
|
|
581
|
+
local variant_id = cursor.read_vlq(c)
|
|
582
|
+
if variant_id == 0 then
|
|
583
|
+
value = nil
|
|
584
|
+
else
|
|
585
|
+
value = (variants :: Array<any>)[variant_id]
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
local preprocessor = options and options.preprocessor
|
|
590
|
+
if preprocessor then
|
|
591
|
+
value = preprocessor.deserialize(value)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
return value
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
local function read_component_value(self: Client, component: Component, c: Cursor, variants: Array<any>?): any
|
|
598
|
+
return read_variant_value(c, self.component_serialization[component], variants)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
-- a pair value resolves its options like the server writes them: the exact
|
|
602
|
+
-- pair first, then the relation's wildcard pair
|
|
603
|
+
-- returns the pair id, the value
|
|
604
|
+
local function read_pair_value(
|
|
605
|
+
self: Client,
|
|
606
|
+
relation: Component,
|
|
607
|
+
target: Entity,
|
|
608
|
+
c: Cursor,
|
|
609
|
+
variants: Array<any>?
|
|
610
|
+
): (Component, any)
|
|
611
|
+
local pair_id = PAIR(relation, target)
|
|
612
|
+
local component_serialization = self.component_serialization
|
|
613
|
+
local options = component_serialization[pair_id] or component_serialization[PAIR(relation, WILDCARD)]
|
|
614
|
+
return pair_id, read_variant_value(c, options, variants)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
-- a section's vlq count is only written when its mask bit is set
|
|
618
|
+
local function read_vlq_bitmask(c: Cursor, mask: number, bit: number): number
|
|
619
|
+
if bit32.btest(mask, bit32.lshift(1, bit)) then
|
|
620
|
+
return cursor.read_vlq(c)
|
|
621
|
+
end
|
|
622
|
+
return 0
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
-- pair value whose target isn't resolved yet (negative id): keeps a cursor
|
|
626
|
+
-- frozen at the value so it can be read once the target's custom id resolves
|
|
627
|
+
-- and the pair's serdes is known. with buffers inactive the target is final
|
|
628
|
+
-- and the value is read right away
|
|
629
|
+
local function entity_set_postponed_pair(
|
|
630
|
+
self: Client,
|
|
631
|
+
entity: Entity,
|
|
632
|
+
relation: Component,
|
|
633
|
+
target: Entity,
|
|
634
|
+
c: Cursor,
|
|
635
|
+
variants: Array<any>?
|
|
636
|
+
): any
|
|
637
|
+
if self.command_buffers_active then
|
|
638
|
+
local command_buffer = ensure_command_buffer(self, entity)
|
|
639
|
+
local pairs_unprocessed = command_buffer.pairs_unprocessed
|
|
640
|
+
local unprocessed: UnprocessedPair = {
|
|
641
|
+
cursor = c,
|
|
642
|
+
variants = variants,
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
local pair_relation = pairs_unprocessed[relation]
|
|
646
|
+
if pair_relation then
|
|
647
|
+
pair_relation[target] = unprocessed
|
|
648
|
+
else
|
|
649
|
+
pairs_unprocessed[relation] = { [target] = unprocessed }
|
|
650
|
+
end
|
|
651
|
+
return nil
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
local pair_id, value = read_pair_value(self, relation, target, c, variants)
|
|
655
|
+
entity_set(self, entity, pair_id, value, self.network_hooks.changed[PAIR(self.components.relation, relation)])
|
|
656
|
+
return value
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
-- pair tag: [target id, relation]
|
|
660
|
+
local function process_entity_pair(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
|
|
661
|
+
local relation = read_component_id(self, c)
|
|
662
|
+
local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
|
|
663
|
+
process_entity(self, c, custom_ids, function(target)
|
|
664
|
+
entity_add_pair(self, entity, relation, target, relation_hooks)
|
|
665
|
+
end)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
-- pair component: [value, vlq value size, target id, relation]. the size lets
|
|
669
|
+
-- the value be skipped when the target is an unresolved custom id, since the
|
|
670
|
+
-- serdes to read it with is only known once the final pair is
|
|
671
|
+
local function process_entity_pair_value(
|
|
672
|
+
self: Client,
|
|
673
|
+
entity: Entity,
|
|
674
|
+
c: Cursor,
|
|
675
|
+
custom_ids: CustomIdMap,
|
|
676
|
+
variants: Array<any>?
|
|
677
|
+
)
|
|
678
|
+
local relation = read_component_id(self, c)
|
|
679
|
+
local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
|
|
680
|
+
process_entity(self, c, custom_ids, function(target)
|
|
681
|
+
local value_size = cursor.read_vlq(c)
|
|
682
|
+
local value_start = c.offset - value_size
|
|
683
|
+
|
|
684
|
+
if (target :: any) > 0 then
|
|
685
|
+
local _, value = read_pair_value(self, relation, target, c, variants)
|
|
686
|
+
entity_set_pair(self, entity, relation, target, value, relation_hooks)
|
|
687
|
+
-- the serdes must have consumed exactly the written size; pin the
|
|
688
|
+
-- cursor to where the server said the value starts either way
|
|
689
|
+
c.offset = value_start
|
|
690
|
+
else
|
|
691
|
+
local frozen_cursor = cursor.from_offset(c.buffer, c.offset)
|
|
692
|
+
c.offset = value_start
|
|
693
|
+
entity_set_postponed_pair(self, entity, relation, target, frozen_cursor, variants)
|
|
694
|
+
end
|
|
695
|
+
end)
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
-- relation tags: [target ids..., vlq count, relation]
|
|
699
|
+
local function process_entity_relations(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
|
|
700
|
+
local relation = read_component_id(self, c)
|
|
701
|
+
local total_targets = cursor.read_vlq(c)
|
|
702
|
+
local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
|
|
703
|
+
|
|
704
|
+
for _ = 1, total_targets do
|
|
705
|
+
process_entity(self, c, custom_ids, function(target)
|
|
706
|
+
entity_add_pair(self, entity, relation, target, relation_hooks)
|
|
707
|
+
end)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
-- relation components: [(target id, value)..., vlq count, relation]; values
|
|
712
|
+
-- use the relation's own options, not a per-pair one
|
|
713
|
+
local function process_entity_relation_values(
|
|
714
|
+
self: Client,
|
|
715
|
+
entity: Entity,
|
|
716
|
+
c: Cursor,
|
|
717
|
+
custom_ids: CustomIdMap,
|
|
718
|
+
variants: Array<any>?
|
|
719
|
+
)
|
|
720
|
+
local relation = read_component_id(self, c)
|
|
721
|
+
local total_targets = cursor.read_vlq(c)
|
|
722
|
+
local relation_hooks = self.network_hooks.changed[PAIR(self.components.relation, relation)]
|
|
723
|
+
|
|
724
|
+
for _ = 1, total_targets do
|
|
725
|
+
local value = read_component_value(self, relation, c, variants)
|
|
726
|
+
process_entity(self, c, custom_ids, function(target)
|
|
727
|
+
entity_set_pair(self, entity, relation, target, value, relation_hooks)
|
|
728
|
+
end)
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
-- one entity of a full snapshot, after its id: the FULL_BITS mask, then each
|
|
733
|
+
-- flagged section in write-reverse order (tags first, relation components
|
|
734
|
+
-- last)
|
|
735
|
+
local function process_full_entity(
|
|
736
|
+
self: Client,
|
|
737
|
+
entity: Entity,
|
|
738
|
+
c: Cursor,
|
|
739
|
+
custom_ids: CustomIdMap,
|
|
740
|
+
variants: Array<any>?
|
|
741
|
+
)
|
|
742
|
+
local mask = cursor.readu8(c)
|
|
743
|
+
local changed_hooks = self.network_hooks.changed
|
|
744
|
+
local components = self.components
|
|
745
|
+
|
|
746
|
+
local total_tags = read_vlq_bitmask(c, mask, FULL_BITS.tags)
|
|
747
|
+
for _ = 1, total_tags do
|
|
748
|
+
local tag = read_component_id(self, c)
|
|
749
|
+
entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
local total_components = read_vlq_bitmask(c, mask, FULL_BITS.components)
|
|
753
|
+
for _ = 1, total_components do
|
|
754
|
+
local component = read_component_id(self, c)
|
|
755
|
+
local value = read_component_value(self, component, c, variants)
|
|
756
|
+
entity_set(self, entity, component, value, changed_hooks[PAIR(components.reliable, component)])
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
local total_pair_tags = read_vlq_bitmask(c, mask, FULL_BITS.pair_tags)
|
|
760
|
+
for _ = 1, total_pair_tags do
|
|
761
|
+
process_entity_pair(self, entity, c, custom_ids)
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
local total_pair_components = read_vlq_bitmask(c, mask, FULL_BITS.pair_components)
|
|
765
|
+
for _ = 1, total_pair_components do
|
|
766
|
+
process_entity_pair_value(self, entity, c, custom_ids, variants)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
local total_relation_tags = read_vlq_bitmask(c, mask, FULL_BITS.relation_tags)
|
|
770
|
+
for _ = 1, total_relation_tags do
|
|
771
|
+
process_entity_relations(self, entity, c, custom_ids)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
local total_relation_components = read_vlq_bitmask(c, mask, FULL_BITS.relation_components)
|
|
775
|
+
for _ = 1, total_relation_components do
|
|
776
|
+
process_entity_relation_values(self, entity, c, custom_ids, variants)
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
-- pair tag removal: [target id, relation]
|
|
781
|
+
local function process_entity_pair_removal(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
|
|
782
|
+
local relation = read_component_id(self, c)
|
|
783
|
+
local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
|
|
784
|
+
process_entity(self, c, custom_ids, function(target)
|
|
785
|
+
entity_remove_pair(self, entity, relation, target, relation_hooks)
|
|
786
|
+
end)
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
-- relation target removals: [target ids..., vlq count, relation]
|
|
790
|
+
local function process_entity_relation_removals(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
|
|
791
|
+
local relation = read_component_id(self, c)
|
|
792
|
+
local total_targets = cursor.read_vlq(c)
|
|
793
|
+
local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
|
|
794
|
+
|
|
795
|
+
for _ = 1, total_targets do
|
|
796
|
+
process_entity(self, c, custom_ids, function(target)
|
|
797
|
+
entity_remove_pair(self, entity, relation, target, relation_hooks)
|
|
798
|
+
end)
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
-- one entity of an update packet, after its id: the UPDATE_BITS mask, then
|
|
803
|
+
-- each flagged section in write-reverse order (reliable changed first,
|
|
804
|
+
-- relations removed last). unreliable tracked components ride the reliable
|
|
805
|
+
-- changed section for their presence, so they resolve reliable hooks here
|
|
806
|
+
local function process_entity_updates(
|
|
807
|
+
self: Client,
|
|
808
|
+
entity: Entity,
|
|
809
|
+
c: Cursor,
|
|
810
|
+
custom_ids: CustomIdMap,
|
|
811
|
+
variants: Array<any>?
|
|
812
|
+
)
|
|
813
|
+
local mask = cursor.readu16(c)
|
|
814
|
+
local changed_hooks = self.network_hooks.changed
|
|
815
|
+
local removed_hooks = self.network_hooks.removed
|
|
816
|
+
local components = self.components
|
|
817
|
+
|
|
818
|
+
-- reliables
|
|
819
|
+
|
|
820
|
+
local total_changed = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_changed)
|
|
821
|
+
for _ = 1, total_changed do
|
|
822
|
+
local component = read_component_id(self, c)
|
|
823
|
+
local value = read_component_value(self, component, c, variants)
|
|
824
|
+
entity_set(self, entity, component, value, changed_hooks[PAIR(components.reliable, component)])
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
local total_added = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_added)
|
|
828
|
+
for _ = 1, total_added do
|
|
829
|
+
local tag = read_component_id(self, c)
|
|
830
|
+
entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
local total_removed = read_vlq_bitmask(c, mask, UPDATE_BITS.reliable_removed)
|
|
834
|
+
for _ = 1, total_removed do
|
|
835
|
+
local component = read_component_id(self, c)
|
|
836
|
+
entity_remove(self, entity, component, removed_hooks[PAIR(components.reliable, component)])
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
-- pairs
|
|
840
|
+
|
|
841
|
+
local total_pair_changed = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_changed)
|
|
842
|
+
for _ = 1, total_pair_changed do
|
|
843
|
+
process_entity_pair_value(self, entity, c, custom_ids, variants)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
local total_pair_added = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_added)
|
|
847
|
+
for _ = 1, total_pair_added do
|
|
848
|
+
process_entity_pair(self, entity, c, custom_ids)
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
local total_pair_removed = read_vlq_bitmask(c, mask, UPDATE_BITS.pair_removed)
|
|
852
|
+
for _ = 1, total_pair_removed do
|
|
853
|
+
process_entity_pair_removal(self, entity, c, custom_ids)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
-- relations
|
|
857
|
+
|
|
858
|
+
if bit32.btest(mask, bit32.lshift(1, UPDATE_BITS.changed_relations)) then
|
|
859
|
+
local total_relations_removed = cursor.read_vlq(c)
|
|
860
|
+
for _ = 1, total_relations_removed do
|
|
861
|
+
process_entity_relation_removals(self, entity, c, custom_ids)
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
local total_relations_added = cursor.read_vlq(c)
|
|
865
|
+
for _ = 1, total_relations_added do
|
|
866
|
+
process_entity_relations(self, entity, c, custom_ids)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
local total_relations_changed = cursor.read_vlq(c)
|
|
870
|
+
for _ = 1, total_relations_changed do
|
|
871
|
+
process_entity_relation_values(self, entity, c, custom_ids, variants)
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
if bit32.btest(mask, bit32.lshift(1, UPDATE_BITS.full_relations)) then
|
|
876
|
+
local total_relation_tags = cursor.read_vlq(c)
|
|
877
|
+
for _ = 1, total_relation_tags do
|
|
878
|
+
process_entity_relations(self, entity, c, custom_ids)
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
local total_relation_components = cursor.read_vlq(c)
|
|
882
|
+
for _ = 1, total_relation_components do
|
|
883
|
+
process_entity_relation_values(self, entity, c, custom_ids, variants)
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
local total_relations_cleared = read_vlq_bitmask(c, mask, UPDATE_BITS.relations_removed)
|
|
888
|
+
for _ = 1, total_relations_cleared do
|
|
889
|
+
local relation = read_component_id(self, c)
|
|
890
|
+
entity_clear_relation(self, entity, relation, removed_hooks[PAIR(components.relation, relation)])
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
-- one update group: [u16 group mask, deleted ids, added ids, per entity
|
|
895
|
+
-- blocks] in read order. deletions come first so a server id reused by an
|
|
896
|
+
-- addition in the same group starts from a fresh entity
|
|
897
|
+
local function process_update_group(self: Client, c: Cursor, custom_ids: CustomIdMap, variants: Array<any>?)
|
|
898
|
+
local mask = cursor.readu16(c)
|
|
899
|
+
|
|
900
|
+
local total_deleted = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.deleted_entities)
|
|
901
|
+
for _ = 1, total_deleted do
|
|
902
|
+
local entity, server_id = find_entity(self, c)
|
|
903
|
+
if entity then
|
|
904
|
+
delete_entity(self, entity, server_id)
|
|
905
|
+
end
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
local total_added = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.added_entities)
|
|
909
|
+
for _ = 1, total_added do
|
|
910
|
+
process_entity(self, c, custom_ids)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
local total_entities = read_vlq_bitmask(c, mask, UPDATE_GROUP_BITS.updates)
|
|
914
|
+
for _ = 1, total_entities do
|
|
915
|
+
process_entity(self, c, custom_ids, function(entity)
|
|
916
|
+
process_entity_updates(self, entity, c, custom_ids, variants)
|
|
917
|
+
end)
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
local function apply_command_buffer(
|
|
922
|
+
self: Client,
|
|
923
|
+
entity: Entity,
|
|
924
|
+
command_buffer: CommandBuffer,
|
|
925
|
+
request: (e: Entity,...any) -> Entity?
|
|
926
|
+
)
|
|
927
|
+
local components = self.components
|
|
928
|
+
local changed_hooks = self.network_hooks.changed
|
|
929
|
+
local removed_hooks = self.network_hooks.removed
|
|
930
|
+
|
|
931
|
+
for tag in command_buffer.tags do
|
|
932
|
+
entity_add(self, entity, tag, changed_hooks[PAIR(components.reliable, tag)])
|
|
933
|
+
end
|
|
934
|
+
for component, value in command_buffer.components do
|
|
935
|
+
entity_set(self, entity, component, value.value, changed_hooks[PAIR(components.reliable, component)])
|
|
936
|
+
end
|
|
937
|
+
for component, value in command_buffer.unreliable_components do
|
|
938
|
+
if WORLD_HAS(self.world, entity, component) then
|
|
939
|
+
entity_set(self, entity, component, value.value, changed_hooks[PAIR(components.unreliable, component)])
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
for component in command_buffer.remove do
|
|
943
|
+
entity_remove(self, entity, component, removed_hooks[PAIR(components.reliable, component)])
|
|
944
|
+
end
|
|
945
|
+
for relation, targets in command_buffer.pairs_add do
|
|
946
|
+
local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
|
|
947
|
+
for target in targets do
|
|
948
|
+
local client_target = request(target)
|
|
949
|
+
if client_target then
|
|
950
|
+
entity_add_pair(self, entity, relation, client_target, relation_hooks)
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
end
|
|
954
|
+
for relation, targets in command_buffer.pairs_set do
|
|
955
|
+
local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
|
|
956
|
+
for target, value in targets do
|
|
957
|
+
local client_target = request(target)
|
|
958
|
+
if client_target then
|
|
959
|
+
entity_set_pair(self, entity, relation, client_target, value.value, relation_hooks)
|
|
960
|
+
end
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
for relation, targets in command_buffer.pairs_unprocessed do
|
|
964
|
+
local relation_hooks = changed_hooks[PAIR(components.relation, relation)]
|
|
965
|
+
for target, unprocessed in targets do
|
|
966
|
+
local client_target = request(target)
|
|
967
|
+
if client_target then
|
|
968
|
+
local processed_value = unprocessed.processed_value
|
|
969
|
+
if processed_value then
|
|
970
|
+
entity_set_pair(self, entity, relation, client_target, processed_value.value, relation_hooks)
|
|
971
|
+
else
|
|
972
|
+
local value = entity_set_postponed_pair(
|
|
973
|
+
self,
|
|
974
|
+
entity,
|
|
975
|
+
relation,
|
|
976
|
+
client_target,
|
|
977
|
+
unprocessed.cursor,
|
|
978
|
+
unprocessed.variants
|
|
979
|
+
)
|
|
980
|
+
unprocessed.processed_value = { value = value }
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
end
|
|
985
|
+
for relation, targets in command_buffer.pairs_remove do
|
|
986
|
+
local relation_hooks = removed_hooks[PAIR(components.relation, relation)]
|
|
987
|
+
for target in targets do
|
|
988
|
+
local client_target = request(target)
|
|
989
|
+
if client_target then
|
|
990
|
+
entity_remove_pair(self, entity, relation, client_target, relation_hooks)
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
end
|
|
994
|
+
for relation in command_buffer.relations_clear do
|
|
995
|
+
entity_clear_relation(self, entity, relation, removed_hooks[PAIR(components.relation, relation)])
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
local function find_component_in_buffer(command_buffer: CommandBuffer, component: Entity): { value: any }?
|
|
1000
|
+
return command_buffer.components[component] or command_buffer.unreliable_components[component]
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
local function find_has_in_buffer(command_buffer: CommandBuffer, tag: Entity): boolean
|
|
1004
|
+
return (command_buffer.tags[tag] ~= nil)
|
|
1005
|
+
or (command_buffer.components[tag] ~= nil)
|
|
1006
|
+
or (command_buffer.unreliable_components[tag] ~= nil)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
local function find_pair_in_buffer(
|
|
1010
|
+
command_buffer: CommandBuffer,
|
|
1011
|
+
relation: Entity,
|
|
1012
|
+
target: Entity,
|
|
1013
|
+
find: (unprocessed_entity: Entity) -> (Entity?, Entity)
|
|
1014
|
+
): (boolean, any)
|
|
1015
|
+
local value_relations = command_buffer.pairs_set[relation]
|
|
1016
|
+
if value_relations then
|
|
1017
|
+
for buffer_target, value in value_relations do
|
|
1018
|
+
local found = find(buffer_target)
|
|
1019
|
+
if found == target then
|
|
1020
|
+
return true, value.value
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
local relations = command_buffer.pairs_add[relation]
|
|
1026
|
+
if relations then
|
|
1027
|
+
for buffer_target in relations do
|
|
1028
|
+
local found = find(buffer_target)
|
|
1029
|
+
if found == target then
|
|
1030
|
+
return true, nil
|
|
1031
|
+
end
|
|
1032
|
+
end
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
local unprocessed_relations = command_buffer.pairs_unprocessed[relation]
|
|
1036
|
+
if unprocessed_relations then
|
|
1037
|
+
for buffer_target, unprocessed in unprocessed_relations do
|
|
1038
|
+
local found = find(buffer_target)
|
|
1039
|
+
if found == target then
|
|
1040
|
+
local processed_value = unprocessed.processed_value
|
|
1041
|
+
return true, processed_value and processed_value.value
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
return false, nil
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
local function find_target_in_buffer(command_buffer: CommandBuffer, relation: Entity, index: number?): Entity?
|
|
1050
|
+
local i = 0
|
|
1051
|
+
|
|
1052
|
+
local pairs = command_buffer.pairs_add[relation]
|
|
1053
|
+
if pairs then
|
|
1054
|
+
for target in pairs do
|
|
1055
|
+
if i == (index or 0) then
|
|
1056
|
+
return target
|
|
1057
|
+
end
|
|
1058
|
+
i += 1
|
|
1059
|
+
end
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
local pairs_values = command_buffer.pairs_set[relation]
|
|
1063
|
+
if pairs_values then
|
|
1064
|
+
for target in pairs_values do
|
|
1065
|
+
if i == (index or 0) then
|
|
1066
|
+
return target
|
|
1067
|
+
end
|
|
1068
|
+
i += 1
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
local unprocessed_pairs = command_buffer.pairs_unprocessed[relation]
|
|
1073
|
+
if unprocessed_pairs then
|
|
1074
|
+
for target in unprocessed_pairs do
|
|
1075
|
+
if i == (index or 0) then
|
|
1076
|
+
return target
|
|
1077
|
+
end
|
|
1078
|
+
i += 1
|
|
1079
|
+
end
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
return nil
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
-- resolves every custom id collected while reading a packet, then drains the
|
|
1086
|
+
-- command buffers. ids are negative server ids until resolved; a handler can
|
|
1087
|
+
-- pull other unresolved entities through its context, so resolution recurses
|
|
1088
|
+
-- and each server id is attempted once
|
|
1089
|
+
local function finish_custom_ids(self: Client, custom_ids: CustomIdMap, command_buffers: { [Entity]: CommandBuffer })
|
|
1090
|
+
local world = self.world
|
|
1091
|
+
local components = self.components
|
|
1092
|
+
local processed: { [number]: boolean } = {}
|
|
1093
|
+
local create_context: (unprocessed_entity: Entity) -> customid.HandleContext
|
|
1094
|
+
|
|
1095
|
+
-- positive ids are already client entities; negative ones look up the
|
|
1096
|
+
-- server id, which may have resolved earlier in this pass
|
|
1097
|
+
local function find(unprocessed_entity: Entity): (Entity?, Entity)
|
|
1098
|
+
if (unprocessed_entity :: any) > 0 then
|
|
1099
|
+
return unprocessed_entity, unprocessed_entity
|
|
1100
|
+
end
|
|
1101
|
+
local server_id = -(unprocessed_entity :: any)
|
|
1102
|
+
return self.server_ids[server_id], server_id
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
local function process(unprocessed_entity: Entity, custom_handler: (CustomId | Entity)?): Entity?
|
|
1106
|
+
local client_entity, server_id = find(unprocessed_entity)
|
|
1107
|
+
if client_entity then
|
|
1108
|
+
return client_entity
|
|
1109
|
+
end
|
|
1110
|
+
if processed[server_id :: any] then
|
|
1111
|
+
return nil
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
custom_handler = custom_handler or custom_ids[server_id :: any]
|
|
1115
|
+
if not custom_handler then
|
|
1116
|
+
return nil
|
|
1117
|
+
end
|
|
1118
|
+
processed[server_id :: any] = true
|
|
1119
|
+
|
|
1120
|
+
local command_buffer = command_buffers[unprocessed_entity]
|
|
1121
|
+
local new_entity: Entity?
|
|
1122
|
+
|
|
1123
|
+
if type(custom_handler) == "number" then
|
|
1124
|
+
local component: Entity = custom_handler :: any
|
|
1125
|
+
local handler = WORLD_GET(world, component, components.custom_handler)
|
|
1126
|
+
if not handler then
|
|
1127
|
+
common.log_error(
|
|
1128
|
+
"received a custom id for a component without a handler, consider adding custom_handler to component:",
|
|
1129
|
+
common.log_component(world, component)
|
|
1130
|
+
)
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
local value = command_buffer and find_component_in_buffer(command_buffer, component)
|
|
1134
|
+
if not value then
|
|
1135
|
+
common.log_warn(`no component found for custom id handler: {common.log_component(world, component)}`)
|
|
1136
|
+
end
|
|
1137
|
+
new_entity = handler(value and value.value)
|
|
1138
|
+
else
|
|
1139
|
+
local custom = custom_handler :: CustomId
|
|
1140
|
+
local handle_callback = custom.handle_callback
|
|
1141
|
+
if not handle_callback then
|
|
1142
|
+
common.log_error("no handler callback was set for custom id:", custom.identifier)
|
|
1143
|
+
end
|
|
1144
|
+
new_entity = (handle_callback :: any)(create_context(unprocessed_entity))
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
if new_entity then
|
|
1148
|
+
self.server_ids[server_id :: any] = new_entity
|
|
1149
|
+
self.client_ids[new_entity] = server_id :: any
|
|
1150
|
+
track_alive(self, new_entity)
|
|
1151
|
+
|
|
1152
|
+
if command_buffer then
|
|
1153
|
+
apply_command_buffer(self, new_entity, command_buffer, process)
|
|
1154
|
+
command_buffers[unprocessed_entity] = nil
|
|
1155
|
+
end
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
return new_entity
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
-- what a custom id handler sees of the entity: its buffered state, with
|
|
1162
|
+
-- targets and referenced entities resolved on demand
|
|
1163
|
+
function create_context(unprocessed_entity: Entity): customid.HandleContext
|
|
1164
|
+
local command_buffer = command_buffers[unprocessed_entity]
|
|
1165
|
+
|
|
1166
|
+
local function component(id: Entity): any
|
|
1167
|
+
if not command_buffer then
|
|
1168
|
+
return nil
|
|
1169
|
+
end
|
|
1170
|
+
local value = find_component_in_buffer(command_buffer, id)
|
|
1171
|
+
return value and value.value
|
|
1172
|
+
end
|
|
1173
|
+
local function has(tag: Entity): boolean
|
|
1174
|
+
if not command_buffer then
|
|
1175
|
+
return false
|
|
1176
|
+
end
|
|
1177
|
+
return find_has_in_buffer(command_buffer, tag)
|
|
1178
|
+
end
|
|
1179
|
+
local function target(relation: Entity, index: number?): Entity?
|
|
1180
|
+
if not command_buffer then
|
|
1181
|
+
return nil
|
|
1182
|
+
end
|
|
1183
|
+
local buffer_target = find_target_in_buffer(command_buffer, relation, index)
|
|
1184
|
+
if buffer_target then
|
|
1185
|
+
return process(buffer_target)
|
|
1186
|
+
end
|
|
1187
|
+
return nil
|
|
1188
|
+
end
|
|
1189
|
+
local function pair_value(relation: Entity, pair_target: Entity): any
|
|
1190
|
+
if not command_buffer then
|
|
1191
|
+
return nil
|
|
1192
|
+
end
|
|
1193
|
+
local _, value = find_pair_in_buffer(command_buffer, relation, pair_target, find)
|
|
1194
|
+
return value
|
|
1195
|
+
end
|
|
1196
|
+
local function has_pair(relation: Entity, pair_target: Entity): boolean
|
|
1197
|
+
if not command_buffer then
|
|
1198
|
+
return false
|
|
1199
|
+
end
|
|
1200
|
+
local found = find_pair_in_buffer(command_buffer, relation, pair_target, find)
|
|
1201
|
+
return found
|
|
1202
|
+
end
|
|
1203
|
+
local function entity(server_id: number): Entity
|
|
1204
|
+
return process(-server_id :: any) :: Entity
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1207
|
+
-- the context's generic accessors are typed for callers; the buffer
|
|
1208
|
+
-- lookups behind them are untyped
|
|
1209
|
+
local context = {
|
|
1210
|
+
entity_id = math.abs(unprocessed_entity :: any),
|
|
1211
|
+
has = has,
|
|
1212
|
+
component = component,
|
|
1213
|
+
target = target,
|
|
1214
|
+
pair_value = pair_value,
|
|
1215
|
+
has_pair = has_pair,
|
|
1216
|
+
entity = entity,
|
|
1217
|
+
}
|
|
1218
|
+
return context :: any
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
for server_id, custom_handler in custom_ids do
|
|
1222
|
+
process(-server_id :: any, custom_handler)
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
-- known entities whose buffers reference custom ids, now resolvable
|
|
1226
|
+
for entity, command_buffer in command_buffers do
|
|
1227
|
+
if (entity :: any) < 0 then
|
|
1228
|
+
continue
|
|
1229
|
+
end
|
|
1230
|
+
apply_command_buffer(self, entity, command_buffer, process)
|
|
1231
|
+
end
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
local function finish_replication(self: Client)
|
|
1235
|
+
self.is_replicating = false
|
|
1236
|
+
for _, callback in self.after_replication_callbacks do
|
|
1237
|
+
callback()
|
|
1238
|
+
end
|
|
1239
|
+
table.clear(self.after_replication_callbacks)
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
-- a full snapshot from get_full: [vlq entity count, per entity: id + sections].
|
|
1243
|
+
-- entities whose id is a custom id are buffered until finish_custom_ids can
|
|
1244
|
+
-- resolve them
|
|
1245
|
+
local function check_packet_type(c: Cursor, expected_byte: number)
|
|
1246
|
+
local got_byte = cursor.readu8(c)
|
|
1247
|
+
if got_byte == expected_byte then
|
|
1248
|
+
return
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
local got_type: string?
|
|
1252
|
+
local expected_type: string?
|
|
1253
|
+
for packet_type, byte in PACKET_TYPES :: { [string]: number } do
|
|
1254
|
+
if byte == got_byte then
|
|
1255
|
+
got_type = packet_type
|
|
1256
|
+
elseif byte == expected_byte then
|
|
1257
|
+
expected_type = packet_type
|
|
1258
|
+
end
|
|
1259
|
+
end
|
|
1260
|
+
common.log_error(
|
|
1261
|
+
`packet type mismatch, expected: {expected_type or "(unknown)"} got: {got_type or "(unknown)"} instead`
|
|
1262
|
+
)
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
function client.apply_full(self: Client, buf: buffer, variants: { any }?)
|
|
1266
|
+
self:resolve_dirty()
|
|
1267
|
+
self.is_replicating = true
|
|
1268
|
+
|
|
1269
|
+
local c = cursor.from(buf)
|
|
1270
|
+
check_packet_type(c, PACKET_TYPES.full)
|
|
1271
|
+
local command_buffers: { [Entity]: CommandBuffer } = {}
|
|
1272
|
+
local custom_ids: CustomIdMap = {}
|
|
1273
|
+
self.command_buffers = command_buffers
|
|
1274
|
+
|
|
1275
|
+
local total_entities = cursor.read_vlq(c)
|
|
1276
|
+
for _ = 1, total_entities do
|
|
1277
|
+
process_entity(self, c, custom_ids, function(entity)
|
|
1278
|
+
process_full_entity(self, entity, c, custom_ids, variants)
|
|
1279
|
+
end)
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
finish_custom_ids(self, custom_ids, command_buffers)
|
|
1283
|
+
self.command_buffers = nil
|
|
1284
|
+
finish_replication(self)
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
-- a combined packet from collect_entity: [inner packets..., vlq packet count],
|
|
1288
|
+
-- each inner packet a full snapshot of the same entity holding the members of
|
|
1289
|
+
-- one filter group. the cursor reads from the end, so packets come out last
|
|
1290
|
+
-- to first and pick their variants list by index
|
|
1291
|
+
function client.apply_entity(self: Client, buf: buffer, all_variants: { { any } }?)
|
|
1292
|
+
self:resolve_dirty()
|
|
1293
|
+
self.is_replicating = true
|
|
1294
|
+
|
|
1295
|
+
local c = cursor.from(buf)
|
|
1296
|
+
check_packet_type(c, PACKET_TYPES.entity)
|
|
1297
|
+
local command_buffers: { [Entity]: CommandBuffer } = {}
|
|
1298
|
+
local custom_ids: CustomIdMap = {}
|
|
1299
|
+
self.command_buffers = command_buffers
|
|
1300
|
+
|
|
1301
|
+
local total_packets = cursor.read_vlq(c)
|
|
1302
|
+
for packet_index = total_packets, 1, -1 do
|
|
1303
|
+
local variants = if all_variants then all_variants[packet_index] else nil
|
|
1304
|
+
local total_entities = cursor.read_vlq(c)
|
|
1305
|
+
for _ = 1, total_entities do
|
|
1306
|
+
process_entity(self, c, custom_ids, function(entity)
|
|
1307
|
+
process_full_entity(self, entity, c, custom_ids, variants)
|
|
1308
|
+
end)
|
|
1309
|
+
end
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
finish_custom_ids(self, custom_ids, command_buffers)
|
|
1313
|
+
self.command_buffers = nil
|
|
1314
|
+
finish_replication(self)
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
-- a combined packet from collect_updates: [group packets..., vlq packet
|
|
1318
|
+
-- count], one inner packet per visibility group the player belongs to. read
|
|
1319
|
+
-- like apply_entity: last group first, variants picked by index
|
|
1320
|
+
function client.apply_updates(self: Client, buf: buffer, all_variants: { { any } }?)
|
|
1321
|
+
self:resolve_dirty()
|
|
1322
|
+
self.is_replicating = true
|
|
1323
|
+
|
|
1324
|
+
local c = cursor.from(buf)
|
|
1325
|
+
check_packet_type(c, PACKET_TYPES.updates)
|
|
1326
|
+
local command_buffers: { [Entity]: CommandBuffer } = {}
|
|
1327
|
+
local custom_ids: CustomIdMap = {}
|
|
1328
|
+
self.command_buffers = command_buffers
|
|
1329
|
+
|
|
1330
|
+
local total_packets = cursor.read_vlq(c)
|
|
1331
|
+
for packet_index = total_packets, 1, -1 do
|
|
1332
|
+
local variants = if all_variants then all_variants[packet_index] else nil
|
|
1333
|
+
process_update_group(self, c, custom_ids, variants)
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
finish_custom_ids(self, custom_ids, command_buffers)
|
|
1337
|
+
self.command_buffers = nil
|
|
1338
|
+
finish_replication(self)
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
-- one entity of an unreliable packet, after its id: [vlq pair count, pairs...,
|
|
1342
|
+
-- vlq component count, components...]. the packet carries current values
|
|
1343
|
+
-- only, never presence: an entity or member the client doesn't have yet is
|
|
1344
|
+
-- skipped (its adds travel reliably), but every value is still read so the
|
|
1345
|
+
-- cursor stays aligned. `entity` is nil when the entity is unknown
|
|
1346
|
+
local function process_unreliable_entity(self: Client, entity: Entity?, c: Cursor, variants: Array<any>?)
|
|
1347
|
+
local world = self.world
|
|
1348
|
+
local changed_hooks = self.network_hooks.changed
|
|
1349
|
+
local components = self.components
|
|
1350
|
+
|
|
1351
|
+
local total_pairs = cursor.read_vlq(c)
|
|
1352
|
+
for _ = 1, total_pairs do
|
|
1353
|
+
local relation = read_component_id(self, c)
|
|
1354
|
+
local target = find_entity(self, c)
|
|
1355
|
+
local value_size = cursor.read_vlq(c)
|
|
1356
|
+
local value_start = c.offset - value_size
|
|
1357
|
+
|
|
1358
|
+
if entity and target then
|
|
1359
|
+
local pair_id, value = read_pair_value(self, relation, target, c, variants)
|
|
1360
|
+
if WORLD_HAS(world, entity, pair_id) then
|
|
1361
|
+
entity_set(self, entity, pair_id, value, changed_hooks[PAIR(components.relation, relation)])
|
|
1362
|
+
end
|
|
1363
|
+
end
|
|
1364
|
+
-- the size skips the value when nothing can receive it
|
|
1365
|
+
c.offset = value_start
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
local total_components = cursor.read_vlq(c)
|
|
1369
|
+
for _ = 1, total_components do
|
|
1370
|
+
local component = read_component_id(self, c)
|
|
1371
|
+
local value = read_component_value(self, component, c, variants)
|
|
1372
|
+
if entity then
|
|
1373
|
+
entity_set_unreliable(self, entity, component, value, changed_hooks[PAIR(components.unreliable, component)])
|
|
1374
|
+
end
|
|
1375
|
+
end
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
-- a combined packet from collect_unreliable: [inner packets..., vlq packet
|
|
1379
|
+
-- count], each inner packet [per entity: id + values, vlq entity count].
|
|
1380
|
+
-- entities are only looked up, never created: the channel is lossy and
|
|
1381
|
+
-- carries no presence, so no command buffers or custom id resolution here
|
|
1382
|
+
function client.apply_unreliable(self: Client, buf: buffer, all_variants: { { any } }?)
|
|
1383
|
+
self:resolve_dirty()
|
|
1384
|
+
self.is_replicating = true
|
|
1385
|
+
|
|
1386
|
+
local c = cursor.from(buf)
|
|
1387
|
+
check_packet_type(c, PACKET_TYPES.unreliable)
|
|
1388
|
+
|
|
1389
|
+
local total_packets = cursor.read_vlq(c)
|
|
1390
|
+
for packet_index = total_packets, 1, -1 do
|
|
1391
|
+
local variants = if all_variants then all_variants[packet_index] else nil
|
|
1392
|
+
local total_entities = cursor.read_vlq(c)
|
|
1393
|
+
for _ = 1, total_entities do
|
|
1394
|
+
local entity = find_entity(self, c)
|
|
1395
|
+
process_unreliable_entity(self, entity, c, variants)
|
|
1396
|
+
end
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
finish_replication(self)
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
function client.after_replication(self: Client, callback: () -> ())
|
|
1403
|
+
if self.is_replicating then
|
|
1404
|
+
table.insert(self.after_replication_callbacks, callback)
|
|
1405
|
+
else
|
|
1406
|
+
callback()
|
|
1407
|
+
end
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
function client.added(self: Client, callback: (entity: Entity) -> ()): Disconnect
|
|
1411
|
+
table.insert(self.addition_hooks, callback)
|
|
1412
|
+
return function()
|
|
1413
|
+
local index = table.find(self.addition_hooks, callback)
|
|
1414
|
+
if index then
|
|
1415
|
+
table.remove(self.addition_hooks, index)
|
|
1416
|
+
end
|
|
1417
|
+
end
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
local function add_hook_entry(self: Client, action: string, ...): ({ overrides: boolean }, Disconnect)
|
|
1421
|
+
if action == "changed" or action == "removed" then
|
|
1422
|
+
local relation = select(1, ...) :: Entity
|
|
1423
|
+
local callback = select(2, ...) :: (entity: Entity, id: Component, value: any) -> ()
|
|
1424
|
+
local hooks = action == "changed" and self.network_hooks.changed or self.network_hooks.removed
|
|
1425
|
+
local entries = hooks[relation] :: ChangedHooksEntry
|
|
1426
|
+
|
|
1427
|
+
if not entries then
|
|
1428
|
+
entries = {
|
|
1429
|
+
overrides = false,
|
|
1430
|
+
callbacks = {},
|
|
1431
|
+
}
|
|
1432
|
+
hooks[relation] = entries :: any
|
|
1433
|
+
end
|
|
1434
|
+
table.insert(entries.callbacks, callback)
|
|
1435
|
+
|
|
1436
|
+
local function disconnect()
|
|
1437
|
+
local callbacks = entries.callbacks
|
|
1438
|
+
local index = table.find(callbacks, callback)
|
|
1439
|
+
if index then
|
|
1440
|
+
table.remove(callbacks, index)
|
|
1441
|
+
if #callbacks == 0 then
|
|
1442
|
+
hooks[relation] = nil :: any
|
|
1443
|
+
end
|
|
1444
|
+
end
|
|
1445
|
+
end
|
|
1446
|
+
return entries, disconnect
|
|
1447
|
+
elseif action == "deleted" then
|
|
1448
|
+
local entity = select(1, ...) :: Entity
|
|
1449
|
+
local callback = select(2, ...) :: (entity: Entity) -> ()
|
|
1450
|
+
local hooks = self.network_hooks.deleted
|
|
1451
|
+
local entries = hooks[entity]
|
|
1452
|
+
|
|
1453
|
+
if not entries then
|
|
1454
|
+
entries = {
|
|
1455
|
+
overrides = false,
|
|
1456
|
+
callbacks = {},
|
|
1457
|
+
}
|
|
1458
|
+
hooks[entity] = entries
|
|
1459
|
+
end
|
|
1460
|
+
table.insert(entries.callbacks, callback)
|
|
1461
|
+
|
|
1462
|
+
local function disconnect()
|
|
1463
|
+
local callbacks = entries.callbacks
|
|
1464
|
+
local index = table.find(callbacks, callback)
|
|
1465
|
+
if index then
|
|
1466
|
+
table.remove(callbacks, index)
|
|
1467
|
+
if #callbacks == 0 then
|
|
1468
|
+
hooks[entity] = nil
|
|
1469
|
+
end
|
|
1470
|
+
end
|
|
1471
|
+
end
|
|
1472
|
+
return entries, disconnect
|
|
1473
|
+
else
|
|
1474
|
+
error("invalid hook action: " .. action)
|
|
1475
|
+
end
|
|
1476
|
+
end
|
|
1477
|
+
|
|
1478
|
+
function client.hook(self: Client, action: string, ...)
|
|
1479
|
+
local entry, disconnect = add_hook_entry(self, action, ...)
|
|
1480
|
+
entry.overrides = false
|
|
1481
|
+
return disconnect
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
function client.override(self: Client, action: string, ...)
|
|
1485
|
+
local entry, disconnect = add_hook_entry(self, action, ...)
|
|
1486
|
+
entry.overrides = true
|
|
1487
|
+
return disconnect
|
|
1488
|
+
end
|
|
1489
|
+
|
|
1490
|
+
function client.get_server_entity(self: Client, client_entity: Entity): number?
|
|
1491
|
+
return self.client_ids[client_entity]
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
function client.get_client_entity(self: Client, server_entity: number): Entity?
|
|
1495
|
+
return self.server_ids[server_entity]
|
|
1496
|
+
end
|
|
1497
|
+
|
|
1498
|
+
function client.register_entity(self: Client, entity: Entity, server_entity: number)
|
|
1499
|
+
self.server_ids[server_entity] = entity
|
|
1500
|
+
self.client_ids[entity] = server_entity
|
|
1501
|
+
track_alive(self, entity)
|
|
1502
|
+
end
|
|
1503
|
+
|
|
1504
|
+
function client.unregister_entity(self: Client, entity: Entity)
|
|
1505
|
+
local server_id = self.client_ids[entity]
|
|
1506
|
+
if server_id then
|
|
1507
|
+
self.server_ids[server_id] = nil
|
|
1508
|
+
self.client_ids[entity] = nil
|
|
1509
|
+
end
|
|
1510
|
+
end
|
|
1511
|
+
|
|
1512
|
+
function client.handle_global(self: Client, handler: (id: number) -> Entity)
|
|
1513
|
+
self.global_handler = handler
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1516
|
+
function client.register_custom_id(self: Client, custom_id: CustomId)
|
|
1517
|
+
self.registered_custom_ids[custom_id] = true
|
|
1518
|
+
self.is_shared_dirty = true
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
local function ensure_component_serialization(self: Client, component: Component): ComponentSerializationOptions
|
|
1522
|
+
local options = self.component_serialization[component]
|
|
1523
|
+
if not options then
|
|
1524
|
+
options = {} :: ComponentSerializationOptions
|
|
1525
|
+
self.component_serialization[component] = options
|
|
1526
|
+
end
|
|
1527
|
+
return options
|
|
1528
|
+
end
|
|
1529
|
+
|
|
1530
|
+
local function prune_component_serialization(self: Client, component: Component, options: ComponentSerializationOptions)
|
|
1531
|
+
if options.serdes == nil and options.preprocessor == nil and options.delta_serializer == nil then
|
|
1532
|
+
self.component_serialization[component] = nil
|
|
1533
|
+
end
|
|
1534
|
+
end
|
|
1535
|
+
|
|
1536
|
+
-- serdes take part in the handshake, preprocessors and delta serializers are
|
|
1537
|
+
-- local to each side and never mark shared dirty
|
|
1538
|
+
function client.set_serdes(self: Client, component: Component, serdes: types.Serdes)
|
|
1539
|
+
ensure_component_serialization(self, component).serdes = serdes
|
|
1540
|
+
self.is_shared_dirty = true
|
|
1541
|
+
end
|
|
1542
|
+
|
|
1543
|
+
function client.remove_serdes(self: Client, component: Component)
|
|
1544
|
+
local options = self.component_serialization[component]
|
|
1545
|
+
if not options then
|
|
1546
|
+
return
|
|
1547
|
+
end
|
|
1548
|
+
options.serdes = nil
|
|
1549
|
+
prune_component_serialization(self, component, options)
|
|
1550
|
+
self.is_shared_dirty = true
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
function client.set_preprocessor(self: Client, component: Component, preprocessor: types.Preprocessor)
|
|
1554
|
+
ensure_component_serialization(self, component).preprocessor = preprocessor
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
function client.remove_preprocessor(self: Client, component: Component)
|
|
1558
|
+
local options = self.component_serialization[component]
|
|
1559
|
+
if not options then
|
|
1560
|
+
return
|
|
1561
|
+
end
|
|
1562
|
+
options.preprocessor = nil
|
|
1563
|
+
prune_component_serialization(self, component, options)
|
|
1564
|
+
end
|
|
1565
|
+
|
|
1566
|
+
function client.set_delta_serializer(self: Client, component: Component, delta_serializer: types.DeltaSerializer)
|
|
1567
|
+
ensure_component_serialization(self, component).delta_serializer = delta_serializer
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
function client.remove_delta_serializer(self: Client, component: Component)
|
|
1571
|
+
local options = self.component_serialization[component]
|
|
1572
|
+
if not options then
|
|
1573
|
+
return
|
|
1574
|
+
end
|
|
1575
|
+
options.delta_serializer = nil
|
|
1576
|
+
prune_component_serialization(self, component, options)
|
|
1577
|
+
end
|
|
1578
|
+
|
|
1579
|
+
-- rebuilds the shared lookups (component ids, custom ids, serdes) when
|
|
1580
|
+
-- flagged; cheap no-op otherwise, so every deserialize entry point calls it
|
|
1581
|
+
function client.resolve_dirty(self: Client)
|
|
1582
|
+
if not self.is_shared_dirty then
|
|
1583
|
+
return
|
|
1584
|
+
end
|
|
1585
|
+
|
|
1586
|
+
local serdes: { [Component]: types.Serdes } = {}
|
|
1587
|
+
for component, options in self.component_serialization do
|
|
1588
|
+
if options.serdes then
|
|
1589
|
+
serdes[component] = options.serdes
|
|
1590
|
+
end
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
|
|
1594
|
+
self.is_shared_dirty = false
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
-- snapshot of the shared lookups for the server to verify against its own;
|
|
1598
|
+
-- resolves pending shared changes first so the handshake never describes a
|
|
1599
|
+
-- stale state
|
|
1600
|
+
-- the wire id of a shared component, stable across server and client once the
|
|
1601
|
+
-- handshake passes; 0 and an error for anything not shared
|
|
1602
|
+
function client.encode_component(self: Client, component: Component): number
|
|
1603
|
+
self:resolve_dirty()
|
|
1604
|
+
local encoded = self.shared.components.members[component]
|
|
1605
|
+
if not encoded then
|
|
1606
|
+
common.log_error(`attempted to encode a non-shared component `, common.log_component(self.world, component))
|
|
1607
|
+
return 0
|
|
1608
|
+
end
|
|
1609
|
+
return encoded
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
function client.decode_component(self: Client, encoded: number): Component?
|
|
1613
|
+
self:resolve_dirty()
|
|
1614
|
+
return self.shared.components.indexes[encoded]
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
function client.get_shared_count(self: Client): number
|
|
1618
|
+
self:resolve_dirty()
|
|
1619
|
+
return #self.shared.components.keys
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
function client.generate_handshake(self: Client): types.HandshakeInfo
|
|
1623
|
+
self:resolve_dirty()
|
|
1624
|
+
return handshake.generate_handshake(self.shared)
|
|
1625
|
+
end
|
|
1626
|
+
|
|
1627
|
+
function client.verify_handshake(self: Client, server_handshake: types.HandshakeInfo): (boolean, string?)
|
|
1628
|
+
self:resolve_dirty()
|
|
1629
|
+
return handshake.verify_handshake(self.shared, server_handshake, "server", "client")
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
function client.init(self: Client, wrd: World?)
|
|
1633
|
+
if self.inited == true then
|
|
1634
|
+
return common.log_warn "attempted to init a client twice"
|
|
1635
|
+
end
|
|
1636
|
+
if self.inited == nil then
|
|
1637
|
+
return common.log_warn "attempted to re-init a destroyed client"
|
|
1638
|
+
end
|
|
1639
|
+
self.inited = true
|
|
1640
|
+
|
|
1641
|
+
local world = wrd or self.world
|
|
1642
|
+
self.world = world
|
|
1643
|
+
|
|
1644
|
+
if not world then
|
|
1645
|
+
common.log_error "Providing a world is required to start replecs"
|
|
1646
|
+
end
|
|
1647
|
+
|
|
1648
|
+
if not self.components then
|
|
1649
|
+
self.components = utils.create_world_components(world)
|
|
1650
|
+
end
|
|
1651
|
+
local components = self.components
|
|
1652
|
+
|
|
1653
|
+
local function hook(unhook: () -> ())
|
|
1654
|
+
table.insert(self.cleanups, unhook)
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
-- shared components and their names feed the handshake lookups; any change
|
|
1658
|
+
-- rebuilds them at the next resolve_dirty
|
|
1659
|
+
local function mark_shared_dirty()
|
|
1660
|
+
self.is_shared_dirty = true
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
for _, component in { components.shared, ECS_NAME } do
|
|
1664
|
+
hook(HOOK_ADDED(world, component, mark_shared_dirty))
|
|
1665
|
+
hook(HOOK_CHANGED(world, component, mark_shared_dirty))
|
|
1666
|
+
hook(HOOK_REMOVED(world, component, mark_shared_dirty))
|
|
1667
|
+
end
|
|
1668
|
+
|
|
1669
|
+
hook(HOOK_ADDED(world, components.serdes, function(component, _, serdes)
|
|
1670
|
+
self:set_serdes(component, serdes)
|
|
1671
|
+
end))
|
|
1672
|
+
hook(HOOK_CHANGED(world, components.serdes, function(component, _, serdes)
|
|
1673
|
+
self:set_serdes(component, serdes)
|
|
1674
|
+
end))
|
|
1675
|
+
hook(HOOK_REMOVED(world, components.serdes, function(component)
|
|
1676
|
+
self:remove_serdes(component)
|
|
1677
|
+
end))
|
|
1678
|
+
|
|
1679
|
+
-- a replicated entity deleted locally (or by delete_entity) drops out of
|
|
1680
|
+
-- the id maps, so the server id can be bound to a fresh entity later
|
|
1681
|
+
hook(HOOK_REMOVED(world, components.__alive_tracking__, function(entity)
|
|
1682
|
+
local server_id = self.client_ids[entity]
|
|
1683
|
+
if server_id then
|
|
1684
|
+
self.server_ids[server_id] = nil
|
|
1685
|
+
self.client_ids[entity] = nil
|
|
1686
|
+
end
|
|
1687
|
+
self.network_hooks.deleted[entity] = nil
|
|
1688
|
+
end))
|
|
1689
|
+
|
|
1690
|
+
for component, serdes in world:query(components.serdes):iter() do
|
|
1691
|
+
self:set_serdes(component, serdes)
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
self.is_shared_dirty = true
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
function client.destroy(self: Client)
|
|
1698
|
+
if self.inited == nil then
|
|
1699
|
+
return common.log_warn "attempted to destroy a client twice"
|
|
1700
|
+
end
|
|
1701
|
+
self.inited = nil
|
|
1702
|
+
for _, cleanup in self.cleanups do
|
|
1703
|
+
cleanup()
|
|
1704
|
+
end
|
|
1705
|
+
table.clear(self.cleanups)
|
|
1706
|
+
end
|
|
1707
|
+
|
|
1708
|
+
-- components may be nil without preregistration: they are created from the
|
|
1709
|
+
-- world here when one is given, otherwise at init
|
|
1710
|
+
local function create(world: World?, components: types.Components?): Client
|
|
1711
|
+
local self = {} :: Client
|
|
1712
|
+
|
|
1713
|
+
self.world = world :: any
|
|
1714
|
+
self.components = components or (if world then utils.create_world_components(world) else nil) :: any
|
|
1715
|
+
self.inited = false
|
|
1716
|
+
|
|
1717
|
+
self.shared = {} :: types.Shared
|
|
1718
|
+
self.registered_custom_ids = {}
|
|
1719
|
+
self.component_serialization = {}
|
|
1720
|
+
-- starts dirty so the first resolve_dirty builds the lookups
|
|
1721
|
+
self.is_shared_dirty = true
|
|
1722
|
+
|
|
1723
|
+
self.is_replicating = false
|
|
1724
|
+
self.after_replication_callbacks = {}
|
|
1725
|
+
|
|
1726
|
+
self.global_handler = nil
|
|
1727
|
+
self.server_ids = {}
|
|
1728
|
+
self.client_ids = {}
|
|
1729
|
+
self.command_buffers = nil
|
|
1730
|
+
self.command_buffers_active = false
|
|
1731
|
+
|
|
1732
|
+
self.addition_hooks = {}
|
|
1733
|
+
self.network_hooks = {
|
|
1734
|
+
deleted = {},
|
|
1735
|
+
changed = {},
|
|
1736
|
+
removed = {},
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
self.cleanups = {}
|
|
1740
|
+
return setmetatable(self, client) :: any
|
|
1741
|
+
end
|
|
1742
|
+
|
|
1743
|
+
return {
|
|
1744
|
+
create = create,
|
|
1745
|
+
|
|
1746
|
+
read_component_id = read_component_id,
|
|
1747
|
+
read_entity_id = read_entity_id,
|
|
1748
|
+
find_entity = find_entity,
|
|
1749
|
+
process_entity = process_entity,
|
|
1750
|
+
|
|
1751
|
+
read_component_value = read_component_value,
|
|
1752
|
+
read_pair_value = read_pair_value,
|
|
1753
|
+
|
|
1754
|
+
process_entity_pair = process_entity_pair,
|
|
1755
|
+
process_entity_pair_value = process_entity_pair_value,
|
|
1756
|
+
process_entity_relations = process_entity_relations,
|
|
1757
|
+
process_entity_relation_values = process_entity_relation_values,
|
|
1758
|
+
process_full_entity = process_full_entity,
|
|
1759
|
+
delete_entity = delete_entity,
|
|
1760
|
+
}
|