Skip to content

fix: Add global model cache to prevent duplicate loading and browser lag (#93)#678

Open
1234-ad wants to merge 1 commit intotscircuit:mainfrom
1234-ad:fix/obj-loader-caching-93
Open

fix: Add global model cache to prevent duplicate loading and browser lag (#93)#678
1234-ad wants to merge 1 commit intotscircuit:mainfrom
1234-ad:fix/obj-loader-caching-93

Conversation

@1234-ad
Copy link

@1234-ad 1234-ad commented Feb 5, 2026

Description

This PR fixes the laggy browser issue caused by duplicate model loading by implementing a global model cache for all 3D model types (OBJ, STL, GLTF, GLB, WRL).

Problem

The current implementation in load3DModel() creates new loader instances and loads models from scratch every time they're requested, even if the same model URL has been loaded before. This causes:

  • Duplicate network requests for the same model files
  • Redundant parsing and geometry creation
  • Excessive memory usage
  • Browser lag, especially with complex models or multiple instances

Example project showing the issue: https://tscircuit.com/editor?snippet_id=22c651e7-a6d2-436d-b2b6-ba0b0921e95e

Solution

Implemented a global model cache (TSCIRCUIT_3D_MODEL_CACHE) that:

  1. Caches loaded models - Stores successfully loaded models in a global Map keyed by URL
  2. Deduplicates in-flight requests - If a model is currently being loaded, subsequent requests wait for the same promise instead of creating new requests
  3. Returns cloned instances - Each request gets a cloned copy of the cached model to avoid sharing the same Three.js object (which would cause issues with transformations)
  4. Handles all model types - Works with OBJ, STL, GLTF, GLB, and WRL files
  5. URL cleaning - Removes cache-busting parameters before caching to maximize cache hits

Changes

Modified Files

  • src/utils/load-model.ts - Added global caching mechanism

Key Implementation Details

// Global cache structure
interface ModelCacheItem {
  promise: Promise<THREE.Object3D | null>
  result: THREE.Object3D | null
}

window.TSCIRCUIT_3D_MODEL_CACHE = new Map<string, ModelCacheItem>()

Cache Flow:

  1. Clean URL (remove cache-busting params)
  2. Check if model exists in cache
    • If cached with result → return cloned copy
    • If loading in progress → wait for promise, then clone
  3. If not in cache → load model, store promise immediately, store result when loaded

Benefits

Performance: Models are loaded once and reused
Memory efficient: Single copy of geometry data per unique model
Network efficient: No duplicate HTTP requests
Browser responsiveness: Eliminates lag from redundant loading
Backward compatible: No API changes, drop-in replacement

Testing

Tested with:

  • Multiple instances of the same OBJ model
  • Mixed model types (OBJ, STL, GLTF)
  • Concurrent requests for the same model
  • Cache-busting URLs

Notes

  • This implementation is similar to the existing useGlobalObjLoader hook but applies to the lower-level load3DModel utility
  • The cache persists for the lifetime of the page, which is appropriate for 3D models
  • Cloning ensures each component can transform its model independently

Closes

Closes #93

Bounty

This PR addresses the $5 bounty issue.

@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
3d-viewer Ready Ready Preview, Comment Feb 5, 2026 5:54pm

Request Review

@buildingvibes
Copy link

The format-check CI is failing due to trailing whitespace on 3 blank lines in src/utils/load-model.ts.

Fix: Lines 58, 64, and 69 have trailing spaces that need to be removed. Running biome format . --write (or npm run format) resolves the issue.

Here's the diff:

--- a/src/utils/load-model.ts
+++ b/src/utils/load-model.ts
@@ -55,18 +55,18 @@ async function loadModelFromUrl(url: string): Promise<THREE.Object3D | null> {
 export async function load3DModel(url: string): Promise<THREE.Object3D | null> {
   // Clean the URL (remove cache busting parameters)
   const cleanUrl = url.replace(/&cachebust_origin=$/, "")
-  
+
   const cache = window.TSCIRCUIT_3D_MODEL_CACHE
 
   // Check if model is already in cache
   if (cache.has(cleanUrl)) {
     const cacheItem = cache.get(cleanUrl)!
-    
+
     // If we have a cached result, clone it to avoid sharing the same object
     if (cacheItem.result) {
       return cacheItem.result.clone()
     }
-    
+
     // If we're still loading, wait for the existing promise and clone the result
     return cacheItem.promise.then((result) => {
       return result ? result.clone() : null

I've also pushed the fix to buildingvibes/3d-viewer on branch fix/obj-loader-caching-93 if a maintainer wants to cherry-pick it.

@buildingvibes
Copy link

Closing in favor of #685 which includes the same changes with the format-check CI fix applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve 3d model loading to avoid laggy browser

2 participants