@rlabs-inc/tui 0.1.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.
- package/README.md +141 -0
- package/index.ts +45 -0
- package/package.json +59 -0
- package/src/api/index.ts +7 -0
- package/src/api/mount.ts +230 -0
- package/src/engine/arrays/core.ts +60 -0
- package/src/engine/arrays/dimensions.ts +68 -0
- package/src/engine/arrays/index.ts +166 -0
- package/src/engine/arrays/interaction.ts +112 -0
- package/src/engine/arrays/layout.ts +175 -0
- package/src/engine/arrays/spacing.ts +100 -0
- package/src/engine/arrays/text.ts +55 -0
- package/src/engine/arrays/visual.ts +140 -0
- package/src/engine/index.ts +25 -0
- package/src/engine/inheritance.ts +138 -0
- package/src/engine/registry.ts +180 -0
- package/src/pipeline/frameBuffer.ts +473 -0
- package/src/pipeline/layout/index.ts +105 -0
- package/src/pipeline/layout/titan-engine.ts +798 -0
- package/src/pipeline/layout/types.ts +194 -0
- package/src/pipeline/layout/utils/hierarchy.ts +202 -0
- package/src/pipeline/layout/utils/math.ts +134 -0
- package/src/pipeline/layout/utils/text-measure.ts +160 -0
- package/src/pipeline/layout.ts +30 -0
- package/src/primitives/box.ts +312 -0
- package/src/primitives/index.ts +12 -0
- package/src/primitives/text.ts +199 -0
- package/src/primitives/types.ts +222 -0
- package/src/primitives/utils.ts +37 -0
- package/src/renderer/ansi.ts +625 -0
- package/src/renderer/buffer.ts +667 -0
- package/src/renderer/index.ts +40 -0
- package/src/renderer/input.ts +518 -0
- package/src/renderer/output.ts +451 -0
- package/src/state/cursor.ts +176 -0
- package/src/state/focus.ts +241 -0
- package/src/state/index.ts +43 -0
- package/src/state/keyboard.ts +771 -0
- package/src/state/mouse.ts +524 -0
- package/src/state/scroll.ts +341 -0
- package/src/state/theme.ts +687 -0
- package/src/types/color.ts +401 -0
- package/src/types/index.ts +316 -0
- package/src/utils/text.ts +471 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - Visual Arrays
|
|
3
|
+
*
|
|
4
|
+
* Colors, borders, and visual styling.
|
|
5
|
+
* Colors stored as RGBA objects for alpha blending support.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: Use regular arrays (NOT state!) to preserve binding getters.
|
|
8
|
+
* state() proxies snapshot getter values, breaking reactivity.
|
|
9
|
+
*
|
|
10
|
+
* Border styles:
|
|
11
|
+
* 0 = none
|
|
12
|
+
* 1 = single (─ │ ┌ ┐ └ ┘)
|
|
13
|
+
* 2 = double (═ ║ ╔ ╗ ╚ ╝)
|
|
14
|
+
* 3 = rounded (─ │ ╭ ╮ ╰ ╯)
|
|
15
|
+
* 4 = heavy (━ ┃ ┏ ┓ ┗ ┛)
|
|
16
|
+
* 5 = dashed (╌ ╎ ┌ ┐ └ ┘)
|
|
17
|
+
* 6 = dotted (· · · · · ·)
|
|
18
|
+
* 7 = ascii (- | + + + +)
|
|
19
|
+
* 8 = block (█ █ █ █ █ █)
|
|
20
|
+
* 9 = mixedDoubleH (═ │ ╒ ╕ ╘ ╛)
|
|
21
|
+
* 10 = mixedDoubleV (─ ║ ╓ ╖ ╙ ╜)
|
|
22
|
+
*
|
|
23
|
+
* Per-side borders can have independent styles.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { bind, disconnectBinding, type Binding } from '@rlabs-inc/signals'
|
|
27
|
+
import type { RGBA } from '../../types'
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// COLORS - Regular arrays to preserve binding reactivity
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/** Foreground color (text) - null means inherit from parent */
|
|
34
|
+
export const fgColor: Binding<RGBA | null>[] = []
|
|
35
|
+
|
|
36
|
+
/** Background color - null means transparent/inherit */
|
|
37
|
+
export const bgColor: Binding<RGBA | null>[] = []
|
|
38
|
+
|
|
39
|
+
/** Opacity 0-1 (1 = fully opaque) */
|
|
40
|
+
export const opacity: Binding<number>[] = []
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// BORDERS - Per-side independent styles
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
/** Default border style for all sides (0-10) */
|
|
47
|
+
export const borderStyle: Binding<number>[] = []
|
|
48
|
+
|
|
49
|
+
/** Default border color for all sides - null means use foreground */
|
|
50
|
+
export const borderColor: Binding<RGBA | null>[] = []
|
|
51
|
+
|
|
52
|
+
/** Top border style (0=none or inherit from borderStyle, 1-10=specific style) */
|
|
53
|
+
export const borderTop: Binding<number>[] = []
|
|
54
|
+
|
|
55
|
+
/** Right border style */
|
|
56
|
+
export const borderRight: Binding<number>[] = []
|
|
57
|
+
|
|
58
|
+
/** Bottom border style */
|
|
59
|
+
export const borderBottom: Binding<number>[] = []
|
|
60
|
+
|
|
61
|
+
/** Left border style */
|
|
62
|
+
export const borderLeft: Binding<number>[] = []
|
|
63
|
+
|
|
64
|
+
/** Per-side border colors - null means use borderColor or foreground */
|
|
65
|
+
export const borderColorTop: Binding<RGBA | null>[] = []
|
|
66
|
+
export const borderColorRight: Binding<RGBA | null>[] = []
|
|
67
|
+
export const borderColorBottom: Binding<RGBA | null>[] = []
|
|
68
|
+
export const borderColorLeft: Binding<RGBA | null>[] = []
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// FOCUS RING
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/** Show focus ring when focused (1=yes, 0=no) */
|
|
75
|
+
export const showFocusRing: Binding<number>[] = []
|
|
76
|
+
|
|
77
|
+
/** Focus ring color */
|
|
78
|
+
export const focusRingColor: Binding<RGBA>[] = []
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// DEFAULT VALUES
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
const DEFAULT_FOCUS_COLOR: RGBA = { r: 100, g: 149, b: 237, a: 255 } // cornflowerblue
|
|
85
|
+
|
|
86
|
+
/** LAZY BINDING: Push undefined, primitives create bindings for used props only */
|
|
87
|
+
export function ensureCapacity(index: number): void {
|
|
88
|
+
while (fgColor.length <= index) {
|
|
89
|
+
fgColor.push(undefined as any)
|
|
90
|
+
bgColor.push(undefined as any)
|
|
91
|
+
opacity.push(undefined as any)
|
|
92
|
+
borderStyle.push(undefined as any)
|
|
93
|
+
borderColor.push(undefined as any)
|
|
94
|
+
borderTop.push(undefined as any)
|
|
95
|
+
borderRight.push(undefined as any)
|
|
96
|
+
borderBottom.push(undefined as any)
|
|
97
|
+
borderLeft.push(undefined as any)
|
|
98
|
+
borderColorTop.push(undefined as any)
|
|
99
|
+
borderColorRight.push(undefined as any)
|
|
100
|
+
borderColorBottom.push(undefined as any)
|
|
101
|
+
borderColorLeft.push(undefined as any)
|
|
102
|
+
showFocusRing.push(undefined as any)
|
|
103
|
+
focusRingColor.push(undefined as any)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function clearAtIndex(index: number): void {
|
|
108
|
+
if (index < fgColor.length) {
|
|
109
|
+
disconnectBinding(fgColor[index])
|
|
110
|
+
disconnectBinding(bgColor[index])
|
|
111
|
+
disconnectBinding(opacity[index])
|
|
112
|
+
disconnectBinding(borderStyle[index])
|
|
113
|
+
disconnectBinding(borderColor[index])
|
|
114
|
+
disconnectBinding(borderTop[index])
|
|
115
|
+
disconnectBinding(borderRight[index])
|
|
116
|
+
disconnectBinding(borderBottom[index])
|
|
117
|
+
disconnectBinding(borderLeft[index])
|
|
118
|
+
disconnectBinding(borderColorTop[index])
|
|
119
|
+
disconnectBinding(borderColorRight[index])
|
|
120
|
+
disconnectBinding(borderColorBottom[index])
|
|
121
|
+
disconnectBinding(borderColorLeft[index])
|
|
122
|
+
disconnectBinding(showFocusRing[index])
|
|
123
|
+
disconnectBinding(focusRingColor[index])
|
|
124
|
+
fgColor[index] = undefined as any
|
|
125
|
+
bgColor[index] = undefined as any
|
|
126
|
+
opacity[index] = undefined as any
|
|
127
|
+
borderStyle[index] = undefined as any
|
|
128
|
+
borderColor[index] = undefined as any
|
|
129
|
+
borderTop[index] = undefined as any
|
|
130
|
+
borderRight[index] = undefined as any
|
|
131
|
+
borderBottom[index] = undefined as any
|
|
132
|
+
borderLeft[index] = undefined as any
|
|
133
|
+
borderColorTop[index] = undefined as any
|
|
134
|
+
borderColorRight[index] = undefined as any
|
|
135
|
+
borderColorBottom[index] = undefined as any
|
|
136
|
+
borderColorLeft[index] = undefined as any
|
|
137
|
+
showFocusRing[index] = undefined as any
|
|
138
|
+
focusRingColor[index] = undefined as any
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - Engine
|
|
3
|
+
*
|
|
4
|
+
* The component engine using parallel arrays pattern.
|
|
5
|
+
* Components allocate indices and write to arrays.
|
|
6
|
+
* Deriveds read arrays and RETURN computed values.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Registry
|
|
10
|
+
export {
|
|
11
|
+
allocateIndex,
|
|
12
|
+
releaseIndex,
|
|
13
|
+
getIndex,
|
|
14
|
+
getId,
|
|
15
|
+
getAllocatedIndices,
|
|
16
|
+
isAllocated,
|
|
17
|
+
getAllocatedCount,
|
|
18
|
+
getCurrentParentIndex,
|
|
19
|
+
pushParentContext,
|
|
20
|
+
popParentContext,
|
|
21
|
+
resetRegistry,
|
|
22
|
+
} from './registry'
|
|
23
|
+
|
|
24
|
+
// Parallel arrays
|
|
25
|
+
export * as arrays from './arrays'
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - Color and Style Inheritance
|
|
3
|
+
*
|
|
4
|
+
* Utilities for walking up the component tree to inherit colors and styles.
|
|
5
|
+
* Used when a component has null colors (meaning "inherit from parent").
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { unwrap } from '@rlabs-inc/signals'
|
|
9
|
+
import type { RGBA } from '../types'
|
|
10
|
+
import { Colors, TERMINAL_DEFAULT } from '../types/color'
|
|
11
|
+
import * as core from './arrays/core'
|
|
12
|
+
import * as visual from './arrays/visual'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get inherited foreground color by walking up the parent tree.
|
|
16
|
+
* Returns TERMINAL_DEFAULT if no explicit color is found.
|
|
17
|
+
*/
|
|
18
|
+
export function getInheritedFg(index: number): RGBA {
|
|
19
|
+
let current: number = index
|
|
20
|
+
|
|
21
|
+
while (current >= 0) {
|
|
22
|
+
const fg = unwrap(visual.fgColor[current])
|
|
23
|
+
if (fg !== null && fg !== undefined) return fg
|
|
24
|
+
const parent = unwrap(core.parentIndex[current])
|
|
25
|
+
if (parent === undefined || parent < 0) break
|
|
26
|
+
current = parent
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return TERMINAL_DEFAULT
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get inherited background color by walking up the parent tree.
|
|
34
|
+
* Returns TERMINAL_DEFAULT if no explicit color is found.
|
|
35
|
+
*/
|
|
36
|
+
export function getInheritedBg(index: number): RGBA {
|
|
37
|
+
let current: number = index
|
|
38
|
+
|
|
39
|
+
while (current >= 0) {
|
|
40
|
+
const bg = unwrap(visual.bgColor[current])
|
|
41
|
+
if (bg !== null && bg !== undefined) return bg
|
|
42
|
+
const parent = unwrap(core.parentIndex[current])
|
|
43
|
+
if (parent === undefined || parent < 0) break
|
|
44
|
+
current = parent
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return TERMINAL_DEFAULT
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get inherited border color for a specific side.
|
|
52
|
+
* Falls back to unified border color, then foreground color.
|
|
53
|
+
*/
|
|
54
|
+
export function getInheritedBorderColor(index: number, side: 'top' | 'right' | 'bottom' | 'left'): RGBA {
|
|
55
|
+
const colorArray = {
|
|
56
|
+
top: visual.borderColorTop,
|
|
57
|
+
right: visual.borderColorRight,
|
|
58
|
+
bottom: visual.borderColorBottom,
|
|
59
|
+
left: visual.borderColorLeft,
|
|
60
|
+
}[side]
|
|
61
|
+
|
|
62
|
+
const color = unwrap(colorArray[index])
|
|
63
|
+
if (color !== null && color !== undefined) return color
|
|
64
|
+
|
|
65
|
+
// Try unified border color
|
|
66
|
+
const unifiedColor = unwrap(visual.borderColor[index])
|
|
67
|
+
if (unifiedColor !== null && unifiedColor !== undefined) return unifiedColor
|
|
68
|
+
|
|
69
|
+
// Fall back to foreground color
|
|
70
|
+
return getInheritedFg(index)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all four border colors for a component.
|
|
75
|
+
*/
|
|
76
|
+
export function getBorderColors(index: number): {
|
|
77
|
+
top: RGBA
|
|
78
|
+
right: RGBA
|
|
79
|
+
bottom: RGBA
|
|
80
|
+
left: RGBA
|
|
81
|
+
} {
|
|
82
|
+
const fg = getInheritedFg(index)
|
|
83
|
+
const unified = unwrap(visual.borderColor[index])
|
|
84
|
+
const fallback = unified ?? fg
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
top: unwrap(visual.borderColorTop[index]) ?? fallback,
|
|
88
|
+
right: unwrap(visual.borderColorRight[index]) ?? fallback,
|
|
89
|
+
bottom: unwrap(visual.borderColorBottom[index]) ?? fallback,
|
|
90
|
+
left: unwrap(visual.borderColorLeft[index]) ?? fallback,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get all four border styles for a component.
|
|
96
|
+
* Falls back to the unified borderStyle if per-side not set.
|
|
97
|
+
*/
|
|
98
|
+
export function getBorderStyles(index: number): {
|
|
99
|
+
top: number
|
|
100
|
+
right: number
|
|
101
|
+
bottom: number
|
|
102
|
+
left: number
|
|
103
|
+
} {
|
|
104
|
+
const unified = unwrap(visual.borderStyle[index]) || 0
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
top: unwrap(visual.borderTop[index]) || unified,
|
|
108
|
+
right: unwrap(visual.borderRight[index]) || unified,
|
|
109
|
+
bottom: unwrap(visual.borderBottom[index]) || unified,
|
|
110
|
+
left: unwrap(visual.borderLeft[index]) || unified,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a component has any border.
|
|
116
|
+
*/
|
|
117
|
+
export function hasBorder(index: number): boolean {
|
|
118
|
+
const styles = getBorderStyles(index)
|
|
119
|
+
return styles.top > 0 || styles.right > 0 || styles.bottom > 0 || styles.left > 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get effective opacity by multiplying down the parent chain.
|
|
124
|
+
*/
|
|
125
|
+
export function getEffectiveOpacity(index: number): number {
|
|
126
|
+
let opacity = 1
|
|
127
|
+
let current: number | undefined = index
|
|
128
|
+
|
|
129
|
+
while (current !== undefined && current >= 0) {
|
|
130
|
+
const nodeOpacity = unwrap(visual.opacity[current])
|
|
131
|
+
if (nodeOpacity !== undefined && nodeOpacity !== 1) {
|
|
132
|
+
opacity *= nodeOpacity
|
|
133
|
+
}
|
|
134
|
+
current = unwrap(core.parentIndex[current])
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return opacity
|
|
138
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - Component Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages index allocation for the parallel arrays pattern.
|
|
5
|
+
* Each component gets a unique index, which is used across all arrays.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - ID ↔ Index bidirectional mapping
|
|
9
|
+
* - Free index pool for O(1) reuse
|
|
10
|
+
* - ReactiveSet for allocatedIndices (deriveds react to add/remove)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ReactiveSet } from '@rlabs-inc/signals'
|
|
14
|
+
import { ensureAllCapacity, clearAllAtIndex, resetAllArrays } from './arrays'
|
|
15
|
+
import { resetTitanArrays } from '../pipeline/layout/titan-engine'
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Registry State
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/** Map component ID to array index */
|
|
22
|
+
const idToIndex = new Map<string, number>()
|
|
23
|
+
|
|
24
|
+
/** Map array index to component ID */
|
|
25
|
+
const indexToId = new Map<number, string>()
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set of currently allocated indices (for iteration).
|
|
29
|
+
*
|
|
30
|
+
* Using ReactiveSet so deriveds that iterate over this set
|
|
31
|
+
* automatically react when components are added or removed.
|
|
32
|
+
*/
|
|
33
|
+
const allocatedIndices = new ReactiveSet<number>()
|
|
34
|
+
|
|
35
|
+
/** Pool of freed indices for reuse */
|
|
36
|
+
const freeIndices: number[] = []
|
|
37
|
+
|
|
38
|
+
/** Next index to allocate if pool is empty */
|
|
39
|
+
let nextIndex = 0
|
|
40
|
+
|
|
41
|
+
/** Counter for generating unique IDs */
|
|
42
|
+
let idCounter = 0
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Parent Context Stack
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
/** Stack of parent indices for nested component creation */
|
|
49
|
+
const parentStack: number[] = []
|
|
50
|
+
|
|
51
|
+
/** Get current parent index (-1 if at root) */
|
|
52
|
+
export function getCurrentParentIndex(): number {
|
|
53
|
+
return parentStack.length > 0 ? (parentStack[parentStack.length - 1] ?? -1) : -1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Push a parent index onto the stack */
|
|
57
|
+
export function pushParentContext(index: number): void {
|
|
58
|
+
parentStack.push(index)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Pop a parent index from the stack */
|
|
62
|
+
export function popParentContext(): void {
|
|
63
|
+
parentStack.pop()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Index Allocation
|
|
68
|
+
// =============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Allocate an index for a new component.
|
|
72
|
+
*
|
|
73
|
+
* @param id - Optional component ID. If not provided, one is generated.
|
|
74
|
+
* @returns The allocated index.
|
|
75
|
+
*/
|
|
76
|
+
export function allocateIndex(id?: string): number {
|
|
77
|
+
// Generate ID if not provided
|
|
78
|
+
const componentId = id ?? `c${idCounter++}`
|
|
79
|
+
|
|
80
|
+
// Check if already allocated
|
|
81
|
+
const existing = idToIndex.get(componentId)
|
|
82
|
+
if (existing !== undefined) {
|
|
83
|
+
return existing
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Reuse free index or allocate new
|
|
87
|
+
const index = freeIndices.length > 0
|
|
88
|
+
? freeIndices.pop()!
|
|
89
|
+
: nextIndex++
|
|
90
|
+
|
|
91
|
+
// Register mappings
|
|
92
|
+
idToIndex.set(componentId, index)
|
|
93
|
+
indexToId.set(index, componentId)
|
|
94
|
+
allocatedIndices.add(index)
|
|
95
|
+
|
|
96
|
+
// Ensure arrays have capacity for this index
|
|
97
|
+
ensureAllCapacity(index)
|
|
98
|
+
|
|
99
|
+
return index
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Release an index back to the pool.
|
|
104
|
+
*
|
|
105
|
+
* @param index - The index to release.
|
|
106
|
+
*/
|
|
107
|
+
export function releaseIndex(index: number): void {
|
|
108
|
+
const id = indexToId.get(index)
|
|
109
|
+
if (id === undefined) return
|
|
110
|
+
|
|
111
|
+
// Clean up mappings
|
|
112
|
+
idToIndex.delete(id)
|
|
113
|
+
indexToId.delete(index)
|
|
114
|
+
allocatedIndices.delete(index)
|
|
115
|
+
|
|
116
|
+
// Clear all array values at this index
|
|
117
|
+
clearAllAtIndex(index)
|
|
118
|
+
|
|
119
|
+
// Return to pool for reuse
|
|
120
|
+
freeIndices.push(index)
|
|
121
|
+
|
|
122
|
+
// AUTO-CLEANUP: When all components destroyed, reset all arrays to free memory
|
|
123
|
+
if (allocatedIndices.size === 0) {
|
|
124
|
+
resetAllArrays()
|
|
125
|
+
resetTitanArrays()
|
|
126
|
+
freeIndices.length = 0
|
|
127
|
+
nextIndex = 0
|
|
128
|
+
// Note: GC is NOT forced here to avoid performance hit during rapid create/destroy cycles
|
|
129
|
+
// The FinalizationRegistry will clean up automatically when GC runs naturally
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// Lookups
|
|
135
|
+
// =============================================================================
|
|
136
|
+
|
|
137
|
+
/** Get index for a component ID */
|
|
138
|
+
export function getIndex(id: string): number | undefined {
|
|
139
|
+
return idToIndex.get(id)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Get ID for an index */
|
|
143
|
+
export function getId(index: number): string | undefined {
|
|
144
|
+
return indexToId.get(index)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Get all currently allocated indices */
|
|
148
|
+
export function getAllocatedIndices(): Set<number> {
|
|
149
|
+
return allocatedIndices
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Check if an index is currently allocated */
|
|
153
|
+
export function isAllocated(index: number): boolean {
|
|
154
|
+
return allocatedIndices.has(index)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Get the current capacity (highest index that would be allocated next) */
|
|
158
|
+
export function getCapacity(): number {
|
|
159
|
+
return nextIndex
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Get the count of currently allocated components */
|
|
163
|
+
export function getAllocatedCount(): number {
|
|
164
|
+
return allocatedIndices.size
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// Reset (for testing)
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
/** Reset all registry state (for testing) */
|
|
172
|
+
export function resetRegistry(): void {
|
|
173
|
+
idToIndex.clear()
|
|
174
|
+
indexToId.clear()
|
|
175
|
+
allocatedIndices.clear()
|
|
176
|
+
freeIndices.length = 0
|
|
177
|
+
nextIndex = 0
|
|
178
|
+
idCounter = 0
|
|
179
|
+
parentStack.length = 0
|
|
180
|
+
}
|