maplibre-copc-layer 0.0.1

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,184 @@
1
+ import * as THREE from 'three'
2
+
3
+ export interface CachedNodeData {
4
+ nodeId: string
5
+ positions: Float64Array
6
+ colors: Float32Array
7
+ pointCount: number
8
+ geometry?: THREE.BufferGeometry
9
+ points?: THREE.Points
10
+ materialConfig: {
11
+ colorMode: string
12
+ pointSize: number
13
+ depthTest: boolean
14
+ }
15
+ lastAccessed: number
16
+ sizeBytes: number
17
+ }
18
+
19
+ export interface CacheManagerOptions {
20
+ maxNodes?: number
21
+ maxMemoryBytes?: number
22
+ debug?: boolean
23
+ }
24
+
25
+ export class CacheManager {
26
+ private cache = new Map<string, CachedNodeData>()
27
+ private memoryUsage = 0
28
+ private options: Required<CacheManagerOptions>
29
+
30
+ constructor(options: CacheManagerOptions = {}) {
31
+ this.options = {
32
+ maxNodes: options.maxNodes ?? 100,
33
+ maxMemoryBytes: options.maxMemoryBytes ?? 100 * 1024 * 1024,
34
+ debug: options.debug ?? false,
35
+ }
36
+ }
37
+
38
+ get(nodeId: string): CachedNodeData | null {
39
+ const data = this.cache.get(nodeId)
40
+ if (data) {
41
+ // Move to end of Map iteration order (most recently used)
42
+ this.cache.delete(nodeId)
43
+ this.cache.set(nodeId, data)
44
+ data.lastAccessed = Date.now()
45
+ return data
46
+ }
47
+ return null
48
+ }
49
+
50
+ set(nodeData: CachedNodeData, protectedNodes?: Set<string>): void {
51
+ const { nodeId } = nodeData
52
+ const existing = this.cache.get(nodeId)
53
+ if (existing) {
54
+ this.disposeNodeResources(existing)
55
+ this.memoryUsage -= existing.sizeBytes
56
+ this.cache.delete(nodeId)
57
+ }
58
+
59
+ this.ensureCacheLimits(nodeData.sizeBytes, protectedNodes)
60
+
61
+ nodeData.lastAccessed = Date.now()
62
+ this.cache.set(nodeId, nodeData)
63
+ this.memoryUsage += nodeData.sizeBytes
64
+
65
+ this.log(`Cached node ${nodeId} (${this.formatBytes(nodeData.sizeBytes)})`)
66
+ }
67
+
68
+ has(nodeId: string): boolean {
69
+ return this.cache.has(nodeId)
70
+ }
71
+
72
+ delete(nodeId: string): boolean {
73
+ const data = this.cache.get(nodeId)
74
+ if (!data) return false
75
+
76
+ this.disposeNodeResources(data)
77
+ this.cache.delete(nodeId)
78
+ this.memoryUsage -= data.sizeBytes
79
+
80
+ this.log(`Removed node ${nodeId} from cache`)
81
+ return true
82
+ }
83
+
84
+ clear(): void {
85
+ for (const data of this.cache.values()) {
86
+ this.disposeNodeResources(data)
87
+ }
88
+ this.cache.clear()
89
+ this.memoryUsage = 0
90
+ }
91
+
92
+ updateOptions(
93
+ newOptions: Partial<CacheManagerOptions>,
94
+ protectedNodes?: Set<string>,
95
+ ): void {
96
+ Object.assign(this.options, newOptions)
97
+ this.ensureCacheLimits(0, protectedNodes)
98
+ }
99
+
100
+ getCachedNodeIds(): string[] {
101
+ return Array.from(this.cache.keys())
102
+ }
103
+
104
+ size(): number {
105
+ return this.cache.size
106
+ }
107
+
108
+ static estimateNodeSize(
109
+ positions: Float64Array | Float32Array,
110
+ colors: Float32Array,
111
+ ): number {
112
+ const positionSize =
113
+ positions.length * (positions instanceof Float64Array ? 8 : 4)
114
+ const colorSize = colors.length * 4
115
+ return positionSize + colorSize + 1024
116
+ }
117
+
118
+ static createNodeData(
119
+ nodeId: string,
120
+ positions: Float64Array,
121
+ colors: Float32Array,
122
+ materialConfig: CachedNodeData['materialConfig'],
123
+ ): CachedNodeData {
124
+ return {
125
+ nodeId,
126
+ positions: new Float64Array(positions),
127
+ colors: new Float32Array(colors),
128
+ pointCount: positions.length / 3,
129
+ materialConfig: { ...materialConfig },
130
+ lastAccessed: Date.now(),
131
+ sizeBytes: CacheManager.estimateNodeSize(positions, colors),
132
+ }
133
+ }
134
+
135
+ private ensureCacheLimits(
136
+ newItemSize: number,
137
+ protectedNodes?: Set<string>,
138
+ ): void {
139
+ while (
140
+ this.cache.size >= this.options.maxNodes ||
141
+ this.memoryUsage + newItemSize > this.options.maxMemoryBytes
142
+ ) {
143
+ if (this.cache.size === 0) break
144
+
145
+ // Map iterates in insertion order; first key is the LRU candidate
146
+ let lruNodeId: string | null = null
147
+ for (const nodeId of this.cache.keys()) {
148
+ if (!protectedNodes?.has(nodeId)) {
149
+ lruNodeId = nodeId
150
+ break
151
+ }
152
+ }
153
+
154
+ if (!lruNodeId) {
155
+ this.log(
156
+ 'Warning: Cannot evict any nodes - all are protected. Cache limit exceeded.',
157
+ )
158
+ break
159
+ }
160
+
161
+ this.delete(lruNodeId)
162
+ }
163
+ }
164
+
165
+ private disposeNodeResources(data: CachedNodeData): void {
166
+ data.geometry?.dispose()
167
+ if (data.points?.material instanceof THREE.Material) {
168
+ data.points.material.dispose()
169
+ }
170
+ }
171
+
172
+ private formatBytes(bytes: number): string {
173
+ const sizes = ['B', 'KB', 'MB', 'GB']
174
+ if (bytes === 0) return '0 B'
175
+ const i = Math.floor(Math.log(bytes) / Math.log(1024))
176
+ return `${Math.round((bytes / 1024 ** i) * 100) / 100} ${sizes[i]}`
177
+ }
178
+
179
+ private log(message: string): void {
180
+ if (this.options.debug) {
181
+ console.log('[CacheManager]', message)
182
+ }
183
+ }
184
+ }