@rbxts/replecs 0.3.1 → 0.4.0-rc.2
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 +3937 -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,3937 @@
|
|
|
1
|
+
--!optimize 2
|
|
2
|
+
|
|
3
|
+
local common = require (script.Parent:WaitForChild('common'))
|
|
4
|
+
local cursor = require (script.Parent:WaitForChild('cursor'))
|
|
5
|
+
local registry = require (script:WaitForChild('registry'))
|
|
6
|
+
local bitmasks = require (script:WaitForChild('bitmasks'))
|
|
7
|
+
local filter_groups = require (script:WaitForChild('filter_group'))
|
|
8
|
+
local types = require (script.Parent:WaitForChild('types'))
|
|
9
|
+
local customid = require (script.Parent:WaitForChild('customid'))
|
|
10
|
+
local shared = require (script.Parent:WaitForChild('shared'))
|
|
11
|
+
local handshake = require (script.Parent:WaitForChild('handshake'))
|
|
12
|
+
local utils = require (script.Parent:WaitForChild('utils'))
|
|
13
|
+
|
|
14
|
+
type Set<T> = { [T]: boolean }
|
|
15
|
+
type Map<K, V> = { [K]: V }
|
|
16
|
+
type Array<T> = { T }
|
|
17
|
+
|
|
18
|
+
type Player = any
|
|
19
|
+
|
|
20
|
+
type FilterAny = types.FilterAny
|
|
21
|
+
type FilterSet = types.FilterSet
|
|
22
|
+
type FilterPredicator = types.FilterPredicator
|
|
23
|
+
type FilterPlayer = types.FilterPlayer
|
|
24
|
+
type FilterGroup = filter_groups.FilterGroup
|
|
25
|
+
|
|
26
|
+
-- what the public set_* API accepts as a filter
|
|
27
|
+
type FilterInput = FilterAny | FilterGroup
|
|
28
|
+
|
|
29
|
+
type MaskHash = string
|
|
30
|
+
|
|
31
|
+
type World = common.World
|
|
32
|
+
type Entity = common.Entity
|
|
33
|
+
type Component<T = any> = common.Component<T>
|
|
34
|
+
type Bitmask = buffer
|
|
35
|
+
type Bitset = number
|
|
36
|
+
type TrackType = number
|
|
37
|
+
|
|
38
|
+
type BitmaskDeltas = bitmasks.BitmaskDeltas
|
|
39
|
+
type Cursor = cursor.Cursor
|
|
40
|
+
|
|
41
|
+
export type ServerImp = {
|
|
42
|
+
set_networked: (self: Server, entity: Entity, filter: FilterInput?) -> (),
|
|
43
|
+
set_reliable: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
|
|
44
|
+
set_unreliable: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
|
|
45
|
+
set_once: (self: Server, entity: Entity, component: Component, filter: FilterInput?) -> (),
|
|
46
|
+
set_pair: (self: Server, entity: Entity, id: Component, filter: FilterInput?) -> (),
|
|
47
|
+
set_unreliable_pair: (self: Server, entity: Entity, id: Component, filter: FilterInput?) -> (),
|
|
48
|
+
set_relation: (self: Server, entity: Entity, relation: Component, filter: FilterInput?) -> (),
|
|
49
|
+
|
|
50
|
+
stop_networked: (self: Server, entity: Entity) -> (),
|
|
51
|
+
stop_reliable: (self: Server, entity: Entity, component: Component) -> (),
|
|
52
|
+
stop_unreliable: (self: Server, entity: Entity, component: Component) -> (),
|
|
53
|
+
stop_once: (self: Server, entity: Entity, component: Component) -> (),
|
|
54
|
+
stop_pair: (self: Server, entity: Entity, id: Component) -> (),
|
|
55
|
+
stop_unreliable_pair: (self: Server, entity: Entity, id: Component) -> (),
|
|
56
|
+
stop_relation: (self: Server, entity: Entity, relation: Component) -> (),
|
|
57
|
+
|
|
58
|
+
force_change: (self: Server, entity: Entity, component: Component) -> (),
|
|
59
|
+
|
|
60
|
+
set_global: (self: Server, entity: Entity, global_id: number) -> (),
|
|
61
|
+
remove_global: (self: Server, entity: Entity) -> (),
|
|
62
|
+
set_custom: (self: Server, entity: Entity, custom: Component | CustomId) -> (),
|
|
63
|
+
remove_custom: (self: Server, entity: Entity) -> (),
|
|
64
|
+
register_custom_id: (self: Server, custom_id: CustomId) -> (),
|
|
65
|
+
|
|
66
|
+
set_serdes: <T>(self: Server, component: Component<T>, serdes: types.Serdes<T>) -> (),
|
|
67
|
+
remove_serdes: (self: Server, component: Component) -> (),
|
|
68
|
+
set_preprocessor: <B, A>(self: Server, component: Component<B>, preprocessor: types.Preprocessor<B, A>) -> (),
|
|
69
|
+
remove_preprocessor: (self: Server, component: Component) -> (),
|
|
70
|
+
set_delta_serializer: <T, D>(self: Server, component: Component<T>, delta: types.DeltaSerializer<T, D>) -> (),
|
|
71
|
+
remove_delta_serializer: (self: Server, component: Component) -> (),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
-- filter == nil means "replicate to all active clients", resolved at collect time
|
|
75
|
+
type StorageBase = {
|
|
76
|
+
filter: Bitmask?,
|
|
77
|
+
last_replicated: Bitmask?,
|
|
78
|
+
|
|
79
|
+
-- set while a filter group drives this storage's filter; the listener is
|
|
80
|
+
-- kept so it can be disconnected when the filter changes or the storage dies
|
|
81
|
+
filter_group: FilterGroup?,
|
|
82
|
+
filter_listener: ((Bitmask) -> ())?,
|
|
83
|
+
filter_input: FilterInput?,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type TrackingStorageBase = StorageBase & {
|
|
87
|
+
track_type: TrackType,
|
|
88
|
+
|
|
89
|
+
dirty_flags: Bitset,
|
|
90
|
+
is_component: boolean,
|
|
91
|
+
-- once storages replicate on add, removal and visibility gain only; world
|
|
92
|
+
-- value changes are ignored unless force_change is called
|
|
93
|
+
is_once: boolean?,
|
|
94
|
+
-- set by stop_*: the filter is forced empty so collect sends the removal
|
|
95
|
+
-- delta, then the storage is discarded; a start_* before that revives it
|
|
96
|
+
is_dead: boolean,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type ReliableStorage = TrackingStorageBase & {}
|
|
100
|
+
type PairStorage = TrackingStorageBase & {}
|
|
101
|
+
type UnreliableStorage = TrackingStorageBase & {}
|
|
102
|
+
type UnreliablePairStorage = TrackingStorageBase & {}
|
|
103
|
+
|
|
104
|
+
type RelationStorage = TrackingStorageBase & {
|
|
105
|
+
dirty_targets: Set<Entity>,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type NetworkedStorage = StorageBase & {
|
|
109
|
+
dirty_flags: Bitset,
|
|
110
|
+
is_dead: boolean,
|
|
111
|
+
-- live (not stopped) unreliable + unreliable_pair storages; keeps
|
|
112
|
+
-- unreliable_networked membership in sync without scanning storages
|
|
113
|
+
unreliable_count: number,
|
|
114
|
+
-- set by cleanup_entity: unlike a stopped storage, a cleaned one cannot be
|
|
115
|
+
-- revived; set_* calls error until collect_updates discards it
|
|
116
|
+
cleaned: boolean?,
|
|
117
|
+
|
|
118
|
+
dirty_reliable: Array<Component>,
|
|
119
|
+
dirty_unreliable: Array<Component>,
|
|
120
|
+
dirty_pairs: Array<Component>,
|
|
121
|
+
dirty_unreliable_pairs: Array<Component>,
|
|
122
|
+
dirty_relations: Array<Component>,
|
|
123
|
+
|
|
124
|
+
replicate_exclude: Set<Component>?,
|
|
125
|
+
|
|
126
|
+
reliable_storages: { [Component]: ReliableStorage },
|
|
127
|
+
unreliable_storages: { [Component]: UnreliableStorage },
|
|
128
|
+
pair_storages: { [Component]: PairStorage },
|
|
129
|
+
unreliable_pair_storages: { [Component]: UnreliablePairStorage },
|
|
130
|
+
relation_storages: { [Component]: RelationStorage },
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type PostponedComponent = {
|
|
134
|
+
filter: FilterInput?,
|
|
135
|
+
once: boolean?,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
-- a predicate-driven filter, re-evaluated before every collect_updates cycle
|
|
139
|
+
type FunctionFilterEntry = {
|
|
140
|
+
predicator: registry.MemberPredicator,
|
|
141
|
+
entity: Entity,
|
|
142
|
+
networked: NetworkedStorage,
|
|
143
|
+
|
|
144
|
+
-- set for reliable component filters, nil for entity-level ones
|
|
145
|
+
tracking: Component?,
|
|
146
|
+
tracking_storage: TrackingStorageBase?,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type NetworkedReferencesInfo = {
|
|
150
|
+
networked: NetworkedStorage,
|
|
151
|
+
tracking_storage: TrackingStorageBase,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type HookedInfo = {
|
|
155
|
+
networked_references: Map<Entity, NetworkedReferencesInfo>,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type PostponedComponentsList = {
|
|
159
|
+
[TrackType]: Map<Component, PostponedComponent>,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type ComponentSerializationOptions = {
|
|
163
|
+
serdes: types.Serdes?,
|
|
164
|
+
preprocessor: types.Preprocessor?,
|
|
165
|
+
delta_serializer: types.DeltaSerializer?,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type CustomId = customid.CustomId
|
|
169
|
+
|
|
170
|
+
-- how an entity id is written on the wire; absent means plain entity / shared
|
|
171
|
+
type EntitySerializationOptions = {
|
|
172
|
+
global_id: number?,
|
|
173
|
+
custom: (Component | CustomId)?,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type Server = ServerImp & {
|
|
177
|
+
world: World,
|
|
178
|
+
inited: boolean?,
|
|
179
|
+
|
|
180
|
+
registry: registry.Registry,
|
|
181
|
+
-- byte size of every bitmask, fixed by the client cap given to create
|
|
182
|
+
bitmask_size: number,
|
|
183
|
+
ready_players_bitmask: Bitmask,
|
|
184
|
+
components: types.Components,
|
|
185
|
+
shared: types.Shared,
|
|
186
|
+
|
|
187
|
+
networked: Map<Entity, NetworkedStorage>,
|
|
188
|
+
-- live networked entities with at least one live unreliable storage; the
|
|
189
|
+
-- unreliable collect iterates this instead of every networked entity
|
|
190
|
+
unreliable_networked: Set<Entity>,
|
|
191
|
+
postponed_components: Map<Entity, PostponedComponentsList>,
|
|
192
|
+
-- single lookup for global/custom id resolution at write time
|
|
193
|
+
entity_serialization: Map<Entity, EntitySerializationOptions>,
|
|
194
|
+
registered_custom_ids: Set<CustomId>,
|
|
195
|
+
-- single lookup for serdes/preprocessor/delta at write time
|
|
196
|
+
component_serialization: Map<Component, ComponentSerializationOptions>,
|
|
197
|
+
-- serdes and custom ids feed the handshake; set when either changes
|
|
198
|
+
is_shared_dirty: boolean,
|
|
199
|
+
|
|
200
|
+
hooked_components: Map<Component, HookedInfo>,
|
|
201
|
+
hooked_unreliable: Map<Component, HookedInfo>,
|
|
202
|
+
hooked_pairs: Map<Component, HookedInfo>,
|
|
203
|
+
hooked_unreliable_pairs: Map<Component, HookedInfo>,
|
|
204
|
+
hooked_relations: Map<Component, HookedInfo>,
|
|
205
|
+
|
|
206
|
+
dirty_networked: Array<Entity>,
|
|
207
|
+
function_filters: Map<StorageBase, FunctionFilterEntry>,
|
|
208
|
+
|
|
209
|
+
empty_bitmask: Bitmask,
|
|
210
|
+
draft_bitmask: Bitmask,
|
|
211
|
+
-- scratch for the entity target of the networked entity being collected
|
|
212
|
+
target_bitmask: Bitmask,
|
|
213
|
+
filled_bitmask: Bitmask,
|
|
214
|
+
|
|
215
|
+
client_active_bitmask: Bitmask,
|
|
216
|
+
last_active_bitmask: Bitmask,
|
|
217
|
+
newly_active_bitmask: Bitmask,
|
|
218
|
+
newly_unactive_bitmask: Bitmask,
|
|
219
|
+
|
|
220
|
+
entity_bitmask_deltas: BitmaskDeltas,
|
|
221
|
+
component_bitmask_deltas: BitmaskDeltas,
|
|
222
|
+
|
|
223
|
+
cleanups: { () -> () },
|
|
224
|
+
|
|
225
|
+
resolve_dirty: (self: Server) -> (),
|
|
226
|
+
get_full: (self: Server, player: Player) -> (buffer, Array<any>),
|
|
227
|
+
collect_entity: (self: Server, entity: Entity) -> PacketIterator,
|
|
228
|
+
collect_updates: (self: Server) -> PacketIterator,
|
|
229
|
+
collect_unreliable: (self: Server) -> PacketIterator,
|
|
230
|
+
|
|
231
|
+
register_player: (self: Server, player: Player) -> (),
|
|
232
|
+
add_player_alias: (self: Server, player: Player, alias: any) -> (),
|
|
233
|
+
remove_player_alias: (self: Server, alias: any) -> (),
|
|
234
|
+
mark_player_ready: (self: Server, player: Player) -> (),
|
|
235
|
+
is_player_ready: (self: Server, player: Player) -> boolean,
|
|
236
|
+
remove_player: (self: Server, player: Player) -> (),
|
|
237
|
+
|
|
238
|
+
encode_component: (self: Server, component: Component) -> number,
|
|
239
|
+
decode_component: (self: Server, encoded: number) -> Component?,
|
|
240
|
+
get_shared_count: (self: Server) -> number,
|
|
241
|
+
|
|
242
|
+
generate_handshake: (self: Server) -> types.HandshakeInfo,
|
|
243
|
+
verify_handshake: (self: Server, handshake: types.HandshakeInfo) -> (boolean, string?),
|
|
244
|
+
|
|
245
|
+
cleanup_entity: (self: Server, entity: Entity) -> (),
|
|
246
|
+
|
|
247
|
+
init: (self: Server, world: World?) -> (),
|
|
248
|
+
destroy: (self: Server) -> (),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
-- matches replecs.MAX_CLIENTS; only reached when server.create
|
|
252
|
+
-- is called directly without a cap
|
|
253
|
+
local DEFAULT_MAX_CLIENTS = 128
|
|
254
|
+
|
|
255
|
+
local RELATIONSHIPS = common.relationships
|
|
256
|
+
local WILDCARD = common.wildcard
|
|
257
|
+
local ECS_NAME = common.ecs_name
|
|
258
|
+
|
|
259
|
+
local IS_PAIR = common.is_pair
|
|
260
|
+
local PAIR = common.pair
|
|
261
|
+
local PAIR_FIRST = common.pair_first
|
|
262
|
+
local PAIR_SECOND = common.pair_second
|
|
263
|
+
local WORLD_ADD = common.world_add
|
|
264
|
+
local WORLD_HAS = common.world_has
|
|
265
|
+
local WORLD_GET = common.world_get
|
|
266
|
+
local WORLD_TARGET = common.world_target
|
|
267
|
+
local IS_COMPONENT = common.is_component
|
|
268
|
+
|
|
269
|
+
local LISTEN_RELATION = common.listen_relation
|
|
270
|
+
|
|
271
|
+
local HOOK_ADDED = common.hook_added
|
|
272
|
+
local HOOK_CHANGED = common.hook_changed
|
|
273
|
+
local HOOK_REMOVED = common.hook_removed
|
|
274
|
+
|
|
275
|
+
local LOG_ERROR = common.log_error
|
|
276
|
+
|
|
277
|
+
local function HOOK_PAIR_ADDED(world: World, pair_id: Entity, callback: (Entity, Component, any) -> ())
|
|
278
|
+
local relation = PAIR_FIRST(world, pair_id)
|
|
279
|
+
|
|
280
|
+
return HOOK_ADDED(world, relation, function(entity, id, value)
|
|
281
|
+
if id ~= pair_id then
|
|
282
|
+
return
|
|
283
|
+
end
|
|
284
|
+
callback(entity, id, value)
|
|
285
|
+
end)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
local function HOOK_PAIR_CHANGED(world: World, pair_id: Entity, callback: (Entity, Component, any) -> ())
|
|
289
|
+
local relation = PAIR_FIRST(world, pair_id)
|
|
290
|
+
|
|
291
|
+
return HOOK_CHANGED(world, relation, function(entity, id, value)
|
|
292
|
+
if id ~= pair_id then
|
|
293
|
+
return
|
|
294
|
+
end
|
|
295
|
+
callback(entity, id, value)
|
|
296
|
+
end)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
local function HOOK_PAIR_REMOVED(world: World, pair_id: Entity, callback: (Entity, Component) -> ())
|
|
300
|
+
local relation = PAIR_FIRST(world, pair_id)
|
|
301
|
+
|
|
302
|
+
return HOOK_REMOVED(world, relation, function(entity, id)
|
|
303
|
+
if id ~= pair_id then
|
|
304
|
+
return
|
|
305
|
+
end
|
|
306
|
+
callback(entity, id)
|
|
307
|
+
end)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
local DIRTY_CHANGED_FLAG = 0b001
|
|
311
|
+
local DIRTY_FILTER_FLAG = 0b010
|
|
312
|
+
local DIRTY_COMPONENT_FLAG = 0b100
|
|
313
|
+
|
|
314
|
+
local VLQ_ZERO = 128
|
|
315
|
+
|
|
316
|
+
-- global ids are written as a single byte; values below the offset are
|
|
317
|
+
-- reserved for the ENTITY_ID_TYPES tags that trail every other id form
|
|
318
|
+
local GLOBAL_ID_OFFSET = 10
|
|
319
|
+
|
|
320
|
+
local MAX_GLOBAL_ID = 255 - GLOBAL_ID_OFFSET
|
|
321
|
+
|
|
322
|
+
-- unreliable remotes drop packets over the transport budget, so combined
|
|
323
|
+
-- batches stay under the ~1kB roblox limit
|
|
324
|
+
local MAX_UNRELIABLE_PACKET_SIZE = 900
|
|
325
|
+
|
|
326
|
+
local TRACK_TYPES = {
|
|
327
|
+
reliable = 1,
|
|
328
|
+
pair = 2,
|
|
329
|
+
relation = 3,
|
|
330
|
+
unreliable = 4,
|
|
331
|
+
unreliable_pair = 5,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
-- sentinel for nil component values (tags), keeps value arrays hole-free
|
|
335
|
+
local NIL_VALUE = newproxy()
|
|
336
|
+
|
|
337
|
+
-- wraps a world value before it enters a value array
|
|
338
|
+
local function SET_VALUE(value: any): any
|
|
339
|
+
return if value == nil then NIL_VALUE else value
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
-- unwraps a value array entry back into the world value
|
|
343
|
+
local function READ_VALUE(value: any): any
|
|
344
|
+
return if value == NIL_VALUE then nil else value
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
local ENTITY_ID_TYPES = {
|
|
348
|
+
entity = 1,
|
|
349
|
+
custom_handler = 2,
|
|
350
|
+
custom_id = 3,
|
|
351
|
+
shared = 4,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
-- u8 trailing every outgoing buffer (the client reads it first) so a packet
|
|
355
|
+
-- fed to the wrong apply function fails loudly instead of desyncing
|
|
356
|
+
local PACKET_TYPES = {
|
|
357
|
+
full = 1,
|
|
358
|
+
entity = 2,
|
|
359
|
+
updates = 3,
|
|
360
|
+
unreliable = 4,
|
|
361
|
+
}
|
|
362
|
+
local PACKET_TYPE_SPAN = 1
|
|
363
|
+
|
|
364
|
+
local server = {}
|
|
365
|
+
server.__index = server
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
local function flag_test(bitset: Bitset, flags: Bitset): boolean
|
|
369
|
+
return bit32.band(bitset, flags) == flags
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
function mark_networked_dirty(self: Server, entity: Entity, networked: NetworkedStorage, flag: Bitset)
|
|
374
|
+
if networked.dirty_flags == 0 then
|
|
375
|
+
table.insert(self.dirty_networked, entity)
|
|
376
|
+
end
|
|
377
|
+
networked.dirty_flags = bit32.bor(networked.dirty_flags, flag)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
local function get_dirty_list(networked: NetworkedStorage, track_type: TrackType): Array<Component>
|
|
382
|
+
if track_type == TRACK_TYPES.reliable then
|
|
383
|
+
return networked.dirty_reliable
|
|
384
|
+
elseif track_type == TRACK_TYPES.unreliable then
|
|
385
|
+
return networked.dirty_unreliable
|
|
386
|
+
elseif track_type == TRACK_TYPES.pair then
|
|
387
|
+
return networked.dirty_pairs
|
|
388
|
+
elseif track_type == TRACK_TYPES.unreliable_pair then
|
|
389
|
+
return networked.dirty_unreliable_pairs
|
|
390
|
+
else
|
|
391
|
+
return networked.dirty_relations
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
function mark_tracking_storage_dirty(
|
|
397
|
+
self: Server,
|
|
398
|
+
entity: Entity,
|
|
399
|
+
component: Component,
|
|
400
|
+
networked: NetworkedStorage,
|
|
401
|
+
storage: TrackingStorageBase,
|
|
402
|
+
flag: Bitset
|
|
403
|
+
)
|
|
404
|
+
if storage.dirty_flags == 0 then
|
|
405
|
+
table.insert(get_dirty_list(networked, storage.track_type), component)
|
|
406
|
+
end
|
|
407
|
+
storage.dirty_flags = bit32.bor(storage.dirty_flags, flag)
|
|
408
|
+
mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
local function increment_unreliable(self: Server, entity: Entity, networked: NetworkedStorage)
|
|
412
|
+
networked.unreliable_count += 1
|
|
413
|
+
if not networked.is_dead then
|
|
414
|
+
self.unreliable_networked[entity] = true
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
local function decrement_unreliable(self: Server, entity: Entity, networked: NetworkedStorage)
|
|
419
|
+
networked.unreliable_count -= 1
|
|
420
|
+
if networked.unreliable_count <= 0 then
|
|
421
|
+
self.unreliable_networked[entity] = nil
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
local function edit_storage_filter(self: Server, storage: StorageBase, filter: Bitmask?)
|
|
427
|
+
if not storage.last_replicated then
|
|
428
|
+
storage.last_replicated = bitmasks.clone(storage.filter or self.last_active_bitmask)
|
|
429
|
+
end
|
|
430
|
+
storage.filter = filter
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
local function resolve_filter_bitmask(self: Server, filter: FilterAny): Bitmask
|
|
435
|
+
local registry = self.registry
|
|
436
|
+
local type = typeof(filter)
|
|
437
|
+
if type == "table" then
|
|
438
|
+
return registry:create_bitmask_from_set(filter :: FilterSet)
|
|
439
|
+
elseif type == "function" then
|
|
440
|
+
return registry:create_bitmask_from_function(filter :: registry.MemberPredicator)
|
|
441
|
+
else
|
|
442
|
+
return registry:create_bitmask_from_member(filter :: Player)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
local function disconnect_filter_group(storage: StorageBase)
|
|
447
|
+
local group = storage.filter_group
|
|
448
|
+
local listener = storage.filter_listener
|
|
449
|
+
if group and listener then
|
|
450
|
+
group:disconnect(listener)
|
|
451
|
+
end
|
|
452
|
+
storage.filter_group = nil
|
|
453
|
+
storage.filter_listener = nil
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
-- releases whatever was driving this storage's filter (group or predicate)
|
|
457
|
+
local function clear_filter_source(self: Server, storage: StorageBase)
|
|
458
|
+
disconnect_filter_group(storage)
|
|
459
|
+
self.function_filters[storage] = nil
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
-- a nil-filter storage inherits entity visibility, so its replicated set is
|
|
463
|
+
-- exactly the entity's; clients entering the entity filter then get the
|
|
464
|
+
-- storage through its own added delta
|
|
465
|
+
local function snapshot_tracking_replicated(storage: TrackingStorageBase, entity_last: Bitmask)
|
|
466
|
+
if storage.last_replicated then
|
|
467
|
+
return
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
local filter = storage.filter
|
|
471
|
+
if not filter then
|
|
472
|
+
storage.last_replicated = bitmasks.clone(entity_last)
|
|
473
|
+
return
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
local snapshot = bitmasks.clone(filter)
|
|
477
|
+
bitmasks.band(snapshot, entity_last, snapshot)
|
|
478
|
+
storage.last_replicated = snapshot
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
local function edit_networked_filter(self: Server, entity: Entity, networked: NetworkedStorage, bitmask: Bitmask?)
|
|
482
|
+
edit_storage_filter(self, networked, bitmask)
|
|
483
|
+
mark_networked_dirty(self, entity, networked, DIRTY_FILTER_FLAG)
|
|
484
|
+
mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
|
|
485
|
+
|
|
486
|
+
local entity_last = networked.last_replicated :: Bitmask
|
|
487
|
+
|
|
488
|
+
for component, reliable_storage in networked.reliable_storages do
|
|
489
|
+
snapshot_tracking_replicated(reliable_storage, entity_last)
|
|
490
|
+
mark_tracking_storage_dirty(self, entity, component, networked, reliable_storage, DIRTY_FILTER_FLAG)
|
|
491
|
+
end
|
|
492
|
+
for component, unreliable_storage in networked.unreliable_storages do
|
|
493
|
+
snapshot_tracking_replicated(unreliable_storage, entity_last)
|
|
494
|
+
mark_tracking_storage_dirty(self, entity, component, networked, unreliable_storage, DIRTY_FILTER_FLAG)
|
|
495
|
+
end
|
|
496
|
+
for id, pair_storage in networked.pair_storages do
|
|
497
|
+
snapshot_tracking_replicated(pair_storage, entity_last)
|
|
498
|
+
mark_tracking_storage_dirty(self, entity, id, networked, pair_storage, DIRTY_FILTER_FLAG)
|
|
499
|
+
end
|
|
500
|
+
for id, unreliable_pair_storage in networked.unreliable_pair_storages do
|
|
501
|
+
snapshot_tracking_replicated(unreliable_pair_storage, entity_last)
|
|
502
|
+
mark_tracking_storage_dirty(self, entity, id, networked, unreliable_pair_storage, DIRTY_FILTER_FLAG)
|
|
503
|
+
end
|
|
504
|
+
for rel, relation_storage in networked.relation_storages do
|
|
505
|
+
snapshot_tracking_replicated(relation_storage, entity_last)
|
|
506
|
+
mark_tracking_storage_dirty(self, entity, rel, networked, relation_storage, DIRTY_FILTER_FLAG)
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
local function edit_tracking_filter(
|
|
511
|
+
self: Server,
|
|
512
|
+
entity: Entity,
|
|
513
|
+
component: Component,
|
|
514
|
+
networked: NetworkedStorage,
|
|
515
|
+
storage: TrackingStorageBase,
|
|
516
|
+
bitmask: Bitmask?
|
|
517
|
+
)
|
|
518
|
+
if not storage.last_replicated then
|
|
519
|
+
-- only components in entity visibiblity were ever replicated, so the snapshot
|
|
520
|
+
-- must not claim clients that were outside the entity filter
|
|
521
|
+
local last_active = self.last_active_bitmask
|
|
522
|
+
local snapshot = bitmasks.clone(storage.filter or last_active)
|
|
523
|
+
local entity_last = networked.last_replicated or networked.filter or last_active
|
|
524
|
+
bitmasks.band(snapshot, entity_last, snapshot)
|
|
525
|
+
storage.last_replicated = snapshot
|
|
526
|
+
end
|
|
527
|
+
storage.filter = bitmask
|
|
528
|
+
mark_tracking_storage_dirty(self, entity, component, networked, storage, DIRTY_FILTER_FLAG)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
local function connect_networked_group(self: Server, entity: Entity, networked: NetworkedStorage, group: FilterGroup)
|
|
532
|
+
local function listener(bitmask: Bitmask)
|
|
533
|
+
edit_networked_filter(self, entity, networked, bitmask)
|
|
534
|
+
end
|
|
535
|
+
networked.filter_group = group
|
|
536
|
+
networked.filter_listener = listener
|
|
537
|
+
group:connect(listener)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
local function connect_tracking_group(
|
|
541
|
+
self: Server,
|
|
542
|
+
entity: Entity,
|
|
543
|
+
component: Component,
|
|
544
|
+
networked: NetworkedStorage,
|
|
545
|
+
storage: TrackingStorageBase,
|
|
546
|
+
group: FilterGroup
|
|
547
|
+
)
|
|
548
|
+
local function listener(bitmask: Bitmask)
|
|
549
|
+
edit_tracking_filter(self, entity, component, networked, storage, bitmask)
|
|
550
|
+
end
|
|
551
|
+
storage.filter_group = group
|
|
552
|
+
storage.filter_listener = listener
|
|
553
|
+
group:connect(listener)
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
function ensure_hooked_component(self: Server, component: Component): HookedInfo
|
|
557
|
+
local info = self.hooked_components[component]
|
|
558
|
+
if info then
|
|
559
|
+
return info
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
|
|
563
|
+
local new_info: HookedInfo = {
|
|
564
|
+
networked_references = networked_references,
|
|
565
|
+
}
|
|
566
|
+
self.hooked_components[component] = new_info
|
|
567
|
+
|
|
568
|
+
local function hook(unhook: () -> ())
|
|
569
|
+
table.insert(self.cleanups, unhook)
|
|
570
|
+
end
|
|
571
|
+
local world = self.world
|
|
572
|
+
|
|
573
|
+
local function mark_component_dirty(entity: Entity)
|
|
574
|
+
local reference = networked_references[entity]
|
|
575
|
+
if not reference then
|
|
576
|
+
return
|
|
577
|
+
end
|
|
578
|
+
mark_tracking_storage_dirty(
|
|
579
|
+
self,
|
|
580
|
+
entity,
|
|
581
|
+
component,
|
|
582
|
+
reference.networked,
|
|
583
|
+
reference.tracking_storage,
|
|
584
|
+
DIRTY_CHANGED_FLAG
|
|
585
|
+
)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
-- once storages only replicate the value on add, removal and visibility
|
|
589
|
+
-- gain; in-place world changes are ignored (force_change bypasses this)
|
|
590
|
+
local function mark_component_changed(entity: Entity)
|
|
591
|
+
local reference = networked_references[entity]
|
|
592
|
+
if not reference or reference.tracking_storage.is_once then
|
|
593
|
+
return
|
|
594
|
+
end
|
|
595
|
+
mark_tracking_storage_dirty(
|
|
596
|
+
self,
|
|
597
|
+
entity,
|
|
598
|
+
component,
|
|
599
|
+
reference.networked,
|
|
600
|
+
reference.tracking_storage,
|
|
601
|
+
DIRTY_CHANGED_FLAG
|
|
602
|
+
)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
hook(HOOK_ADDED(world, component, mark_component_dirty))
|
|
606
|
+
hook(HOOK_CHANGED(world, component, mark_component_changed))
|
|
607
|
+
hook(HOOK_REMOVED(world, component, mark_component_dirty))
|
|
608
|
+
|
|
609
|
+
return new_info
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
local function ensure_hooked_relation(self: Server, rel: Component): HookedInfo
|
|
613
|
+
local info = self.hooked_relations[rel]
|
|
614
|
+
if info then
|
|
615
|
+
return info
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
|
|
619
|
+
local new_info: HookedInfo = {
|
|
620
|
+
networked_references = networked_references,
|
|
621
|
+
}
|
|
622
|
+
self.hooked_relations[rel] = new_info
|
|
623
|
+
|
|
624
|
+
local function hook(unhook: () -> ())
|
|
625
|
+
table.insert(self.cleanups, unhook)
|
|
626
|
+
end
|
|
627
|
+
local world = self.world
|
|
628
|
+
|
|
629
|
+
local function mark_relation_dirty(entity: Entity, id: Entity)
|
|
630
|
+
if not IS_PAIR(id) then
|
|
631
|
+
return
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
local reference = networked_references[entity]
|
|
635
|
+
if not reference then
|
|
636
|
+
return
|
|
637
|
+
end
|
|
638
|
+
local affected = PAIR_SECOND(world, id)
|
|
639
|
+
local relation_storage = reference.tracking_storage :: RelationStorage
|
|
640
|
+
|
|
641
|
+
mark_tracking_storage_dirty(self, entity, rel, reference.networked, relation_storage, DIRTY_CHANGED_FLAG)
|
|
642
|
+
relation_storage.dirty_targets[affected] = true
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
hook(HOOK_ADDED(world, LISTEN_RELATION(rel), mark_relation_dirty))
|
|
646
|
+
hook(HOOK_CHANGED(world, LISTEN_RELATION(rel), mark_relation_dirty))
|
|
647
|
+
hook(HOOK_REMOVED(world, LISTEN_RELATION(rel), mark_relation_dirty))
|
|
648
|
+
|
|
649
|
+
return new_info
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
local function ensure_hooked_pair(self: Server, id: Entity)
|
|
653
|
+
local info = self.hooked_pairs[id]
|
|
654
|
+
if info then
|
|
655
|
+
return info
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
|
|
659
|
+
local new_info: HookedInfo = {
|
|
660
|
+
networked_references = networked_references,
|
|
661
|
+
}
|
|
662
|
+
self.hooked_pairs[id] = new_info
|
|
663
|
+
|
|
664
|
+
local function hook(unhook: () -> ())
|
|
665
|
+
table.insert(self.cleanups, unhook)
|
|
666
|
+
end
|
|
667
|
+
local world = self.world
|
|
668
|
+
|
|
669
|
+
local function mark_pair_dirty(entity: Entity)
|
|
670
|
+
local reference = networked_references[entity]
|
|
671
|
+
if not reference then
|
|
672
|
+
return
|
|
673
|
+
end
|
|
674
|
+
mark_tracking_storage_dirty(self, entity, id, reference.networked, reference.tracking_storage, DIRTY_CHANGED_FLAG)
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
hook(HOOK_PAIR_ADDED(world, id, mark_pair_dirty))
|
|
678
|
+
hook(HOOK_PAIR_CHANGED(world, id, mark_pair_dirty))
|
|
679
|
+
hook(HOOK_PAIR_REMOVED(world, id, mark_pair_dirty))
|
|
680
|
+
|
|
681
|
+
return new_info
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
local function ensure_hooked_unreliable(self: Server, component: Component): HookedInfo
|
|
685
|
+
local info = self.hooked_unreliable[component]
|
|
686
|
+
if info then
|
|
687
|
+
return info
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
|
|
691
|
+
local new_info: HookedInfo = {
|
|
692
|
+
networked_references = networked_references,
|
|
693
|
+
}
|
|
694
|
+
self.hooked_unreliable[component] = new_info
|
|
695
|
+
|
|
696
|
+
local function hook(unhook: () -> ())
|
|
697
|
+
table.insert(self.cleanups, unhook)
|
|
698
|
+
end
|
|
699
|
+
local world = self.world
|
|
700
|
+
|
|
701
|
+
local function mark_unreliable_dirty(entity: Entity)
|
|
702
|
+
local reference = networked_references[entity]
|
|
703
|
+
if not reference then
|
|
704
|
+
return
|
|
705
|
+
end
|
|
706
|
+
mark_tracking_storage_dirty(
|
|
707
|
+
self,
|
|
708
|
+
entity,
|
|
709
|
+
component,
|
|
710
|
+
reference.networked,
|
|
711
|
+
reference.tracking_storage,
|
|
712
|
+
DIRTY_CHANGED_FLAG
|
|
713
|
+
)
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
hook(HOOK_ADDED(world, component, mark_unreliable_dirty))
|
|
717
|
+
hook(HOOK_REMOVED(world, component, mark_unreliable_dirty))
|
|
718
|
+
|
|
719
|
+
return new_info
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
local function ensure_hooked_unreliable_pair(self: Server, id: Entity): HookedInfo
|
|
723
|
+
local info = self.hooked_unreliable_pairs[id]
|
|
724
|
+
if info then
|
|
725
|
+
return info
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
local networked_references: Map<Entity, NetworkedReferencesInfo> = {}
|
|
729
|
+
local new_info: HookedInfo = {
|
|
730
|
+
networked_references = networked_references,
|
|
731
|
+
}
|
|
732
|
+
self.hooked_unreliable_pairs[id] = new_info
|
|
733
|
+
|
|
734
|
+
local function hook(unhook: () -> ())
|
|
735
|
+
table.insert(self.cleanups, unhook)
|
|
736
|
+
end
|
|
737
|
+
local world = self.world
|
|
738
|
+
|
|
739
|
+
local function mark_unreliable_pair_dirty(entity: Entity)
|
|
740
|
+
local reference = networked_references[entity]
|
|
741
|
+
if not reference then
|
|
742
|
+
return
|
|
743
|
+
end
|
|
744
|
+
mark_tracking_storage_dirty(self, entity, id, reference.networked, reference.tracking_storage, DIRTY_CHANGED_FLAG)
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
hook(HOOK_PAIR_ADDED(world, id, mark_unreliable_pair_dirty))
|
|
748
|
+
hook(HOOK_PAIR_REMOVED(world, id, mark_unreliable_pair_dirty))
|
|
749
|
+
|
|
750
|
+
return new_info
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
local function get_hooked_map(self: Server, track_type: TrackType): Map<Component, HookedInfo>
|
|
754
|
+
if track_type == TRACK_TYPES.reliable then
|
|
755
|
+
return self.hooked_components
|
|
756
|
+
elseif track_type == TRACK_TYPES.unreliable then
|
|
757
|
+
return self.hooked_unreliable
|
|
758
|
+
elseif track_type == TRACK_TYPES.pair then
|
|
759
|
+
return self.hooked_pairs
|
|
760
|
+
elseif track_type == TRACK_TYPES.unreliable_pair then
|
|
761
|
+
return self.hooked_unreliable_pairs
|
|
762
|
+
else
|
|
763
|
+
return self.hooked_relations
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
-- hook references live as long as their storage: a stopped storage keeps
|
|
768
|
+
-- receiving world changes so a same-frame revive still replicates them. the
|
|
769
|
+
-- reference goes away only when collect discards the storage
|
|
770
|
+
local function clear_hook_reference(self: Server, entity: Entity, component: Component, storage: TrackingStorageBase)
|
|
771
|
+
local info = get_hooked_map(self, storage.track_type)[component]
|
|
772
|
+
if not info then
|
|
773
|
+
return
|
|
774
|
+
end
|
|
775
|
+
local reference = info.networked_references[entity]
|
|
776
|
+
if reference and reference.tracking_storage == storage then
|
|
777
|
+
info.networked_references[entity] = nil
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
local function clear_networked_references(self: Server, entity: Entity, networked: NetworkedStorage)
|
|
782
|
+
for component, storage in networked.reliable_storages do
|
|
783
|
+
clear_hook_reference(self, entity, component, storage)
|
|
784
|
+
end
|
|
785
|
+
for component, storage in networked.unreliable_storages do
|
|
786
|
+
clear_hook_reference(self, entity, component, storage)
|
|
787
|
+
end
|
|
788
|
+
for id, storage in networked.pair_storages do
|
|
789
|
+
clear_hook_reference(self, entity, id, storage)
|
|
790
|
+
end
|
|
791
|
+
for id, storage in networked.unreliable_pair_storages do
|
|
792
|
+
clear_hook_reference(self, entity, id, storage)
|
|
793
|
+
end
|
|
794
|
+
for rel, storage in networked.relation_storages do
|
|
795
|
+
clear_hook_reference(self, entity, rel, storage)
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
-- resolves a filter input into a bitmask and attaches its driving source
|
|
800
|
+
-- (group listener or predicate entry) to the storage; nil filter means
|
|
801
|
+
-- "replicate to all active clients", resolved at collect
|
|
802
|
+
local function apply_tracking_filter(
|
|
803
|
+
self: Server,
|
|
804
|
+
entity: Entity,
|
|
805
|
+
component: Component,
|
|
806
|
+
networked: NetworkedStorage,
|
|
807
|
+
storage: TrackingStorageBase,
|
|
808
|
+
filter: FilterInput?
|
|
809
|
+
): Bitmask?
|
|
810
|
+
local bitmask: Bitmask?
|
|
811
|
+
|
|
812
|
+
if filter then
|
|
813
|
+
if filter_groups.is_filter_group(filter) then
|
|
814
|
+
local group = filter :: FilterGroup
|
|
815
|
+
bitmask = group:calculate_up()
|
|
816
|
+
connect_tracking_group(self, entity, component, networked, storage, group)
|
|
817
|
+
elseif typeof(filter) == "function" then
|
|
818
|
+
local predicator = filter :: registry.MemberPredicator
|
|
819
|
+
bitmask = self.registry:create_bitmask_from_function(predicator)
|
|
820
|
+
|
|
821
|
+
self.function_filters[storage] = {
|
|
822
|
+
predicator = predicator,
|
|
823
|
+
entity = entity,
|
|
824
|
+
networked = networked,
|
|
825
|
+
|
|
826
|
+
tracking = component,
|
|
827
|
+
tracking_storage = storage,
|
|
828
|
+
}
|
|
829
|
+
else
|
|
830
|
+
bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
return bitmask
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
local function change_tracking(
|
|
838
|
+
self: Server,
|
|
839
|
+
entity: Entity,
|
|
840
|
+
component: Component,
|
|
841
|
+
networked: NetworkedStorage,
|
|
842
|
+
storage: TrackingStorageBase,
|
|
843
|
+
filter: FilterInput?
|
|
844
|
+
)
|
|
845
|
+
-- old filter source stops driving this storage before the new one takes over
|
|
846
|
+
clear_filter_source(self, storage)
|
|
847
|
+
|
|
848
|
+
local bitmask = apply_tracking_filter(self, entity, component, networked, storage, filter)
|
|
849
|
+
|
|
850
|
+
-- a stopped storage keeps its last_replicated (forced at stop), so reviving
|
|
851
|
+
-- here sends only the delta between the pre-stop and the new filter
|
|
852
|
+
storage.is_dead = false
|
|
853
|
+
storage.filter_input = filter
|
|
854
|
+
edit_tracking_filter(self, entity, component, networked, storage, bitmask)
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
-- the last filter is forced into last_replicated and the current one becomes
|
|
858
|
+
-- the empty bitmask: collect emits the removal to everyone who had it, then
|
|
859
|
+
-- drops the storage. hooks stop feeding it right away
|
|
860
|
+
local function stop_tracking(
|
|
861
|
+
self: Server,
|
|
862
|
+
entity: Entity,
|
|
863
|
+
component: Component,
|
|
864
|
+
networked: NetworkedStorage,
|
|
865
|
+
storage: TrackingStorageBase
|
|
866
|
+
)
|
|
867
|
+
storage.is_dead = true
|
|
868
|
+
storage.filter_input = nil
|
|
869
|
+
clear_filter_source(self, storage)
|
|
870
|
+
edit_tracking_filter(self, entity, component, networked, storage, self.empty_bitmask)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
local function remove_postponed_component(self: Server, entity: Entity, track_type: TrackType, component: Component)
|
|
874
|
+
local postponed = self.postponed_components[entity]
|
|
875
|
+
if not postponed then
|
|
876
|
+
return
|
|
877
|
+
end
|
|
878
|
+
local postponed_list = postponed[track_type]
|
|
879
|
+
if postponed_list then
|
|
880
|
+
postponed_list[component] = nil
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
local function create_tracking_storage(
|
|
885
|
+
self: Server,
|
|
886
|
+
component: Component,
|
|
887
|
+
track_type: TrackType,
|
|
888
|
+
filter: FilterInput?
|
|
889
|
+
): TrackingStorageBase
|
|
890
|
+
return {
|
|
891
|
+
filter = nil :: Bitmask?,
|
|
892
|
+
last_replicated = buffer.create(self.bitmask_size),
|
|
893
|
+
|
|
894
|
+
track_type = track_type,
|
|
895
|
+
is_component = IS_COMPONENT(self.world, component),
|
|
896
|
+
|
|
897
|
+
dirty_flags = 0,
|
|
898
|
+
is_dead = false,
|
|
899
|
+
filter_input = filter,
|
|
900
|
+
}
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
local function start_reliable(
|
|
904
|
+
self: Server,
|
|
905
|
+
entity: Entity,
|
|
906
|
+
component: Component,
|
|
907
|
+
networked: NetworkedStorage,
|
|
908
|
+
filter: FilterInput?,
|
|
909
|
+
once: boolean?
|
|
910
|
+
)
|
|
911
|
+
local reliable_storage = networked.reliable_storages[component]
|
|
912
|
+
|
|
913
|
+
if reliable_storage then
|
|
914
|
+
-- revived storage keeps its last_replicated, collect sends only the delta
|
|
915
|
+
change_tracking(self, entity, component, networked, reliable_storage, filter)
|
|
916
|
+
-- set_once / set_reliable on an existing storage switches its mode
|
|
917
|
+
reliable_storage.is_once = once
|
|
918
|
+
else
|
|
919
|
+
local new_storage = create_tracking_storage(self, component, TRACK_TYPES.reliable, filter) :: ReliableStorage
|
|
920
|
+
new_storage.is_once = once
|
|
921
|
+
networked.reliable_storages[component] = new_storage
|
|
922
|
+
|
|
923
|
+
new_storage.filter = apply_tracking_filter(self, entity, component, networked, new_storage, filter)
|
|
924
|
+
mark_tracking_storage_dirty(self, entity, component, networked, new_storage, DIRTY_FILTER_FLAG)
|
|
925
|
+
reliable_storage = new_storage
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
local hook_info = ensure_hooked_component(self, component)
|
|
929
|
+
hook_info.networked_references[entity] = {
|
|
930
|
+
tracking_storage = reliable_storage,
|
|
931
|
+
networked = networked,
|
|
932
|
+
}
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
local function start_unreliable(
|
|
936
|
+
self: Server,
|
|
937
|
+
entity: Entity,
|
|
938
|
+
component: Component,
|
|
939
|
+
networked: NetworkedStorage,
|
|
940
|
+
filter: FilterInput?
|
|
941
|
+
)
|
|
942
|
+
local unreliable_storage = networked.unreliable_storages[component]
|
|
943
|
+
|
|
944
|
+
if unreliable_storage then
|
|
945
|
+
-- reviving a stopped storage regains the count its stop gave up
|
|
946
|
+
if unreliable_storage.is_dead then
|
|
947
|
+
increment_unreliable(self, entity, networked)
|
|
948
|
+
end
|
|
949
|
+
change_tracking(self, entity, component, networked, unreliable_storage, filter)
|
|
950
|
+
else
|
|
951
|
+
local new_storage = create_tracking_storage(self, component, TRACK_TYPES.unreliable, filter)
|
|
952
|
+
networked.unreliable_storages[component] = new_storage
|
|
953
|
+
increment_unreliable(self, entity, networked)
|
|
954
|
+
|
|
955
|
+
new_storage.filter = apply_tracking_filter(self, entity, component, networked, new_storage, filter)
|
|
956
|
+
mark_tracking_storage_dirty(self, entity, component, networked, new_storage, DIRTY_FILTER_FLAG)
|
|
957
|
+
unreliable_storage = new_storage
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
local hook_info = ensure_hooked_unreliable(self, component)
|
|
961
|
+
hook_info.networked_references[entity] = {
|
|
962
|
+
tracking_storage = unreliable_storage,
|
|
963
|
+
networked = networked,
|
|
964
|
+
}
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
local function start_pair(
|
|
968
|
+
self: Server,
|
|
969
|
+
entity: Entity,
|
|
970
|
+
id: Component,
|
|
971
|
+
networked: NetworkedStorage,
|
|
972
|
+
filter: FilterInput?
|
|
973
|
+
)
|
|
974
|
+
local pair_storage = networked.pair_storages[id]
|
|
975
|
+
|
|
976
|
+
if pair_storage then
|
|
977
|
+
change_tracking(self, entity, id, networked, pair_storage, filter)
|
|
978
|
+
else
|
|
979
|
+
local new_storage = create_tracking_storage(self, id, TRACK_TYPES.pair, filter) :: PairStorage
|
|
980
|
+
networked.pair_storages[id] = new_storage
|
|
981
|
+
|
|
982
|
+
new_storage.filter = apply_tracking_filter(self, entity, id, networked, new_storage, filter)
|
|
983
|
+
mark_tracking_storage_dirty(self, entity, id, networked, new_storage, DIRTY_FILTER_FLAG)
|
|
984
|
+
pair_storage = new_storage
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
local hook_info = ensure_hooked_pair(self, id)
|
|
988
|
+
hook_info.networked_references[entity] = {
|
|
989
|
+
tracking_storage = pair_storage,
|
|
990
|
+
networked = networked,
|
|
991
|
+
}
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
local function start_unreliable_pair(
|
|
995
|
+
self: Server,
|
|
996
|
+
entity: Entity,
|
|
997
|
+
id: Component,
|
|
998
|
+
networked: NetworkedStorage,
|
|
999
|
+
filter: FilterInput?
|
|
1000
|
+
)
|
|
1001
|
+
local unreliable_pair_storage = networked.unreliable_pair_storages[id]
|
|
1002
|
+
|
|
1003
|
+
if unreliable_pair_storage then
|
|
1004
|
+
-- reviving a stopped storage regains the count its stop gave up
|
|
1005
|
+
if unreliable_pair_storage.is_dead then
|
|
1006
|
+
increment_unreliable(self, entity, networked)
|
|
1007
|
+
end
|
|
1008
|
+
-- revived storage keeps its last_replicated, collect sends only the delta
|
|
1009
|
+
change_tracking(self, entity, id, networked, unreliable_pair_storage, filter)
|
|
1010
|
+
else
|
|
1011
|
+
local new_storage =
|
|
1012
|
+
create_tracking_storage(self, id, TRACK_TYPES.unreliable_pair, filter) :: UnreliablePairStorage
|
|
1013
|
+
networked.unreliable_pair_storages[id] = new_storage
|
|
1014
|
+
increment_unreliable(self, entity, networked)
|
|
1015
|
+
|
|
1016
|
+
new_storage.filter = apply_tracking_filter(self, entity, id, networked, new_storage, filter)
|
|
1017
|
+
mark_tracking_storage_dirty(self, entity, id, networked, new_storage, DIRTY_FILTER_FLAG)
|
|
1018
|
+
unreliable_pair_storage = new_storage
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
local hook_info = ensure_hooked_unreliable_pair(self, id)
|
|
1022
|
+
hook_info.networked_references[entity] = {
|
|
1023
|
+
tracking_storage = unreliable_pair_storage,
|
|
1024
|
+
networked = networked,
|
|
1025
|
+
}
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
local function start_relation(
|
|
1029
|
+
self: Server,
|
|
1030
|
+
entity: Entity,
|
|
1031
|
+
rel: Component,
|
|
1032
|
+
networked: NetworkedStorage,
|
|
1033
|
+
filter: FilterInput?
|
|
1034
|
+
)
|
|
1035
|
+
local relation_storage = networked.relation_storages[rel]
|
|
1036
|
+
|
|
1037
|
+
if relation_storage then
|
|
1038
|
+
change_tracking(self, entity, rel, networked, relation_storage, filter)
|
|
1039
|
+
else
|
|
1040
|
+
local new_storage = create_tracking_storage(self, rel, TRACK_TYPES.relation, filter) :: RelationStorage
|
|
1041
|
+
new_storage.dirty_targets = {}
|
|
1042
|
+
networked.relation_storages[rel] = new_storage
|
|
1043
|
+
|
|
1044
|
+
new_storage.filter = apply_tracking_filter(self, entity, rel, networked, new_storage, filter)
|
|
1045
|
+
mark_tracking_storage_dirty(self, entity, rel, networked, new_storage, DIRTY_FILTER_FLAG)
|
|
1046
|
+
relation_storage = new_storage
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
local hook_info = ensure_hooked_relation(self, rel)
|
|
1050
|
+
hook_info.networked_references[entity] = {
|
|
1051
|
+
tracking_storage = relation_storage,
|
|
1052
|
+
networked = networked,
|
|
1053
|
+
}
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
local function ensure_networked(self: Server, entity: Entity): (NetworkedStorage, boolean)
|
|
1057
|
+
local existing = self.networked[entity]
|
|
1058
|
+
if existing then
|
|
1059
|
+
existing.is_dead = false
|
|
1060
|
+
return existing, true
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
local new_networked: NetworkedStorage = {
|
|
1064
|
+
filter = nil :: any,
|
|
1065
|
+
last_replicated = buffer.create(self.bitmask_size),
|
|
1066
|
+
dirty_flags = 0,
|
|
1067
|
+
is_dead = false,
|
|
1068
|
+
unreliable_count = 0,
|
|
1069
|
+
|
|
1070
|
+
dirty_reliable = {},
|
|
1071
|
+
dirty_unreliable = {},
|
|
1072
|
+
dirty_pairs = {},
|
|
1073
|
+
dirty_unreliable_pairs = {},
|
|
1074
|
+
dirty_relations = {},
|
|
1075
|
+
|
|
1076
|
+
reliable_storages = {},
|
|
1077
|
+
unreliable_storages = {},
|
|
1078
|
+
pair_storages = {},
|
|
1079
|
+
unreliable_pair_storages = {},
|
|
1080
|
+
relation_storages = {},
|
|
1081
|
+
}
|
|
1082
|
+
self.networked[entity] = new_networked
|
|
1083
|
+
return new_networked, false
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
function start_networked(self: Server, entity: Entity, filter: FilterInput?)
|
|
1087
|
+
local networked, revived = ensure_networked(self, entity)
|
|
1088
|
+
|
|
1089
|
+
-- the removal hook on this tag is how world:delete reaches the server
|
|
1090
|
+
local alive_tracking = self.components.__alive_tracking__
|
|
1091
|
+
if not WORLD_HAS(self.world, entity, alive_tracking) then
|
|
1092
|
+
WORLD_ADD(self.world, entity, alive_tracking)
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
if revived then
|
|
1096
|
+
-- old filter snapshots into last_replicated, clients leaving the filter
|
|
1097
|
+
-- get a removal delta instead of the pending deletion
|
|
1098
|
+
change_networked(self, entity, networked, filter)
|
|
1099
|
+
else
|
|
1100
|
+
local bitmask: Bitmask?
|
|
1101
|
+
|
|
1102
|
+
if filter then
|
|
1103
|
+
if filter_groups.is_filter_group(filter) then
|
|
1104
|
+
local group = filter :: FilterGroup
|
|
1105
|
+
bitmask = group:calculate_up()
|
|
1106
|
+
connect_networked_group(self, entity, networked, group)
|
|
1107
|
+
elseif typeof(filter) == "function" then
|
|
1108
|
+
local predicator = filter :: registry.MemberPredicator
|
|
1109
|
+
bitmask = self.registry:create_bitmask_from_function(predicator)
|
|
1110
|
+
|
|
1111
|
+
self.function_filters[networked] = {
|
|
1112
|
+
predicator = predicator,
|
|
1113
|
+
entity = entity,
|
|
1114
|
+
networked = networked,
|
|
1115
|
+
}
|
|
1116
|
+
else
|
|
1117
|
+
bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
|
|
1118
|
+
end
|
|
1119
|
+
-- bitmask stays nil: replicate to all active clients, resolved at collect
|
|
1120
|
+
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
networked.filter = bitmask
|
|
1124
|
+
mark_networked_dirty(self, entity, networked, DIRTY_FILTER_FLAG)
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1127
|
+
local postponed = self.postponed_components[entity]
|
|
1128
|
+
if postponed then
|
|
1129
|
+
self.postponed_components[entity] = nil
|
|
1130
|
+
|
|
1131
|
+
for track_type, components_list in postponed do
|
|
1132
|
+
if track_type == TRACK_TYPES.reliable then
|
|
1133
|
+
for component, info in components_list do
|
|
1134
|
+
start_reliable(self, entity, component, networked, info.filter, info.once)
|
|
1135
|
+
end
|
|
1136
|
+
elseif track_type == TRACK_TYPES.unreliable then
|
|
1137
|
+
for component, info in components_list do
|
|
1138
|
+
start_unreliable(self, entity, component, networked, info.filter)
|
|
1139
|
+
end
|
|
1140
|
+
elseif track_type == TRACK_TYPES.pair then
|
|
1141
|
+
for id, info in components_list do
|
|
1142
|
+
start_pair(self, entity, id, networked, info.filter)
|
|
1143
|
+
end
|
|
1144
|
+
elseif track_type == TRACK_TYPES.unreliable_pair then
|
|
1145
|
+
for id, info in components_list do
|
|
1146
|
+
start_unreliable_pair(self, entity, id, networked, info.filter)
|
|
1147
|
+
end
|
|
1148
|
+
elseif track_type == TRACK_TYPES.relation then
|
|
1149
|
+
for rel, info in components_list do
|
|
1150
|
+
start_relation(self, entity, rel, networked, info.filter)
|
|
1151
|
+
end
|
|
1152
|
+
end
|
|
1153
|
+
end
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
-- a revived entity keeps its live unreliable storages (their count never
|
|
1157
|
+
-- dropped), so membership lost at stop_networked comes back here
|
|
1158
|
+
if networked.unreliable_count > 0 then
|
|
1159
|
+
self.unreliable_networked[entity] = true
|
|
1160
|
+
end
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
function change_networked(self: Server, entity: Entity, networked: NetworkedStorage, filter: FilterInput?)
|
|
1164
|
+
-- old filter source stops driving this storage before the new one takes over
|
|
1165
|
+
clear_filter_source(self, networked)
|
|
1166
|
+
|
|
1167
|
+
local bitmask: Bitmask?
|
|
1168
|
+
if filter then
|
|
1169
|
+
if filter_groups.is_filter_group(filter) then
|
|
1170
|
+
local group = filter :: FilterGroup
|
|
1171
|
+
bitmask = group:calculate_up()
|
|
1172
|
+
connect_networked_group(self, entity, networked, group)
|
|
1173
|
+
elseif typeof(filter) == "function" then
|
|
1174
|
+
local predicator = filter :: registry.MemberPredicator
|
|
1175
|
+
bitmask = self.registry:create_bitmask_from_function(predicator)
|
|
1176
|
+
self.function_filters[networked] = {
|
|
1177
|
+
predicator = predicator,
|
|
1178
|
+
entity = entity,
|
|
1179
|
+
networked = networked,
|
|
1180
|
+
}
|
|
1181
|
+
else
|
|
1182
|
+
bitmask = resolve_filter_bitmask(self, filter :: FilterAny)
|
|
1183
|
+
end
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
edit_networked_filter(self, entity, networked, bitmask)
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
function server.set_networked(self: Server, entity: Entity, filter: FilterInput?)
|
|
1190
|
+
local networked = self.networked[entity]
|
|
1191
|
+
|
|
1192
|
+
if networked and networked.cleaned then
|
|
1193
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
if networked and not networked.is_dead then
|
|
1197
|
+
change_networked(self, entity, networked, filter)
|
|
1198
|
+
else
|
|
1199
|
+
start_networked(self, entity, filter)
|
|
1200
|
+
end
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
function push_postponed_component(
|
|
1204
|
+
postponed: PostponedComponentsList,
|
|
1205
|
+
track_type: TrackType,
|
|
1206
|
+
component: Component,
|
|
1207
|
+
filter: any,
|
|
1208
|
+
once: boolean?
|
|
1209
|
+
)
|
|
1210
|
+
local postponed_list = postponed[track_type]
|
|
1211
|
+
if not postponed_list then
|
|
1212
|
+
postponed_list = {}
|
|
1213
|
+
postponed[track_type] = postponed_list
|
|
1214
|
+
end
|
|
1215
|
+
postponed_list[component] = { filter = filter, once = once }
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
function ensure_postponed(self: Server, entity: Entity): PostponedComponentsList
|
|
1219
|
+
local postponed = self.postponed_components[entity]
|
|
1220
|
+
if not postponed then
|
|
1221
|
+
postponed = {}
|
|
1222
|
+
self.postponed_components[entity] = postponed
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
return postponed
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
function server.stop_networked(self: Server, entity: Entity)
|
|
1229
|
+
local networked = self.networked[entity]
|
|
1230
|
+
-- already dead: postponing again would resurrect storages a stop_* dropped
|
|
1231
|
+
if not networked or networked.cleaned or networked.is_dead then
|
|
1232
|
+
return
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
networked.is_dead = true
|
|
1236
|
+
clear_filter_source(self, networked)
|
|
1237
|
+
mark_networked_dirty(self, entity, networked, DIRTY_COMPONENT_FLAG)
|
|
1238
|
+
-- count stays: live storages get postponed and come back on revive
|
|
1239
|
+
self.unreliable_networked[entity] = nil
|
|
1240
|
+
|
|
1241
|
+
local postponed = ensure_postponed(self, entity)
|
|
1242
|
+
|
|
1243
|
+
-- storages stopped with stop_* must not come back with the entity, so only
|
|
1244
|
+
-- live ones get postponed. hook references stay: world changes during the
|
|
1245
|
+
-- dead window keep marking the storage so a revive replicates them
|
|
1246
|
+
for component, reliable_storage in networked.reliable_storages do
|
|
1247
|
+
if reliable_storage.is_dead then
|
|
1248
|
+
continue
|
|
1249
|
+
end
|
|
1250
|
+
clear_filter_source(self, reliable_storage)
|
|
1251
|
+
push_postponed_component(
|
|
1252
|
+
postponed,
|
|
1253
|
+
TRACK_TYPES.reliable,
|
|
1254
|
+
component,
|
|
1255
|
+
reliable_storage.filter_input,
|
|
1256
|
+
reliable_storage.is_once
|
|
1257
|
+
)
|
|
1258
|
+
end
|
|
1259
|
+
for component, unreliable_storage in networked.unreliable_storages do
|
|
1260
|
+
if unreliable_storage.is_dead then
|
|
1261
|
+
continue
|
|
1262
|
+
end
|
|
1263
|
+
clear_filter_source(self, unreliable_storage)
|
|
1264
|
+
push_postponed_component(postponed, TRACK_TYPES.unreliable, component, unreliable_storage.filter_input)
|
|
1265
|
+
end
|
|
1266
|
+
for id, pair_storage in networked.pair_storages do
|
|
1267
|
+
if pair_storage.is_dead then
|
|
1268
|
+
continue
|
|
1269
|
+
end
|
|
1270
|
+
clear_filter_source(self, pair_storage)
|
|
1271
|
+
push_postponed_component(postponed, TRACK_TYPES.pair, id, pair_storage.filter_input)
|
|
1272
|
+
end
|
|
1273
|
+
for id, unreliable_pair_storage in networked.unreliable_pair_storages do
|
|
1274
|
+
if unreliable_pair_storage.is_dead then
|
|
1275
|
+
continue
|
|
1276
|
+
end
|
|
1277
|
+
-- filter_input keeps the group or predicate itself, so a later start reattaches it
|
|
1278
|
+
clear_filter_source(self, unreliable_pair_storage)
|
|
1279
|
+
push_postponed_component(postponed, TRACK_TYPES.unreliable_pair, id, unreliable_pair_storage.filter_input)
|
|
1280
|
+
end
|
|
1281
|
+
for rel, relation_storage in networked.relation_storages do
|
|
1282
|
+
if relation_storage.is_dead then
|
|
1283
|
+
continue
|
|
1284
|
+
end
|
|
1285
|
+
clear_filter_source(self, relation_storage)
|
|
1286
|
+
push_postponed_component(postponed, TRACK_TYPES.relation, rel, relation_storage.filter_input)
|
|
1287
|
+
end
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
function server.cleanup_entity(self: Server, entity: Entity)
|
|
1291
|
+
local networked = self.networked[entity]
|
|
1292
|
+
|
|
1293
|
+
if networked then
|
|
1294
|
+
if not networked.is_dead then
|
|
1295
|
+
self:stop_networked(entity)
|
|
1296
|
+
end
|
|
1297
|
+
networked.cleaned = true
|
|
1298
|
+
end
|
|
1299
|
+
|
|
1300
|
+
-- stop_networked postpones components for a revive; the entity is gone, drop them
|
|
1301
|
+
self.postponed_components[entity] = nil
|
|
1302
|
+
self.unreliable_networked[entity] = nil
|
|
1303
|
+
|
|
1304
|
+
-- custom ids travel with the server id, so the client resolves the
|
|
1305
|
+
-- deletion without them; a global id is the only address the deletion
|
|
1306
|
+
-- delta has, so it outlives the storage until collect_updates discards it
|
|
1307
|
+
local options = self.entity_serialization[entity]
|
|
1308
|
+
if options then
|
|
1309
|
+
options.custom = nil
|
|
1310
|
+
if not networked or options.global_id == nil then
|
|
1311
|
+
self.entity_serialization[entity] = nil
|
|
1312
|
+
end
|
|
1313
|
+
end
|
|
1314
|
+
end
|
|
1315
|
+
|
|
1316
|
+
function server.set_reliable(self: Server, entity: Entity, component: Component, filter: FilterInput?)
|
|
1317
|
+
local networked = self.networked[entity]
|
|
1318
|
+
|
|
1319
|
+
if networked and networked.cleaned then
|
|
1320
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
if networked and not networked.is_dead then
|
|
1324
|
+
start_reliable(self, entity, component, networked, filter, false)
|
|
1325
|
+
else
|
|
1326
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.reliable, component, filter, false)
|
|
1327
|
+
end
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
-- the unreliable channel only carries values, so tags cannot be tracked
|
|
1331
|
+
-- unreliably; use set_reliable for presence-only replication
|
|
1332
|
+
function server.set_unreliable(self: Server, entity: Entity, component: Component, filter: FilterInput?)
|
|
1333
|
+
local networked = self.networked[entity]
|
|
1334
|
+
|
|
1335
|
+
if networked and networked.cleaned then
|
|
1336
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1337
|
+
end
|
|
1338
|
+
if not IS_COMPONENT(self.world, component) then
|
|
1339
|
+
LOG_ERROR(
|
|
1340
|
+
"attempted to track a tag unreliably, unreliables only send values: ",
|
|
1341
|
+
common.log_component(self.world, component)
|
|
1342
|
+
)
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
if networked and not networked.is_dead then
|
|
1346
|
+
start_unreliable(self, entity, component, networked, filter)
|
|
1347
|
+
else
|
|
1348
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.unreliable, component, filter)
|
|
1349
|
+
end
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
function server.set_once(self: Server, entity: Entity, component: Component, filter: FilterInput?)
|
|
1353
|
+
local networked = self.networked[entity]
|
|
1354
|
+
|
|
1355
|
+
if networked and networked.cleaned then
|
|
1356
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
if networked and not networked.is_dead then
|
|
1360
|
+
start_reliable(self, entity, component, networked, filter, true)
|
|
1361
|
+
else
|
|
1362
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.reliable, component, filter, true)
|
|
1363
|
+
end
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
-- a dead storage or entity still takes the mark: it is harmless if collect
|
|
1367
|
+
-- discards it and needed if a revive lands before then
|
|
1368
|
+
function server.force_change(self: Server, entity: Entity, component: Component)
|
|
1369
|
+
local networked = self.networked[entity]
|
|
1370
|
+
if not networked or networked.cleaned then
|
|
1371
|
+
return
|
|
1372
|
+
end
|
|
1373
|
+
|
|
1374
|
+
local reliable_storage = networked.reliable_storages[component]
|
|
1375
|
+
if not reliable_storage then
|
|
1376
|
+
return
|
|
1377
|
+
end
|
|
1378
|
+
mark_tracking_storage_dirty(self, entity, component, networked, reliable_storage, DIRTY_CHANGED_FLAG)
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
function server.set_pair(self: Server, entity: Entity, id: Component, filter: FilterInput?)
|
|
1382
|
+
local networked = self.networked[entity]
|
|
1383
|
+
|
|
1384
|
+
if networked and networked.cleaned then
|
|
1385
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
if networked and not networked.is_dead then
|
|
1389
|
+
start_pair(self, entity, id, networked, filter)
|
|
1390
|
+
else
|
|
1391
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.pair, id, filter)
|
|
1392
|
+
end
|
|
1393
|
+
end
|
|
1394
|
+
|
|
1395
|
+
-- unreliable pairs replicate their value reliably on add and remove; later
|
|
1396
|
+
-- value changes are left to the unreliable channel. pair tags are rejected,
|
|
1397
|
+
-- the channel has nothing to send for them
|
|
1398
|
+
function server.set_unreliable_pair(self: Server, entity: Entity, id: Component, filter: FilterInput?)
|
|
1399
|
+
local networked = self.networked[entity]
|
|
1400
|
+
|
|
1401
|
+
if networked and networked.cleaned then
|
|
1402
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1403
|
+
end
|
|
1404
|
+
if not IS_COMPONENT(self.world, id) then
|
|
1405
|
+
LOG_ERROR(
|
|
1406
|
+
"attempted to track a pair tag unreliably, unreliables only send values: ",
|
|
1407
|
+
common.log_component(self.world, id)
|
|
1408
|
+
)
|
|
1409
|
+
end
|
|
1410
|
+
|
|
1411
|
+
if networked and not networked.is_dead then
|
|
1412
|
+
start_unreliable_pair(self, entity, id, networked, filter)
|
|
1413
|
+
else
|
|
1414
|
+
-- dead entities postpone too; a revive replays this alongside the rest
|
|
1415
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.unreliable_pair, id, filter)
|
|
1416
|
+
end
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
function server.set_relation(self: Server, entity: Entity, relation: Component, filter: FilterInput?)
|
|
1420
|
+
local networked = self.networked[entity]
|
|
1421
|
+
|
|
1422
|
+
if networked and networked.cleaned then
|
|
1423
|
+
LOG_ERROR "attempted to network a dead entity"
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
if networked and not networked.is_dead then
|
|
1427
|
+
start_relation(self, entity, relation, networked, filter)
|
|
1428
|
+
else
|
|
1429
|
+
push_postponed_component(ensure_postponed(self, entity), TRACK_TYPES.relation, relation, filter)
|
|
1430
|
+
end
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1433
|
+
-- stop_* marks the storage dead (removal goes out at the next collect) and
|
|
1434
|
+
-- drops its postponed entry so a later start_networked does not bring it
|
|
1435
|
+
-- back. a dead entity keeps its live storages until collect, so both happen:
|
|
1436
|
+
-- otherwise a same-frame revive would keep the storage alive. a set_* call
|
|
1437
|
+
-- before the collect revives the storage in place
|
|
1438
|
+
|
|
1439
|
+
-- resolves the networked storage a stop_* acts on; nil means only the
|
|
1440
|
+
-- postponed entry needs dropping
|
|
1441
|
+
local function stop_target(self: Server, entity: Entity, track_type: TrackType, component: Component): NetworkedStorage?
|
|
1442
|
+
local networked = self.networked[entity]
|
|
1443
|
+
if networked and networked.cleaned then
|
|
1444
|
+
return nil
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
if not networked or networked.is_dead then
|
|
1448
|
+
remove_postponed_component(self, entity, track_type, component)
|
|
1449
|
+
end
|
|
1450
|
+
return networked
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1453
|
+
function server.stop_reliable(self: Server, entity: Entity, component: Component)
|
|
1454
|
+
local networked = stop_target(self, entity, TRACK_TYPES.reliable, component)
|
|
1455
|
+
if not networked then
|
|
1456
|
+
return
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
local reliable_storage = networked.reliable_storages[component]
|
|
1460
|
+
if reliable_storage and not reliable_storage.is_dead then
|
|
1461
|
+
stop_tracking(self, entity, component, networked, reliable_storage)
|
|
1462
|
+
end
|
|
1463
|
+
end
|
|
1464
|
+
|
|
1465
|
+
-- once storages live in the reliable storages, same stop path
|
|
1466
|
+
function server.stop_once(self: Server, entity: Entity, component: Component)
|
|
1467
|
+
self:stop_reliable(entity, component)
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1470
|
+
function server.stop_unreliable(self: Server, entity: Entity, component: Component)
|
|
1471
|
+
local networked = stop_target(self, entity, TRACK_TYPES.unreliable, component)
|
|
1472
|
+
if not networked then
|
|
1473
|
+
return
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
local unreliable_storage = networked.unreliable_storages[component]
|
|
1477
|
+
if unreliable_storage and not unreliable_storage.is_dead then
|
|
1478
|
+
stop_tracking(self, entity, component, networked, unreliable_storage)
|
|
1479
|
+
decrement_unreliable(self, entity, networked)
|
|
1480
|
+
end
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
function server.stop_pair(self: Server, entity: Entity, id: Component)
|
|
1484
|
+
local networked = stop_target(self, entity, TRACK_TYPES.pair, id)
|
|
1485
|
+
if not networked then
|
|
1486
|
+
return
|
|
1487
|
+
end
|
|
1488
|
+
|
|
1489
|
+
local pair_storage = networked.pair_storages[id]
|
|
1490
|
+
if pair_storage and not pair_storage.is_dead then
|
|
1491
|
+
stop_tracking(self, entity, id, networked, pair_storage)
|
|
1492
|
+
end
|
|
1493
|
+
end
|
|
1494
|
+
|
|
1495
|
+
function server.stop_unreliable_pair(self: Server, entity: Entity, id: Component)
|
|
1496
|
+
local networked = stop_target(self, entity, TRACK_TYPES.unreliable_pair, id)
|
|
1497
|
+
if not networked then
|
|
1498
|
+
return
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
local unreliable_pair_storage = networked.unreliable_pair_storages[id]
|
|
1502
|
+
if unreliable_pair_storage and not unreliable_pair_storage.is_dead then
|
|
1503
|
+
stop_tracking(self, entity, id, networked, unreliable_pair_storage)
|
|
1504
|
+
decrement_unreliable(self, entity, networked)
|
|
1505
|
+
end
|
|
1506
|
+
end
|
|
1507
|
+
|
|
1508
|
+
function server.stop_relation(self: Server, entity: Entity, relation: Component)
|
|
1509
|
+
local networked = stop_target(self, entity, TRACK_TYPES.relation, relation)
|
|
1510
|
+
if not networked then
|
|
1511
|
+
return
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
local relation_storage = networked.relation_storages[relation]
|
|
1515
|
+
if relation_storage and not relation_storage.is_dead then
|
|
1516
|
+
stop_tracking(self, entity, relation, networked, relation_storage)
|
|
1517
|
+
end
|
|
1518
|
+
end
|
|
1519
|
+
|
|
1520
|
+
local function ensure_entity_serialization(self: Server, entity: Entity): EntitySerializationOptions
|
|
1521
|
+
local options = self.entity_serialization[entity]
|
|
1522
|
+
if not options then
|
|
1523
|
+
options = {} :: EntitySerializationOptions
|
|
1524
|
+
self.entity_serialization[entity] = options
|
|
1525
|
+
end
|
|
1526
|
+
return options
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
local function prune_entity_serialization(self: Server, entity: Entity, options: EntitySerializationOptions)
|
|
1530
|
+
if options.global_id == nil and options.custom == nil then
|
|
1531
|
+
self.entity_serialization[entity] = nil
|
|
1532
|
+
end
|
|
1533
|
+
end
|
|
1534
|
+
|
|
1535
|
+
function server.set_global(self: Server, entity: Entity, global_id: number)
|
|
1536
|
+
if global_id > MAX_GLOBAL_ID then
|
|
1537
|
+
LOG_ERROR(`global id size exceeded, max is {MAX_GLOBAL_ID}`)
|
|
1538
|
+
end
|
|
1539
|
+
ensure_entity_serialization(self, entity).global_id = global_id
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
function server.remove_global(self: Server, entity: Entity)
|
|
1543
|
+
local options = self.entity_serialization[entity]
|
|
1544
|
+
if not options then
|
|
1545
|
+
return
|
|
1546
|
+
end
|
|
1547
|
+
options.global_id = nil
|
|
1548
|
+
prune_entity_serialization(self, entity, options)
|
|
1549
|
+
end
|
|
1550
|
+
|
|
1551
|
+
function server.set_custom(self: Server, entity: Entity, custom: Component | CustomId)
|
|
1552
|
+
local options = ensure_entity_serialization(self, entity)
|
|
1553
|
+
if options.custom then
|
|
1554
|
+
common.log_warn("attempted to register a custom_id twice for the same entity", debug.traceback())
|
|
1555
|
+
return
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
if type(custom) ~= "number" and not self.registered_custom_ids[custom :: CustomId] then
|
|
1559
|
+
LOG_ERROR "attempted to use a custom id that wasn't registered, use register_custom_id first"
|
|
1560
|
+
end
|
|
1561
|
+
options.custom = custom
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
function server.remove_custom(self: Server, entity: Entity)
|
|
1565
|
+
local options = self.entity_serialization[entity]
|
|
1566
|
+
if not options then
|
|
1567
|
+
return
|
|
1568
|
+
end
|
|
1569
|
+
options.custom = nil
|
|
1570
|
+
prune_entity_serialization(self, entity, options)
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
function server.register_custom_id(self: Server, custom_id: CustomId)
|
|
1574
|
+
self.registered_custom_ids[custom_id] = true
|
|
1575
|
+
self.is_shared_dirty = true
|
|
1576
|
+
end
|
|
1577
|
+
|
|
1578
|
+
local function ensure_component_serialization(self: Server, component: Component): ComponentSerializationOptions
|
|
1579
|
+
local options = self.component_serialization[component]
|
|
1580
|
+
if not options then
|
|
1581
|
+
options = {} :: ComponentSerializationOptions
|
|
1582
|
+
self.component_serialization[component] = options
|
|
1583
|
+
end
|
|
1584
|
+
return options
|
|
1585
|
+
end
|
|
1586
|
+
|
|
1587
|
+
local function prune_component_serialization(self: Server, component: Component, options: ComponentSerializationOptions)
|
|
1588
|
+
if options.serdes == nil and options.preprocessor == nil and options.delta_serializer == nil then
|
|
1589
|
+
self.component_serialization[component] = nil
|
|
1590
|
+
end
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
function server.set_serdes(self: Server, component: Component, serdes: types.Serdes)
|
|
1594
|
+
ensure_component_serialization(self, component).serdes = serdes
|
|
1595
|
+
self.is_shared_dirty = true
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
function server.remove_serdes(self: Server, component: Component)
|
|
1599
|
+
local options = self.component_serialization[component]
|
|
1600
|
+
if not options then
|
|
1601
|
+
return
|
|
1602
|
+
end
|
|
1603
|
+
options.serdes = nil
|
|
1604
|
+
prune_component_serialization(self, component, options)
|
|
1605
|
+
self.is_shared_dirty = true
|
|
1606
|
+
end
|
|
1607
|
+
|
|
1608
|
+
function server.set_preprocessor(self: Server, component: Component, preprocessor: types.Preprocessor)
|
|
1609
|
+
ensure_component_serialization(self, component).preprocessor = preprocessor
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
function server.remove_preprocessor(self: Server, component: Component)
|
|
1613
|
+
local options = self.component_serialization[component]
|
|
1614
|
+
if not options then
|
|
1615
|
+
return
|
|
1616
|
+
end
|
|
1617
|
+
options.preprocessor = nil
|
|
1618
|
+
prune_component_serialization(self, component, options)
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1621
|
+
function server.set_delta_serializer(self: Server, component: Component, delta_serializer: types.DeltaSerializer)
|
|
1622
|
+
ensure_component_serialization(self, component).delta_serializer = delta_serializer
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
function server.remove_delta_serializer(self: Server, component: Component)
|
|
1626
|
+
local options = self.component_serialization[component]
|
|
1627
|
+
if not options then
|
|
1628
|
+
return
|
|
1629
|
+
end
|
|
1630
|
+
options.delta_serializer = nil
|
|
1631
|
+
prune_component_serialization(self, component, options)
|
|
1632
|
+
end
|
|
1633
|
+
|
|
1634
|
+
type ChangedSerializeEntry = {
|
|
1635
|
+
components: Array<Component>,
|
|
1636
|
+
values: Array<any>,
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
type FullRelationEntry = {
|
|
1640
|
+
components: {
|
|
1641
|
+
[Component]: {
|
|
1642
|
+
targets: { Entity },
|
|
1643
|
+
values: { any },
|
|
1644
|
+
},
|
|
1645
|
+
},
|
|
1646
|
+
tags: {
|
|
1647
|
+
[Component]: { Entity },
|
|
1648
|
+
},
|
|
1649
|
+
}
|
|
1650
|
+
type ChangedRelationEntry = {
|
|
1651
|
+
added: {
|
|
1652
|
+
[Component]: { Entity },
|
|
1653
|
+
},
|
|
1654
|
+
removed: {
|
|
1655
|
+
[Component]: { Entity },
|
|
1656
|
+
},
|
|
1657
|
+
changed: {
|
|
1658
|
+
[Component]: {
|
|
1659
|
+
targets: { Entity },
|
|
1660
|
+
values: { any },
|
|
1661
|
+
},
|
|
1662
|
+
},
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
-- every bucket is created on first push, so an entity only pays for the
|
|
1666
|
+
-- categories it actually touched this collect
|
|
1667
|
+
export type EntityUpdates = {
|
|
1668
|
+
changed: ChangedSerializeEntry?,
|
|
1669
|
+
added: Array<Component>?,
|
|
1670
|
+
removed: Array<Component>?,
|
|
1671
|
+
|
|
1672
|
+
pair_changed: ChangedSerializeEntry?,
|
|
1673
|
+
pair_added: Array<Component>?,
|
|
1674
|
+
pair_removed: Array<Component>?,
|
|
1675
|
+
|
|
1676
|
+
full_relations: FullRelationEntry?,
|
|
1677
|
+
changed_relations: ChangedRelationEntry?,
|
|
1678
|
+
removed_relations: Array<Component>?,
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
type GroupedKey = "added" | "removed" | "pair_added" | "pair_removed" | "removed_relations"
|
|
1682
|
+
|
|
1683
|
+
type MemberPacket = {
|
|
1684
|
+
buffer: buffer,
|
|
1685
|
+
variants: Array<any>,
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
export type MemberUpdatePackets = {
|
|
1689
|
+
-- combined byte size of every buffer in `updates`
|
|
1690
|
+
total_size: number,
|
|
1691
|
+
updates: Array<MemberPacket>,
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
type PacketIterator = () -> (Player, buffer, { { any } })
|
|
1695
|
+
|
|
1696
|
+
type SerializeGroupOutput = {
|
|
1697
|
+
buffer: buffer,
|
|
1698
|
+
variants: Array<any>,
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
export type SerializeGroup = {
|
|
1702
|
+
bitmask: Bitmask,
|
|
1703
|
+
deleted_entities: Array<Entity>,
|
|
1704
|
+
added_entities: Array<Entity>,
|
|
1705
|
+
|
|
1706
|
+
updates: Map<Entity, EntityUpdates>,
|
|
1707
|
+
|
|
1708
|
+
output: SerializeGroupOutput,
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
-- unreliable packets carry only current values: presence, filters and
|
|
1712
|
+
-- relations all travel on the reliable channel
|
|
1713
|
+
type UnreliableSerializeEntry = {
|
|
1714
|
+
components: ChangedSerializeEntry?,
|
|
1715
|
+
pairs: ChangedSerializeEntry?,
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
export type UnreliableSerializeGroup = {
|
|
1719
|
+
bitmask: Bitmask,
|
|
1720
|
+
updates: Map<Entity, UnreliableSerializeEntry>,
|
|
1721
|
+
|
|
1722
|
+
outputs: Array<SerializeGroupOutput>,
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
function calculate_component_deltas(
|
|
1726
|
+
last_replicated: Bitmask,
|
|
1727
|
+
component_target: Bitmask,
|
|
1728
|
+
entity_target: Bitmask,
|
|
1729
|
+
deltas: BitmaskDeltas
|
|
1730
|
+
)
|
|
1731
|
+
bitmasks.load_bitmask_deltas(deltas)
|
|
1732
|
+
bitmasks.calculate_bitmask_deltas(last_replicated, component_target, deltas)
|
|
1733
|
+
|
|
1734
|
+
-- clients outside entity visibility get entity add/delete instead
|
|
1735
|
+
bitmasks.band(deltas.added, entity_target, deltas.added)
|
|
1736
|
+
bitmasks.band(deltas.removed, entity_target, deltas.removed)
|
|
1737
|
+
end
|
|
1738
|
+
|
|
1739
|
+
function exclude_active_deltas(deltas: BitmaskDeltas, newly_active: Bitmask, newly_unactive: Bitmask)
|
|
1740
|
+
bitmasks.exclude(deltas.added, newly_active, deltas.added)
|
|
1741
|
+
bitmasks.exclude(deltas.removed, newly_active, deltas.removed)
|
|
1742
|
+
bitmasks.exclude(deltas.removed, newly_unactive, deltas.removed)
|
|
1743
|
+
end
|
|
1744
|
+
|
|
1745
|
+
local function entity_updates(group: SerializeGroup, entity: Entity): EntityUpdates
|
|
1746
|
+
local entry = group.updates[entity]
|
|
1747
|
+
if not entry then
|
|
1748
|
+
entry = {} :: EntityUpdates
|
|
1749
|
+
group.updates[entity] = entry
|
|
1750
|
+
end
|
|
1751
|
+
return entry
|
|
1752
|
+
end
|
|
1753
|
+
|
|
1754
|
+
local function push_grouped_component(entry: EntityUpdates, key: GroupedKey, component: Component)
|
|
1755
|
+
local list = (entry :: any)[key] :: Array<Component>?
|
|
1756
|
+
if not list then
|
|
1757
|
+
list = {}
|
|
1758
|
+
(entry :: any)[key] = list
|
|
1759
|
+
end
|
|
1760
|
+
table.insert(list :: Array<Component>, component)
|
|
1761
|
+
end
|
|
1762
|
+
|
|
1763
|
+
local function push_changed_component(entry: EntityUpdates, is_pair: boolean, component: Component, value: any)
|
|
1764
|
+
local changed = if is_pair then entry.pair_changed else entry.changed
|
|
1765
|
+
if not changed then
|
|
1766
|
+
changed = { components = {}, values = {} }
|
|
1767
|
+
if is_pair then
|
|
1768
|
+
entry.pair_changed = changed
|
|
1769
|
+
else
|
|
1770
|
+
entry.changed = changed
|
|
1771
|
+
end
|
|
1772
|
+
end
|
|
1773
|
+
table.insert((changed :: ChangedSerializeEntry).components, component)
|
|
1774
|
+
table.insert((changed :: ChangedSerializeEntry).values, SET_VALUE(value))
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1777
|
+
local function push_unreliable_update(
|
|
1778
|
+
updates: Map<Entity, UnreliableSerializeEntry>,
|
|
1779
|
+
entity: Entity,
|
|
1780
|
+
is_pair: boolean,
|
|
1781
|
+
component: Component,
|
|
1782
|
+
value: any
|
|
1783
|
+
)
|
|
1784
|
+
local entry = updates[entity]
|
|
1785
|
+
if not entry then
|
|
1786
|
+
entry = {} :: UnreliableSerializeEntry
|
|
1787
|
+
updates[entity] = entry
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
local changed = if is_pair then entry.pairs else entry.components
|
|
1791
|
+
if not changed then
|
|
1792
|
+
changed = { components = {}, values = {} }
|
|
1793
|
+
if is_pair then
|
|
1794
|
+
entry.pairs = changed
|
|
1795
|
+
else
|
|
1796
|
+
entry.components = changed
|
|
1797
|
+
end
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
table.insert((changed :: ChangedSerializeEntry).components, component)
|
|
1801
|
+
table.insert((changed :: ChangedSerializeEntry).values, SET_VALUE(value))
|
|
1802
|
+
end
|
|
1803
|
+
|
|
1804
|
+
function push_storage_updates(
|
|
1805
|
+
storage: TrackingStorageBase,
|
|
1806
|
+
entry: EntityUpdates,
|
|
1807
|
+
is_pair: boolean,
|
|
1808
|
+
component: Component,
|
|
1809
|
+
has_component: boolean,
|
|
1810
|
+
value: any
|
|
1811
|
+
)
|
|
1812
|
+
if storage.is_component then
|
|
1813
|
+
if has_component then
|
|
1814
|
+
push_changed_component(entry, is_pair, component, value)
|
|
1815
|
+
else
|
|
1816
|
+
push_grouped_component(entry, if is_pair then "pair_removed" else "removed", component)
|
|
1817
|
+
end
|
|
1818
|
+
else
|
|
1819
|
+
if has_component then
|
|
1820
|
+
push_grouped_component(entry, if is_pair then "pair_added" else "added", component)
|
|
1821
|
+
else
|
|
1822
|
+
push_grouped_component(entry, if is_pair then "pair_removed" else "removed", component)
|
|
1823
|
+
end
|
|
1824
|
+
end
|
|
1825
|
+
end
|
|
1826
|
+
|
|
1827
|
+
function push_relation_full(
|
|
1828
|
+
world: World,
|
|
1829
|
+
storage: RelationStorage,
|
|
1830
|
+
entry: EntityUpdates,
|
|
1831
|
+
entity: Entity,
|
|
1832
|
+
rel: Component
|
|
1833
|
+
)
|
|
1834
|
+
local bucket: FullRelationEntry = entry.full_relations or { components = {}, tags = {} }
|
|
1835
|
+
entry.full_relations = bucket
|
|
1836
|
+
|
|
1837
|
+
if storage.is_component then
|
|
1838
|
+
local targets: Array<Entity> = {}
|
|
1839
|
+
local values: Array<any> = {}
|
|
1840
|
+
|
|
1841
|
+
local index = 0
|
|
1842
|
+
while true do
|
|
1843
|
+
local target = WORLD_TARGET(world, entity, rel, index)
|
|
1844
|
+
if not target then
|
|
1845
|
+
break
|
|
1846
|
+
end
|
|
1847
|
+
index += 1
|
|
1848
|
+
|
|
1849
|
+
local value = WORLD_GET(world, entity, PAIR(rel, target))
|
|
1850
|
+
table.insert(targets, target)
|
|
1851
|
+
table.insert(values, SET_VALUE(value))
|
|
1852
|
+
end
|
|
1853
|
+
|
|
1854
|
+
bucket.components[rel] = { targets = targets, values = values }
|
|
1855
|
+
else
|
|
1856
|
+
local targets: Array<Entity> = {}
|
|
1857
|
+
|
|
1858
|
+
local index = 0
|
|
1859
|
+
while true do
|
|
1860
|
+
local target = WORLD_TARGET(world, entity, rel, index)
|
|
1861
|
+
if not target then
|
|
1862
|
+
break
|
|
1863
|
+
end
|
|
1864
|
+
index += 1
|
|
1865
|
+
table.insert(targets, target)
|
|
1866
|
+
end
|
|
1867
|
+
|
|
1868
|
+
bucket.tags[rel] = targets
|
|
1869
|
+
end
|
|
1870
|
+
end
|
|
1871
|
+
|
|
1872
|
+
function push_relation_updates(
|
|
1873
|
+
world: World,
|
|
1874
|
+
storage: RelationStorage,
|
|
1875
|
+
entry: EntityUpdates,
|
|
1876
|
+
entity: Entity,
|
|
1877
|
+
rel: Component
|
|
1878
|
+
)
|
|
1879
|
+
local bucket: ChangedRelationEntry = entry.changed_relations or { added = {}, removed = {}, changed = {} }
|
|
1880
|
+
entry.changed_relations = bucket
|
|
1881
|
+
|
|
1882
|
+
local is_component = storage.is_component
|
|
1883
|
+
|
|
1884
|
+
for target in storage.dirty_targets do
|
|
1885
|
+
if WORLD_HAS(world, entity, PAIR(rel, target)) then
|
|
1886
|
+
if is_component then
|
|
1887
|
+
local changed = bucket.changed[rel]
|
|
1888
|
+
if not changed then
|
|
1889
|
+
changed = { targets = {}, values = {} }
|
|
1890
|
+
bucket.changed[rel] = changed
|
|
1891
|
+
end
|
|
1892
|
+
|
|
1893
|
+
local value = WORLD_GET(world, entity, PAIR(rel, target))
|
|
1894
|
+
table.insert(changed.targets, target)
|
|
1895
|
+
table.insert(changed.values, SET_VALUE(value))
|
|
1896
|
+
else
|
|
1897
|
+
local added = bucket.added[rel]
|
|
1898
|
+
if not added then
|
|
1899
|
+
added = {}
|
|
1900
|
+
bucket.added[rel] = added
|
|
1901
|
+
end
|
|
1902
|
+
table.insert(added, target)
|
|
1903
|
+
end
|
|
1904
|
+
else
|
|
1905
|
+
local removed = bucket.removed[rel]
|
|
1906
|
+
if not removed then
|
|
1907
|
+
removed = {}
|
|
1908
|
+
bucket.removed[rel] = removed
|
|
1909
|
+
end
|
|
1910
|
+
table.insert(removed, target)
|
|
1911
|
+
end
|
|
1912
|
+
end
|
|
1913
|
+
end
|
|
1914
|
+
|
|
1915
|
+
local function setbit(mask: number, bit: number)
|
|
1916
|
+
return bit32.bor(mask, bit32.lshift(1, bit))
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1919
|
+
local function vlq_span(length: number): number
|
|
1920
|
+
if length < 0x80 then
|
|
1921
|
+
return 1
|
|
1922
|
+
elseif length < 0x4000 then
|
|
1923
|
+
return 2
|
|
1924
|
+
elseif length < 0x200000 then
|
|
1925
|
+
return 3
|
|
1926
|
+
else
|
|
1927
|
+
return 4
|
|
1928
|
+
end
|
|
1929
|
+
end
|
|
1930
|
+
|
|
1931
|
+
local function write_vlq_bitmask(c: Cursor, value: number, mask: number, bit: number): number
|
|
1932
|
+
if value > 0 then
|
|
1933
|
+
cursor.write_vlq(c, value)
|
|
1934
|
+
return setbit(mask, bit)
|
|
1935
|
+
else
|
|
1936
|
+
return mask
|
|
1937
|
+
end
|
|
1938
|
+
end
|
|
1939
|
+
|
|
1940
|
+
local function write_variant_value(
|
|
1941
|
+
self: Server,
|
|
1942
|
+
c: Cursor,
|
|
1943
|
+
log_component: Component,
|
|
1944
|
+
options: ComponentSerializationOptions?,
|
|
1945
|
+
value: any,
|
|
1946
|
+
variants: Array<any>
|
|
1947
|
+
)
|
|
1948
|
+
local serialize_value = READ_VALUE(value)
|
|
1949
|
+
|
|
1950
|
+
if options then
|
|
1951
|
+
local serdes = options.serdes
|
|
1952
|
+
local preprocessor = options.preprocessor
|
|
1953
|
+
|
|
1954
|
+
if preprocessor then
|
|
1955
|
+
serialize_value = preprocessor.serialize(serialize_value)
|
|
1956
|
+
end
|
|
1957
|
+
|
|
1958
|
+
if serdes then
|
|
1959
|
+
local output, serdes_variants = serdes.serialize(serialize_value)
|
|
1960
|
+
|
|
1961
|
+
if serdes.includes_variants then
|
|
1962
|
+
if serdes_variants == nil then
|
|
1963
|
+
cursor.writeu8(c, VLQ_ZERO)
|
|
1964
|
+
else
|
|
1965
|
+
local start_variants = #variants
|
|
1966
|
+
if #serdes_variants > 0 then
|
|
1967
|
+
table.move(serdes_variants, 1, #serdes_variants, #variants + 1, variants)
|
|
1968
|
+
end
|
|
1969
|
+
cursor.write_vlq(c, #serdes_variants)
|
|
1970
|
+
cursor.write_vlq(c, start_variants + 1)
|
|
1971
|
+
end
|
|
1972
|
+
elseif serdes_variants ~= nil then
|
|
1973
|
+
common.log_error(
|
|
1974
|
+
"serdes: serializer provided variants yet includes_variants is not set to true for component: ",
|
|
1975
|
+
common.log_component(self.world, log_component)
|
|
1976
|
+
)
|
|
1977
|
+
end
|
|
1978
|
+
|
|
1979
|
+
local bytespan = serdes.bytespan
|
|
1980
|
+
cursor.write_buffer(c, output)
|
|
1981
|
+
if bytespan then
|
|
1982
|
+
if bytespan ~= buffer.len(output) then
|
|
1983
|
+
common.log_error(
|
|
1984
|
+
`bytespan: {bytespan} mismatch for buffer length: {buffer.len(output)} in component: `,
|
|
1985
|
+
common.log_component(self.world, log_component)
|
|
1986
|
+
)
|
|
1987
|
+
end
|
|
1988
|
+
else
|
|
1989
|
+
cursor.write_vlq(c, buffer.len(output))
|
|
1990
|
+
end
|
|
1991
|
+
return
|
|
1992
|
+
end
|
|
1993
|
+
end
|
|
1994
|
+
|
|
1995
|
+
if serialize_value == nil then
|
|
1996
|
+
cursor.writeu8(c, VLQ_ZERO)
|
|
1997
|
+
else
|
|
1998
|
+
table.insert(variants, serialize_value)
|
|
1999
|
+
cursor.write_vlq(c, #variants)
|
|
2000
|
+
end
|
|
2001
|
+
end
|
|
2002
|
+
|
|
2003
|
+
local function write_component_value(self: Server, c: Cursor, component: Component, value: any, variants: Array<any>)
|
|
2004
|
+
local options = self.component_serialization[component]
|
|
2005
|
+
write_variant_value(self, c, component, options, value, variants)
|
|
2006
|
+
end
|
|
2007
|
+
|
|
2008
|
+
local function write_pair_value(
|
|
2009
|
+
self: Server,
|
|
2010
|
+
c: Cursor,
|
|
2011
|
+
relation: Component,
|
|
2012
|
+
id: Component,
|
|
2013
|
+
value: any,
|
|
2014
|
+
variants: Array<any>
|
|
2015
|
+
)
|
|
2016
|
+
local options = self.component_serialization[id] or self.component_serialization[PAIR(relation, WILDCARD)]
|
|
2017
|
+
write_variant_value(self, c, relation, options, value, variants)
|
|
2018
|
+
end
|
|
2019
|
+
|
|
2020
|
+
local function write_component_id(self: Server, c: Cursor, component: Entity)
|
|
2021
|
+
local shared_components = self.shared.components
|
|
2022
|
+
local encoded = shared_components.members[component]
|
|
2023
|
+
if not encoded then
|
|
2024
|
+
LOG_ERROR(`attempted to replicate a non-shared component `, common.log_component(self.world, component))
|
|
2025
|
+
return
|
|
2026
|
+
end
|
|
2027
|
+
cursor.write_span(c, encoded, #shared_components.keys)
|
|
2028
|
+
end
|
|
2029
|
+
|
|
2030
|
+
-- ids are written back-to-front like the rest of the wire format: payload
|
|
2031
|
+
-- first, then the ENTITY_ID_TYPES tag the client reads first. global ids are
|
|
2032
|
+
-- the exception, a single byte with no tag.
|
|
2033
|
+
local function write_entity_id(self: Server, c: Cursor, entity: Entity)
|
|
2034
|
+
local options = self.entity_serialization[entity]
|
|
2035
|
+
|
|
2036
|
+
if options then
|
|
2037
|
+
local global_id = options.global_id
|
|
2038
|
+
if global_id then
|
|
2039
|
+
cursor.writeu8(c, global_id + GLOBAL_ID_OFFSET)
|
|
2040
|
+
return
|
|
2041
|
+
end
|
|
2042
|
+
|
|
2043
|
+
local custom = options.custom
|
|
2044
|
+
if custom then
|
|
2045
|
+
if type(custom) == "number" then
|
|
2046
|
+
write_component_id(self, c, custom :: any)
|
|
2047
|
+
cursor.writeu40(c, entity :: any)
|
|
2048
|
+
cursor.writeu8(c, ENTITY_ID_TYPES.custom_id)
|
|
2049
|
+
else
|
|
2050
|
+
cursor.writeu8(c, self.shared.custom_ids.members[custom :: CustomId])
|
|
2051
|
+
cursor.writeu40(c, entity :: any)
|
|
2052
|
+
cursor.writeu8(c, ENTITY_ID_TYPES.custom_handler)
|
|
2053
|
+
end
|
|
2054
|
+
return
|
|
2055
|
+
end
|
|
2056
|
+
end
|
|
2057
|
+
|
|
2058
|
+
if self.shared.components.members[entity] then
|
|
2059
|
+
write_component_id(self, c, entity)
|
|
2060
|
+
cursor.writeu8(c, ENTITY_ID_TYPES.shared)
|
|
2061
|
+
else
|
|
2062
|
+
cursor.writeu40(c, entity :: any)
|
|
2063
|
+
cursor.writeu8(c, ENTITY_ID_TYPES.entity)
|
|
2064
|
+
end
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
local function write_entity_pair_value(
|
|
2068
|
+
self: Server,
|
|
2069
|
+
world: World,
|
|
2070
|
+
c: Cursor,
|
|
2071
|
+
id: Component,
|
|
2072
|
+
value: any,
|
|
2073
|
+
variants: Array<any>
|
|
2074
|
+
)
|
|
2075
|
+
local first, second = PAIR_FIRST(world, id), PAIR_SECOND(world, id)
|
|
2076
|
+
local pointer_start = c.offset
|
|
2077
|
+
|
|
2078
|
+
write_pair_value(self, c, first, id, value, variants)
|
|
2079
|
+
|
|
2080
|
+
-- if the entity is a custom id, we dont know if this has a serdes in the client, as it needs to be processed first to know the final pair
|
|
2081
|
+
-- we write a vlq so the client can jump in the cursor to skip this value entirely and process it later
|
|
2082
|
+
local pointer_offset = c.offset - pointer_start
|
|
2083
|
+
cursor.write_vlq(c, pointer_offset)
|
|
2084
|
+
|
|
2085
|
+
write_entity_id(self, c, second)
|
|
2086
|
+
write_component_id(self, c, first)
|
|
2087
|
+
end
|
|
2088
|
+
|
|
2089
|
+
local function write_entity_pair(self: Server, world: World, c: Cursor, id: Component)
|
|
2090
|
+
local first, second = PAIR_FIRST(world, id), PAIR_SECOND(world, id)
|
|
2091
|
+
write_entity_id(self, c, second)
|
|
2092
|
+
write_component_id(self, c, first)
|
|
2093
|
+
end
|
|
2094
|
+
|
|
2095
|
+
local ENTITY_BITS = {
|
|
2096
|
+
reliable_removed = 0,
|
|
2097
|
+
reliable_added = 1,
|
|
2098
|
+
reliable_changed = 2,
|
|
2099
|
+
pair_removed = 3,
|
|
2100
|
+
pair_added = 4,
|
|
2101
|
+
pair_changed = 5,
|
|
2102
|
+
relations_removed = 6,
|
|
2103
|
+
full_relations = 7,
|
|
2104
|
+
changed_relations = 8,
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
-- per group trailer mask
|
|
2108
|
+
local GROUP_BITS = {
|
|
2109
|
+
updates = 0,
|
|
2110
|
+
added_entities = 1,
|
|
2111
|
+
deleted_entities = 2,
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
local function write_relation_targets(self: Server, c: Cursor, relation: Component, targets: Array<Entity>)
|
|
2115
|
+
for _, target in targets do
|
|
2116
|
+
write_entity_id(self, c, target)
|
|
2117
|
+
end
|
|
2118
|
+
cursor.write_vlq(c, #targets)
|
|
2119
|
+
write_component_id(self, c, relation)
|
|
2120
|
+
end
|
|
2121
|
+
|
|
2122
|
+
local function write_relation_targets_values(
|
|
2123
|
+
self: Server,
|
|
2124
|
+
c: Cursor,
|
|
2125
|
+
relation: Component,
|
|
2126
|
+
targets: Array<Entity>,
|
|
2127
|
+
values: Array<any>,
|
|
2128
|
+
variants: Array<any>
|
|
2129
|
+
)
|
|
2130
|
+
for index, target in targets do
|
|
2131
|
+
write_entity_id(self, c, target)
|
|
2132
|
+
write_component_value(self, c, relation, values[index], variants)
|
|
2133
|
+
end
|
|
2134
|
+
cursor.write_vlq(c, #targets)
|
|
2135
|
+
write_component_id(self, c, relation)
|
|
2136
|
+
end
|
|
2137
|
+
|
|
2138
|
+
-- component ids only (tags added, anything removed): ids..., returns count
|
|
2139
|
+
local function write_grouped_components(self: Server, c: Cursor, components: Array<Component>?): number
|
|
2140
|
+
if not components then
|
|
2141
|
+
return 0
|
|
2142
|
+
end
|
|
2143
|
+
for _, component in components do
|
|
2144
|
+
write_component_id(self, c, component)
|
|
2145
|
+
end
|
|
2146
|
+
return #components
|
|
2147
|
+
end
|
|
2148
|
+
|
|
2149
|
+
-- pair ids only (tags added, anything removed): pairs..., returns count
|
|
2150
|
+
local function write_grouped_pairs(self: Server, c: Cursor, ids: Array<Component>?): number
|
|
2151
|
+
if not ids then
|
|
2152
|
+
return 0
|
|
2153
|
+
end
|
|
2154
|
+
local world = self.world
|
|
2155
|
+
for _, id in ids do
|
|
2156
|
+
write_entity_pair(self, world, c, id)
|
|
2157
|
+
end
|
|
2158
|
+
return #ids
|
|
2159
|
+
end
|
|
2160
|
+
|
|
2161
|
+
-- one entity block. every section is written only when non empty, its bit set
|
|
2162
|
+
-- in the entity mask and its count vlq written right after the payload
|
|
2163
|
+
local function serialize_entity_updates(
|
|
2164
|
+
self: Server,
|
|
2165
|
+
c: Cursor,
|
|
2166
|
+
entity: Entity,
|
|
2167
|
+
entry: EntityUpdates,
|
|
2168
|
+
variants: Array<any>
|
|
2169
|
+
)
|
|
2170
|
+
local world = self.world
|
|
2171
|
+
local mask = 0
|
|
2172
|
+
|
|
2173
|
+
-- relations
|
|
2174
|
+
|
|
2175
|
+
local total = write_grouped_components(self, c, entry.removed_relations)
|
|
2176
|
+
mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.relations_removed)
|
|
2177
|
+
|
|
2178
|
+
local full = entry.full_relations
|
|
2179
|
+
if full then
|
|
2180
|
+
local total_components = 0
|
|
2181
|
+
for relation, targets in full.components do
|
|
2182
|
+
write_relation_targets_values(self, c, relation, targets.targets, targets.values, variants)
|
|
2183
|
+
total_components += 1
|
|
2184
|
+
end
|
|
2185
|
+
cursor.write_vlq(c, total_components)
|
|
2186
|
+
|
|
2187
|
+
local total_tags = 0
|
|
2188
|
+
for relation, targets in full.tags do
|
|
2189
|
+
write_relation_targets(self, c, relation, targets)
|
|
2190
|
+
total_tags += 1
|
|
2191
|
+
end
|
|
2192
|
+
cursor.write_vlq(c, total_tags)
|
|
2193
|
+
|
|
2194
|
+
mask = setbit(mask, ENTITY_BITS.full_relations)
|
|
2195
|
+
end
|
|
2196
|
+
|
|
2197
|
+
local changed_relations = entry.changed_relations
|
|
2198
|
+
if changed_relations then
|
|
2199
|
+
local total_changed = 0
|
|
2200
|
+
for relation, targets in changed_relations.changed do
|
|
2201
|
+
write_relation_targets_values(self, c, relation, targets.targets, targets.values, variants)
|
|
2202
|
+
total_changed += 1
|
|
2203
|
+
end
|
|
2204
|
+
cursor.write_vlq(c, total_changed)
|
|
2205
|
+
|
|
2206
|
+
local total_added = 0
|
|
2207
|
+
for relation, targets in changed_relations.added do
|
|
2208
|
+
write_relation_targets(self, c, relation, targets)
|
|
2209
|
+
total_added += 1
|
|
2210
|
+
end
|
|
2211
|
+
cursor.write_vlq(c, total_added)
|
|
2212
|
+
|
|
2213
|
+
local total_removed = 0
|
|
2214
|
+
for relation, targets in changed_relations.removed do
|
|
2215
|
+
write_relation_targets(self, c, relation, targets)
|
|
2216
|
+
total_removed += 1
|
|
2217
|
+
end
|
|
2218
|
+
cursor.write_vlq(c, total_removed)
|
|
2219
|
+
|
|
2220
|
+
mask = setbit(mask, ENTITY_BITS.changed_relations)
|
|
2221
|
+
end
|
|
2222
|
+
|
|
2223
|
+
-- pairs
|
|
2224
|
+
|
|
2225
|
+
total = write_grouped_pairs(self, c, entry.pair_removed)
|
|
2226
|
+
mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.pair_removed)
|
|
2227
|
+
|
|
2228
|
+
total = write_grouped_pairs(self, c, entry.pair_added)
|
|
2229
|
+
mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.pair_added)
|
|
2230
|
+
|
|
2231
|
+
local pair_changed = entry.pair_changed
|
|
2232
|
+
if pair_changed then
|
|
2233
|
+
local ids = pair_changed.components
|
|
2234
|
+
local values = pair_changed.values
|
|
2235
|
+
for index, id in ids do
|
|
2236
|
+
write_entity_pair_value(self, world, c, id, values[index], variants)
|
|
2237
|
+
end
|
|
2238
|
+
mask = write_vlq_bitmask(c, #ids, mask, ENTITY_BITS.pair_changed)
|
|
2239
|
+
end
|
|
2240
|
+
|
|
2241
|
+
-- reliables
|
|
2242
|
+
|
|
2243
|
+
total = write_grouped_components(self, c, entry.removed)
|
|
2244
|
+
mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.reliable_removed)
|
|
2245
|
+
|
|
2246
|
+
total = write_grouped_components(self, c, entry.added)
|
|
2247
|
+
mask = write_vlq_bitmask(c, total, mask, ENTITY_BITS.reliable_added)
|
|
2248
|
+
|
|
2249
|
+
local changed = entry.changed
|
|
2250
|
+
if changed then
|
|
2251
|
+
local components = changed.components
|
|
2252
|
+
local values = changed.values
|
|
2253
|
+
for index, component in components do
|
|
2254
|
+
write_component_value(self, c, component, values[index], variants)
|
|
2255
|
+
write_component_id(self, c, component)
|
|
2256
|
+
end
|
|
2257
|
+
mask = write_vlq_bitmask(c, #components, mask, ENTITY_BITS.reliable_changed)
|
|
2258
|
+
end
|
|
2259
|
+
|
|
2260
|
+
cursor.writeu16(c, mask)
|
|
2261
|
+
write_entity_id(self, c, entity)
|
|
2262
|
+
end
|
|
2263
|
+
|
|
2264
|
+
-- layout (read order is the reverse of write order):
|
|
2265
|
+
-- u16 group mask GROUP_BITS, vlq count per set bit
|
|
2266
|
+
-- deleted entities entity ids
|
|
2267
|
+
-- added entities entity ids
|
|
2268
|
+
-- updates per entity:
|
|
2269
|
+
-- entity id
|
|
2270
|
+
-- u16 entity mask ENTITY_BITS, vlq count per set bit
|
|
2271
|
+
-- reliable changed value + component id, vlq
|
|
2272
|
+
-- reliable added / removed component ids, vlq
|
|
2273
|
+
-- pair changed pair values, vlq
|
|
2274
|
+
-- pair added / removed pairs, vlq
|
|
2275
|
+
-- changed relations [targets..., vlq, relation] for
|
|
2276
|
+
-- changed / added / removed, each vlq counted
|
|
2277
|
+
-- full relations components then tags, each vlq counted
|
|
2278
|
+
-- relations removed relation ids, vlq
|
|
2279
|
+
local function serialize_update_group(self: Server, group: SerializeGroup): (buffer, Array<any>)
|
|
2280
|
+
local c = cursor.new()
|
|
2281
|
+
local variants: Array<any> = {}
|
|
2282
|
+
local group_mask = 0
|
|
2283
|
+
|
|
2284
|
+
local total = 0
|
|
2285
|
+
for entity, entry in group.updates do
|
|
2286
|
+
serialize_entity_updates(self, c, entity, entry, variants)
|
|
2287
|
+
total += 1
|
|
2288
|
+
end
|
|
2289
|
+
group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.updates)
|
|
2290
|
+
|
|
2291
|
+
total = 0
|
|
2292
|
+
for _, entity in group.added_entities do
|
|
2293
|
+
write_entity_id(self, c, entity)
|
|
2294
|
+
total += 1
|
|
2295
|
+
end
|
|
2296
|
+
group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.added_entities)
|
|
2297
|
+
|
|
2298
|
+
total = 0
|
|
2299
|
+
for _, entity in group.deleted_entities do
|
|
2300
|
+
write_entity_id(self, c, entity)
|
|
2301
|
+
total += 1
|
|
2302
|
+
end
|
|
2303
|
+
group_mask = write_vlq_bitmask(c, total, group_mask, GROUP_BITS.deleted_entities)
|
|
2304
|
+
|
|
2305
|
+
cursor.writeu16(c, group_mask)
|
|
2306
|
+
|
|
2307
|
+
return cursor.close(c), variants
|
|
2308
|
+
end
|
|
2309
|
+
|
|
2310
|
+
-- layout (read order is the reverse of write order). packets split at
|
|
2311
|
+
-- MAX_UNRELIABLE_PACKET_SIZE, each one self-contained.
|
|
2312
|
+
-- vlq entity count
|
|
2313
|
+
-- per entity: component values + ids, vlq component count,
|
|
2314
|
+
-- pair values, vlq pair count, entity id
|
|
2315
|
+
local function serialize_unreliable_group(
|
|
2316
|
+
self: Server,
|
|
2317
|
+
group: UnreliableSerializeGroup,
|
|
2318
|
+
outputs: Array<SerializeGroupOutput>
|
|
2319
|
+
)
|
|
2320
|
+
local c = cursor.new()
|
|
2321
|
+
local world = self.world
|
|
2322
|
+
local variants: Array<any> = {}
|
|
2323
|
+
local total_entities = 0
|
|
2324
|
+
|
|
2325
|
+
local checkpoint_offset = 0
|
|
2326
|
+
local checkpoint_variants_count = 0
|
|
2327
|
+
local checkpoint_total_entities = 0
|
|
2328
|
+
|
|
2329
|
+
local function create_checkpoint()
|
|
2330
|
+
checkpoint_offset = c.offset
|
|
2331
|
+
checkpoint_variants_count = #variants
|
|
2332
|
+
checkpoint_total_entities = total_entities
|
|
2333
|
+
end
|
|
2334
|
+
|
|
2335
|
+
local function rollback_checkpoint()
|
|
2336
|
+
c.offset = checkpoint_offset
|
|
2337
|
+
for i = #variants, checkpoint_variants_count + 1, -1 do
|
|
2338
|
+
variants[i] = nil
|
|
2339
|
+
end
|
|
2340
|
+
total_entities = checkpoint_total_entities
|
|
2341
|
+
end
|
|
2342
|
+
|
|
2343
|
+
local function commit()
|
|
2344
|
+
if total_entities <= 0 then
|
|
2345
|
+
return
|
|
2346
|
+
end
|
|
2347
|
+
|
|
2348
|
+
cursor.write_vlq(c, total_entities)
|
|
2349
|
+
table.insert(outputs, {
|
|
2350
|
+
buffer = cursor.close(c),
|
|
2351
|
+
variants = variants,
|
|
2352
|
+
})
|
|
2353
|
+
|
|
2354
|
+
c = cursor.new()
|
|
2355
|
+
variants = {}
|
|
2356
|
+
total_entities = 0
|
|
2357
|
+
end
|
|
2358
|
+
|
|
2359
|
+
local function serialize_entity(entity: Entity, entry: UnreliableSerializeEntry)
|
|
2360
|
+
local components = entry.components
|
|
2361
|
+
if components then
|
|
2362
|
+
local values = components.values
|
|
2363
|
+
for index, component in components.components do
|
|
2364
|
+
write_component_value(self, c, component, values[index], variants)
|
|
2365
|
+
write_component_id(self, c, component)
|
|
2366
|
+
end
|
|
2367
|
+
cursor.write_vlq(c, #components.components)
|
|
2368
|
+
else
|
|
2369
|
+
cursor.write_vlq(c, 0)
|
|
2370
|
+
end
|
|
2371
|
+
|
|
2372
|
+
local pairs = entry.pairs
|
|
2373
|
+
if pairs then
|
|
2374
|
+
local values = pairs.values
|
|
2375
|
+
for index, id in pairs.components do
|
|
2376
|
+
write_entity_pair_value(self, world, c, id, values[index], variants)
|
|
2377
|
+
end
|
|
2378
|
+
cursor.write_vlq(c, #pairs.components)
|
|
2379
|
+
else
|
|
2380
|
+
cursor.write_vlq(c, 0)
|
|
2381
|
+
end
|
|
2382
|
+
|
|
2383
|
+
write_entity_id(self, c, entity)
|
|
2384
|
+
total_entities += 1
|
|
2385
|
+
end
|
|
2386
|
+
|
|
2387
|
+
for entity, entry in group.updates do
|
|
2388
|
+
create_checkpoint()
|
|
2389
|
+
serialize_entity(entity, entry)
|
|
2390
|
+
|
|
2391
|
+
if c.offset + vlq_span(total_entities) + vlq_span(1) + PACKET_TYPE_SPAN > MAX_UNRELIABLE_PACKET_SIZE then
|
|
2392
|
+
rollback_checkpoint()
|
|
2393
|
+
commit()
|
|
2394
|
+
serialize_entity(entity, entry)
|
|
2395
|
+
end
|
|
2396
|
+
end
|
|
2397
|
+
|
|
2398
|
+
commit()
|
|
2399
|
+
end
|
|
2400
|
+
|
|
2401
|
+
local function collect_updates_groups(self: Server): Map<MaskHash, SerializeGroup>
|
|
2402
|
+
local client_registry = self.registry
|
|
2403
|
+
for storage, entry in self.function_filters do
|
|
2404
|
+
local bitmask = client_registry:create_bitmask_from_function(entry.predicator)
|
|
2405
|
+
local current = storage.filter
|
|
2406
|
+
if current and bitmasks.equals(current, bitmask) then
|
|
2407
|
+
continue
|
|
2408
|
+
end
|
|
2409
|
+
|
|
2410
|
+
local tracking_component = entry.tracking
|
|
2411
|
+
|
|
2412
|
+
if tracking_component then
|
|
2413
|
+
-- routes to the right dirty list via the storage's track_type
|
|
2414
|
+
edit_tracking_filter(
|
|
2415
|
+
self,
|
|
2416
|
+
entry.entity,
|
|
2417
|
+
tracking_component,
|
|
2418
|
+
entry.networked,
|
|
2419
|
+
entry.tracking_storage :: TrackingStorageBase,
|
|
2420
|
+
bitmask
|
|
2421
|
+
)
|
|
2422
|
+
else
|
|
2423
|
+
edit_networked_filter(self, entry.entity, entry.networked, bitmask)
|
|
2424
|
+
end
|
|
2425
|
+
end
|
|
2426
|
+
|
|
2427
|
+
local serialize_groups: Map<MaskHash, SerializeGroup> = {}
|
|
2428
|
+
local networked_entities = self.networked
|
|
2429
|
+
local world = self.world
|
|
2430
|
+
|
|
2431
|
+
local function emit(bitmask: Bitmask): SerializeGroup
|
|
2432
|
+
local hash = bitmasks.hash(bitmask)
|
|
2433
|
+
local serialize = serialize_groups[hash]
|
|
2434
|
+
if not serialize then
|
|
2435
|
+
local new_serialize: SerializeGroup = {
|
|
2436
|
+
-- deltas are reused scratch buffers, groups need a stable copy
|
|
2437
|
+
bitmask = bitmasks.clone(bitmask),
|
|
2438
|
+
deleted_entities = {},
|
|
2439
|
+
added_entities = {},
|
|
2440
|
+
|
|
2441
|
+
updates = {},
|
|
2442
|
+
|
|
2443
|
+
variants = nil :: any,
|
|
2444
|
+
output = nil :: any,
|
|
2445
|
+
}
|
|
2446
|
+
serialize_groups[hash] = new_serialize
|
|
2447
|
+
return new_serialize
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
return serialize
|
|
2451
|
+
end
|
|
2452
|
+
|
|
2453
|
+
local active = self.client_active_bitmask
|
|
2454
|
+
local last_active = self.last_active_bitmask
|
|
2455
|
+
local newly_active = self.newly_active_bitmask
|
|
2456
|
+
local newly_unactive = self.newly_unactive_bitmask
|
|
2457
|
+
|
|
2458
|
+
bitmasks.exclude(active, last_active, newly_active)
|
|
2459
|
+
bitmasks.exclude(last_active, active, newly_unactive)
|
|
2460
|
+
|
|
2461
|
+
local is_active_dirty = not bitmasks.is_empty(newly_active) or not bitmasks.is_empty(newly_unactive)
|
|
2462
|
+
|
|
2463
|
+
local entity_deltas = self.entity_bitmask_deltas
|
|
2464
|
+
local component_deltas = self.component_bitmask_deltas
|
|
2465
|
+
local draft = self.draft_bitmask
|
|
2466
|
+
local entity_target = self.target_bitmask
|
|
2467
|
+
|
|
2468
|
+
for _, entity in self.dirty_networked do
|
|
2469
|
+
local networked = networked_entities[entity]
|
|
2470
|
+
|
|
2471
|
+
if networked.is_dead then
|
|
2472
|
+
-- deletion goes to everyone who last had the entity and is still here
|
|
2473
|
+
local last = networked.last_replicated or networked.filter or last_active
|
|
2474
|
+
bitmasks.load(last, draft)
|
|
2475
|
+
bitmasks.band(draft, active, draft)
|
|
2476
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2477
|
+
if not bitmasks.is_empty(draft) then
|
|
2478
|
+
table.insert(emit(draft).deleted_entities, entity)
|
|
2479
|
+
end
|
|
2480
|
+
clear_networked_references(self, entity, networked)
|
|
2481
|
+
networked_entities[entity] = nil
|
|
2482
|
+
if networked.cleaned then
|
|
2483
|
+
self.entity_serialization[entity] = nil
|
|
2484
|
+
end
|
|
2485
|
+
continue
|
|
2486
|
+
end
|
|
2487
|
+
|
|
2488
|
+
-- filter sources (groups especially) can carry freed or never active
|
|
2489
|
+
-- indexes; only active clients ever receive anything. every storage
|
|
2490
|
+
-- target below is clipped by this one
|
|
2491
|
+
local target: Bitmask = active
|
|
2492
|
+
local entity_filter = networked.filter
|
|
2493
|
+
if entity_filter then
|
|
2494
|
+
bitmasks.load(entity_filter, entity_target)
|
|
2495
|
+
bitmasks.band(entity_target, active, entity_target)
|
|
2496
|
+
target = entity_target
|
|
2497
|
+
end
|
|
2498
|
+
local is_entity_filter_dirty = flag_test(networked.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2499
|
+
|
|
2500
|
+
if is_entity_filter_dirty then
|
|
2501
|
+
local last = networked.last_replicated or networked.filter or last_active
|
|
2502
|
+
bitmasks.load_bitmask_deltas(entity_deltas)
|
|
2503
|
+
bitmasks.calculate_bitmask_deltas(last, target, entity_deltas)
|
|
2504
|
+
-- newly active clients get a full snapshot at this same boundary instead;
|
|
2505
|
+
-- newly unactive clients are gone, nobody to send removals to
|
|
2506
|
+
if is_active_dirty then
|
|
2507
|
+
exclude_active_deltas(entity_deltas, newly_active, newly_unactive)
|
|
2508
|
+
end
|
|
2509
|
+
|
|
2510
|
+
if not bitmasks.is_empty(entity_deltas.added) then
|
|
2511
|
+
table.insert(emit(entity_deltas.added).added_entities, entity)
|
|
2512
|
+
end
|
|
2513
|
+
if not bitmasks.is_empty(entity_deltas.removed) then
|
|
2514
|
+
table.insert(emit(entity_deltas.removed).deleted_entities, entity)
|
|
2515
|
+
end
|
|
2516
|
+
networked.last_replicated = nil
|
|
2517
|
+
end
|
|
2518
|
+
|
|
2519
|
+
for _, component in networked.dirty_reliable do
|
|
2520
|
+
local reliable_storage = networked.reliable_storages[component]
|
|
2521
|
+
local is_component_filter_dirty = flag_test(reliable_storage.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2522
|
+
|
|
2523
|
+
local has_delta_source = reliable_storage.filter ~= nil or reliable_storage.last_replicated ~= nil
|
|
2524
|
+
local component_target = reliable_storage.filter or target
|
|
2525
|
+
local is_component_added = false
|
|
2526
|
+
local has_component = WORLD_HAS(world, entity, component)
|
|
2527
|
+
local has_value = has_component and reliable_storage.is_component
|
|
2528
|
+
-- `and/or` would turn a false value into nil
|
|
2529
|
+
local value = if has_value then WORLD_GET(world, entity, component) else nil
|
|
2530
|
+
|
|
2531
|
+
if has_delta_source and is_component_filter_dirty then
|
|
2532
|
+
local last = reliable_storage.last_replicated or reliable_storage.filter :: Bitmask
|
|
2533
|
+
|
|
2534
|
+
calculate_component_deltas(last, component_target, target, component_deltas)
|
|
2535
|
+
if is_active_dirty then
|
|
2536
|
+
exclude_active_deltas(component_deltas, newly_active, newly_unactive)
|
|
2537
|
+
end
|
|
2538
|
+
|
|
2539
|
+
is_component_added = not bitmasks.is_empty(component_deltas.added)
|
|
2540
|
+
|
|
2541
|
+
if is_component_added then
|
|
2542
|
+
local entry = entity_updates(emit(component_deltas.added), entity)
|
|
2543
|
+
push_storage_updates(reliable_storage, entry, false, component, has_component, value)
|
|
2544
|
+
end
|
|
2545
|
+
|
|
2546
|
+
if not bitmasks.is_empty(component_deltas.removed) then
|
|
2547
|
+
local entry = entity_updates(emit(component_deltas.removed), entity)
|
|
2548
|
+
push_grouped_component(entry, "removed", component)
|
|
2549
|
+
end
|
|
2550
|
+
reliable_storage.last_replicated = nil
|
|
2551
|
+
end
|
|
2552
|
+
|
|
2553
|
+
if flag_test(reliable_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
|
|
2554
|
+
bitmasks.load(component_target, draft)
|
|
2555
|
+
bitmasks.band(draft, target, draft)
|
|
2556
|
+
if is_active_dirty then
|
|
2557
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2558
|
+
bitmasks.exclude(draft, newly_unactive, draft)
|
|
2559
|
+
end
|
|
2560
|
+
|
|
2561
|
+
if is_component_added then
|
|
2562
|
+
bitmasks.exclude(draft, component_deltas.added, draft)
|
|
2563
|
+
end
|
|
2564
|
+
if is_entity_filter_dirty then
|
|
2565
|
+
bitmasks.exclude(draft, entity_deltas.added, draft)
|
|
2566
|
+
end
|
|
2567
|
+
|
|
2568
|
+
if not bitmasks.is_empty(draft) then
|
|
2569
|
+
local entry = entity_updates(emit(draft), entity)
|
|
2570
|
+
push_storage_updates(reliable_storage, entry, false, component, has_component, value)
|
|
2571
|
+
end
|
|
2572
|
+
end
|
|
2573
|
+
reliable_storage.dirty_flags = 0
|
|
2574
|
+
-- the removal delta above was its last word
|
|
2575
|
+
if reliable_storage.is_dead then
|
|
2576
|
+
clear_hook_reference(self, entity, component, reliable_storage)
|
|
2577
|
+
networked.reliable_storages[component] = nil
|
|
2578
|
+
end
|
|
2579
|
+
end
|
|
2580
|
+
|
|
2581
|
+
for _, component in networked.dirty_unreliable do
|
|
2582
|
+
local unreliable_storage = networked.unreliable_storages[component]
|
|
2583
|
+
local is_unreliable_filter_dirty = flag_test(unreliable_storage.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2584
|
+
|
|
2585
|
+
local has_delta_source = unreliable_storage.filter ~= nil or unreliable_storage.last_replicated ~= nil
|
|
2586
|
+
local component_target = unreliable_storage.filter or target
|
|
2587
|
+
local is_unreliable_added = false
|
|
2588
|
+
local has_value = WORLD_HAS(world, entity, component)
|
|
2589
|
+
local value = if has_value then WORLD_GET(world, entity, component) else nil
|
|
2590
|
+
|
|
2591
|
+
if has_delta_source and is_unreliable_filter_dirty then
|
|
2592
|
+
local last = unreliable_storage.last_replicated or unreliable_storage.filter :: Bitmask
|
|
2593
|
+
|
|
2594
|
+
calculate_component_deltas(last, component_target, target, component_deltas)
|
|
2595
|
+
if is_active_dirty then
|
|
2596
|
+
exclude_active_deltas(component_deltas, newly_active, newly_unactive)
|
|
2597
|
+
end
|
|
2598
|
+
|
|
2599
|
+
is_unreliable_added = not bitmasks.is_empty(component_deltas.added)
|
|
2600
|
+
|
|
2601
|
+
if is_unreliable_added then
|
|
2602
|
+
local entry = entity_updates(emit(component_deltas.added), entity)
|
|
2603
|
+
if has_value then
|
|
2604
|
+
push_changed_component(entry, false, component, value)
|
|
2605
|
+
else
|
|
2606
|
+
push_grouped_component(entry, "removed", component)
|
|
2607
|
+
end
|
|
2608
|
+
end
|
|
2609
|
+
|
|
2610
|
+
if not bitmasks.is_empty(component_deltas.removed) then
|
|
2611
|
+
local entry = entity_updates(emit(component_deltas.removed), entity)
|
|
2612
|
+
push_grouped_component(entry, "removed", component)
|
|
2613
|
+
end
|
|
2614
|
+
unreliable_storage.last_replicated = nil
|
|
2615
|
+
end
|
|
2616
|
+
|
|
2617
|
+
if flag_test(unreliable_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
|
|
2618
|
+
bitmasks.load(component_target, draft)
|
|
2619
|
+
bitmasks.band(draft, target, draft)
|
|
2620
|
+
if is_active_dirty then
|
|
2621
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2622
|
+
bitmasks.exclude(draft, newly_unactive, draft)
|
|
2623
|
+
end
|
|
2624
|
+
|
|
2625
|
+
if is_unreliable_added then
|
|
2626
|
+
bitmasks.exclude(draft, component_deltas.added, draft)
|
|
2627
|
+
end
|
|
2628
|
+
if is_entity_filter_dirty then
|
|
2629
|
+
bitmasks.exclude(draft, entity_deltas.added, draft)
|
|
2630
|
+
end
|
|
2631
|
+
|
|
2632
|
+
if not bitmasks.is_empty(draft) then
|
|
2633
|
+
local entry = entity_updates(emit(draft), entity)
|
|
2634
|
+
if has_value then
|
|
2635
|
+
push_changed_component(entry, false, component, value)
|
|
2636
|
+
else
|
|
2637
|
+
push_grouped_component(entry, "removed", component)
|
|
2638
|
+
end
|
|
2639
|
+
end
|
|
2640
|
+
end
|
|
2641
|
+
unreliable_storage.dirty_flags = 0
|
|
2642
|
+
if unreliable_storage.is_dead then
|
|
2643
|
+
clear_hook_reference(self, entity, component, unreliable_storage)
|
|
2644
|
+
networked.unreliable_storages[component] = nil
|
|
2645
|
+
end
|
|
2646
|
+
end
|
|
2647
|
+
|
|
2648
|
+
for _, id in networked.dirty_pairs do
|
|
2649
|
+
local pair_storage = networked.pair_storages[id]
|
|
2650
|
+
local is_pair_filter_dirty = flag_test(pair_storage.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2651
|
+
|
|
2652
|
+
local has_delta_source = pair_storage.filter ~= nil or pair_storage.last_replicated ~= nil
|
|
2653
|
+
local component_target = pair_storage.filter or target
|
|
2654
|
+
local is_pair_added = false
|
|
2655
|
+
local has_pair = WORLD_HAS(world, entity, id)
|
|
2656
|
+
local has_value = (has_pair and pair_storage.is_component)
|
|
2657
|
+
local value = if has_value then WORLD_GET(world, entity, id) else nil
|
|
2658
|
+
|
|
2659
|
+
if has_delta_source and is_pair_filter_dirty then
|
|
2660
|
+
local last = pair_storage.last_replicated or pair_storage.filter :: Bitmask
|
|
2661
|
+
|
|
2662
|
+
calculate_component_deltas(last, component_target, target, component_deltas)
|
|
2663
|
+
if is_active_dirty then
|
|
2664
|
+
exclude_active_deltas(component_deltas, newly_active, newly_unactive)
|
|
2665
|
+
end
|
|
2666
|
+
|
|
2667
|
+
is_pair_added = not bitmasks.is_empty(component_deltas.added)
|
|
2668
|
+
|
|
2669
|
+
if is_pair_added then
|
|
2670
|
+
local entry = entity_updates(emit(component_deltas.added), entity)
|
|
2671
|
+
push_storage_updates(pair_storage, entry, true, id, has_pair, value)
|
|
2672
|
+
end
|
|
2673
|
+
|
|
2674
|
+
if not bitmasks.is_empty(component_deltas.removed) then
|
|
2675
|
+
local entry = entity_updates(emit(component_deltas.removed), entity)
|
|
2676
|
+
push_grouped_component(entry, "pair_removed", id)
|
|
2677
|
+
end
|
|
2678
|
+
pair_storage.last_replicated = nil
|
|
2679
|
+
end
|
|
2680
|
+
|
|
2681
|
+
if flag_test(pair_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
|
|
2682
|
+
bitmasks.load(component_target, draft)
|
|
2683
|
+
bitmasks.band(draft, target, draft)
|
|
2684
|
+
if is_active_dirty then
|
|
2685
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2686
|
+
bitmasks.exclude(draft, newly_unactive, draft)
|
|
2687
|
+
end
|
|
2688
|
+
|
|
2689
|
+
if is_pair_added then
|
|
2690
|
+
bitmasks.exclude(draft, component_deltas.added, draft)
|
|
2691
|
+
end
|
|
2692
|
+
if is_entity_filter_dirty then
|
|
2693
|
+
bitmasks.exclude(draft, entity_deltas.added, draft)
|
|
2694
|
+
end
|
|
2695
|
+
|
|
2696
|
+
if not bitmasks.is_empty(draft) then
|
|
2697
|
+
local entry = entity_updates(emit(draft), entity)
|
|
2698
|
+
push_storage_updates(pair_storage, entry, true, id, has_pair, value)
|
|
2699
|
+
end
|
|
2700
|
+
end
|
|
2701
|
+
pair_storage.dirty_flags = 0
|
|
2702
|
+
if pair_storage.is_dead then
|
|
2703
|
+
clear_hook_reference(self, entity, id, pair_storage)
|
|
2704
|
+
networked.pair_storages[id] = nil
|
|
2705
|
+
end
|
|
2706
|
+
end
|
|
2707
|
+
|
|
2708
|
+
for _, id in networked.dirty_unreliable_pairs do
|
|
2709
|
+
local unreliable_pair_storage = networked.unreliable_pair_storages[id]
|
|
2710
|
+
local is_pair_filter_dirty = flag_test(unreliable_pair_storage.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2711
|
+
|
|
2712
|
+
local has_delta_source = unreliable_pair_storage.filter ~= nil
|
|
2713
|
+
or unreliable_pair_storage.last_replicated ~= nil
|
|
2714
|
+
local component_target = unreliable_pair_storage.filter or target
|
|
2715
|
+
local is_pair_added = false
|
|
2716
|
+
local has_pair = WORLD_HAS(world, entity, id)
|
|
2717
|
+
local has_value = (has_pair and unreliable_pair_storage.is_component)
|
|
2718
|
+
local value = has_value and WORLD_GET(world, entity, id) or nil
|
|
2719
|
+
|
|
2720
|
+
if has_delta_source and is_pair_filter_dirty then
|
|
2721
|
+
local last = unreliable_pair_storage.last_replicated or unreliable_pair_storage.filter :: Bitmask
|
|
2722
|
+
|
|
2723
|
+
calculate_component_deltas(last, component_target, target, component_deltas)
|
|
2724
|
+
if is_active_dirty then
|
|
2725
|
+
exclude_active_deltas(component_deltas, newly_active, newly_unactive)
|
|
2726
|
+
end
|
|
2727
|
+
|
|
2728
|
+
is_pair_added = not bitmasks.is_empty(component_deltas.added)
|
|
2729
|
+
|
|
2730
|
+
if is_pair_added then
|
|
2731
|
+
local entry = entity_updates(emit(component_deltas.added), entity)
|
|
2732
|
+
push_storage_updates(unreliable_pair_storage, entry, true, id, has_pair, value)
|
|
2733
|
+
end
|
|
2734
|
+
|
|
2735
|
+
if not bitmasks.is_empty(component_deltas.removed) then
|
|
2736
|
+
local entry = entity_updates(emit(component_deltas.removed), entity)
|
|
2737
|
+
push_grouped_component(entry, "pair_removed", id)
|
|
2738
|
+
end
|
|
2739
|
+
unreliable_pair_storage.last_replicated = nil
|
|
2740
|
+
end
|
|
2741
|
+
|
|
2742
|
+
if flag_test(unreliable_pair_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
|
|
2743
|
+
bitmasks.load(component_target, draft)
|
|
2744
|
+
bitmasks.band(draft, target, draft)
|
|
2745
|
+
if is_active_dirty then
|
|
2746
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2747
|
+
bitmasks.exclude(draft, newly_unactive, draft)
|
|
2748
|
+
end
|
|
2749
|
+
|
|
2750
|
+
if is_pair_added then
|
|
2751
|
+
bitmasks.exclude(draft, component_deltas.added, draft)
|
|
2752
|
+
end
|
|
2753
|
+
if is_entity_filter_dirty then
|
|
2754
|
+
bitmasks.exclude(draft, entity_deltas.added, draft)
|
|
2755
|
+
end
|
|
2756
|
+
|
|
2757
|
+
if not bitmasks.is_empty(draft) then
|
|
2758
|
+
local entry = entity_updates(emit(draft), entity)
|
|
2759
|
+
push_storage_updates(unreliable_pair_storage, entry, true, id, has_pair, value)
|
|
2760
|
+
end
|
|
2761
|
+
end
|
|
2762
|
+
unreliable_pair_storage.dirty_flags = 0
|
|
2763
|
+
if unreliable_pair_storage.is_dead then
|
|
2764
|
+
clear_hook_reference(self, entity, id, unreliable_pair_storage)
|
|
2765
|
+
networked.unreliable_pair_storages[id] = nil
|
|
2766
|
+
end
|
|
2767
|
+
end
|
|
2768
|
+
|
|
2769
|
+
for _, rel in networked.dirty_relations do
|
|
2770
|
+
local relation_storage = networked.relation_storages[rel]
|
|
2771
|
+
local is_rel_filter_dirty = flag_test(relation_storage.dirty_flags, DIRTY_FILTER_FLAG)
|
|
2772
|
+
|
|
2773
|
+
local has_delta_source = relation_storage.filter ~= nil or relation_storage.last_replicated ~= nil
|
|
2774
|
+
local component_target = relation_storage.filter or target
|
|
2775
|
+
local is_rel_added = false
|
|
2776
|
+
|
|
2777
|
+
if has_delta_source and is_rel_filter_dirty then
|
|
2778
|
+
local last = relation_storage.last_replicated or relation_storage.filter :: Bitmask
|
|
2779
|
+
|
|
2780
|
+
calculate_component_deltas(last, component_target, target, component_deltas)
|
|
2781
|
+
if is_active_dirty then
|
|
2782
|
+
exclude_active_deltas(component_deltas, newly_active, newly_unactive)
|
|
2783
|
+
end
|
|
2784
|
+
|
|
2785
|
+
is_rel_added = not bitmasks.is_empty(component_deltas.added)
|
|
2786
|
+
|
|
2787
|
+
if is_rel_added then
|
|
2788
|
+
local entry = entity_updates(emit(component_deltas.added), entity)
|
|
2789
|
+
push_relation_full(world, relation_storage, entry, entity, rel)
|
|
2790
|
+
end
|
|
2791
|
+
|
|
2792
|
+
if not bitmasks.is_empty(component_deltas.removed) then
|
|
2793
|
+
local entry = entity_updates(emit(component_deltas.removed), entity)
|
|
2794
|
+
push_grouped_component(entry, "removed_relations", rel)
|
|
2795
|
+
end
|
|
2796
|
+
relation_storage.last_replicated = nil
|
|
2797
|
+
end
|
|
2798
|
+
|
|
2799
|
+
if flag_test(relation_storage.dirty_flags, DIRTY_CHANGED_FLAG) then
|
|
2800
|
+
bitmasks.load(component_target, draft)
|
|
2801
|
+
bitmasks.band(draft, target, draft)
|
|
2802
|
+
if is_active_dirty then
|
|
2803
|
+
bitmasks.exclude(draft, newly_active, draft)
|
|
2804
|
+
bitmasks.exclude(draft, newly_unactive, draft)
|
|
2805
|
+
end
|
|
2806
|
+
|
|
2807
|
+
if is_rel_added then
|
|
2808
|
+
bitmasks.exclude(draft, component_deltas.added, draft)
|
|
2809
|
+
end
|
|
2810
|
+
if is_entity_filter_dirty then
|
|
2811
|
+
bitmasks.exclude(draft, entity_deltas.added, draft)
|
|
2812
|
+
end
|
|
2813
|
+
|
|
2814
|
+
if not bitmasks.is_empty(draft) then
|
|
2815
|
+
local entry = entity_updates(emit(draft), entity)
|
|
2816
|
+
push_relation_updates(world, relation_storage, entry, entity, rel)
|
|
2817
|
+
end
|
|
2818
|
+
end
|
|
2819
|
+
relation_storage.dirty_flags = 0
|
|
2820
|
+
table.clear(relation_storage.dirty_targets)
|
|
2821
|
+
if relation_storage.is_dead then
|
|
2822
|
+
clear_hook_reference(self, entity, rel, relation_storage)
|
|
2823
|
+
networked.relation_storages[rel] = nil
|
|
2824
|
+
end
|
|
2825
|
+
end
|
|
2826
|
+
|
|
2827
|
+
table.clear(networked.dirty_reliable)
|
|
2828
|
+
table.clear(networked.dirty_unreliable)
|
|
2829
|
+
table.clear(networked.dirty_pairs)
|
|
2830
|
+
table.clear(networked.dirty_unreliable_pairs)
|
|
2831
|
+
table.clear(networked.dirty_relations)
|
|
2832
|
+
networked.dirty_flags = 0
|
|
2833
|
+
end
|
|
2834
|
+
table.clear(self.dirty_networked)
|
|
2835
|
+
|
|
2836
|
+
bitmasks.load(active, last_active)
|
|
2837
|
+
bitmasks.clear(newly_active)
|
|
2838
|
+
bitmasks.clear(newly_unactive)
|
|
2839
|
+
|
|
2840
|
+
return serialize_groups
|
|
2841
|
+
end
|
|
2842
|
+
|
|
2843
|
+
-- fills every group's output buffer and variants in place
|
|
2844
|
+
local function serialize_update_groups(self: Server, groups: Map<MaskHash, SerializeGroup>)
|
|
2845
|
+
for _, group in groups do
|
|
2846
|
+
local buf, variants = serialize_update_group(self, group)
|
|
2847
|
+
group.output = {
|
|
2848
|
+
buffer = buf,
|
|
2849
|
+
variants = variants,
|
|
2850
|
+
}
|
|
2851
|
+
end
|
|
2852
|
+
end
|
|
2853
|
+
|
|
2854
|
+
local function collect_unreliable_groups(self: Server): Map<MaskHash, UnreliableSerializeGroup>
|
|
2855
|
+
local unreliable_groups: Map<MaskHash, UnreliableSerializeGroup> = {}
|
|
2856
|
+
local world = self.world
|
|
2857
|
+
local active = self.client_active_bitmask
|
|
2858
|
+
local draft = self.draft_bitmask
|
|
2859
|
+
|
|
2860
|
+
local function emit(bitmask: Bitmask): UnreliableSerializeGroup
|
|
2861
|
+
local hash = bitmasks.hash(bitmask)
|
|
2862
|
+
local group = unreliable_groups[hash]
|
|
2863
|
+
if not group then
|
|
2864
|
+
local new_group: UnreliableSerializeGroup = {
|
|
2865
|
+
bitmask = bitmasks.clone(bitmask),
|
|
2866
|
+
updates = {},
|
|
2867
|
+
|
|
2868
|
+
outputs = {},
|
|
2869
|
+
}
|
|
2870
|
+
unreliable_groups[hash] = new_group
|
|
2871
|
+
return new_group
|
|
2872
|
+
end
|
|
2873
|
+
return group
|
|
2874
|
+
end
|
|
2875
|
+
|
|
2876
|
+
for entity in self.unreliable_networked do
|
|
2877
|
+
local networked = self.networked[entity]
|
|
2878
|
+
local entity_filter = networked.filter
|
|
2879
|
+
|
|
2880
|
+
for component, storage in networked.unreliable_storages do
|
|
2881
|
+
if storage.is_dead or not WORLD_HAS(world, entity, component) then
|
|
2882
|
+
continue
|
|
2883
|
+
end
|
|
2884
|
+
|
|
2885
|
+
-- visibility right now: active clients inside both the entity and
|
|
2886
|
+
-- storage filters. no delta correction exists here, so inactive
|
|
2887
|
+
-- clients are always clipped out
|
|
2888
|
+
bitmasks.load(active, draft)
|
|
2889
|
+
if entity_filter then
|
|
2890
|
+
bitmasks.band(draft, entity_filter, draft)
|
|
2891
|
+
end
|
|
2892
|
+
local filter = storage.filter
|
|
2893
|
+
if filter then
|
|
2894
|
+
bitmasks.band(draft, filter, draft)
|
|
2895
|
+
end
|
|
2896
|
+
if bitmasks.is_empty(draft) then
|
|
2897
|
+
continue
|
|
2898
|
+
end
|
|
2899
|
+
|
|
2900
|
+
push_unreliable_update(emit(draft).updates, entity, false, component, WORLD_GET(world, entity, component))
|
|
2901
|
+
end
|
|
2902
|
+
|
|
2903
|
+
for id, storage in networked.unreliable_pair_storages do
|
|
2904
|
+
-- pair tags have no value to resend, their presence is reliable
|
|
2905
|
+
if storage.is_dead or not storage.is_component or not WORLD_HAS(world, entity, id) then
|
|
2906
|
+
continue
|
|
2907
|
+
end
|
|
2908
|
+
|
|
2909
|
+
bitmasks.load(active, draft)
|
|
2910
|
+
if entity_filter then
|
|
2911
|
+
bitmasks.band(draft, entity_filter, draft)
|
|
2912
|
+
end
|
|
2913
|
+
local filter = storage.filter
|
|
2914
|
+
if filter then
|
|
2915
|
+
bitmasks.band(draft, filter, draft)
|
|
2916
|
+
end
|
|
2917
|
+
if bitmasks.is_empty(draft) then
|
|
2918
|
+
continue
|
|
2919
|
+
end
|
|
2920
|
+
|
|
2921
|
+
push_unreliable_update(emit(draft).updates, entity, true, id, WORLD_GET(world, entity, id))
|
|
2922
|
+
end
|
|
2923
|
+
end
|
|
2924
|
+
|
|
2925
|
+
return unreliable_groups
|
|
2926
|
+
end
|
|
2927
|
+
|
|
2928
|
+
local function serialize_unreliable_groups(self: Server, groups: Map<MaskHash, UnreliableSerializeGroup>)
|
|
2929
|
+
for _, group in groups do
|
|
2930
|
+
serialize_unreliable_group(self, group, group.outputs)
|
|
2931
|
+
end
|
|
2932
|
+
end
|
|
2933
|
+
|
|
2934
|
+
-- rebuilds the shared lookups (component ids, custom ids, serdes) when
|
|
2935
|
+
-- flagged; no-op otherwise.
|
|
2936
|
+
function server.resolve_dirty(self: Server)
|
|
2937
|
+
if not self.is_shared_dirty then
|
|
2938
|
+
return
|
|
2939
|
+
end
|
|
2940
|
+
|
|
2941
|
+
local serdes: { [Component]: types.Serdes } = {}
|
|
2942
|
+
for component, options in self.component_serialization do
|
|
2943
|
+
if options.serdes then
|
|
2944
|
+
serdes[component] = options.serdes
|
|
2945
|
+
end
|
|
2946
|
+
end
|
|
2947
|
+
|
|
2948
|
+
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
|
|
2949
|
+
self.is_shared_dirty = false
|
|
2950
|
+
end
|
|
2951
|
+
|
|
2952
|
+
local FULL_BITS = {
|
|
2953
|
+
tags = 0,
|
|
2954
|
+
components = 1,
|
|
2955
|
+
pair_tags = 2,
|
|
2956
|
+
pair_components = 3,
|
|
2957
|
+
relation_tags = 4,
|
|
2958
|
+
relation_components = 5,
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
-- full snapshot for a single player, serialized straight into one buffer.
|
|
2962
|
+
--
|
|
2963
|
+
-- layout in read order (sections are written in the reverse):
|
|
2964
|
+
-- vlq entity count
|
|
2965
|
+
-- per entity:
|
|
2966
|
+
-- entity id
|
|
2967
|
+
-- u8 entity mask FULL_BITS, vlq count per set bit
|
|
2968
|
+
-- tags component ids (tags and unreliable presence)
|
|
2969
|
+
-- components value + component id
|
|
2970
|
+
-- pair tags pairs
|
|
2971
|
+
-- pair components pair values
|
|
2972
|
+
-- relation tags per relation: [targets..., vlq, relation]
|
|
2973
|
+
-- relation components per relation: [target + value..., vlq, relation]
|
|
2974
|
+
local function write_full_entity(
|
|
2975
|
+
self: Server,
|
|
2976
|
+
c: Cursor,
|
|
2977
|
+
entity: Entity,
|
|
2978
|
+
networked: NetworkedStorage,
|
|
2979
|
+
index: number,
|
|
2980
|
+
variants: Array<any>
|
|
2981
|
+
)
|
|
2982
|
+
local world = self.world
|
|
2983
|
+
local mask = 0
|
|
2984
|
+
|
|
2985
|
+
-- relation components: every current target with its value
|
|
2986
|
+
local total = 0
|
|
2987
|
+
for rel, relation_storage in networked.relation_storages do
|
|
2988
|
+
if not relation_storage.is_component then
|
|
2989
|
+
continue
|
|
2990
|
+
end
|
|
2991
|
+
local filter = relation_storage.filter
|
|
2992
|
+
if filter and not bitmasks.test(filter, index) then
|
|
2993
|
+
continue
|
|
2994
|
+
end
|
|
2995
|
+
|
|
2996
|
+
local targets: Array<Entity> = {}
|
|
2997
|
+
local values: Array<any> = {}
|
|
2998
|
+
local target_index = 0
|
|
2999
|
+
while true do
|
|
3000
|
+
local target = WORLD_TARGET(world, entity, rel, target_index)
|
|
3001
|
+
if not target then
|
|
3002
|
+
break
|
|
3003
|
+
end
|
|
3004
|
+
target_index += 1
|
|
3005
|
+
local value = WORLD_GET(world, entity, PAIR(rel, target))
|
|
3006
|
+
table.insert(targets, target)
|
|
3007
|
+
table.insert(values, SET_VALUE(value))
|
|
3008
|
+
end
|
|
3009
|
+
write_relation_targets_values(self, c, rel, targets, values, variants)
|
|
3010
|
+
total += 1
|
|
3011
|
+
end
|
|
3012
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.relation_components)
|
|
3013
|
+
|
|
3014
|
+
-- relation tags: every current target
|
|
3015
|
+
total = 0
|
|
3016
|
+
for rel, relation_storage in networked.relation_storages do
|
|
3017
|
+
if relation_storage.is_component then
|
|
3018
|
+
continue
|
|
3019
|
+
end
|
|
3020
|
+
local filter = relation_storage.filter
|
|
3021
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3022
|
+
continue
|
|
3023
|
+
end
|
|
3024
|
+
|
|
3025
|
+
local targets: Array<Entity> = {}
|
|
3026
|
+
local target_index = 0
|
|
3027
|
+
while true do
|
|
3028
|
+
local target = WORLD_TARGET(world, entity, rel, target_index)
|
|
3029
|
+
if not target then
|
|
3030
|
+
break
|
|
3031
|
+
end
|
|
3032
|
+
target_index += 1
|
|
3033
|
+
table.insert(targets, target)
|
|
3034
|
+
end
|
|
3035
|
+
write_relation_targets(self, c, rel, targets)
|
|
3036
|
+
total += 1
|
|
3037
|
+
end
|
|
3038
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.relation_tags)
|
|
3039
|
+
|
|
3040
|
+
-- pair components, unreliable values included
|
|
3041
|
+
total = 0
|
|
3042
|
+
for id, storage in networked.pair_storages do
|
|
3043
|
+
if not storage.is_component then
|
|
3044
|
+
continue
|
|
3045
|
+
end
|
|
3046
|
+
local filter = storage.filter
|
|
3047
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3048
|
+
continue
|
|
3049
|
+
end
|
|
3050
|
+
if WORLD_HAS(world, entity, id) then
|
|
3051
|
+
write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
|
|
3052
|
+
total += 1
|
|
3053
|
+
end
|
|
3054
|
+
end
|
|
3055
|
+
for id, storage in networked.unreliable_pair_storages do
|
|
3056
|
+
if not storage.is_component then
|
|
3057
|
+
continue
|
|
3058
|
+
end
|
|
3059
|
+
local filter = storage.filter
|
|
3060
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3061
|
+
continue
|
|
3062
|
+
end
|
|
3063
|
+
if WORLD_HAS(world, entity, id) then
|
|
3064
|
+
write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
|
|
3065
|
+
total += 1
|
|
3066
|
+
end
|
|
3067
|
+
end
|
|
3068
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.pair_components)
|
|
3069
|
+
|
|
3070
|
+
-- pair tags: reliable without a value
|
|
3071
|
+
total = 0
|
|
3072
|
+
for id, storage in networked.pair_storages do
|
|
3073
|
+
if storage.is_component then
|
|
3074
|
+
continue
|
|
3075
|
+
end
|
|
3076
|
+
local filter = storage.filter
|
|
3077
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3078
|
+
continue
|
|
3079
|
+
end
|
|
3080
|
+
if WORLD_HAS(world, entity, id) then
|
|
3081
|
+
write_entity_pair(self, world, c, id)
|
|
3082
|
+
total += 1
|
|
3083
|
+
end
|
|
3084
|
+
end
|
|
3085
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.pair_tags)
|
|
3086
|
+
|
|
3087
|
+
-- components: unreliable values ride along too, a snapshot is the only
|
|
3088
|
+
-- reliable chance the client gets to see them
|
|
3089
|
+
total = 0
|
|
3090
|
+
for component, storage in networked.reliable_storages do
|
|
3091
|
+
if not storage.is_component then
|
|
3092
|
+
continue
|
|
3093
|
+
end
|
|
3094
|
+
local filter = storage.filter
|
|
3095
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3096
|
+
continue
|
|
3097
|
+
end
|
|
3098
|
+
if WORLD_HAS(world, entity, component) then
|
|
3099
|
+
write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
|
|
3100
|
+
write_component_id(self, c, component)
|
|
3101
|
+
total += 1
|
|
3102
|
+
end
|
|
3103
|
+
end
|
|
3104
|
+
for component, storage in networked.unreliable_storages do
|
|
3105
|
+
local filter = storage.filter
|
|
3106
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3107
|
+
continue
|
|
3108
|
+
end
|
|
3109
|
+
if WORLD_HAS(world, entity, component) then
|
|
3110
|
+
write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
|
|
3111
|
+
write_component_id(self, c, component)
|
|
3112
|
+
total += 1
|
|
3113
|
+
end
|
|
3114
|
+
end
|
|
3115
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.components)
|
|
3116
|
+
|
|
3117
|
+
-- tags: reliable without a value
|
|
3118
|
+
total = 0
|
|
3119
|
+
for component, storage in networked.reliable_storages do
|
|
3120
|
+
if storage.is_component then
|
|
3121
|
+
continue
|
|
3122
|
+
end
|
|
3123
|
+
local filter = storage.filter
|
|
3124
|
+
if filter and not bitmasks.test(filter, index) then
|
|
3125
|
+
continue
|
|
3126
|
+
end
|
|
3127
|
+
if WORLD_HAS(world, entity, component) then
|
|
3128
|
+
write_component_id(self, c, component)
|
|
3129
|
+
total += 1
|
|
3130
|
+
end
|
|
3131
|
+
end
|
|
3132
|
+
mask = write_vlq_bitmask(c, total, mask, FULL_BITS.tags)
|
|
3133
|
+
|
|
3134
|
+
cursor.writeu8(c, mask)
|
|
3135
|
+
write_entity_id(self, c, entity)
|
|
3136
|
+
end
|
|
3137
|
+
|
|
3138
|
+
function server.get_full(self: Server, player: Player): (buffer, Array<any>)
|
|
3139
|
+
self:resolve_dirty()
|
|
3140
|
+
|
|
3141
|
+
local index = self.registry.client_indexes[player]
|
|
3142
|
+
if index == nil then
|
|
3143
|
+
LOG_ERROR "attempted to get full of a non registered client"
|
|
3144
|
+
end
|
|
3145
|
+
|
|
3146
|
+
local is_active = bitmasks.test(self.client_active_bitmask, index)
|
|
3147
|
+
if not is_active then
|
|
3148
|
+
common.log_warn(
|
|
3149
|
+
"attempted to get full of a client that is not active, the packet may be incomplete.",
|
|
3150
|
+
"mark the player as ready first with mark_player_ready and run collect_updates"
|
|
3151
|
+
)
|
|
3152
|
+
end
|
|
3153
|
+
|
|
3154
|
+
local c = cursor.new()
|
|
3155
|
+
local variants: Array<any> = {}
|
|
3156
|
+
local total_entities = 0
|
|
3157
|
+
|
|
3158
|
+
for entity, networked in self.networked do
|
|
3159
|
+
if networked.is_dead then
|
|
3160
|
+
continue
|
|
3161
|
+
end
|
|
3162
|
+
|
|
3163
|
+
local entity_filter = networked.filter
|
|
3164
|
+
local visible = if entity_filter then bitmasks.test(entity_filter, index) else is_active
|
|
3165
|
+
if not visible then
|
|
3166
|
+
continue
|
|
3167
|
+
end
|
|
3168
|
+
|
|
3169
|
+
write_full_entity(self, c, entity, networked, index, variants)
|
|
3170
|
+
total_entities += 1
|
|
3171
|
+
end
|
|
3172
|
+
|
|
3173
|
+
cursor.write_vlq(c, total_entities)
|
|
3174
|
+
cursor.writeu8(c, PACKET_TYPES.full)
|
|
3175
|
+
return cursor.close(c), variants
|
|
3176
|
+
end
|
|
3177
|
+
|
|
3178
|
+
local function distribute_update_groups(groups: Map<MaskHash, SerializeGroup>): Map<number, MemberUpdatePackets>
|
|
3179
|
+
local member_updates: Map<number, MemberUpdatePackets> = {}
|
|
3180
|
+
for _, group in groups do
|
|
3181
|
+
local output = group.output
|
|
3182
|
+
|
|
3183
|
+
local buf = output.buffer
|
|
3184
|
+
local size = buffer.len(buf)
|
|
3185
|
+
local update: MemberPacket = {
|
|
3186
|
+
buffer = buf,
|
|
3187
|
+
variants = output.variants :: Array<any>,
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
bitmasks.iter_set(group.bitmask, function(index)
|
|
3191
|
+
local member = member_updates[index]
|
|
3192
|
+
if not member then
|
|
3193
|
+
member = { total_size = 0, updates = {} }
|
|
3194
|
+
member_updates[index] = member
|
|
3195
|
+
end
|
|
3196
|
+
member.total_size += size
|
|
3197
|
+
table.insert(member.updates, update)
|
|
3198
|
+
end)
|
|
3199
|
+
end
|
|
3200
|
+
return member_updates
|
|
3201
|
+
end
|
|
3202
|
+
|
|
3203
|
+
local function distribute_unreliable_groups(
|
|
3204
|
+
groups: Map<MaskHash, UnreliableSerializeGroup>
|
|
3205
|
+
): Map<number, MemberUpdatePackets>
|
|
3206
|
+
local member_updates: Map<number, MemberUpdatePackets> = {}
|
|
3207
|
+
|
|
3208
|
+
for _, group in groups do
|
|
3209
|
+
local outputs = group.outputs
|
|
3210
|
+
local updates: Array<MemberPacket> = {}
|
|
3211
|
+
|
|
3212
|
+
for _, output in outputs do
|
|
3213
|
+
local buf = output.buffer
|
|
3214
|
+
local update: MemberPacket = {
|
|
3215
|
+
buffer = buf,
|
|
3216
|
+
variants = output.variants :: Array<any>,
|
|
3217
|
+
}
|
|
3218
|
+
table.insert(updates, update)
|
|
3219
|
+
end
|
|
3220
|
+
|
|
3221
|
+
bitmasks.iter_set(group.bitmask, function(index)
|
|
3222
|
+
for _, update in updates do
|
|
3223
|
+
local buf = update.buffer
|
|
3224
|
+
local size = buffer.len(buf)
|
|
3225
|
+
|
|
3226
|
+
local member = member_updates[index]
|
|
3227
|
+
if not member then
|
|
3228
|
+
member = { total_size = 0, updates = {} }
|
|
3229
|
+
member_updates[index] = member
|
|
3230
|
+
end
|
|
3231
|
+
member.total_size += size
|
|
3232
|
+
table.insert(member.updates, update)
|
|
3233
|
+
end
|
|
3234
|
+
end)
|
|
3235
|
+
end
|
|
3236
|
+
|
|
3237
|
+
return member_updates
|
|
3238
|
+
end
|
|
3239
|
+
|
|
3240
|
+
local function combine_packet_outputs(
|
|
3241
|
+
outputs: Array<MemberPacket>,
|
|
3242
|
+
total_size: number,
|
|
3243
|
+
packet_type: number
|
|
3244
|
+
): (buffer, Array<Array<any>>)
|
|
3245
|
+
local count = #outputs
|
|
3246
|
+
local combined = buffer.create(total_size + vlq_span(count) + PACKET_TYPE_SPAN)
|
|
3247
|
+
local variants = table.create(count) :: Array<Array<any>>
|
|
3248
|
+
|
|
3249
|
+
local offset = 0
|
|
3250
|
+
for _, update in outputs do
|
|
3251
|
+
local buf = update.buffer
|
|
3252
|
+
buffer.copy(combined, offset, buf)
|
|
3253
|
+
offset += buffer.len(buf)
|
|
3254
|
+
table.insert(variants, update.variants)
|
|
3255
|
+
end
|
|
3256
|
+
|
|
3257
|
+
local tail = cursor.from_offset(combined, offset)
|
|
3258
|
+
cursor.write_vlq(tail, count)
|
|
3259
|
+
cursor.writeu8(tail, packet_type)
|
|
3260
|
+
return combined, variants
|
|
3261
|
+
end
|
|
3262
|
+
|
|
3263
|
+
local function create_packet_iterator(
|
|
3264
|
+
self: Server,
|
|
3265
|
+
member_updates: Map<number, MemberUpdatePackets>,
|
|
3266
|
+
packet_type: number
|
|
3267
|
+
): PacketIterator
|
|
3268
|
+
local index_clients = self.registry.index_clients
|
|
3269
|
+
local iterated: number?
|
|
3270
|
+
local function iterator()
|
|
3271
|
+
local index, member = next(member_updates, iterated)
|
|
3272
|
+
if index == nil then
|
|
3273
|
+
return nil :: any
|
|
3274
|
+
end
|
|
3275
|
+
iterated = index
|
|
3276
|
+
|
|
3277
|
+
local player = index_clients[index]
|
|
3278
|
+
if player == nil then
|
|
3279
|
+
LOG_ERROR "packet addressed to a client that is no longer registered, send packets right after collecting"
|
|
3280
|
+
end
|
|
3281
|
+
|
|
3282
|
+
local combined, variants = combine_packet_outputs(member.updates, member.total_size, packet_type)
|
|
3283
|
+
return player, combined, variants
|
|
3284
|
+
end
|
|
3285
|
+
return iterator :: PacketIterator
|
|
3286
|
+
end
|
|
3287
|
+
|
|
3288
|
+
export type EntitySerializeGroup = {
|
|
3289
|
+
bitmask: Bitmask,
|
|
3290
|
+
relation_components: Array<Component>,
|
|
3291
|
+
relation_tags: Array<Component>,
|
|
3292
|
+
pair_components: Array<Component>,
|
|
3293
|
+
pair_tags: Array<Component>,
|
|
3294
|
+
components: Array<Component>,
|
|
3295
|
+
tags: Array<Component>,
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
local function serialize_entity_group(
|
|
3299
|
+
self: Server,
|
|
3300
|
+
c: Cursor,
|
|
3301
|
+
entity: Entity,
|
|
3302
|
+
group: EntitySerializeGroup,
|
|
3303
|
+
variants: Array<any>
|
|
3304
|
+
)
|
|
3305
|
+
local world = self.world
|
|
3306
|
+
local mask = 0
|
|
3307
|
+
|
|
3308
|
+
local relation_components = group.relation_components
|
|
3309
|
+
for _, rel in relation_components do
|
|
3310
|
+
local targets: Array<Entity> = {}
|
|
3311
|
+
local values: Array<any> = {}
|
|
3312
|
+
local target_index = 0
|
|
3313
|
+
while true do
|
|
3314
|
+
local target = WORLD_TARGET(world, entity, rel, target_index)
|
|
3315
|
+
if not target then
|
|
3316
|
+
break
|
|
3317
|
+
end
|
|
3318
|
+
target_index += 1
|
|
3319
|
+
local value = WORLD_GET(world, entity, PAIR(rel, target))
|
|
3320
|
+
table.insert(targets, target)
|
|
3321
|
+
table.insert(values, SET_VALUE(value))
|
|
3322
|
+
end
|
|
3323
|
+
write_relation_targets_values(self, c, rel, targets, values, variants)
|
|
3324
|
+
end
|
|
3325
|
+
mask = write_vlq_bitmask(c, #relation_components, mask, FULL_BITS.relation_components)
|
|
3326
|
+
|
|
3327
|
+
local relation_tags = group.relation_tags
|
|
3328
|
+
for _, rel in relation_tags do
|
|
3329
|
+
local targets: Array<Entity> = {}
|
|
3330
|
+
local target_index = 0
|
|
3331
|
+
while true do
|
|
3332
|
+
local target = WORLD_TARGET(world, entity, rel, target_index)
|
|
3333
|
+
if not target then
|
|
3334
|
+
break
|
|
3335
|
+
end
|
|
3336
|
+
target_index += 1
|
|
3337
|
+
table.insert(targets, target)
|
|
3338
|
+
end
|
|
3339
|
+
write_relation_targets(self, c, rel, targets)
|
|
3340
|
+
end
|
|
3341
|
+
mask = write_vlq_bitmask(c, #relation_tags, mask, FULL_BITS.relation_tags)
|
|
3342
|
+
|
|
3343
|
+
local pair_components = group.pair_components
|
|
3344
|
+
for _, id in pair_components do
|
|
3345
|
+
write_entity_pair_value(self, world, c, id, WORLD_GET(world, entity, id), variants)
|
|
3346
|
+
end
|
|
3347
|
+
mask = write_vlq_bitmask(c, #pair_components, mask, FULL_BITS.pair_components)
|
|
3348
|
+
|
|
3349
|
+
local pair_tags = group.pair_tags
|
|
3350
|
+
for _, id in pair_tags do
|
|
3351
|
+
write_entity_pair(self, world, c, id)
|
|
3352
|
+
end
|
|
3353
|
+
mask = write_vlq_bitmask(c, #pair_tags, mask, FULL_BITS.pair_tags)
|
|
3354
|
+
|
|
3355
|
+
local components = group.components
|
|
3356
|
+
for _, component in components do
|
|
3357
|
+
write_component_value(self, c, component, WORLD_GET(world, entity, component), variants)
|
|
3358
|
+
write_component_id(self, c, component)
|
|
3359
|
+
end
|
|
3360
|
+
mask = write_vlq_bitmask(c, #components, mask, FULL_BITS.components)
|
|
3361
|
+
|
|
3362
|
+
local tags = group.tags
|
|
3363
|
+
for _, component in tags do
|
|
3364
|
+
write_component_id(self, c, component)
|
|
3365
|
+
end
|
|
3366
|
+
mask = write_vlq_bitmask(c, #tags, mask, FULL_BITS.tags)
|
|
3367
|
+
|
|
3368
|
+
cursor.writeu8(c, mask)
|
|
3369
|
+
write_entity_id(self, c, entity)
|
|
3370
|
+
end
|
|
3371
|
+
|
|
3372
|
+
local function collect_entity_groups(self: Server, entity: Entity): Map<MaskHash, EntitySerializeGroup>
|
|
3373
|
+
local networked = self.networked[entity]
|
|
3374
|
+
if not networked or networked.is_dead then
|
|
3375
|
+
LOG_ERROR "attempted to collect an entity that is not networked"
|
|
3376
|
+
end
|
|
3377
|
+
|
|
3378
|
+
local scratch = self.draft_bitmask
|
|
3379
|
+
local groups: Map<MaskHash, EntitySerializeGroup> = {}
|
|
3380
|
+
|
|
3381
|
+
local function emit(bitmask: Bitmask): EntitySerializeGroup
|
|
3382
|
+
local hash = bitmasks.hash(bitmask)
|
|
3383
|
+
local group = groups[hash]
|
|
3384
|
+
if not group then
|
|
3385
|
+
group = {
|
|
3386
|
+
bitmask = bitmasks.clone(bitmask),
|
|
3387
|
+
relation_components = {},
|
|
3388
|
+
relation_tags = {},
|
|
3389
|
+
pair_components = {},
|
|
3390
|
+
pair_tags = {},
|
|
3391
|
+
components = {},
|
|
3392
|
+
tags = {},
|
|
3393
|
+
}
|
|
3394
|
+
groups[hash] = group
|
|
3395
|
+
end
|
|
3396
|
+
return group
|
|
3397
|
+
end
|
|
3398
|
+
|
|
3399
|
+
-- everyone who sees the entity at all; storage filters clip within it
|
|
3400
|
+
local entity_visible = bitmasks.clone(self.client_active_bitmask)
|
|
3401
|
+
local entity_filter = networked.filter
|
|
3402
|
+
if entity_filter then
|
|
3403
|
+
bitmasks.band(entity_visible, entity_filter, entity_visible)
|
|
3404
|
+
end
|
|
3405
|
+
if bitmasks.is_empty(entity_visible) then
|
|
3406
|
+
return groups
|
|
3407
|
+
end
|
|
3408
|
+
emit(entity_visible)
|
|
3409
|
+
|
|
3410
|
+
-- nil storage filters resolve to the entity's own visibility
|
|
3411
|
+
local function group_for(storage: StorageBase): EntitySerializeGroup?
|
|
3412
|
+
local filter = storage.filter
|
|
3413
|
+
if not filter then
|
|
3414
|
+
return emit(entity_visible)
|
|
3415
|
+
end
|
|
3416
|
+
bitmasks.band(filter, entity_visible, scratch)
|
|
3417
|
+
if bitmasks.is_empty(scratch) then
|
|
3418
|
+
return nil
|
|
3419
|
+
end
|
|
3420
|
+
return emit(scratch)
|
|
3421
|
+
end
|
|
3422
|
+
|
|
3423
|
+
local world = self.world
|
|
3424
|
+
for rel, storage in networked.relation_storages do
|
|
3425
|
+
local group = group_for(storage)
|
|
3426
|
+
if not group then
|
|
3427
|
+
continue
|
|
3428
|
+
end
|
|
3429
|
+
table.insert(if storage.is_component then group.relation_components else group.relation_tags, rel)
|
|
3430
|
+
end
|
|
3431
|
+
for id, storage in networked.pair_storages do
|
|
3432
|
+
if not WORLD_HAS(world, entity, id) then
|
|
3433
|
+
continue
|
|
3434
|
+
end
|
|
3435
|
+
local group = group_for(storage)
|
|
3436
|
+
if not group then
|
|
3437
|
+
continue
|
|
3438
|
+
end
|
|
3439
|
+
table.insert(if storage.is_component then group.pair_components else group.pair_tags, id)
|
|
3440
|
+
end
|
|
3441
|
+
|
|
3442
|
+
for id, storage in networked.unreliable_pair_storages do
|
|
3443
|
+
if not WORLD_HAS(world, entity, id) then
|
|
3444
|
+
continue
|
|
3445
|
+
end
|
|
3446
|
+
local group = group_for(storage)
|
|
3447
|
+
if group then
|
|
3448
|
+
table.insert(group.pair_components, id)
|
|
3449
|
+
end
|
|
3450
|
+
end
|
|
3451
|
+
for component, storage in networked.reliable_storages do
|
|
3452
|
+
if not WORLD_HAS(world, entity, component) then
|
|
3453
|
+
continue
|
|
3454
|
+
end
|
|
3455
|
+
local group = group_for(storage)
|
|
3456
|
+
if not group then
|
|
3457
|
+
continue
|
|
3458
|
+
end
|
|
3459
|
+
table.insert(if storage.is_component then group.components else group.tags, component)
|
|
3460
|
+
end
|
|
3461
|
+
for component, storage in networked.unreliable_storages do
|
|
3462
|
+
if not WORLD_HAS(world, entity, component) then
|
|
3463
|
+
continue
|
|
3464
|
+
end
|
|
3465
|
+
local group = group_for(storage)
|
|
3466
|
+
if group then
|
|
3467
|
+
table.insert(group.components, component)
|
|
3468
|
+
end
|
|
3469
|
+
end
|
|
3470
|
+
|
|
3471
|
+
return groups
|
|
3472
|
+
end
|
|
3473
|
+
|
|
3474
|
+
function server.collect_entity(self: Server, entity: Entity): PacketIterator
|
|
3475
|
+
self:resolve_dirty()
|
|
3476
|
+
local groups = collect_entity_groups(self, entity)
|
|
3477
|
+
|
|
3478
|
+
local member_updates: Map<number, MemberUpdatePackets> = {}
|
|
3479
|
+
for _, group in groups do
|
|
3480
|
+
local c = cursor.new()
|
|
3481
|
+
local variants: Array<any> = {}
|
|
3482
|
+
serialize_entity_group(self, c, entity, group, variants)
|
|
3483
|
+
cursor.write_vlq(c, 1)
|
|
3484
|
+
|
|
3485
|
+
local buf = cursor.close(c)
|
|
3486
|
+
local size = buffer.len(buf)
|
|
3487
|
+
local update: MemberPacket = { buffer = buf, variants = variants }
|
|
3488
|
+
|
|
3489
|
+
bitmasks.iter_set(group.bitmask, function(index)
|
|
3490
|
+
local member = member_updates[index]
|
|
3491
|
+
if not member then
|
|
3492
|
+
member = { total_size = 0, updates = {} }
|
|
3493
|
+
member_updates[index] = member
|
|
3494
|
+
end
|
|
3495
|
+
member.total_size += size
|
|
3496
|
+
table.insert(member.updates, update)
|
|
3497
|
+
end)
|
|
3498
|
+
end
|
|
3499
|
+
|
|
3500
|
+
return create_packet_iterator(self, member_updates, PACKET_TYPES.entity)
|
|
3501
|
+
end
|
|
3502
|
+
|
|
3503
|
+
function server.collect_updates(self: Server): PacketIterator
|
|
3504
|
+
self:resolve_dirty()
|
|
3505
|
+
local groups = collect_updates_groups(self)
|
|
3506
|
+
serialize_update_groups(self, groups)
|
|
3507
|
+
|
|
3508
|
+
return create_packet_iterator(self, distribute_update_groups(groups), PACKET_TYPES.updates)
|
|
3509
|
+
end
|
|
3510
|
+
|
|
3511
|
+
local function create_unreliable_packet_iterator(
|
|
3512
|
+
self: Server,
|
|
3513
|
+
member_updates: Map<number, MemberUpdatePackets>
|
|
3514
|
+
): PacketIterator
|
|
3515
|
+
local index_clients = self.registry.index_clients
|
|
3516
|
+
|
|
3517
|
+
return coroutine.wrap(function()
|
|
3518
|
+
for index, member in member_updates do
|
|
3519
|
+
local player = index_clients[index]
|
|
3520
|
+
if player == nil then
|
|
3521
|
+
LOG_ERROR "packet addressed to a client that is no longer registered, send packets right after collecting"
|
|
3522
|
+
end
|
|
3523
|
+
local updates = member.updates
|
|
3524
|
+
|
|
3525
|
+
table.sort(updates, function(a, b)
|
|
3526
|
+
return buffer.len(a.buffer) < buffer.len(b.buffer)
|
|
3527
|
+
end)
|
|
3528
|
+
local total_packets = #updates
|
|
3529
|
+
|
|
3530
|
+
local current = 1
|
|
3531
|
+
while current <= total_packets do
|
|
3532
|
+
local sending: Array<MemberPacket> = {}
|
|
3533
|
+
local batch_size = 0
|
|
3534
|
+
|
|
3535
|
+
while current <= total_packets do
|
|
3536
|
+
local packet = updates[current]
|
|
3537
|
+
local packet_size = buffer.len(packet.buffer)
|
|
3538
|
+
|
|
3539
|
+
-- combine appends the packet count and type byte, so they
|
|
3540
|
+
-- count toward the budget
|
|
3541
|
+
local next_count = #sending + 1
|
|
3542
|
+
local tail = vlq_span(next_count) + PACKET_TYPE_SPAN
|
|
3543
|
+
if batch_size + packet_size + tail > MAX_UNRELIABLE_PACKET_SIZE and #sending > 0 then
|
|
3544
|
+
break
|
|
3545
|
+
end
|
|
3546
|
+
|
|
3547
|
+
table.insert(sending, packet)
|
|
3548
|
+
batch_size += packet_size
|
|
3549
|
+
current += 1
|
|
3550
|
+
|
|
3551
|
+
if batch_size + tail >= MAX_UNRELIABLE_PACKET_SIZE then
|
|
3552
|
+
break
|
|
3553
|
+
end
|
|
3554
|
+
end
|
|
3555
|
+
|
|
3556
|
+
if #sending > 0 then
|
|
3557
|
+
local combined, variants = combine_packet_outputs(sending, batch_size, PACKET_TYPES.unreliable)
|
|
3558
|
+
coroutine.yield(player, combined, variants)
|
|
3559
|
+
end
|
|
3560
|
+
end
|
|
3561
|
+
end
|
|
3562
|
+
end)
|
|
3563
|
+
end
|
|
3564
|
+
|
|
3565
|
+
function server.collect_unreliable(self: Server): PacketIterator
|
|
3566
|
+
self:resolve_dirty()
|
|
3567
|
+
local groups = collect_unreliable_groups(self)
|
|
3568
|
+
serialize_unreliable_groups(self, groups)
|
|
3569
|
+
return create_unreliable_packet_iterator(self, distribute_unreliable_groups(groups))
|
|
3570
|
+
end
|
|
3571
|
+
|
|
3572
|
+
function server.register_player(self: Server, player: Player)
|
|
3573
|
+
self.registry:register_client(player)
|
|
3574
|
+
end
|
|
3575
|
+
|
|
3576
|
+
function server.add_player_alias(self: Server, player: Player, alias: any)
|
|
3577
|
+
self.registry:add_alias(player, alias)
|
|
3578
|
+
end
|
|
3579
|
+
|
|
3580
|
+
function server.remove_player_alias(self: Server, alias: any)
|
|
3581
|
+
self.registry:remove_alias(alias)
|
|
3582
|
+
end
|
|
3583
|
+
|
|
3584
|
+
function server.mark_player_ready(self: Server, player: Player)
|
|
3585
|
+
local registry = self.registry
|
|
3586
|
+
registry:register_client(player)
|
|
3587
|
+
|
|
3588
|
+
local index = registry.client_indexes[player]
|
|
3589
|
+
if not index then
|
|
3590
|
+
return
|
|
3591
|
+
end
|
|
3592
|
+
bitmasks.set(self.client_active_bitmask, index)
|
|
3593
|
+
end
|
|
3594
|
+
|
|
3595
|
+
function server.is_player_ready(self: Server, player: Player): boolean
|
|
3596
|
+
local index = self.registry.client_indexes[player]
|
|
3597
|
+
if not index then
|
|
3598
|
+
return false
|
|
3599
|
+
end
|
|
3600
|
+
return bitmasks.test(self.client_active_bitmask, index)
|
|
3601
|
+
end
|
|
3602
|
+
|
|
3603
|
+
function server.encode_component(self: Server, component: Component): number
|
|
3604
|
+
self:resolve_dirty()
|
|
3605
|
+
local encoded = self.shared.components.members[component]
|
|
3606
|
+
if not encoded then
|
|
3607
|
+
LOG_ERROR(`attempted to encode a non-shared component `, common.log_component(self.world, component))
|
|
3608
|
+
return 0
|
|
3609
|
+
end
|
|
3610
|
+
return encoded
|
|
3611
|
+
end
|
|
3612
|
+
|
|
3613
|
+
function server.decode_component(self: Server, encoded: number): Component?
|
|
3614
|
+
self:resolve_dirty()
|
|
3615
|
+
return self.shared.components.indexes[encoded]
|
|
3616
|
+
end
|
|
3617
|
+
|
|
3618
|
+
function server.get_shared_count(self: Server): number
|
|
3619
|
+
self:resolve_dirty()
|
|
3620
|
+
return #self.shared.components.keys
|
|
3621
|
+
end
|
|
3622
|
+
|
|
3623
|
+
function server.generate_handshake(self: Server): types.HandshakeInfo
|
|
3624
|
+
self:resolve_dirty()
|
|
3625
|
+
return handshake.generate_handshake(self.shared)
|
|
3626
|
+
end
|
|
3627
|
+
|
|
3628
|
+
function server.verify_handshake(self: Server, client_handshake: types.HandshakeInfo): (boolean, string?)
|
|
3629
|
+
self:resolve_dirty()
|
|
3630
|
+
return handshake.verify_handshake(self.shared, client_handshake, "client", "server")
|
|
3631
|
+
end
|
|
3632
|
+
|
|
3633
|
+
|
|
3634
|
+
local function invalidate_client_index(storage: StorageBase, hole_mask: number, index: number)
|
|
3635
|
+
local filter = storage.filter
|
|
3636
|
+
if filter then
|
|
3637
|
+
bitmasks.punch_hole_mask(filter, hole_mask, index)
|
|
3638
|
+
end
|
|
3639
|
+
local last_replicated = storage.last_replicated
|
|
3640
|
+
if last_replicated then
|
|
3641
|
+
bitmasks.punch_hole_mask(last_replicated, hole_mask, index)
|
|
3642
|
+
end
|
|
3643
|
+
end
|
|
3644
|
+
|
|
3645
|
+
function server.remove_player(self: Server, player: Player)
|
|
3646
|
+
local registry = self.registry
|
|
3647
|
+
local index = registry.client_indexes[player]
|
|
3648
|
+
|
|
3649
|
+
if index then
|
|
3650
|
+
bitmasks.reset(self.client_active_bitmask, index)
|
|
3651
|
+
bitmasks.reset(self.last_active_bitmask, index)
|
|
3652
|
+
|
|
3653
|
+
local hole_mask = bitmasks.get_hole_mask(index)
|
|
3654
|
+
for _, networked in self.networked do
|
|
3655
|
+
invalidate_client_index(networked, hole_mask, index)
|
|
3656
|
+
for _, reliable_storage in networked.reliable_storages do
|
|
3657
|
+
invalidate_client_index(reliable_storage, hole_mask, index)
|
|
3658
|
+
end
|
|
3659
|
+
for _, unreliable_storage in networked.unreliable_storages do
|
|
3660
|
+
invalidate_client_index(unreliable_storage, hole_mask, index)
|
|
3661
|
+
end
|
|
3662
|
+
for _, pair_storage in networked.pair_storages do
|
|
3663
|
+
invalidate_client_index(pair_storage, hole_mask, index)
|
|
3664
|
+
end
|
|
3665
|
+
for _, unreliable_pair_storage in networked.unreliable_pair_storages do
|
|
3666
|
+
invalidate_client_index(unreliable_pair_storage, hole_mask, index)
|
|
3667
|
+
end
|
|
3668
|
+
for _, relation_storage in networked.relation_storages do
|
|
3669
|
+
invalidate_client_index(relation_storage, hole_mask, index)
|
|
3670
|
+
end
|
|
3671
|
+
end
|
|
3672
|
+
end
|
|
3673
|
+
|
|
3674
|
+
registry:remove_client_aliases(player)
|
|
3675
|
+
registry:unregister_client(player)
|
|
3676
|
+
end
|
|
3677
|
+
|
|
3678
|
+
local function hook_tracking_relation(
|
|
3679
|
+
self: Server,
|
|
3680
|
+
hook: (() -> ()) -> (),
|
|
3681
|
+
track_type: Component,
|
|
3682
|
+
set: (
|
|
3683
|
+
self: Server,
|
|
3684
|
+
entity: Entity,
|
|
3685
|
+
component: Component,
|
|
3686
|
+
filter: FilterInput?
|
|
3687
|
+
) -> (),
|
|
3688
|
+
stop: (self: Server, entity: Entity, component: Component) -> ()
|
|
3689
|
+
)
|
|
3690
|
+
local world = self.world
|
|
3691
|
+
hook(HOOK_ADDED(world, LISTEN_RELATION(track_type), function(entity, id, filter)
|
|
3692
|
+
set(self, entity, PAIR_SECOND(world, id), filter)
|
|
3693
|
+
end))
|
|
3694
|
+
hook(HOOK_CHANGED(world, LISTEN_RELATION(track_type), function(entity, id, filter)
|
|
3695
|
+
set(self, entity, PAIR_SECOND(world, id), filter)
|
|
3696
|
+
end))
|
|
3697
|
+
hook(HOOK_REMOVED(world, LISTEN_RELATION(track_type), function(entity, id)
|
|
3698
|
+
stop(self, entity, PAIR_SECOND(world, id))
|
|
3699
|
+
end))
|
|
3700
|
+
end
|
|
3701
|
+
|
|
3702
|
+
local function discover_tracking_relation(
|
|
3703
|
+
self: Server,
|
|
3704
|
+
track_type: Component,
|
|
3705
|
+
set: (
|
|
3706
|
+
self: Server,
|
|
3707
|
+
entity: Entity,
|
|
3708
|
+
component: Component,
|
|
3709
|
+
filter: FilterInput?
|
|
3710
|
+
) -> ()
|
|
3711
|
+
)
|
|
3712
|
+
local world = self.world
|
|
3713
|
+
for entity in world:query(PAIR(track_type, WILDCARD)):iter() do
|
|
3714
|
+
local index = 0
|
|
3715
|
+
while true do
|
|
3716
|
+
local component = WORLD_TARGET(world, entity, track_type, index)
|
|
3717
|
+
if not component then
|
|
3718
|
+
break
|
|
3719
|
+
end
|
|
3720
|
+
index += 1
|
|
3721
|
+
set(self, entity, component, WORLD_GET(world, entity, PAIR(track_type, component)))
|
|
3722
|
+
end
|
|
3723
|
+
end
|
|
3724
|
+
end
|
|
3725
|
+
|
|
3726
|
+
local function discover_current(self: Server)
|
|
3727
|
+
local world = self.world
|
|
3728
|
+
local components = self.components
|
|
3729
|
+
|
|
3730
|
+
for component, serdes in world:query(components.serdes):iter() do
|
|
3731
|
+
self:set_serdes(component, serdes)
|
|
3732
|
+
end
|
|
3733
|
+
|
|
3734
|
+
for entity, filter in world:query(components.networked):iter() do
|
|
3735
|
+
self:set_networked(entity, filter)
|
|
3736
|
+
end
|
|
3737
|
+
|
|
3738
|
+
if RELATIONSHIPS then
|
|
3739
|
+
discover_tracking_relation(self, components.reliable, self.set_reliable)
|
|
3740
|
+
discover_tracking_relation(self, components.once, self.set_once)
|
|
3741
|
+
discover_tracking_relation(self, components.unreliable, self.set_unreliable)
|
|
3742
|
+
discover_tracking_relation(self, components.relation, self.set_relation)
|
|
3743
|
+
|
|
3744
|
+
for entity in world:query(PAIR(components.custom, WILDCARD)):iter() do
|
|
3745
|
+
local handler = WORLD_TARGET(world, entity, components.custom, 0)
|
|
3746
|
+
if handler then
|
|
3747
|
+
self:set_custom(entity, handler)
|
|
3748
|
+
end
|
|
3749
|
+
end
|
|
3750
|
+
end
|
|
3751
|
+
end
|
|
3752
|
+
|
|
3753
|
+
function server.init(self: Server, wrd: World?)
|
|
3754
|
+
if self.inited == true then
|
|
3755
|
+
return common.log_warn "attempted to init a server twice"
|
|
3756
|
+
end
|
|
3757
|
+
if self.inited == nil then
|
|
3758
|
+
return common.log_warn "attempted to re-init a destroyed server"
|
|
3759
|
+
end
|
|
3760
|
+
self.inited = true
|
|
3761
|
+
|
|
3762
|
+
local world = wrd or self.world
|
|
3763
|
+
self.world = world
|
|
3764
|
+
|
|
3765
|
+
if not world then
|
|
3766
|
+
common.log_error "Providing a world is required to start replecs"
|
|
3767
|
+
end
|
|
3768
|
+
|
|
3769
|
+
if not self.components then
|
|
3770
|
+
self.components = utils.create_world_components(world)
|
|
3771
|
+
end
|
|
3772
|
+
local components = self.components
|
|
3773
|
+
|
|
3774
|
+
local function hook(unhook: () -> ())
|
|
3775
|
+
table.insert(self.cleanups, unhook)
|
|
3776
|
+
end
|
|
3777
|
+
|
|
3778
|
+
-- shared components and their names feed the handshake lookups; any change
|
|
3779
|
+
-- rebuilds them at the next resolve_dirty
|
|
3780
|
+
local function mark_shared_dirty()
|
|
3781
|
+
self.is_shared_dirty = true
|
|
3782
|
+
end
|
|
3783
|
+
|
|
3784
|
+
for _, component in { components.shared, ECS_NAME } do
|
|
3785
|
+
hook(HOOK_ADDED(world, component, mark_shared_dirty))
|
|
3786
|
+
hook(HOOK_CHANGED(world, component, mark_shared_dirty))
|
|
3787
|
+
hook(HOOK_REMOVED(world, component, mark_shared_dirty))
|
|
3788
|
+
end
|
|
3789
|
+
|
|
3790
|
+
hook(HOOK_ADDED(world, components.networked, function(entity, _, filter)
|
|
3791
|
+
self:set_networked(entity, filter)
|
|
3792
|
+
end))
|
|
3793
|
+
hook(HOOK_CHANGED(world, components.networked, function(entity, _, filter)
|
|
3794
|
+
self:set_networked(entity, filter)
|
|
3795
|
+
end))
|
|
3796
|
+
hook(HOOK_REMOVED(world, components.networked, function(entity)
|
|
3797
|
+
self:stop_networked(entity)
|
|
3798
|
+
end))
|
|
3799
|
+
|
|
3800
|
+
if RELATIONSHIPS then
|
|
3801
|
+
hook_tracking_relation(self, hook, components.reliable, self.set_reliable, self.stop_reliable)
|
|
3802
|
+
hook_tracking_relation(self, hook, components.once, self.set_once, self.stop_once)
|
|
3803
|
+
hook_tracking_relation(self, hook, components.unreliable, self.set_unreliable, self.stop_unreliable)
|
|
3804
|
+
hook_tracking_relation(self, hook, components.relation, self.set_relation, self.stop_relation)
|
|
3805
|
+
|
|
3806
|
+
hook(HOOK_ADDED(world, LISTEN_RELATION(components.custom), function(entity, id)
|
|
3807
|
+
if not IS_PAIR(id) then
|
|
3808
|
+
LOG_ERROR "replecs.custom should be used as a pair in the server: pair(custom, handler)"
|
|
3809
|
+
end
|
|
3810
|
+
self:set_custom(entity, PAIR_SECOND(world, id))
|
|
3811
|
+
end))
|
|
3812
|
+
hook(HOOK_REMOVED(world, LISTEN_RELATION(components.custom), function(entity)
|
|
3813
|
+
self:remove_custom(entity)
|
|
3814
|
+
end))
|
|
3815
|
+
end
|
|
3816
|
+
|
|
3817
|
+
hook(HOOK_ADDED(world, components.serdes, function(component, _, serdes)
|
|
3818
|
+
self:set_serdes(component, serdes)
|
|
3819
|
+
end))
|
|
3820
|
+
hook(HOOK_CHANGED(world, components.serdes, function(component, _, serdes)
|
|
3821
|
+
self:set_serdes(component, serdes)
|
|
3822
|
+
end))
|
|
3823
|
+
hook(HOOK_REMOVED(world, components.serdes, function(component)
|
|
3824
|
+
self:remove_serdes(component)
|
|
3825
|
+
end))
|
|
3826
|
+
|
|
3827
|
+
hook(HOOK_REMOVED(world, components.__alive_tracking__, function(entity)
|
|
3828
|
+
self:cleanup_entity(entity)
|
|
3829
|
+
end))
|
|
3830
|
+
|
|
3831
|
+
discover_current(self)
|
|
3832
|
+
|
|
3833
|
+
if game and game:GetService("RunService"):IsServer() then
|
|
3834
|
+
local Players = game:GetService "Players"
|
|
3835
|
+
|
|
3836
|
+
local added = Players.PlayerAdded:Connect(function(player)
|
|
3837
|
+
self:register_player(player)
|
|
3838
|
+
end)
|
|
3839
|
+
local removing = Players.PlayerRemoving:Connect(function(player)
|
|
3840
|
+
self:remove_player(player)
|
|
3841
|
+
end)
|
|
3842
|
+
for _, player in Players:GetPlayers() do
|
|
3843
|
+
self:register_player(player)
|
|
3844
|
+
end
|
|
3845
|
+
|
|
3846
|
+
hook(function()
|
|
3847
|
+
added:Disconnect()
|
|
3848
|
+
removing:Disconnect()
|
|
3849
|
+
end)
|
|
3850
|
+
end
|
|
3851
|
+
|
|
3852
|
+
self.is_shared_dirty = true
|
|
3853
|
+
end
|
|
3854
|
+
|
|
3855
|
+
function server.destroy(self: Server)
|
|
3856
|
+
if self.inited == nil then
|
|
3857
|
+
return common.log_warn "attempted to destroy a server twice"
|
|
3858
|
+
end
|
|
3859
|
+
self.inited = nil
|
|
3860
|
+
for _, cleanup in self.cleanups do
|
|
3861
|
+
cleanup()
|
|
3862
|
+
end
|
|
3863
|
+
table.clear(self.cleanups)
|
|
3864
|
+
end
|
|
3865
|
+
|
|
3866
|
+
function server.create(world: World?, components: types.Components?, max_clients: number?): Server
|
|
3867
|
+
local self = {} :: Server
|
|
3868
|
+
|
|
3869
|
+
self.world = world :: any
|
|
3870
|
+
self.components = components or (if world then utils.create_world_components(world) else nil) :: any
|
|
3871
|
+
self.inited = false
|
|
3872
|
+
|
|
3873
|
+
self.registry = registry.create(max_clients or DEFAULT_MAX_CLIENTS)
|
|
3874
|
+
self.bitmask_size = self.registry.bitmask_size
|
|
3875
|
+
self.ready_players_bitmask = buffer.create(self.bitmask_size)
|
|
3876
|
+
self.shared = {} :: types.Shared
|
|
3877
|
+
|
|
3878
|
+
self.networked = {}
|
|
3879
|
+
self.unreliable_networked = {}
|
|
3880
|
+
self.postponed_components = {}
|
|
3881
|
+
self.entity_serialization = {}
|
|
3882
|
+
self.registered_custom_ids = {}
|
|
3883
|
+
self.component_serialization = {}
|
|
3884
|
+
-- starts dirty so the first resolve_dirty builds the lookups
|
|
3885
|
+
self.is_shared_dirty = true
|
|
3886
|
+
|
|
3887
|
+
self.hooked_components = {}
|
|
3888
|
+
self.hooked_unreliable = {}
|
|
3889
|
+
self.hooked_relations = {}
|
|
3890
|
+
self.hooked_pairs = {}
|
|
3891
|
+
self.hooked_unreliable_pairs = {}
|
|
3892
|
+
|
|
3893
|
+
self.dirty_networked = {}
|
|
3894
|
+
self.function_filters = {}
|
|
3895
|
+
|
|
3896
|
+
self.empty_bitmask = buffer.create(self.bitmask_size)
|
|
3897
|
+
self.draft_bitmask = buffer.create(self.bitmask_size)
|
|
3898
|
+
self.target_bitmask = buffer.create(self.bitmask_size)
|
|
3899
|
+
self.filled_bitmask = buffer.create(self.bitmask_size)
|
|
3900
|
+
buffer.fill(self.filled_bitmask, 0, 0xFF, self.bitmask_size)
|
|
3901
|
+
|
|
3902
|
+
self.client_active_bitmask = buffer.create(self.bitmask_size)
|
|
3903
|
+
self.last_active_bitmask = buffer.create(self.bitmask_size)
|
|
3904
|
+
self.newly_active_bitmask = buffer.create(self.bitmask_size)
|
|
3905
|
+
self.newly_unactive_bitmask = buffer.create(self.bitmask_size)
|
|
3906
|
+
|
|
3907
|
+
self.entity_bitmask_deltas = {
|
|
3908
|
+
added = buffer.create(self.bitmask_size),
|
|
3909
|
+
removed = buffer.create(self.bitmask_size),
|
|
3910
|
+
}
|
|
3911
|
+
self.component_bitmask_deltas = {
|
|
3912
|
+
added = buffer.create(self.bitmask_size),
|
|
3913
|
+
removed = buffer.create(self.bitmask_size),
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
self.cleanups = {}
|
|
3917
|
+
return setmetatable(self, server) :: any
|
|
3918
|
+
end
|
|
3919
|
+
|
|
3920
|
+
return {
|
|
3921
|
+
create = server.create,
|
|
3922
|
+
|
|
3923
|
+
collect_updates_groups = function(self: Server): Map<MaskHash, SerializeGroup>
|
|
3924
|
+
self:resolve_dirty()
|
|
3925
|
+
return collect_updates_groups(self)
|
|
3926
|
+
end,
|
|
3927
|
+
collect_unreliable_groups = function(self: Server): Map<MaskHash, UnreliableSerializeGroup>
|
|
3928
|
+
self:resolve_dirty()
|
|
3929
|
+
return collect_unreliable_groups(self)
|
|
3930
|
+
end,
|
|
3931
|
+
serialize_update_groups = serialize_update_groups,
|
|
3932
|
+
serialize_unreliable_groups = serialize_unreliable_groups,
|
|
3933
|
+
collect_entity_groups = function(self: Server, entity: Entity): Map<MaskHash, EntitySerializeGroup>
|
|
3934
|
+
self:resolve_dirty()
|
|
3935
|
+
return collect_entity_groups(self, entity)
|
|
3936
|
+
end,
|
|
3937
|
+
}
|