@rbxts/replecs 0.4.0-rc.3 → 0.4.0-rc.5
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 +23 -9
- package/out/replecs/handshake.luau +35 -0
- package/out/replecs/index.d.ts +43 -0
- package/out/replecs/init.luau +8 -0
- package/out/replecs/server/filter_group.luau +296 -126
- package/out/replecs/server/init.luau +174 -134
- package/out/replecs/server/registry.luau +30 -1
- package/out/replecs/shared.luau +3 -1
- package/out/replecs/types.luau +3 -0
- package/out/replecs/ver.luau +1 -1
- package/package.json +3 -2
|
@@ -21,10 +21,11 @@ type FilterAny = types.FilterAny
|
|
|
21
21
|
type FilterSet = types.FilterSet
|
|
22
22
|
type FilterPredicator = types.FilterPredicator
|
|
23
23
|
type FilterPlayer = types.FilterPlayer
|
|
24
|
-
type
|
|
24
|
+
type FilterSimple = types.FilterSimple
|
|
25
|
+
export type FilterNode = filter_groups.FilterNode
|
|
25
26
|
|
|
26
27
|
-- what the public set_* API accepts as a filter
|
|
27
|
-
type FilterInput = FilterAny |
|
|
28
|
+
type FilterInput = FilterAny | FilterNode
|
|
28
29
|
|
|
29
30
|
type MaskHash = string
|
|
30
31
|
|
|
@@ -57,6 +58,10 @@ export type ServerImp = {
|
|
|
57
58
|
|
|
58
59
|
force_change: (self: Server, entity: Entity, component: Component) -> (),
|
|
59
60
|
|
|
61
|
+
filter: (self: Server, initial: FilterSimple?) -> FilterNode,
|
|
62
|
+
filter_predicator: (self: Server, predicator: FilterPredicator) -> FilterNode,
|
|
63
|
+
everyone: (self: Server) -> FilterNode,
|
|
64
|
+
|
|
60
65
|
set_global: (self: Server, entity: Entity, global_id: number) -> (),
|
|
61
66
|
remove_global: (self: Server, entity: Entity) -> (),
|
|
62
67
|
set_custom: (self: Server, entity: Entity, custom: Component | CustomId) -> (),
|
|
@@ -76,10 +81,10 @@ type StorageBase = {
|
|
|
76
81
|
filter: Bitmask?,
|
|
77
82
|
last_replicated: Bitmask?,
|
|
78
83
|
|
|
79
|
-
-- set while a filter
|
|
80
|
-
--
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
-- set while a filter node drives this storage's filter, with the slot of
|
|
85
|
+
-- this storage in the node's binding list so unbind is a swap remove
|
|
86
|
+
filter_node: FilterNode?,
|
|
87
|
+
binding_index: number?,
|
|
83
88
|
filter_input: FilterInput?,
|
|
84
89
|
}
|
|
85
90
|
|
|
@@ -135,15 +140,23 @@ type PostponedComponent = {
|
|
|
135
140
|
once: boolean?,
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
-- a
|
|
139
|
-
type
|
|
140
|
-
predicator: registry.MemberPredicator,
|
|
143
|
+
-- a storage driven by a filter node, re-applied whenever the node's version moves
|
|
144
|
+
type FilterBinding = {
|
|
141
145
|
entity: Entity,
|
|
142
146
|
networked: NetworkedStorage,
|
|
147
|
+
storage: StorageBase,
|
|
143
148
|
|
|
144
|
-
-- set for
|
|
149
|
+
-- set for component filters, nil for entity-level ones
|
|
145
150
|
tracking: Component?,
|
|
146
151
|
tracking_storage: TrackingStorageBase?,
|
|
152
|
+
-- node version last written into the storage filter
|
|
153
|
+
applied_version: number,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type NodeBindings = {
|
|
157
|
+
entries: Array<FilterBinding>,
|
|
158
|
+
-- every entry sits at this version once a resolve pass completes
|
|
159
|
+
applied_version: number,
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
type NetworkedReferencesInfo = {
|
|
@@ -204,7 +217,7 @@ export type Server = ServerImp & {
|
|
|
204
217
|
hooked_relations: Map<Component, HookedInfo>,
|
|
205
218
|
|
|
206
219
|
dirty_networked: Array<Entity>,
|
|
207
|
-
|
|
220
|
+
filter_bindings: Map<FilterNode, NodeBindings>,
|
|
208
221
|
|
|
209
222
|
empty_bitmask: Bitmask,
|
|
210
223
|
draft_bitmask: Bitmask,
|
|
@@ -443,20 +456,28 @@ local function resolve_filter_bitmask(self: Server, filter: FilterAny): Bitmask
|
|
|
443
456
|
end
|
|
444
457
|
end
|
|
445
458
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
459
|
+
-- releases the node driving this storage's filter, if any. the entry is swap
|
|
460
|
+
-- removed and the moved entry's storage takes over the freed slot
|
|
461
|
+
local function clear_filter_source(self: Server, storage: StorageBase)
|
|
462
|
+
local node = storage.filter_node
|
|
463
|
+
if not node then
|
|
464
|
+
return
|
|
451
465
|
end
|
|
452
|
-
|
|
453
|
-
storage.
|
|
454
|
-
|
|
466
|
+
local bindings = self.filter_bindings[node]
|
|
467
|
+
local index = storage.binding_index :: number
|
|
468
|
+
local entries = bindings.entries
|
|
455
469
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
470
|
+
local last = table.remove(entries)
|
|
471
|
+
if last and last.storage ~= storage then
|
|
472
|
+
entries[index] = last
|
|
473
|
+
last.storage.binding_index = index
|
|
474
|
+
end
|
|
475
|
+
if #entries == 0 then
|
|
476
|
+
self.filter_bindings[node] = nil
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
storage.filter_node = nil
|
|
480
|
+
storage.binding_index = nil
|
|
460
481
|
end
|
|
461
482
|
|
|
462
483
|
-- a nil-filter storage inherits entity visibility, so its replicated set is
|
|
@@ -528,29 +549,62 @@ local function edit_tracking_filter(
|
|
|
528
549
|
mark_tracking_storage_dirty(self, entity, component, networked, storage, DIRTY_FILTER_FLAG)
|
|
529
550
|
end
|
|
530
551
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
552
|
+
-- attaches a node to a storage and returns the node's current mask as the
|
|
553
|
+
-- storage's own copy: storage filters get holes punched at remove_player and
|
|
554
|
+
-- are snapshotted in place, so they never alias the node buffer
|
|
555
|
+
local function bind_filter(
|
|
556
|
+
self: Server,
|
|
557
|
+
entity: Entity,
|
|
558
|
+
component: Component?,
|
|
559
|
+
networked: NetworkedStorage,
|
|
560
|
+
storage: StorageBase,
|
|
561
|
+
node: FilterNode
|
|
562
|
+
): Bitmask
|
|
563
|
+
filter_groups.bind(node, self.registry)
|
|
564
|
+
local value = filter_groups.resolve(node)
|
|
565
|
+
|
|
566
|
+
local bindings = self.filter_bindings[node]
|
|
567
|
+
if not bindings then
|
|
568
|
+
bindings = { entries = {}, applied_version = node.version }
|
|
569
|
+
self.filter_bindings[node] = bindings
|
|
570
|
+
end
|
|
571
|
+
local entries = bindings.entries
|
|
572
|
+
table.insert(entries, {
|
|
573
|
+
entity = entity,
|
|
574
|
+
networked = networked,
|
|
575
|
+
storage = storage,
|
|
576
|
+
tracking = component,
|
|
577
|
+
tracking_storage = if component then storage :: TrackingStorageBase else nil,
|
|
578
|
+
applied_version = node.version,
|
|
579
|
+
})
|
|
580
|
+
storage.filter_node = node
|
|
581
|
+
storage.binding_index = #entries
|
|
582
|
+
|
|
583
|
+
return bitmasks.clone(value)
|
|
538
584
|
end
|
|
539
585
|
|
|
540
|
-
|
|
586
|
+
-- resolves a filter input into a bitmask and, for nodes and predicates,
|
|
587
|
+
-- binds the source so later changes keep driving the storage. nil means
|
|
588
|
+
-- "replicate to all active clients", resolved at collect
|
|
589
|
+
local function resolve_filter_input(
|
|
541
590
|
self: Server,
|
|
542
591
|
entity: Entity,
|
|
543
|
-
component: Component
|
|
592
|
+
component: Component?,
|
|
544
593
|
networked: NetworkedStorage,
|
|
545
|
-
storage:
|
|
546
|
-
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
|
|
594
|
+
storage: StorageBase,
|
|
595
|
+
filter: FilterInput?
|
|
596
|
+
): Bitmask?
|
|
597
|
+
if filter == nil then
|
|
598
|
+
return nil
|
|
599
|
+
end
|
|
600
|
+
-- a node is a table, so this check goes before the set one
|
|
601
|
+
if filter_groups.is_filter_node(filter) then
|
|
602
|
+
return bind_filter(self, entity, component, networked, storage, filter :: FilterNode)
|
|
603
|
+
elseif typeof(filter) == "function" then
|
|
604
|
+
local node = filter_groups.create_predicate(self.registry, filter :: FilterPredicator)
|
|
605
|
+
return bind_filter(self, entity, component, networked, storage, node)
|
|
550
606
|
end
|
|
551
|
-
|
|
552
|
-
storage.filter_listener = listener
|
|
553
|
-
group:connect(listener)
|
|
607
|
+
return resolve_filter_bitmask(self, filter :: FilterAny)
|
|
554
608
|
end
|
|
555
609
|
|
|
556
610
|
function ensure_hooked_component(self: Server, component: Component): HookedInfo
|
|
@@ -796,9 +850,6 @@ local function clear_networked_references(self: Server, entity: Entity, networke
|
|
|
796
850
|
end
|
|
797
851
|
end
|
|
798
852
|
|
|
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
853
|
local function apply_tracking_filter(
|
|
803
854
|
self: Server,
|
|
804
855
|
entity: Entity,
|
|
@@ -807,31 +858,7 @@ local function apply_tracking_filter(
|
|
|
807
858
|
storage: TrackingStorageBase,
|
|
808
859
|
filter: FilterInput?
|
|
809
860
|
): Bitmask?
|
|
810
|
-
|
|
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
|
|
861
|
+
return resolve_filter_input(self, entity, component, networked, storage, filter)
|
|
835
862
|
end
|
|
836
863
|
|
|
837
864
|
local function change_tracking(
|
|
@@ -1097,28 +1124,8 @@ function start_networked(self: Server, entity: Entity, filter: FilterInput?)
|
|
|
1097
1124
|
-- get a removal delta instead of the pending deletion
|
|
1098
1125
|
change_networked(self, entity, networked, filter)
|
|
1099
1126
|
else
|
|
1100
|
-
|
|
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
|
|
1127
|
+
-- nil bitmask: replicate to all active clients, resolved at collect
|
|
1128
|
+
local bitmask = resolve_filter_input(self, entity, nil, networked, networked, filter)
|
|
1122
1129
|
|
|
1123
1130
|
networked.filter = bitmask
|
|
1124
1131
|
mark_networked_dirty(self, entity, networked, DIRTY_FILTER_FLAG)
|
|
@@ -1164,24 +1171,7 @@ function change_networked(self: Server, entity: Entity, networked: NetworkedStor
|
|
|
1164
1171
|
-- old filter source stops driving this storage before the new one takes over
|
|
1165
1172
|
clear_filter_source(self, networked)
|
|
1166
1173
|
|
|
1167
|
-
local 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
|
|
1174
|
+
local bitmask = resolve_filter_input(self, entity, nil, networked, networked, filter)
|
|
1185
1175
|
|
|
1186
1176
|
edit_networked_filter(self, entity, networked, bitmask)
|
|
1187
1177
|
end
|
|
@@ -1605,8 +1595,11 @@ function server.remove_serdes(self: Server, component: Component)
|
|
|
1605
1595
|
self.is_shared_dirty = true
|
|
1606
1596
|
end
|
|
1607
1597
|
|
|
1598
|
+
-- serdes and preprocessors take part in the handshake and mark shared dirty,
|
|
1599
|
+
-- delta serializers are local to each side and never do
|
|
1608
1600
|
function server.set_preprocessor(self: Server, component: Component, preprocessor: types.Preprocessor)
|
|
1609
1601
|
ensure_component_serialization(self, component).preprocessor = preprocessor
|
|
1602
|
+
self.is_shared_dirty = true
|
|
1610
1603
|
end
|
|
1611
1604
|
|
|
1612
1605
|
function server.remove_preprocessor(self: Server, component: Component)
|
|
@@ -1616,6 +1609,7 @@ function server.remove_preprocessor(self: Server, component: Component)
|
|
|
1616
1609
|
end
|
|
1617
1610
|
options.preprocessor = nil
|
|
1618
1611
|
prune_component_serialization(self, component, options)
|
|
1612
|
+
self.is_shared_dirty = true
|
|
1619
1613
|
end
|
|
1620
1614
|
|
|
1621
1615
|
function server.set_delta_serializer(self: Server, component: Component, delta_serializer: types.DeltaSerializer)
|
|
@@ -1882,7 +1876,11 @@ function push_relation_updates(
|
|
|
1882
1876
|
local is_component = storage.is_component
|
|
1883
1877
|
|
|
1884
1878
|
for target in storage.dirty_targets do
|
|
1885
|
-
|
|
1879
|
+
-- the pair id only keeps the target's index: a target deleted and
|
|
1880
|
+
-- recycled in the same frame aliases the live pair, so the pair must
|
|
1881
|
+
-- resolve back to this exact generation to count as present
|
|
1882
|
+
local id = PAIR(rel, target)
|
|
1883
|
+
if WORLD_HAS(world, entity, id) and PAIR_SECOND(world, id) == target then
|
|
1886
1884
|
if is_component then
|
|
1887
1885
|
local changed = bucket.changed[rel]
|
|
1888
1886
|
if not changed then
|
|
@@ -1890,7 +1888,7 @@ function push_relation_updates(
|
|
|
1890
1888
|
bucket.changed[rel] = changed
|
|
1891
1889
|
end
|
|
1892
1890
|
|
|
1893
|
-
local value = WORLD_GET(world, entity,
|
|
1891
|
+
local value = WORLD_GET(world, entity, id)
|
|
1894
1892
|
table.insert(changed.targets, target)
|
|
1895
1893
|
table.insert(changed.values, SET_VALUE(value))
|
|
1896
1894
|
else
|
|
@@ -2398,31 +2396,47 @@ local function serialize_unreliable_group(
|
|
|
2398
2396
|
commit()
|
|
2399
2397
|
end
|
|
2400
2398
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2399
|
+
-- resolves every bound filter node once and re-applies the ones that moved.
|
|
2400
|
+
-- steady state is one integer compare per distinct node. must run before
|
|
2401
|
+
-- dirty_networked is walked and before the active epoch rolls, since
|
|
2402
|
+
-- edit_*_filter snapshots against last_active_bitmask
|
|
2403
|
+
local function resolve_filters(self: Server)
|
|
2404
|
+
self.registry.cycle += 1
|
|
2405
|
+
|
|
2406
|
+
for node, bindings in self.filter_bindings do
|
|
2407
|
+
local value = filter_groups.resolve(node)
|
|
2408
|
+
local version = node.version
|
|
2409
|
+
if bindings.applied_version == version then
|
|
2407
2410
|
continue
|
|
2408
2411
|
end
|
|
2409
2412
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2413
|
+
for _, entry in bindings.entries do
|
|
2414
|
+
if entry.applied_version == version then
|
|
2415
|
+
continue
|
|
2416
|
+
end
|
|
2417
|
+
entry.applied_version = version
|
|
2418
|
+
|
|
2419
|
+
local tracking_component = entry.tracking
|
|
2420
|
+
if tracking_component then
|
|
2421
|
+
-- routes to the right dirty list via the storage's track_type
|
|
2422
|
+
edit_tracking_filter(
|
|
2423
|
+
self,
|
|
2424
|
+
entry.entity,
|
|
2425
|
+
tracking_component,
|
|
2426
|
+
entry.networked,
|
|
2427
|
+
entry.tracking_storage :: TrackingStorageBase,
|
|
2428
|
+
bitmasks.clone(value)
|
|
2429
|
+
)
|
|
2430
|
+
else
|
|
2431
|
+
edit_networked_filter(self, entry.entity, entry.networked, bitmasks.clone(value))
|
|
2432
|
+
end
|
|
2424
2433
|
end
|
|
2434
|
+
bindings.applied_version = version
|
|
2425
2435
|
end
|
|
2436
|
+
end
|
|
2437
|
+
|
|
2438
|
+
local function collect_updates_groups(self: Server): Map<MaskHash, SerializeGroup>
|
|
2439
|
+
resolve_filters(self)
|
|
2426
2440
|
|
|
2427
2441
|
local serialize_groups: Map<MaskHash, SerializeGroup> = {}
|
|
2428
2442
|
local networked_entities = self.networked
|
|
@@ -2931,21 +2945,25 @@ local function serialize_unreliable_groups(self: Server, groups: Map<MaskHash, U
|
|
|
2931
2945
|
end
|
|
2932
2946
|
end
|
|
2933
2947
|
|
|
2934
|
-
-- rebuilds the shared lookups (component ids, custom ids, serdes)
|
|
2935
|
-
-- flagged; no-op otherwise.
|
|
2948
|
+
-- rebuilds the shared lookups (component ids, custom ids, serdes, preprocessors)
|
|
2949
|
+
-- when flagged; no-op otherwise.
|
|
2936
2950
|
function server.resolve_dirty(self: Server)
|
|
2937
2951
|
if not self.is_shared_dirty then
|
|
2938
2952
|
return
|
|
2939
2953
|
end
|
|
2940
2954
|
|
|
2941
2955
|
local serdes: { [Component]: types.Serdes } = {}
|
|
2956
|
+
local preprocessors: { [Component]: true } = {}
|
|
2942
2957
|
for component, options in self.component_serialization do
|
|
2943
2958
|
if options.serdes then
|
|
2944
2959
|
serdes[component] = options.serdes
|
|
2945
2960
|
end
|
|
2961
|
+
if options.preprocessor then
|
|
2962
|
+
preprocessors[component] = true
|
|
2963
|
+
end
|
|
2946
2964
|
end
|
|
2947
2965
|
|
|
2948
|
-
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
|
|
2966
|
+
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes, preprocessors)
|
|
2949
2967
|
self.is_shared_dirty = false
|
|
2950
2968
|
end
|
|
2951
2969
|
|
|
@@ -3137,6 +3155,7 @@ end
|
|
|
3137
3155
|
|
|
3138
3156
|
function server.get_full(self: Server, player: Player): (buffer, Array<any>)
|
|
3139
3157
|
self:resolve_dirty()
|
|
3158
|
+
resolve_filters(self)
|
|
3140
3159
|
|
|
3141
3160
|
local index = self.registry.client_indexes[player]
|
|
3142
3161
|
if index == nil then
|
|
@@ -3473,6 +3492,7 @@ end
|
|
|
3473
3492
|
|
|
3474
3493
|
function server.collect_entity(self: Server, entity: Entity): PacketIterator
|
|
3475
3494
|
self:resolve_dirty()
|
|
3495
|
+
resolve_filters(self)
|
|
3476
3496
|
local groups = collect_entity_groups(self, entity)
|
|
3477
3497
|
|
|
3478
3498
|
local member_updates: Map<number, MemberUpdatePackets> = {}
|
|
@@ -3564,11 +3584,24 @@ end
|
|
|
3564
3584
|
|
|
3565
3585
|
function server.collect_unreliable(self: Server): PacketIterator
|
|
3566
3586
|
self:resolve_dirty()
|
|
3587
|
+
resolve_filters(self)
|
|
3567
3588
|
local groups = collect_unreliable_groups(self)
|
|
3568
3589
|
serialize_unreliable_groups(self, groups)
|
|
3569
3590
|
return create_unreliable_packet_iterator(self, distribute_unreliable_groups(groups))
|
|
3570
3591
|
end
|
|
3571
3592
|
|
|
3593
|
+
function server.filter(self: Server, initial: FilterSimple?): FilterNode
|
|
3594
|
+
return filter_groups.create_source(self.registry, initial)
|
|
3595
|
+
end
|
|
3596
|
+
|
|
3597
|
+
function server.filter_predicator(self: Server, predicator: FilterPredicator): FilterNode
|
|
3598
|
+
return filter_groups.create_predicate(self.registry, predicator)
|
|
3599
|
+
end
|
|
3600
|
+
|
|
3601
|
+
function server.everyone(self: Server): FilterNode
|
|
3602
|
+
return filter_groups.create_everyone(self.registry)
|
|
3603
|
+
end
|
|
3604
|
+
|
|
3572
3605
|
function server.register_player(self: Server, player: Player)
|
|
3573
3606
|
self.registry:register_client(player)
|
|
3574
3607
|
end
|
|
@@ -3891,7 +3924,7 @@ function server.create(world: World?, components: types.Components?, max_clients
|
|
|
3891
3924
|
self.hooked_unreliable_pairs = {}
|
|
3892
3925
|
|
|
3893
3926
|
self.dirty_networked = {}
|
|
3894
|
-
self.
|
|
3927
|
+
self.filter_bindings = {}
|
|
3895
3928
|
|
|
3896
3929
|
self.empty_bitmask = buffer.create(self.bitmask_size)
|
|
3897
3930
|
self.draft_bitmask = buffer.create(self.bitmask_size)
|
|
@@ -3926,12 +3959,19 @@ return {
|
|
|
3926
3959
|
end,
|
|
3927
3960
|
collect_unreliable_groups = function(self: Server): Map<MaskHash, UnreliableSerializeGroup>
|
|
3928
3961
|
self:resolve_dirty()
|
|
3962
|
+
resolve_filters(self)
|
|
3929
3963
|
return collect_unreliable_groups(self)
|
|
3930
3964
|
end,
|
|
3931
3965
|
serialize_update_groups = serialize_update_groups,
|
|
3932
3966
|
serialize_unreliable_groups = serialize_unreliable_groups,
|
|
3933
3967
|
collect_entity_groups = function(self: Server, entity: Entity): Map<MaskHash, EntitySerializeGroup>
|
|
3934
3968
|
self:resolve_dirty()
|
|
3969
|
+
resolve_filters(self)
|
|
3935
3970
|
return collect_entity_groups(self, entity)
|
|
3936
3971
|
end,
|
|
3972
|
+
|
|
3973
|
+
-- unbound source for module level construction, binds on first set_*
|
|
3974
|
+
create_filter = function(initial: FilterSimple?): FilterNode
|
|
3975
|
+
return filter_groups.create_source(nil, initial)
|
|
3976
|
+
end,
|
|
3937
3977
|
}
|
|
@@ -29,6 +29,18 @@ type RegistryClass = {
|
|
|
29
29
|
-- exactly this many bits, so an index past it would fall off the buffer
|
|
30
30
|
max_clients: number,
|
|
31
31
|
bitmask_size: number,
|
|
32
|
+
|
|
33
|
+
-- bumped when a client registers, leaves or an alias changes: filter
|
|
34
|
+
-- nodes re-resolve their raw members against it
|
|
35
|
+
epoch: number,
|
|
36
|
+
-- bumped once per server resolve pass, predicate nodes recompute per cycle
|
|
37
|
+
cycle: number,
|
|
38
|
+
-- bit per registered client, the base a blacklist filter subtracts from
|
|
39
|
+
registered_bitmask: Bitmask,
|
|
40
|
+
-- clients dropped by unregister_client. a filter may still name them and
|
|
41
|
+
-- is_client_valid holds inside PlayerRemoving, so resolve_member must not
|
|
42
|
+
-- register them again (possibly onto their freed index)
|
|
43
|
+
departed: Set<Client>,
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
local registry = {}
|
|
@@ -56,20 +68,23 @@ local function resolve_member(self: Registry, member: Client): number?
|
|
|
56
68
|
end
|
|
57
69
|
end
|
|
58
70
|
|
|
59
|
-
if not utils.is_client_valid(member) then
|
|
71
|
+
if self.departed[member] or not utils.is_client_valid(member) then
|
|
60
72
|
return nil
|
|
61
73
|
end
|
|
62
74
|
|
|
63
75
|
registry.register_client(self, member)
|
|
64
76
|
return self.client_indexes[member]
|
|
65
77
|
end
|
|
78
|
+
registry.resolve_member = resolve_member
|
|
66
79
|
|
|
67
80
|
function registry.add_alias(self: Registry, client: Client, alias: any)
|
|
68
81
|
self.client_aliases[alias] = client
|
|
82
|
+
self.epoch += 1
|
|
69
83
|
end
|
|
70
84
|
|
|
71
85
|
function registry.remove_alias(self: Registry, alias: any)
|
|
72
86
|
self.client_aliases[alias] = nil
|
|
87
|
+
self.epoch += 1
|
|
73
88
|
end
|
|
74
89
|
|
|
75
90
|
-- drops every alias pointing at the client
|
|
@@ -80,6 +95,7 @@ function registry.remove_client_aliases(self: Registry, client: Client)
|
|
|
80
95
|
aliases[alias] = nil
|
|
81
96
|
end
|
|
82
97
|
end
|
|
98
|
+
self.epoch += 1
|
|
83
99
|
end
|
|
84
100
|
|
|
85
101
|
|
|
@@ -143,6 +159,10 @@ function registry.register_client(self: Registry, member: Client)
|
|
|
143
159
|
self.client_indexes[member] = index
|
|
144
160
|
self.index_clients[index] = member
|
|
145
161
|
self.client_count += 1
|
|
162
|
+
|
|
163
|
+
self.departed[member] = nil
|
|
164
|
+
bitmasks.set(self.registered_bitmask, index)
|
|
165
|
+
self.epoch += 1
|
|
146
166
|
end
|
|
147
167
|
|
|
148
168
|
function registry.unregister_client(self: Registry, member: Client)
|
|
@@ -155,6 +175,10 @@ function registry.unregister_client(self: Registry, member: Client)
|
|
|
155
175
|
self.client_indexes[member] = nil
|
|
156
176
|
self.index_clients[index] = nil
|
|
157
177
|
self.client_count -= 1
|
|
178
|
+
|
|
179
|
+
self.departed[member] = true
|
|
180
|
+
bitmasks.reset(self.registered_bitmask, index)
|
|
181
|
+
self.epoch += 1
|
|
158
182
|
end
|
|
159
183
|
|
|
160
184
|
local DEFAULT_MAX_CLIENTS = 128
|
|
@@ -172,6 +196,11 @@ function registry.create(max_clients: number?): Registry
|
|
|
172
196
|
self.free_indexes = {}
|
|
173
197
|
self.client_aliases = {}
|
|
174
198
|
|
|
199
|
+
self.epoch = 0
|
|
200
|
+
self.cycle = 0
|
|
201
|
+
self.registered_bitmask = buffer.create(self.bitmask_size)
|
|
202
|
+
self.departed = setmetatable({}, { __mode = "k" }) :: any
|
|
203
|
+
|
|
175
204
|
return setmetatable(self, registry) :: any
|
|
176
205
|
end
|
|
177
206
|
|
package/out/replecs/shared.luau
CHANGED
|
@@ -38,7 +38,8 @@ function shared.resolve_shared(
|
|
|
38
38
|
world: World,
|
|
39
39
|
components: types.Components,
|
|
40
40
|
registered_custom_ids: { [customid.CustomId]: boolean },
|
|
41
|
-
serdes: { [Component]: types.Serdes }
|
|
41
|
+
serdes: { [Component]: types.Serdes },
|
|
42
|
+
preprocessors: { [Component]: true }
|
|
42
43
|
): types.Shared
|
|
43
44
|
local shared = {} :: types.Shared
|
|
44
45
|
|
|
@@ -61,6 +62,7 @@ function shared.resolve_shared(
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
shared.serdes = serdes
|
|
65
|
+
shared.preprocessors = preprocessors
|
|
64
66
|
shared.components = create_shared_lookup(components_lookup)
|
|
65
67
|
shared.custom_ids = create_shared_lookup(custom_ids_lookup) :: types.SharedInfo<customid.CustomId>
|
|
66
68
|
|
package/out/replecs/types.luau
CHANGED
|
@@ -29,6 +29,8 @@ export type HandshakeInfo = {
|
|
|
29
29
|
includes_variants: boolean?,
|
|
30
30
|
bytespan: number?,
|
|
31
31
|
} },
|
|
32
|
+
-- preprocessors are functions, so only their presence is compared
|
|
33
|
+
preprocessors: { [string]: boolean },
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export type Serdes<T = any> = {
|
|
@@ -77,6 +79,7 @@ export type Shared = {
|
|
|
77
79
|
custom_ids: SharedInfo<customid.CustomId>,
|
|
78
80
|
|
|
79
81
|
serdes: { [Component]: Serdes },
|
|
82
|
+
preprocessors: { [Component]: true },
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
return nil
|
package/out/replecs/ver.luau
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
return "v0.4.0-rc.
|
|
1
|
+
return "v0.4.0-rc.5"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/replecs",
|
|
3
|
-
"version": "0.4.0-rc.
|
|
3
|
+
"version": "0.4.0-rc.5",
|
|
4
4
|
"description": "Replication library for Jecs",
|
|
5
5
|
"main": "out/init.luau",
|
|
6
6
|
"types": "out/index.d.ts",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"!**/*.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"serve": "rojo sourcemap serve.project.json -o sourcemap.json --watch && rojo serve serve.project.json"
|
|
27
|
+
"serve": "rojo sourcemap serve.project.json -o sourcemap.json --watch && rojo serve serve.project.json",
|
|
28
|
+
"docs:dev": "npm run dev --prefix docs"
|
|
28
29
|
},
|
|
29
30
|
"repository": {
|
|
30
31
|
"type": "git",
|