@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
|
@@ -777,13 +777,18 @@ local function process_full_entity(
|
|
|
777
777
|
end
|
|
778
778
|
end
|
|
779
779
|
|
|
780
|
+
-- removals resolve their target with find_entity: a target this client never
|
|
781
|
+
-- saw (filtered out, or deleted earlier in the same packet) has nothing to
|
|
782
|
+
-- remove, and creating a placeholder for it would leak a ghost entity
|
|
783
|
+
|
|
780
784
|
-- pair tag removal: [target id, relation]
|
|
781
785
|
local function process_entity_pair_removal(self: Client, entity: Entity, c: Cursor, custom_ids: CustomIdMap)
|
|
782
786
|
local relation = read_component_id(self, c)
|
|
783
787
|
local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
|
|
784
|
-
|
|
788
|
+
local target = find_entity(self, c)
|
|
789
|
+
if target then
|
|
785
790
|
entity_remove_pair(self, entity, relation, target, relation_hooks)
|
|
786
|
-
end
|
|
791
|
+
end
|
|
787
792
|
end
|
|
788
793
|
|
|
789
794
|
-- relation target removals: [target ids..., vlq count, relation]
|
|
@@ -793,9 +798,10 @@ local function process_entity_relation_removals(self: Client, entity: Entity, c:
|
|
|
793
798
|
local relation_hooks = self.network_hooks.removed[PAIR(self.components.relation, relation)]
|
|
794
799
|
|
|
795
800
|
for _ = 1, total_targets do
|
|
796
|
-
|
|
801
|
+
local target = find_entity(self, c)
|
|
802
|
+
if target then
|
|
797
803
|
entity_remove_pair(self, entity, relation, target, relation_hooks)
|
|
798
|
-
end
|
|
804
|
+
end
|
|
799
805
|
end
|
|
800
806
|
end
|
|
801
807
|
|
|
@@ -1532,8 +1538,8 @@ local function prune_component_serialization(self: Client, component: Component,
|
|
|
1532
1538
|
end
|
|
1533
1539
|
end
|
|
1534
1540
|
|
|
1535
|
-
-- serdes take part in the handshake
|
|
1536
|
-
-- local to each side and never
|
|
1541
|
+
-- serdes and preprocessors take part in the handshake and mark shared dirty,
|
|
1542
|
+
-- delta serializers are local to each side and never do
|
|
1537
1543
|
function client.set_serdes(self: Client, component: Component, serdes: types.Serdes)
|
|
1538
1544
|
ensure_component_serialization(self, component).serdes = serdes
|
|
1539
1545
|
self.is_shared_dirty = true
|
|
@@ -1551,6 +1557,7 @@ end
|
|
|
1551
1557
|
|
|
1552
1558
|
function client.set_preprocessor(self: Client, component: Component, preprocessor: types.Preprocessor)
|
|
1553
1559
|
ensure_component_serialization(self, component).preprocessor = preprocessor
|
|
1560
|
+
self.is_shared_dirty = true
|
|
1554
1561
|
end
|
|
1555
1562
|
|
|
1556
1563
|
function client.remove_preprocessor(self: Client, component: Component)
|
|
@@ -1560,6 +1567,7 @@ function client.remove_preprocessor(self: Client, component: Component)
|
|
|
1560
1567
|
end
|
|
1561
1568
|
options.preprocessor = nil
|
|
1562
1569
|
prune_component_serialization(self, component, options)
|
|
1570
|
+
self.is_shared_dirty = true
|
|
1563
1571
|
end
|
|
1564
1572
|
|
|
1565
1573
|
function client.set_delta_serializer(self: Client, component: Component, delta_serializer: types.DeltaSerializer)
|
|
@@ -1575,21 +1583,25 @@ function client.remove_delta_serializer(self: Client, component: Component)
|
|
|
1575
1583
|
prune_component_serialization(self, component, options)
|
|
1576
1584
|
end
|
|
1577
1585
|
|
|
1578
|
-
-- rebuilds the shared lookups (component ids, custom ids, serdes)
|
|
1579
|
-
-- flagged; cheap no-op otherwise, so every deserialize entry point calls it
|
|
1586
|
+
-- rebuilds the shared lookups (component ids, custom ids, serdes, preprocessors)
|
|
1587
|
+
-- when flagged; cheap no-op otherwise, so every deserialize entry point calls it
|
|
1580
1588
|
function client.resolve_dirty(self: Client)
|
|
1581
1589
|
if not self.is_shared_dirty then
|
|
1582
1590
|
return
|
|
1583
1591
|
end
|
|
1584
1592
|
|
|
1585
1593
|
local serdes: { [Component]: types.Serdes } = {}
|
|
1594
|
+
local preprocessors: { [Component]: true } = {}
|
|
1586
1595
|
for component, options in self.component_serialization do
|
|
1587
1596
|
if options.serdes then
|
|
1588
1597
|
serdes[component] = options.serdes
|
|
1589
1598
|
end
|
|
1599
|
+
if options.preprocessor then
|
|
1600
|
+
preprocessors[component] = true
|
|
1601
|
+
end
|
|
1590
1602
|
end
|
|
1591
1603
|
|
|
1592
|
-
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes)
|
|
1604
|
+
self.shared = shared.resolve_shared(self.world, self.components, self.registered_custom_ids, serdes, preprocessors)
|
|
1593
1605
|
self.is_shared_dirty = false
|
|
1594
1606
|
end
|
|
1595
1607
|
|
|
@@ -1752,6 +1764,8 @@ return {
|
|
|
1752
1764
|
|
|
1753
1765
|
process_entity_pair = process_entity_pair,
|
|
1754
1766
|
process_entity_pair_value = process_entity_pair_value,
|
|
1767
|
+
process_entity_pair_removal = process_entity_pair_removal,
|
|
1768
|
+
process_entity_relation_removals = process_entity_relation_removals,
|
|
1755
1769
|
process_entity_relations = process_entity_relations,
|
|
1756
1770
|
process_entity_relation_values = process_entity_relation_values,
|
|
1757
1771
|
process_full_entity = process_full_entity,
|
|
@@ -13,6 +13,7 @@ function handshake.generate_handshake(shared: types.Shared): types.HandshakeInfo
|
|
|
13
13
|
components = {},
|
|
14
14
|
custom_ids = {},
|
|
15
15
|
serdes = {},
|
|
16
|
+
preprocessors = {},
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
for component_name, component in shared.components.lookup do
|
|
@@ -36,6 +37,16 @@ function handshake.generate_handshake(shared: types.Shared): types.HandshakeInfo
|
|
|
36
37
|
}
|
|
37
38
|
end
|
|
38
39
|
|
|
40
|
+
-- preprocessors are functions, so only their presence is shared
|
|
41
|
+
for component in shared.preprocessors do
|
|
42
|
+
local name = component_to_name[component]
|
|
43
|
+
if not name then
|
|
44
|
+
continue
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
handshake.preprocessors[name] = true
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
return handshake
|
|
40
51
|
end
|
|
41
52
|
|
|
@@ -102,6 +113,30 @@ function handshake.verify_handshake(
|
|
|
102
113
|
end
|
|
103
114
|
end
|
|
104
115
|
|
|
116
|
+
for component_name in handshake.preprocessors do
|
|
117
|
+
local component = shared.components.lookup[component_name]
|
|
118
|
+
if not component then
|
|
119
|
+
common.log_warn(
|
|
120
|
+
`{handshake_side} registered a preprocessor for "{component_name}" that {verify_side} doesn't have`
|
|
121
|
+
)
|
|
122
|
+
continue
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if not shared.preprocessors[component] then
|
|
126
|
+
return false, `missing preprocessor for component "{component_name}" in {verify_side}`
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
for component in shared.preprocessors do
|
|
130
|
+
local component_name = component_to_name[component]
|
|
131
|
+
if not component_name then
|
|
132
|
+
continue
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if not handshake.preprocessors[component_name] then
|
|
136
|
+
return false, `missing preprocessor for component "{component_name}" in {handshake_side}`
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
105
140
|
return true, nil
|
|
106
141
|
end
|
|
107
142
|
|
package/out/replecs/index.d.ts
CHANGED
|
@@ -34,10 +34,42 @@ declare namespace Replecs {
|
|
|
34
34
|
| Player
|
|
35
35
|
| MemberFilterMap
|
|
36
36
|
| MemberFilterPredicate
|
|
37
|
+
| FilterNode
|
|
37
38
|
| Record<string, never>
|
|
38
39
|
| undefined;
|
|
39
40
|
type Member = unknown;
|
|
40
41
|
|
|
42
|
+
/** What a filter combinator accepts: another node or a raw player / set. */
|
|
43
|
+
export type FilterOperand = FilterNode | Player | MemberFilterMap;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One node of the reactive filter graph. Sources own a player set; derived
|
|
47
|
+
* nodes combine their inputs and re-evaluate lazily on the next collect.
|
|
48
|
+
* Structure is fixed at construction, only the value is reactive.
|
|
49
|
+
*/
|
|
50
|
+
export interface FilterNode {
|
|
51
|
+
/** Replaces the member set. A map of `false` values makes a blacklist. Sources only. */
|
|
52
|
+
set(filter: Player | MemberFilterMap): void;
|
|
53
|
+
/** Adds to the member set (on a blacklist source this excludes the player). Sources only. */
|
|
54
|
+
add(player: Player): void;
|
|
55
|
+
/** Sources only. */
|
|
56
|
+
remove(player: Player): void;
|
|
57
|
+
/** Sources only. */
|
|
58
|
+
clear(): void;
|
|
59
|
+
|
|
60
|
+
/** Players in this node and in every operand. */
|
|
61
|
+
filter(...operands: FilterOperand[]): FilterNode;
|
|
62
|
+
/** Players in this node or in any operand. */
|
|
63
|
+
extend(...operands: FilterOperand[]): FilterNode;
|
|
64
|
+
/** Players in this node and in no operand. */
|
|
65
|
+
exclude(...operands: FilterOperand[]): FilterNode;
|
|
66
|
+
|
|
67
|
+
/** Resolves the node and returns its current players. Errors if the node is not bound to a server yet. */
|
|
68
|
+
members(): Map<Player, true>;
|
|
69
|
+
/** Resolves the node and tests one player (aliases accepted). */
|
|
70
|
+
contains(player: Player): boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
export interface SharedInfo<T> {
|
|
42
74
|
lookup: Record<string, T>;
|
|
43
75
|
keys: string[];
|
|
@@ -64,6 +96,7 @@ declare namespace Replecs {
|
|
|
64
96
|
components: SharedInfo<Entity>;
|
|
65
97
|
custom_ids: SharedInfo<CustomId>;
|
|
66
98
|
serdes: Map<Id, SerdesTable>;
|
|
99
|
+
preprocessors: Map<Id, true>;
|
|
67
100
|
}
|
|
68
101
|
interface HandshakeSerdesInfo {
|
|
69
102
|
includes_variants?: boolean;
|
|
@@ -74,6 +107,7 @@ declare namespace Replecs {
|
|
|
74
107
|
components: Record<string, boolean>;
|
|
75
108
|
custom_ids: Record<string, boolean>;
|
|
76
109
|
serdes: Record<string, HandshakeSerdesInfo>;
|
|
110
|
+
preprocessors: Record<string, boolean>;
|
|
77
111
|
}
|
|
78
112
|
|
|
79
113
|
export interface Components {
|
|
@@ -194,6 +228,13 @@ declare namespace Replecs {
|
|
|
194
228
|
}
|
|
195
229
|
|
|
196
230
|
export interface ServerImp extends SerializationOptions {
|
|
231
|
+
/** A source filter bound to this server. */
|
|
232
|
+
filter(initial?: Player | MemberFilterMap): FilterNode;
|
|
233
|
+
/** A filter driven by a predicate over registered players, re-evaluated once per collect. */
|
|
234
|
+
filter_predicator(predicate: MemberFilterPredicate): FilterNode;
|
|
235
|
+
/** Every registered player; the base for blacklists. */
|
|
236
|
+
everyone(): FilterNode;
|
|
237
|
+
|
|
197
238
|
set_networked(entity: Entity, filter?: MemberFilter): void;
|
|
198
239
|
set_reliable(
|
|
199
240
|
entity: Entity,
|
|
@@ -289,6 +330,8 @@ declare namespace Replecs {
|
|
|
289
330
|
identifier: string,
|
|
290
331
|
handler?: (ctx: HandleContext) => Entity,
|
|
291
332
|
) => CustomId;
|
|
333
|
+
/** An unbound source filter; it attaches to a server the first time a `set_*` call receives it. */
|
|
334
|
+
filter: (initial?: Player | MemberFilterMap) => FilterNode;
|
|
292
335
|
}
|
|
293
336
|
}
|
|
294
337
|
|
package/out/replecs/init.luau
CHANGED
|
@@ -10,6 +10,7 @@ local VERSION = require (script:WaitForChild('ver'))
|
|
|
10
10
|
|
|
11
11
|
export type Server = server.Server
|
|
12
12
|
export type Client = client.Client
|
|
13
|
+
export type FilterNode = server.FilterNode
|
|
13
14
|
|
|
14
15
|
type Entity = common.Entity
|
|
15
16
|
type World = common.World
|
|
@@ -46,6 +47,7 @@ type Replecs = types.Components & {
|
|
|
46
47
|
create_server: (world: World?) -> Server,
|
|
47
48
|
create_client: (world: World?) -> Client,
|
|
48
49
|
create_custom_id: (identifier: string, handler: (ctx: HandleContext) -> Entity?) -> CustomId,
|
|
50
|
+
filter: (initial: types.FilterSimple?) -> FilterNode,
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
local replecs = {}
|
|
@@ -103,6 +105,12 @@ function replecs.create_custom_id(identifier: string, handler: (ctx: HandleConte
|
|
|
103
105
|
return customid.create(identifier, handler)
|
|
104
106
|
end
|
|
105
107
|
|
|
108
|
+
-- an unbound source filter: usable from any module, it attaches to the server
|
|
109
|
+
-- the first time a set_* call receives it (directly or through a derived node)
|
|
110
|
+
function replecs.filter(initial: types.FilterSimple?): FilterNode
|
|
111
|
+
return server.create_filter(initial)
|
|
112
|
+
end
|
|
113
|
+
|
|
106
114
|
function replecs.after_replication(lib: ReplecsLib, callback: () -> ())
|
|
107
115
|
local lib_client = lib.client
|
|
108
116
|
if lib_client then
|