UI: Cubes improve memory usage

This commit is contained in:
Johannes Kirschbauer
2025-07-08 22:27:19 +02:00
parent f555dd349e
commit 8493776ea6
2 changed files with 75 additions and 12 deletions

View File

@@ -20,4 +20,4 @@ if (import.meta.env.DEV) {
await import("solid-devtools");
}
render(() => <CubeScene/>, root!);
render(() => <CubeScene />, root!);

View File

@@ -15,10 +15,20 @@ export function CubeScene() {
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let raycaster: THREE.Raycaster;
let controls: any; // OrbitControls type
let meshMap = new Map<string, THREE.Mesh>();
let baseMap = new Map<string, THREE.Mesh>(); // Map for cube bases
let sharedCubeGeometry: THREE.BoxGeometry;
let sharedBaseGeometry: THREE.BoxGeometry;
// Used for development purposes
// Vite does hot-reload but we need to ensure the animation loop doesn't run multiple times
// This flag prevents multiple animation loops from running simultaneously
// It is set to true when the component mounts and false when it unmounts
let isAnimating = false; // Flag to prevent multiple loops
let frameCount = 0;
const [cubes, setCubes] = createSignal<CubeData[]>([]);
const [selectedIds, setSelectedIds] = createSignal<Set<string>>(new Set());
const [cameraInfo, setCameraInfo] = createSignal({
@@ -66,9 +76,8 @@ export function CubeScene() {
// Create white base for cube
function createCubeBase(cube_pos: [number, number, number]) {
const baseGeometry = new THREE.BoxGeometry(1.2, 0.05, 1.2); // 1.2 times cube size, thin height
const baseMaterials = createBaseMaterials();
const base = new THREE.Mesh(baseGeometry, baseMaterials);
const base = new THREE.Mesh(sharedBaseGeometry, baseMaterials);
// tranlate_y = - cube_height / 2 - base_height / 2
base.position.set(cube_pos[0], cube_pos[1] - 0.5 - 0.025, cube_pos[2]); // Position below cube
base.receiveShadow = true;
@@ -88,14 +97,35 @@ export function CubeScene() {
}
function deleteCube(id: string) {
setCubes((prev) => prev.filter((c) => c.id !== id));
// Remove cube mesh
const mesh = meshMap.get(id);
if (mesh) {
scene.remove(mesh);
mesh.geometry.dispose();
(mesh.material as THREE.Material).dispose();
// Dispose materials properly
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) => material.dispose());
} else {
mesh.material.dispose();
}
meshMap.delete(id);
}
// Remove base mesh - THIS WAS MISSING!
const base = baseMap.get(id);
if (base) {
scene.remove(base);
base.geometry.dispose();
// Dispose base materials properly
if (Array.isArray(base.material)) {
base.material.forEach((material) => material.dispose());
} else {
base.material.dispose();
}
baseMap.delete(id);
}
setCubes((prev) => prev.filter((c) => c.id !== id));
}
function toggleSelection(id: string) {
@@ -137,6 +167,18 @@ export function CubeScene() {
}
}
function logMemoryUsage() {
if (renderer && renderer.info) {
console.log("Three.js Memory:", {
geometries: renderer.info.memory.geometries,
textures: renderer.info.memory.textures,
programs: renderer.info.programs?.length || 0,
calls: renderer.info.render.calls,
triangles: renderer.info.render.triangles,
});
}
}
onMount(() => {
// Scene setup
scene = new THREE.Scene();
@@ -196,6 +238,11 @@ export function CubeScene() {
floor.position.y = 0; // Keep at ground level for reference
scene.add(floor);
// Shared geometries for cubes and bases
// This allows us to reuse the same geometry for all cubes and bases
sharedCubeGeometry = new THREE.BoxGeometry(1, 1, 1);
sharedBaseGeometry = new THREE.BoxGeometry(1.2, 0.05, 1.2);
// Basic OrbitControls implementation (simplified)
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
@@ -288,11 +335,18 @@ export function CubeScene() {
renderer.domElement.addEventListener("click", onClick);
// Animation loop
const animate = () => {
if (!isAnimating) return; // Exit if component is unmounted
requestAnimationFrame(animate);
frameCount++;
renderer.render(scene, camera);
// Uncomment for memory debugging:
if (frameCount % 60 === 0) logMemoryUsage(); // Log every 60 frames
};
isAnimating = true;
animate();
// Handle window resize
@@ -305,12 +359,18 @@ export function CubeScene() {
// Cleanup function
onCleanup(() => {
// Stop animation loop
isAnimating = false;
renderer.domElement.removeEventListener("mousedown", onMouseDown);
renderer.domElement.removeEventListener("mouseup", onMouseUp);
renderer.domElement.removeEventListener("mousemove", onMouseMove);
renderer.domElement.removeEventListener("wheel", onWheel);
renderer.domElement.removeEventListener("click", onClick);
window.removeEventListener("resize", handleResize);
if (container) {
container.innerHTML = "";
}
});
});
@@ -322,9 +382,8 @@ export function CubeScene() {
cubes().forEach((cube) => {
if (!meshMap.has(cube.id)) {
// Create cube mesh
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterials = createCubeMaterials();
const mesh = new THREE.Mesh(cubeGeometry, cubeMaterials);
const mesh = new THREE.Mesh(sharedCubeGeometry, cubeMaterials);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.set(...cube.position);
@@ -356,9 +415,7 @@ export function CubeScene() {
});
onCleanup(() => {
renderer?.dispose();
for (const mesh of meshMap.values()) {
mesh.geometry.dispose();
// Handle both single material and material array
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) => material.dispose());
@@ -367,8 +424,8 @@ export function CubeScene() {
}
}
meshMap.clear();
for (const mesh of baseMap.values()) {
mesh.geometry.dispose();
// Handle both single material and material array
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) => material.dispose());
@@ -377,6 +434,12 @@ export function CubeScene() {
}
}
baseMap.clear();
// Dispose shared geometries
sharedCubeGeometry?.dispose();
sharedBaseGeometry?.dispose();
renderer?.dispose();
});
return (