@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.
@@ -0,0 +1,322 @@
1
+ --!optimize 2
2
+
3
+ type Bitmask = buffer
4
+
5
+ local BITS_PER_ENTRY = 32
6
+ local BYTES_PER_ENTRY = 4
7
+
8
+
9
+ local function to_binary(n: number): string
10
+ local result = ""
11
+ while n > 0 do
12
+ result = (n % 2) .. result
13
+ n = math.floor(n / 2)
14
+ end
15
+ return string.rep("0", 32 - #result) .. result
16
+ end
17
+
18
+
19
+ local function bitmask_read_entry(bitmask: Bitmask, len: number, offset: number)
20
+ if offset >= len then
21
+ return 0
22
+ else
23
+ return buffer.readu32(bitmask, offset)
24
+ end
25
+ end
26
+
27
+
28
+ local function bitmasks_band(a: Bitmask, b: Bitmask, output: Bitmask)
29
+ local len_a = buffer.len(a)
30
+ local len_b = buffer.len(b)
31
+ local len_min = math.min(len_a, len_b)
32
+
33
+ for offset = 0, buffer.len(output) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
34
+ if offset >= len_min then
35
+ buffer.writeu32(output, offset, 0)
36
+ else
37
+ local a_val = buffer.readu32(a, offset)
38
+ local b_val = buffer.readu32(b, offset)
39
+ buffer.writeu32(output, offset, bit32.band(a_val, b_val))
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ local function bitmasks_bor(a: Bitmask, b: Bitmask, output: Bitmask)
46
+ local len_a = buffer.len(a)
47
+ local len_b = buffer.len(b)
48
+
49
+ for offset = 0, buffer.len(output) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
50
+ local a_val = if offset >= len_a then 0 else buffer.readu32(a, offset)
51
+ local b_val = if offset >= len_b then 0 else buffer.readu32(b, offset)
52
+ buffer.writeu32(output, offset, bit32.bor(a_val, b_val))
53
+ end
54
+ end
55
+
56
+
57
+ local function bitmasks_read_entry(bitmask: Bitmask, len: number, offset: number)
58
+ if offset >= len then
59
+ return 0
60
+ else
61
+ return buffer.readu32(bitmask, offset)
62
+ end
63
+ end
64
+
65
+
66
+ local function bitmasks_get_index(index: number): (number, number)
67
+ local entry_index = math.floor(index / BITS_PER_ENTRY)
68
+ local bit_position = index % BITS_PER_ENTRY
69
+ return entry_index, bit_position
70
+ end
71
+
72
+
73
+ local function bitmasks_set(bitmask: Bitmask, index: number)
74
+ local entry, offset = bitmasks_get_index(index)
75
+ local buffer_offset = entry * BYTES_PER_ENTRY
76
+ local len = buffer.len(bitmask)
77
+ if buffer_offset >= len then
78
+ return
79
+ end
80
+
81
+ local current = buffer.readu32(bitmask, buffer_offset)
82
+ local mask = bit32.lshift(1, offset)
83
+ buffer.writeu32(bitmask, buffer_offset, bit32.bor(current, mask))
84
+ end
85
+
86
+
87
+ local function bitmasks_reset(bitmask: Bitmask, index: number)
88
+ local entry, offset = bitmasks_get_index(index)
89
+ local buffer_offset = entry * BYTES_PER_ENTRY
90
+ local len = buffer.len(bitmask)
91
+ if buffer_offset >= len then
92
+ return
93
+ end
94
+
95
+ local current = buffer.readu32(bitmask, buffer_offset)
96
+ local mask = bit32.bnot(bit32.lshift(1, offset))
97
+ buffer.writeu32(bitmask, buffer_offset, bit32.band(current, mask))
98
+ end
99
+
100
+
101
+ local function bitmasks_test(bitmask: Bitmask, index: number): boolean
102
+ local entry, offset = bitmasks_get_index(index)
103
+ local buffer_offset = entry * BYTES_PER_ENTRY
104
+
105
+ local len = buffer.len(bitmask)
106
+ if buffer_offset >= len then
107
+ return false
108
+ end
109
+
110
+ local current = buffer.readu32(bitmask, buffer_offset)
111
+ local value = bit32.band(bit32.rshift(current, offset), 1)
112
+ return value == 1
113
+ end
114
+
115
+
116
+ local function bitmasks_exclude(base: Bitmask, exclude: Bitmask, output: Bitmask)
117
+ local len_base = buffer.len(base)
118
+ local len_exclude = buffer.len(exclude)
119
+
120
+ for offset = 0, buffer.len(output) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
121
+ local base_val = bitmasks_read_entry(base, len_base, offset)
122
+ local exclude_val = bitmasks_read_entry(exclude, len_exclude, offset)
123
+ buffer.writeu32(output, offset, bit32.band(base_val, bit32.bnot(exclude_val)))
124
+ end
125
+ end
126
+
127
+
128
+
129
+ local function bitmasks_load(source: Bitmask, target: Bitmask)
130
+ local source_len = buffer.len(source)
131
+ local target_len = buffer.len(target)
132
+
133
+ buffer.copy(target, 0, source, 0)
134
+ local difference = target_len - source_len
135
+ if difference > 0 then
136
+ buffer.fill(target, source_len, 0, difference)
137
+ end
138
+ end
139
+
140
+
141
+
142
+ local function bitmasks_clear(bitmask: Bitmask)
143
+ buffer.fill(bitmask, 0, 0)
144
+ end
145
+
146
+ local function bitmasks_clone(template: Bitmask, size: number?)
147
+ local buf_len = buffer.len(template)
148
+ local cloned = buffer.create((size and math.max(size, buf_len)) or buf_len)
149
+ buffer.copy(cloned, 0, template, 0)
150
+ return cloned
151
+ end
152
+
153
+
154
+ local function bitmask_hash(bitmask: Bitmask)
155
+ local highest_entry = -1
156
+
157
+ for i = buffer.len(bitmask) - BYTES_PER_ENTRY, 0, -BYTES_PER_ENTRY do
158
+ local value = buffer.readu32(bitmask, i)
159
+ if value ~= 0 then
160
+ highest_entry = i
161
+ break
162
+ end
163
+ end
164
+
165
+ if highest_entry == -1 then
166
+ return ""
167
+ end
168
+ do -- raw bytes as the key; trailing zero entries are trimmed so equal bit
169
+ -- sets hash the same regardless of buffer size
170
+
171
+
172
+ return buffer.readstring(bitmask, 0, highest_entry + BYTES_PER_ENTRY)
173
+ end
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+ end
184
+
185
+
186
+
187
+
188
+ local function bitmask_get_hole_mask(index: number): number
189
+ local _, bit_position = bitmasks_get_index(index)
190
+ return bit32.bnot(bit32.lshift(1, bit_position))
191
+ end
192
+
193
+
194
+
195
+
196
+ local function bitmask_punch_hole_mask(bitmask: Bitmask, mask: number, index: number)
197
+ local entry = math.floor(index / BITS_PER_ENTRY)
198
+ local buffer_offset = entry * BYTES_PER_ENTRY
199
+ if buffer_offset >= buffer.len(bitmask) then
200
+ return
201
+ end
202
+
203
+ local current = buffer.readu32(bitmask, buffer_offset)
204
+ buffer.writeu32(bitmask, buffer_offset, bit32.band(current, mask))
205
+ end
206
+
207
+
208
+
209
+
210
+ local function bitmask_iter_set(bitmask: Bitmask, callback: (index: number) -> ())
211
+ for offset = 0, buffer.len(bitmask) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
212
+ local word = buffer.readu32(bitmask, offset)
213
+ local base = offset * 8 -- byte offset * 8 = bit index of entry start
214
+
215
+ while word ~= 0 do
216
+ callback(base + bit32.countrz(word))
217
+ word = bit32.band(word, word - 1)
218
+ end
219
+ end
220
+ end
221
+
222
+ export type BitmaskDeltas = {
223
+ added: Bitmask,
224
+ removed: Bitmask,
225
+ }
226
+
227
+
228
+ local function load_bitmask_deltas(deltas: BitmaskDeltas)
229
+ buffer.fill(deltas.added, 0, 0)
230
+ buffer.fill(deltas.removed, 0, 0)
231
+ end
232
+
233
+
234
+ local function calculate_bitmask_deltas(last: Bitmask, target: Bitmask, output: BitmaskDeltas)
235
+ local len_last = buffer.len(last)
236
+ local len_target = buffer.len(target)
237
+ local added = output.added
238
+ local removed = output.removed
239
+
240
+ -- we asume the output have been loaded, this means it has been cleared
241
+ local len_added = buffer.len(added)
242
+ local len_removed = buffer.len(removed)
243
+
244
+ for offset = 0, math.max(len_added, len_removed) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
245
+ local last_val = bitmask_read_entry(last, len_last, offset)
246
+ local target_val = bitmask_read_entry(target, len_target, offset)
247
+
248
+ local added_delta = bit32.band(target_val, bit32.bnot(last_val))
249
+ local removed_delta = bit32.band(last_val, bit32.bnot(target_val))
250
+
251
+ buffer.writeu32(added, offset, added_delta)
252
+ buffer.writeu32(removed, offset, removed_delta)
253
+ end
254
+ end
255
+
256
+
257
+ local function bitmask_equals(a: Bitmask, b: Bitmask): boolean
258
+ local len_a = buffer.len(a)
259
+ local len_b = buffer.len(b)
260
+
261
+ for offset = 0, math.max(len_a, len_b) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
262
+ if bitmasks_read_entry(a, len_a, offset) ~= bitmasks_read_entry(b, len_b, offset) then
263
+ return false
264
+ end
265
+ end
266
+ return true
267
+ end
268
+
269
+
270
+ local function bitmask_is_empty(bitmask: Bitmask): boolean
271
+ for offset = 0, buffer.len(bitmask) - BYTES_PER_ENTRY, BYTES_PER_ENTRY do
272
+ if buffer.readu32(bitmask, offset) ~= 0 then
273
+ return false
274
+ end
275
+ end
276
+ return true
277
+ end
278
+
279
+
280
+ local function bitmask_get_buffer_size(bits: number)
281
+ return math.ceil(bits / BITS_PER_ENTRY) * BYTES_PER_ENTRY
282
+ end
283
+
284
+
285
+
286
+
287
+ local function bitmask_create_filled(bits: number): Bitmask
288
+ local size = bitmask_get_buffer_size(bits)
289
+ local bitmask = buffer.create(size)
290
+ buffer.fill(bitmask, 0, 0xFF)
291
+
292
+ local tail = bits % BITS_PER_ENTRY
293
+ if tail ~= 0 then
294
+ buffer.writeu32(bitmask, size - BYTES_PER_ENTRY, bit32.lshift(1, tail) - 1)
295
+ end
296
+ return bitmask
297
+ end
298
+
299
+ return {
300
+ band = bitmasks_band,
301
+ bor = bitmasks_bor,
302
+ read_entry = bitmasks_read_entry,
303
+ get_index = bitmasks_get_index,
304
+ set = bitmasks_set,
305
+ reset = bitmasks_reset,
306
+ test = bitmasks_test,
307
+ exclude = bitmasks_exclude,
308
+ iter_set = bitmask_iter_set,
309
+ load = bitmasks_load,
310
+ clear = bitmasks_clear,
311
+ clone = bitmasks_clone,
312
+ hash = bitmask_hash,
313
+ equals = bitmask_equals,
314
+ is_empty = bitmask_is_empty,
315
+ get_buffer_size = bitmask_get_buffer_size,
316
+ create_filled = bitmask_create_filled,
317
+ get_hole_mask = bitmask_get_hole_mask,
318
+ punch_hole_mask = bitmask_punch_hole_mask,
319
+
320
+ calculate_bitmask_deltas = calculate_bitmask_deltas,
321
+ load_bitmask_deltas = load_bitmask_deltas,
322
+ }
@@ -0,0 +1,196 @@
1
+ local types = require (script.Parent.Parent:WaitForChild('types'))
2
+ local registry = require (script.Parent:WaitForChild('registry'))
3
+ local bitmasks = require (script.Parent:WaitForChild('bitmasks'))
4
+ local utils = require (script.Parent:WaitForChild('utils'))
5
+ local common = require (script.Parent.Parent:WaitForChild('common'))
6
+
7
+ type Bitmask = buffer
8
+
9
+ type FilterSimple = types.FilterSimple
10
+
11
+ type Set<T> = { [T]: boolean }
12
+ type Map<K, V> = { [K]: V }
13
+ type Array<T> = { T }
14
+
15
+ type Listener = (Bitmask) -> ()
16
+
17
+ type ListenerConnections = {
18
+ listeners: { Listener },
19
+ listeners_indexes: { [Listener]: number },
20
+ }
21
+
22
+ export type FilterMode = number
23
+
24
+ export type FilterEntry = {
25
+ mode: FilterMode,
26
+ filter: FilterSimple,
27
+ }
28
+
29
+ export type FilterGroupClass = {
30
+ registry: registry.Registry,
31
+ parent: FilterGroup?,
32
+
33
+ derived: Set<FilterGroup>,
34
+ assigned_filter: FilterEntry?,
35
+ connected_listeners: ListenerConnections,
36
+ }
37
+
38
+ local FILTER_MODES = {
39
+ extend = 1,
40
+ exclude = 2,
41
+ include = 3,
42
+ }
43
+
44
+ local filter_group = {}
45
+ filter_group.__index = filter_group
46
+
47
+ export type FilterGroup = FilterGroupClass & typeof(filter_group)
48
+
49
+ function filter_group.is_filter_group(value: any): boolean
50
+ return typeof(value) == "table" and getmetatable(value) == filter_group
51
+ end
52
+
53
+ local function fire_listeners(self: FilterGroup, bitmask: Bitmask)
54
+ for _, listener in self.connected_listeners.listeners do
55
+ listener(bitmask)
56
+ end
57
+ end
58
+
59
+ function filter_group.connect(self: FilterGroup, listener: Listener)
60
+ local connected = self.connected_listeners
61
+ local index = #connected.listeners + 1
62
+ connected.listeners[index] = listener
63
+ connected.listeners_indexes[listener] = index
64
+ end
65
+
66
+ function filter_group.disconnect(self: FilterGroup, listener: Listener)
67
+ local connected = self.connected_listeners
68
+ local index = connected.listeners_indexes[listener]
69
+ if not index then
70
+ return
71
+ end
72
+
73
+ local last_listener = table.remove(connected.listeners)
74
+ if last_listener and last_listener ~= listener then
75
+ connected.listeners[index] = last_listener
76
+ connected.listeners_indexes[last_listener] = index
77
+ end
78
+ connected.listeners_indexes[listener] = nil
79
+ end
80
+
81
+ local function create_filter_bitmask(self: FilterGroup, filter: FilterSimple): Bitmask
82
+ if typeof(filter) == "table" then
83
+ return self.registry:create_bitmask_from_set(filter)
84
+ else
85
+ return self.registry:create_bitmask_from_member(filter)
86
+ end
87
+ end
88
+
89
+ -- the filter bitmask is resolved from the raw filter here, at calculation
90
+ -- time, so clients registered after the filter was assigned still resolve
91
+ function filter_group.apply_filter(self: FilterGroup, bitmask: Bitmask)
92
+ local filter = self.assigned_filter
93
+ if not filter then
94
+ return
95
+ end
96
+
97
+ local filter_bitmask = create_filter_bitmask(self, filter.filter)
98
+
99
+ if filter.mode == FILTER_MODES.extend then
100
+ bitmasks.bor(bitmask, filter_bitmask, bitmask)
101
+ elseif filter.mode == FILTER_MODES.exclude then
102
+ bitmasks.exclude(bitmask, filter_bitmask, bitmask)
103
+ elseif filter.mode == FILTER_MODES.include then
104
+ bitmasks.band(bitmask, filter_bitmask, bitmask)
105
+ end
106
+ end
107
+
108
+ function filter_group.calculate_up(self: FilterGroup): Bitmask
109
+ local parent = self.parent
110
+ local bitmask: Bitmask
111
+ if parent then
112
+ bitmask = filter_group.calculate_up(parent)
113
+ else
114
+ bitmask = bitmasks.create_filled(self.registry.client_next_index)
115
+ end
116
+
117
+ filter_group.apply_filter(self, bitmask)
118
+ return bitmask
119
+ end
120
+
121
+ function filter_group.calculate_down(self: FilterGroup, parent_bitmask: Bitmask)
122
+ local bitmask = bitmasks.clone(parent_bitmask, bitmasks.get_buffer_size(self.registry.client_next_index))
123
+ filter_group.apply_filter(self, bitmask)
124
+
125
+ fire_listeners(self, bitmask)
126
+
127
+ for derived in self.derived do
128
+ filter_group.calculate_down(derived, bitmask)
129
+ end
130
+ end
131
+
132
+ local function recalculate_filter_group(group: FilterGroup)
133
+ local parent = group.parent
134
+
135
+ local parent_bitmask: Bitmask
136
+ if parent then
137
+ parent_bitmask = filter_group.calculate_up(parent)
138
+ else
139
+ parent_bitmask = bitmasks.create_filled(group.registry.client_next_index)
140
+ end
141
+
142
+ filter_group.calculate_down(group, parent_bitmask)
143
+ end
144
+
145
+ function filter_group.extend(self: FilterGroup, filter: FilterSimple)
146
+ if not utils.get_used_filter_type(filter) then
147
+ common.log_error "extend() only accepts include filters"
148
+ end
149
+
150
+ self.assigned_filter = {
151
+ mode = FILTER_MODES.extend,
152
+ filter = filter,
153
+ }
154
+ recalculate_filter_group(self)
155
+ end
156
+
157
+ function filter_group.filter(self: FilterGroup, filter: FilterSimple)
158
+ local mode = FILTER_MODES.exclude
159
+
160
+ if utils.get_used_filter_type(filter) then
161
+ mode = FILTER_MODES.include
162
+ end
163
+
164
+ self.assigned_filter = {
165
+ mode = mode,
166
+ filter = filter,
167
+ }
168
+ recalculate_filter_group(self)
169
+ end
170
+
171
+ function filter_group.create(client_registry: registry.Registry): FilterGroup
172
+ local self: FilterGroupClass = {
173
+ registry = client_registry,
174
+
175
+ parent = nil,
176
+ connected_listeners = {
177
+ listeners = {},
178
+ listeners_indexes = {},
179
+ },
180
+ derived = setmetatable({}, { __mode = "k" }) :: any,
181
+ assigned_filter = nil,
182
+ }
183
+
184
+ setmetatable(self, filter_group)
185
+ return self :: any
186
+ end
187
+
188
+ function filter_group.derive(self: FilterGroup): FilterGroup
189
+ local derived = filter_group.create(self.registry)
190
+ derived.parent = self
191
+
192
+ self.derived[derived] = true
193
+ return derived
194
+ end
195
+
196
+ return filter_group