Proxy-back sceneRegistry.byType for registry/plugin kinds (shim 1/4)
byType was a hardcoded object keyed by the built-in node kinds. With
the registry, kinds can come from @pascal-app/nodes (or future
plugins) — so byType now wraps a Map via a Proxy that auto-creates an
empty Set the first time any kind is touched.
Built-in kinds are still pre-seeded at module init so the fast path
(no Proxy trap) is preserved. clear() iterates the backing Map.
useRegistry's `type` parameter widens from `keyof typeof byType` to
`KnownNodeKind | (string & {})` — preserves autocomplete for
built-ins while accepting plugin-supplied kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4d5226ce10
commit
4c3feea052
@@ -3,49 +3,85 @@
|
|||||||
import { useLayoutEffect } from 'react'
|
import { useLayoutEffect } from 'react'
|
||||||
import type * as THREE from 'three'
|
import type * as THREE from 'three'
|
||||||
|
|
||||||
|
const KNOWN_NODE_KINDS = [
|
||||||
|
'site',
|
||||||
|
'building',
|
||||||
|
'ceiling',
|
||||||
|
'column',
|
||||||
|
'elevator',
|
||||||
|
'level',
|
||||||
|
'wall',
|
||||||
|
'fence',
|
||||||
|
'item',
|
||||||
|
'slab',
|
||||||
|
'spawn',
|
||||||
|
'zone',
|
||||||
|
'roof',
|
||||||
|
'roof-segment',
|
||||||
|
'stair',
|
||||||
|
'stair-segment',
|
||||||
|
'scan',
|
||||||
|
'guide',
|
||||||
|
'window',
|
||||||
|
'door',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type KnownNodeKind = (typeof KNOWN_NODE_KINDS)[number]
|
||||||
|
// Allow registry-registered (plugin) kinds while keeping autocomplete for built-ins.
|
||||||
|
type NodeKind = KnownNodeKind | (string & {})
|
||||||
|
|
||||||
|
type ByTypeShape = Record<KnownNodeKind, Set<string>> & Record<string, Set<string>>
|
||||||
|
|
||||||
|
const byTypeStore = new Map<string, Set<string>>(
|
||||||
|
KNOWN_NODE_KINDS.map((k) => [k, new Set<string>()]),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-creates a Set the first time an unknown kind is accessed. This is what
|
||||||
|
// lets registry-registered (and future plugin-contributed) kinds participate
|
||||||
|
// in `byType` without being hardcoded here.
|
||||||
|
const byTypeProxy = new Proxy({} as ByTypeShape, {
|
||||||
|
get(_target, key) {
|
||||||
|
if (typeof key !== 'string') return undefined
|
||||||
|
let set = byTypeStore.get(key)
|
||||||
|
if (!set) {
|
||||||
|
set = new Set<string>()
|
||||||
|
byTypeStore.set(key, set)
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
},
|
||||||
|
ownKeys() {
|
||||||
|
return Array.from(byTypeStore.keys())
|
||||||
|
},
|
||||||
|
has(_target, key) {
|
||||||
|
return typeof key === 'string' && byTypeStore.has(key)
|
||||||
|
},
|
||||||
|
getOwnPropertyDescriptor(_target, key) {
|
||||||
|
if (typeof key !== 'string') return undefined
|
||||||
|
const set = byTypeStore.get(key)
|
||||||
|
if (!set) return undefined
|
||||||
|
return { configurable: true, enumerable: true, value: set, writable: false }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const sceneRegistry = {
|
export const sceneRegistry = {
|
||||||
// Master lookup: ID -> Object3D
|
// Master lookup: ID -> Object3D
|
||||||
nodes: new Map<string, THREE.Object3D>(),
|
nodes: new Map<string, THREE.Object3D>(),
|
||||||
|
|
||||||
// Categorized lookups: Type -> Set of IDs
|
// Categorized lookups: Kind -> Set of IDs.
|
||||||
// Using a Set is faster for adding/deleting than an Array
|
// Backed by a Proxy so registry-registered kinds get a Set on first touch,
|
||||||
byType: {
|
// while built-in kinds remain present from module init for fast paths.
|
||||||
site: new Set<string>(),
|
byType: byTypeProxy,
|
||||||
building: new Set<string>(),
|
|
||||||
ceiling: new Set<string>(),
|
|
||||||
column: new Set<string>(),
|
|
||||||
elevator: new Set<string>(),
|
|
||||||
level: new Set<string>(),
|
|
||||||
wall: new Set<string>(),
|
|
||||||
fence: new Set<string>(),
|
|
||||||
item: new Set<string>(),
|
|
||||||
slab: new Set<string>(),
|
|
||||||
spawn: new Set<string>(),
|
|
||||||
zone: new Set<string>(),
|
|
||||||
roof: new Set<string>(),
|
|
||||||
'roof-segment': new Set<string>(),
|
|
||||||
stair: new Set<string>(),
|
|
||||||
'stair-segment': new Set<string>(),
|
|
||||||
scan: new Set<string>(),
|
|
||||||
guide: new Set<string>(),
|
|
||||||
window: new Set<string>(),
|
|
||||||
door: new Set<string>(),
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Remove all entries. Call when unloading a scene to prevent stale 3D refs. */
|
/** Remove all entries. Call when unloading a scene to prevent stale 3D refs. */
|
||||||
clear() {
|
clear() {
|
||||||
this.nodes.clear()
|
this.nodes.clear()
|
||||||
for (const set of Object.values(this.byType)) {
|
for (const set of byTypeStore.values()) {
|
||||||
set.clear()
|
set.clear()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRegistry(
|
export function useRegistry(id: string, type: NodeKind, ref: React.RefObject<THREE.Object3D>) {
|
||||||
id: string,
|
|
||||||
type: keyof typeof sceneRegistry.byType,
|
|
||||||
ref: React.RefObject<THREE.Object3D>,
|
|
||||||
) {
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const obj = ref.current
|
const obj = ref.current
|
||||||
if (!obj) return
|
if (!obj) return
|
||||||
@@ -53,13 +89,14 @@ export function useRegistry(
|
|||||||
// 1. Add to master map
|
// 1. Add to master map
|
||||||
sceneRegistry.nodes.set(id, obj)
|
sceneRegistry.nodes.set(id, obj)
|
||||||
|
|
||||||
// 2. Add to type-specific set
|
// 2. Add to type-specific set — Proxy auto-creates on first access so the
|
||||||
sceneRegistry.byType[type].add(id)
|
// assertion is safe; TS just can't see through the Proxy.
|
||||||
|
sceneRegistry.byType[type]!.add(id)
|
||||||
|
|
||||||
// 4. Cleanup when component unmounts
|
// 4. Cleanup when component unmounts
|
||||||
return () => {
|
return () => {
|
||||||
sceneRegistry.nodes.delete(id)
|
sceneRegistry.nodes.delete(id)
|
||||||
sceneRegistry.byType[type].delete(id)
|
sceneRegistry.byType[type]!.delete(id)
|
||||||
}
|
}
|
||||||
}, [id, type, ref])
|
}, [id, type, ref])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user