@rbxts/replecs 0.0.1-rc.0

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,624 @@
1
+ --!native
2
+ --!optimize 2
3
+
4
+ local jecs = require(script.Parent.Parent.jecs)
5
+ local common = require(script.Parent.common)
6
+
7
+ type World = jecs.World
8
+ type Entity<T = any> = jecs.Entity<T>
9
+
10
+ export type Cursor = {
11
+ offset: number,
12
+ buffer: buffer,
13
+ }
14
+
15
+ local ECS_NAME = jecs.Name
16
+ local CHILD_OF = jecs.ChildOf
17
+ local WILDCARD = jecs.Wildcard
18
+
19
+ function PAIR(first: Entity, second: Entity): Entity
20
+ return jecs.pair(first, second)
21
+ end
22
+
23
+ local utils = {}
24
+
25
+ local cursor = {}
26
+
27
+ function cursor.new(): Cursor
28
+ return {
29
+ buffer = buffer.create(100),
30
+ offset = 0,
31
+ }
32
+ end
33
+
34
+ function cursor.from(buf: buffer): Cursor
35
+ return {
36
+ buffer = buf,
37
+ offset = buffer.len(buf),
38
+ }
39
+ end
40
+
41
+ function cursor.tryalloc(cursor: Cursor, bytes: number)
42
+ local buf = cursor.buffer
43
+ local offset = cursor.offset
44
+ local len = buffer.len(buf)
45
+
46
+ if len < offset + bytes then
47
+ local exponent = math.ceil(math.log((bytes + offset) / len, 1.5))
48
+ local new = buffer.create(len * 1.5 ^ exponent)
49
+ buffer.copy(new, 0, buf, 0)
50
+ cursor.buffer = new
51
+ end
52
+ end
53
+
54
+ function cursor.write_buffer(c: Cursor, buf: buffer)
55
+ cursor.tryalloc(c, buffer.len(buf))
56
+ buffer.copy(c.buffer, c.offset, buf, 0, buffer.len(buf))
57
+ c.offset += buffer.len(buf)
58
+ end
59
+
60
+ function cursor.writeu8(c: Cursor, value: number)
61
+ cursor.tryalloc(c, 1)
62
+ buffer.writeu8(c.buffer, c.offset, value)
63
+ c.offset += 1
64
+ end
65
+
66
+ function cursor.writeu16(c: Cursor, value: number)
67
+ cursor.tryalloc(c, 2)
68
+ buffer.writeu16(c.buffer, c.offset, value)
69
+ c.offset += 2
70
+ end
71
+ function cursor.writeu24(c: Cursor, value: number)
72
+ cursor.tryalloc(c, 3)
73
+ buffer.writeu8(c.buffer, c.offset, value)
74
+ buffer.writeu16(c.buffer, c.offset + 1, value // 256)
75
+ c.offset += 3
76
+ end
77
+
78
+ function cursor.writeu32(c: Cursor, value: number)
79
+ cursor.tryalloc(c, 4)
80
+ buffer.writeu32(c.buffer, c.offset, value :: any)
81
+ c.offset += 4
82
+ end
83
+
84
+ function cursor.writei8(c: Cursor, value: number)
85
+ cursor.tryalloc(c, 1)
86
+ buffer.writei8(c.buffer, c.offset, value)
87
+ c.offset += 1
88
+ end
89
+
90
+ function cursor.writei16(c: Cursor, value: number)
91
+ cursor.tryalloc(c, 2)
92
+ buffer.writei16(c.buffer, c.offset, value)
93
+ c.offset += 2
94
+ end
95
+
96
+ function cursor.writei32(c: Cursor, value: number)
97
+ cursor.tryalloc(c, 4)
98
+ buffer.writeu32(c.buffer, c.offset, value :: any)
99
+ c.offset += 4
100
+ end
101
+
102
+ function cursor.read_buffer(c: Cursor, len: number)
103
+ local retrieved = buffer.create(len)
104
+ buffer.copy(retrieved, 0, c.buffer, c.offset - len, len)
105
+ c.offset -= len
106
+ return retrieved
107
+ end
108
+
109
+ function cursor.readu8(c: Cursor): number
110
+ c.offset -= 1
111
+ return buffer.readu8(c.buffer, c.offset)
112
+ end
113
+
114
+ function cursor.readu16(c: Cursor): number
115
+ c.offset -= 2
116
+ return buffer.readu16(c.buffer, c.offset)
117
+ end
118
+
119
+ function cursor.readu24(c: Cursor): number
120
+ c.offset -= 3
121
+ local one = buffer.readu8(c.buffer, c.offset)
122
+ local two = buffer.readu16(c.buffer, c.offset + 1)
123
+ return one + two * 256
124
+ end
125
+ function cursor.readu32(c: Cursor): number
126
+ c.offset -= 4
127
+ return buffer.readu32(c.buffer, c.offset)
128
+ end
129
+
130
+ function cursor.readi8(c: Cursor): number
131
+ c.offset -= 1
132
+ return buffer.readi8(c.buffer, c.offset)
133
+ end
134
+
135
+ function cursor.readi16(c: Cursor): number
136
+ c.offset -= 2
137
+ return buffer.readi16(c.buffer, c.offset)
138
+ end
139
+ function cursor.readi32(c: Cursor): number
140
+ c.offset -= 4
141
+ return buffer.readi32(c.buffer, c.offset)
142
+ end
143
+
144
+ function cursor.write_vlq(c: Cursor, value: number)
145
+ local x0 = value // 128 ^ 0 % 128
146
+ local x1 = value // 128 ^ 1 % 128
147
+ local x2 = value // 128 ^ 2 % 128
148
+ local x3 = value // 128 ^ 3 % 128
149
+
150
+ if x3 ~= 0 then
151
+ cursor.tryalloc(c, 4)
152
+ cursor.writeu32(c, x0 * 256 ^ 3 + x1 * 256 ^ 2 + x2 * 256 + x3 + 128)
153
+ elseif x2 ~= 0 then
154
+ cursor.tryalloc(c, 3)
155
+ cursor.writeu24(c, x0 * 256 ^ 2 + x1 * 256 + x2 + 128)
156
+ elseif x1 ~= 0 then
157
+ cursor.tryalloc(c, 2)
158
+ cursor.writeu16(c, x0 * 256 + x1 + 128)
159
+ else
160
+ cursor.tryalloc(c, 1)
161
+ cursor.writeu8(c, x0 + 128)
162
+ end
163
+ end
164
+
165
+ function cursor.read_vlq(c: Cursor): number
166
+ local b = cursor.readu8(c)
167
+ if b >= 128 then
168
+ return b - 128
169
+ end
170
+ local x = b
171
+
172
+ b = cursor.readu8(c)
173
+ if b >= 128 then
174
+ return x + (b - 128) * 128
175
+ end
176
+ x += b * 128
177
+
178
+ b = cursor.readu8(c)
179
+ if b >= 128 then
180
+ return x + (b - 128) * 128 ^ 2
181
+ end
182
+ x += b * 128 ^ 2
183
+
184
+ b = cursor.readu8(c)
185
+ if b >= 128 then
186
+ return x + (b - 128) * 128 ^ 3
187
+ end
188
+ x += b * 128 ^ 3
189
+
190
+ error "vlq length too large"
191
+ end
192
+
193
+ function cursor.close(c: Cursor): buffer
194
+ local final = buffer.create(c.offset)
195
+ buffer.copy(final, 0, c.buffer, 0, c.offset)
196
+ c.buffer = final
197
+ return final
198
+ end
199
+
200
+ utils.cursor = cursor
201
+
202
+ local bitmask = {}
203
+ bitmask.__index = bitmask
204
+
205
+ export type Bitmask = {
206
+ buffer: buffer,
207
+ capacity: number,
208
+ entries: number,
209
+ } & typeof(bitmask)
210
+
211
+ local BITS_PER_ENTRY = 32
212
+ local BYTES_PER_ENTRY = 4
213
+
214
+ function get_entry_bit(index: number): (number, number)
215
+ local entry_index = math.floor(index / BITS_PER_ENTRY)
216
+ local bit_position = index % BITS_PER_ENTRY
217
+ return entry_index, bit_position
218
+ end
219
+
220
+ function bitmask.create(capacity: number?): Bitmask
221
+ local entries_needed = math.ceil(capacity :: number / BITS_PER_ENTRY)
222
+ local buffer_size = entries_needed * BYTES_PER_ENTRY
223
+
224
+ local self = {
225
+ buffer = buffer.create(buffer_size),
226
+ capacity = capacity,
227
+ entries = entries_needed,
228
+ }
229
+
230
+ return setmetatable(self, bitmask) :: any
231
+ end
232
+
233
+ function bitmask.from_set(indexes: { [any]: number }, filter: { [any]: boolean }, capacity: number?): Bitmask
234
+ local newmask = bitmask.create(capacity or 32)
235
+ for player in filter do
236
+ local index = indexes[player]
237
+ if index == nil then
238
+ continue
239
+ end
240
+ bitmask.set(newmask, index)
241
+ end
242
+ return newmask
243
+ end
244
+
245
+ function bitmask.expand(self: Bitmask, capacity: number)
246
+ if capacity <= self.capacity then
247
+ return
248
+ end
249
+
250
+ local new_entries = math.ceil(capacity / BITS_PER_ENTRY)
251
+ local new_buffer_size = new_entries * BYTES_PER_ENTRY
252
+ local new_buffer = buffer.create(new_buffer_size)
253
+
254
+ buffer.copy(new_buffer, 0, self.buffer, 0)
255
+
256
+ for i = self.entries * BYTES_PER_ENTRY, new_buffer_size - 1 do
257
+ buffer.writeu8(new_buffer, i, 0)
258
+ end
259
+
260
+ self.buffer = new_buffer
261
+ self.capacity = capacity
262
+ self.entries = new_entries
263
+ end
264
+
265
+ function bitmask.read(self: Bitmask, index: number): number
266
+ if index >= self.entries then
267
+ return 0
268
+ end
269
+ return buffer.readu32(self.buffer, index * BYTES_PER_ENTRY)
270
+ end
271
+
272
+ function bitmask.write(self: Bitmask, index: number, value: number)
273
+ if index >= self.entries then
274
+ --
275
+ bitmask.expand(self, (index + 1) * BITS_PER_ENTRY)
276
+ end
277
+ buffer.writeu32(self.buffer, index * BYTES_PER_ENTRY, value)
278
+ end
279
+
280
+ -- BIT MANIPULATIONS
281
+
282
+ function bitmask.set(self: Bitmask, index: number)
283
+ if index < 0 then
284
+ error "Bit index cannot be negative"
285
+ end
286
+
287
+ local entry_index, bit_position = get_entry_bit(index)
288
+ local current_value = bitmask.read(self, entry_index)
289
+ local new_value = bit32.bor(current_value, bit32.lshift(1, bit_position))
290
+ bitmask.write(self, entry_index, new_value)
291
+ end
292
+
293
+ function bitmask.get(self: Bitmask, index: number): boolean
294
+ if index < 0 then
295
+ error "Bit index cannot be negative"
296
+ end
297
+
298
+ local entry_index, bit_position = get_entry_bit(index)
299
+ if entry_index >= self.entries then
300
+ return false
301
+ end
302
+
303
+ local current_value = bitmask.read(self, entry_index)
304
+ return bit32.band(current_value, bit32.lshift(1, bit_position)) ~= 0
305
+ end
306
+
307
+ function bitmask.clear(self: Bitmask, index: number)
308
+ if index < 0 then
309
+ error "Bit index cannot be negative"
310
+ end
311
+
312
+ local entry_index, bit_position = get_entry_bit(index)
313
+ if entry_index >= self.entries then
314
+ return
315
+ end
316
+
317
+ local current_value = bitmask.read(self, entry_index)
318
+ local mask = bit32.bnot(bit32.lshift(1, bit_position))
319
+ local new_value = bit32.band(current_value, mask)
320
+ bitmask.write(self, entry_index, new_value)
321
+ end
322
+
323
+ -- BITWISE OPERATIONS
324
+
325
+ function bitmask.band(self: Bitmask, other: Bitmask): Bitmask
326
+ local max_entries = math.max(self.entries, other.entries)
327
+ local result = bitmask.create(max_entries * BITS_PER_ENTRY)
328
+
329
+ for i = 0, max_entries - 1 do
330
+ local value1 = bitmask.read(self, i)
331
+ local value2 = bitmask.read(other, i)
332
+ bitmask.write(result, i, bit32.band(value1, value2))
333
+ end
334
+
335
+ return result
336
+ end
337
+
338
+ function bitmask.bor(self: Bitmask, other: Bitmask): Bitmask
339
+ local max_entries = math.max(self.entries, other.entries)
340
+ local result = bitmask.create(max_entries * BITS_PER_ENTRY)
341
+
342
+ for i = 0, max_entries - 1 do
343
+ local value1 = bitmask.read(self, i)
344
+ local value2 = bitmask.read(other, i)
345
+ bitmask.write(result, i, bit32.bor(value1, value2))
346
+ end
347
+
348
+ return result
349
+ end
350
+
351
+ function bitmask.bxor(self: Bitmask, other: Bitmask): Bitmask
352
+ local max_entries = math.max(self.entries, other.entries)
353
+ local result = bitmask.create(max_entries * BITS_PER_ENTRY)
354
+
355
+ for i = 0, max_entries - 1 do
356
+ local value1 = bitmask.read(self, i)
357
+ local value2 = bitmask.read(other, i)
358
+ bitmask.write(result, i, bit32.bxor(value1, value2))
359
+ end
360
+
361
+ return result
362
+ end
363
+
364
+ function bitmask.bnot(self: Bitmask): Bitmask
365
+ local result = bitmask.create(self.capacity)
366
+
367
+ for i = 0, self.entries - 1 do
368
+ local value = bitmask.read(self, i)
369
+ bitmask.write(result, i, bit32.bnot(value))
370
+ end
371
+
372
+ return result
373
+ end
374
+
375
+ function bitmask.clone(self: Bitmask): Bitmask
376
+ local result = bitmask.create(self.capacity)
377
+ buffer.copy(result.buffer, 0, self.buffer, 0, self.entries * BYTES_PER_ENTRY)
378
+ return result
379
+ end
380
+
381
+ function bitmask.remap(self: Bitmask, from: { [any]: number }, to: { [any]: number }, new_capacity: number?): Bitmask
382
+ local result = bitmask.create(new_capacity or BITS_PER_ENTRY)
383
+ for member, index in to do
384
+ local old_index = from[member]
385
+ if old_index then
386
+ local old_bit = bitmask.get(self, old_index)
387
+ if old_bit then
388
+ bitmask.set(result, index)
389
+ else
390
+ bitmask.clear(result, index)
391
+ end
392
+ end
393
+ end
394
+ return result
395
+ end
396
+
397
+ function bitmask.lshift(self: Bitmask, n: number): Bitmask
398
+ if n <= 0 then
399
+ return bitmask.clone(self)
400
+ end
401
+
402
+ local entry_shift = math.floor(n / BITS_PER_ENTRY)
403
+ local bit_shift = n % BITS_PER_ENTRY
404
+
405
+ local result = bitmask.create(self.capacity + n)
406
+
407
+ if bit_shift == 0 then
408
+ for i = 0, self.entries - 1 do
409
+ local value = bitmask.read(self, i)
410
+ bitmask.write(result, i + entry_shift, value)
411
+ end
412
+ else
413
+ local carry = 0
414
+ for i = 0, self.entries - 1 do
415
+ local value = bitmask.read(self, i)
416
+ local shifted_value = bit32.lshift(value, bit_shift)
417
+ local new_carry = bit32.rshift(value, BITS_PER_ENTRY - bit_shift)
418
+
419
+ bitmask.write(result, i + entry_shift, bit32.bor(shifted_value, carry))
420
+ carry = new_carry
421
+ end
422
+
423
+ if carry ~= 0 then
424
+ bitmask.write(result, self.entries + entry_shift, carry)
425
+ end
426
+ end
427
+
428
+ return result
429
+ end
430
+
431
+ function bitmask.rshift(self: Bitmask, n: number): Bitmask
432
+ if n <= 0 then
433
+ return bitmask.clone(self)
434
+ end
435
+
436
+ local entry_shift = math.floor(n / BITS_PER_ENTRY)
437
+ local bit_shift = n % BITS_PER_ENTRY
438
+
439
+ if entry_shift >= self.entries then
440
+ return bitmask.create(32)
441
+ end
442
+
443
+ local result = bitmask.create(self.capacity)
444
+
445
+ if bit_shift == 0 then
446
+ for i = entry_shift, self.entries - 1 do
447
+ local value = bitmask.read(self, i)
448
+ bitmask.write(result, i - entry_shift, value)
449
+ end
450
+ else
451
+ for i = entry_shift, self.entries - 1 do
452
+ local value = bitmask.read(self, i)
453
+ local shifted_value = bit32.rshift(value, bit_shift)
454
+
455
+ local next_value = 0
456
+ if i + 1 < self.entries then
457
+ next_value = bitmask.read(self, i + 1)
458
+ end
459
+ local overflow = bit32.lshift(next_value, BITS_PER_ENTRY - bit_shift)
460
+
461
+ bitmask.write(result, i - entry_shift, bit32.bor(shifted_value, overflow))
462
+ end
463
+ end
464
+
465
+ return result
466
+ end
467
+
468
+ local function tobinary(n: number): string
469
+ if n == 0 then
470
+ return "0"
471
+ end
472
+
473
+ local result = ""
474
+ while n > 0 do
475
+ result = (n % 2) .. result
476
+ n = math.floor(n / 2)
477
+ end
478
+ return string.rep("0", 8 - #result) .. result
479
+ end
480
+
481
+ function bitmask.tostring(self: Bitmask): string
482
+ local highest_entry = -1
483
+ for i = self.entries - 1, 0, -1 do
484
+ local value = bitmask.read(self, i)
485
+ if value ~= 0 then
486
+ highest_entry = i
487
+ break
488
+ end
489
+ end
490
+
491
+ if highest_entry == -1 then
492
+ return ""
493
+ end
494
+
495
+ local str = ""
496
+ for i = 0, highest_entry do
497
+ local value = bitmask.read(self, i)
498
+ str ..= tobinary(value)
499
+ end
500
+ return str
501
+ end
502
+
503
+ utils.bitmask = {
504
+ create = bitmask.create,
505
+ from_set = bitmask.from_set,
506
+ }
507
+
508
+ function utils.create_shared_lookup(world: World, components: common.Components)
509
+ local shared: common.Shared = {
510
+ lookup = {},
511
+ serdes = {},
512
+ bytespan = {},
513
+ ids = {},
514
+ components = {},
515
+ }
516
+
517
+ for component, name in world:query(ECS_NAME):with(components.shared):iter() do
518
+ shared.lookup[name] = component
519
+ end
520
+
521
+ for component, serdes in world:query(components.serdes):with(components.shared, ECS_NAME):iter() do
522
+ shared.serdes[component] = serdes
523
+ end
524
+
525
+ for component, bytespan in world:query(components.bytespan):with(components.shared, ECS_NAME):iter() do
526
+ shared.bytespan[component] = bytespan
527
+ end
528
+
529
+ local keys = {}
530
+ local component_ids = {}
531
+ local id_components = {}
532
+
533
+ for name in shared.lookup do
534
+ table.insert(keys, name)
535
+ end
536
+ table.sort(keys)
537
+
538
+ for id, name in keys do
539
+ local component = shared.lookup[name]
540
+ component_ids[shared.lookup[name]] = id
541
+ id_components[id] = component
542
+ end
543
+
544
+ component_ids[CHILD_OF] = #keys + 1
545
+
546
+ shared.ids = component_ids
547
+ shared.components = id_components
548
+ return shared
549
+ end
550
+
551
+ function utils.vlq_span(length: number): number
552
+ if length < 0x80 then
553
+ return 1
554
+ elseif length < 0x4000 then
555
+ return 2
556
+ elseif length < 0x200000 then
557
+ return 3
558
+ else
559
+ return 4
560
+ end
561
+ end
562
+
563
+ function utils.number_span(n: number): number
564
+ if n <= 0xFF then
565
+ return 1
566
+ elseif n <= 0xFFFF then
567
+ return 2
568
+ else
569
+ return 4 -- cant read 24 bits
570
+ end
571
+ end
572
+
573
+ function utils.read_number(buf: buffer, offset: number, span: number): (number, number)
574
+ if span == 1 then
575
+ return buffer.readu8(buf, offset), offset + 1
576
+ elseif span == 2 then
577
+ return buffer.readu16(buf, offset), offset + 2
578
+ else
579
+ return buffer.readu32(buf, offset), offset + 4
580
+ end
581
+ end
582
+
583
+ function utils.remove_all_relations(world: World, entity: Entity, relation: Entity)
584
+ local entity_index = jecs.entity_index_try_get_fast(world.entity_index :: any, entity :: any)
585
+ if not entity_index then
586
+ return
587
+ end
588
+ local archetype = entity_index.archetype
589
+
590
+ local wildcard = PAIR(relation, WILDCARD) :: any
591
+ local idr = world.component_index[wildcard]
592
+ if not idr then
593
+ return
594
+ end
595
+
596
+ local archetype_id = archetype.id
597
+ local count = idr.counts[archetype_id]
598
+ if not count then
599
+ return
600
+ end
601
+
602
+ local start = idr.records[archetype_id]
603
+ if not start then
604
+ return
605
+ end
606
+
607
+ for i = start, start + count - 1 do
608
+ local pair = archetype.types[i]
609
+ world:remove(entity, pair :: any)
610
+ end
611
+ end
612
+
613
+ function utils.logerror(...: string): never
614
+ error("REPLECS ERROR - " .. table.concat({ ... }, " "))
615
+ end
616
+ function utils.logwarn(...: string)
617
+ warn("REPLECS WARN - " .. table.concat({ ... }, " "))
618
+ end
619
+
620
+ function utils.logcomponent(world: World, component: Entity)
621
+ return ` name: {world:get(component, ECS_NAME) or "(no name)"} id: {component}`
622
+ end
623
+
624
+ return utils
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@rbxts/replecs",
3
+ "version": "0.0.1-rc.0",
4
+ "description": "Replication library for Jecs",
5
+ "main": "out/init.lua",
6
+ "types": "out/index.d.ts",
7
+ "directories": {
8
+ "doc": "docs",
9
+ "test": "tests"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "out",
16
+ "!**/*.tsbuildinfo",
17
+ "!**/*.spec.lua",
18
+ "!**/*.spec.d.ts"
19
+ ],
20
+ "scripts": {
21
+ "serve": "rojo sourcemap serve.project.json -o sourcemap.json --watch && rojo serve serve.project.json",
22
+ "build": "rbxtsc",
23
+ "watch": "rbxtsc -w",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/PepeElToro41/replecs.git"
29
+ },
30
+ "author": "PepeElToro41",
31
+ "license": "MIT",
32
+ "bugs": {
33
+ "url": "https://github.com/PepeElToro41/replecs/issues"
34
+ },
35
+ "homepage": "https://github.com/PepeElToro41/replecs#readme",
36
+ "devDependencies": {
37
+ "@rbxts/compiler-types": "^2.3.0-types.1",
38
+ "@rbxts/types": "^1.0.768",
39
+ "roblox-ts": "3.0.0",
40
+ "typescript": "=5.5.3",
41
+ "prettier": "^3.2.5"
42
+ },
43
+ "dependencies": {
44
+ "@rbxts/jecs": "^0.8.3"
45
+ }
46
+ }