Local caching using IndexedDB

Hi!
I would like to share a code snippet for easy local caching of your 3d assets. I am using Dexie (a minimalistic IndexedDB wrapper). https://dexie.org/
Since this example uses Typescript, it’s a good idea to have a look here how to use Dexie with Typescript: Typescript

The code snippet is taken out from a class thus the method definition in step 4 and usage of this in step 5.

Let’s start:

  1. Install Dexie
    npm i dexie

  2. import Dexie in your script
    import Dexie from 'dexie'

  3. define the table and create our database class. The key is the remote url of the file. Data is the binary data itself. It is stored as a Blob object.

interface ICachedModel {
  url: string
  data: Blob
}

class ModelDatabase extends Dexie {
  models: Dexie.Table<ICachedModel, string>

  constructor() {
    super('ModelDatabase')
    this.version(1).stores({
      models: 'url, data'
    })
    this.models = this.table('models')
  }
}
  1. let’s create a method to load from local cache or remote url. The local copy takes precedence. Set forceRemote to force loading from the remote url and store the new data in the local cache.
async loadFromUrlOrLocal(
    url: string,
    filename: string,
    scene: Scene,
    forceRemote = false
  ): Promise<ISceneLoaderAsyncResult | null> {

    try {
      let blob: Blob | null = null

      const db = new ModelDatabase()
      const key = url + filename
      const data = await db.models.get(key)
      if (data && !forceRemote) {
        // local data available and remote is not forced
        blob = data.data

        console.log('Loaded from local using key', key, 'size:', blob.size)
      } else {
        // local data not available or remote is forced
        const data = await fetch(url + filename)
        blob = await data.blob()
 
       // store in local cache
        await db.models.put({
          url: key,
          data: blob
        })

        console.log('Loaded from remote url', url + filename, blob.size)
      }

      if (blob) {
        // import from blob to scene
        const file = new File([blob], filename, { type: 'application/octet-stream' })
        const imported = await SceneLoader.ImportMeshAsync('', '', file, scene)
        return imported
      }
    } catch (e) {
      console.error('Failed to load', url + filename, e)
    }

    // failed to load
    return null
  }
  1. now you can use the new method instead of directly call the SceneLoader.ImportMeshAsync method.
      // const imported = await SceneLoader.ImportMeshAsync('', baseUrl, t.filename, this._scene)
      const forceRemote = true // set this for example using document.location.href.indexOf("forceRemote") > -1 or from a config file or whatever source
      const imported = await this.loadFromUrlOrLocal(baseUrl, t.filename, this._scene, forceRemote)
      if (imported) {
         // process the loaded stuff
      }

Hope it helps! :vulcan_salute:

R.

3 Likes

On a side note, Babylon does have an offline caching mechanism, based on indexeddb:

It is not an on-demand storage but uses a manifest file to cache .babylon scenes (including their textures).

2 Likes

Thank you! There is always something new to learn :vulcan_salute:

1 Like