Asset cache

Usage guide of asset.cache: keeping the game files on the player’s device between visits, stores and fallbacks, quota and eviction, settings, services, events and hooks.

Source: module-kits:kits/asset_cache/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs asset-cacheMCP tool: gripforge_docs { "topic": "asset-cache" }

Asset cache (asset.cache) #

What it is for #

Keeps the game’s files on the player’s device so a second visit downloads nothing that did not change. In a page it works down a chain of stores the game allows; entries are keyed by path and SHA-256, so a new version of a file is another key and the old one is removed. It is one of three kits that work together: see the asset loading guide for the loading plan and the passports.

In a page the stores are tried in this order: IndexedDB; else Cache Storage with a service worker scoped to the game, which serves the kept files to every request of the game; else the browser HTTP cache, driven by the headers of your servers; else nothing (each file is still downloaded once by asset.preload). Outside a page, the native player reads its files from disk and the kit keeps its decisions on an in-memory store.

Turn it on #

gripforge_gamekit_install   { project_id: "gkp_…", id: "asset.cache" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "asset.cache", config: { "quotaMB": 256, "eviction": "lru" } }

First example #

<!-- test: kits/game_engine/host/tests/parity.rs::rust_host_matches_the_typescript_traces (fixture asset.cache.hooks) --> A game refuses to keep an intro video (a hook document) and a mod sets minBytes to 3 and another refuses music. Writing four files keeps only assets/hero.glb: the checksum is compared without regard to case, so reading ABC2 finds it.

{ "id": "intro_fresh", "hook": "asset.cache.store", "match": { "path": "assets/intro.webm" }, "cancel": true, "reason": "fresh at every visit" }

Settings and defaults #

SettingDefaultMeaning
enabledtrueOff: nothing is kept; every visit downloads the files (once each)
quotaMB512Megabytes the store may hold; past it the entries read longest ago leave first (0 keeps nothing)
version1Generation of the store: changing it empties the store at the next visit
store"auto"auto walks the chain of fallbacks in a page, memory elsewhere; a store name (indexeddb, cachestorage, http, memory) forces it; off keeps nothing
fallbacks["indexeddb","cachestorage","http"]The stores a page may use, in order; none left means no cache (each file still downloaded once)
eviction"lru"Which entries leave at the quota: lru (read longest ago), fifo (written first), none (a full store keeps nothing more)
serviceWorkertrueA service worker scoped to the game folder serves the kept files to every request of the game, by checksum only
minBytes0Files smaller than this many bytes are not kept

A game or a mod may change enabled, quotaMB, fallbacks, eviction and minBytes; version, store and serviceWorker belong to how the game is delivered.

Services #

  • stats(): the mode in use (indexeddb, cachestorage, http, memory, off), entries, bytes, quota, hits, misses, writes, evicted, and the reason and tried stores when one was refused.
  • entries(): the kept files as { key, path, sha, bytes, used }.
  • write(path, sha, bytes): keeps a file (an empty checksum or non-byte data is refused; the hook below may refuse it too); answers whether it was kept.
  • read(path, sha): the bytes of a kept file, or null when absent or when the checksum differs (a read counts as a use for the eviction order).
  • invalidate(path) removes every entry of a path; clear() empties the store.

Variables: asset.cache.mode (the store in use), asset.cache.bytes and asset.cache.entries, for a debug panel or a loading screen.

Events #

NameDetail
asset.cache.openedThe cache is open: payload { mode, tried, quota, eviction }, the store in use and the ones refused before it.
asset.cache.hitA file was read from the store, so nothing was downloaded; payload { path, sha, bytes, mode }.
asset.cache.missA file is not in the store or cannot be read, so it is downloaded; payload { path, sha, reason, mode } with reason one of absent, damaged, no_checksum, http, off, refused.
asset.cache.storedA downloaded and checked file was kept; payload { path, sha, bytes, mode }.
asset.cache.evictedEntries left to make room; payload { keys, bytes, reason } (reason quota).
asset.cache.invalidatedThe entries of a file were removed, because a new checksum replaced them or invalidate(path) was called; payload { path, keys }.
asset.cache.unavailableThe store cannot be used; payload { reason, error } with reason refused (private browsing or blocked storage), quota (the browser’s quota is reached) or disabled (the settings). The game goes on without a cache.

Hooks and modding #

A game or a mod taps a hook with a document of its hooks list, without touching the kit:

  • asset.cache.store: before a downloaded, checked file is kept; payload { path, sha, bytes }; cancel to refuse it (a file wanted fresh at every visit, a file too large for this game).
  • asset.cache.evict: before an entry leaves to make room, in the order of the eviction policy; payload { key, path, bytes, used, policy }; cancel to keep it, and the next entry is asked (a file the game keeps whatever happens).

When it does not do what you expect #

  • Nothing is ever a hit: the checksum is missing (no_checksum), the store is off or disabled, or the browser blocks storage (unavailable with refused, as in private browsing); check stats().
  • Everything is downloaded again after a release: the version setting changed, which empties the store at the next visit.

Limits #

  • It stores files by checksum: a file without a SHA-256 is never kept.
  • The native player keeps nothing on disk through this kit; it only applies the same decisions (keys, quota, order of eviction, the store hook) on a store in memory.

Versions #

1.0.0 (2026-10-11): first release. Requires game.engine; used by asset.preload.