Core components
Core engine
Asset management system
17 min
overview the asset management system is responsible for handling all the digital resources that make up a virtual world in the ir engine these resources, known as assets, include 3d models, textures, audio files, and more the system provides a structured approach to locating, loading, processing, and managing these assets efficiently by centralizing asset handling, the engine ensures that resources are loaded when needed, shared between different parts of the application, and properly managed throughout their lifecycle this chapter explores the concepts, structure, and implementation of the asset management system within the ir engine core concepts assets assets are the digital resources that make up a game or virtual environment the ir engine supports various types of assets // simplified from src/assets/constants/assettype ts export enum assettype { model = 'model', // 3d models (characters, objects, environments) image = 'image', // textures and images audio = 'audio', // sound effects and music // and others } export enum assetext { glb = 'glb', // for 3d models in gltf binary format gltf = 'gltf', // for 3d models in gltf text format png = 'png', // for images jpeg = 'jpeg', // for images mp3 = 'mp3', // for audio wav = 'wav', // for audio // and others } these enumerations help the system identify and categorize assets based on their type and file extension, enabling appropriate handling for each asset type asset loaders asset loaders are specialized components that know how to read specific file formats and convert them into usable in memory representations different asset types require different loaders model loaders parse 3d model files (e g , glb, gltf) into geometry, materials, and animations image loaders load image files (e g , png, jpeg) into textures audio loaders process audio files (e g , mp3, wav) into playable sound resources the system selects the appropriate loader based on the asset's file extension // simplified concept from src/assets/classes/assetloader ts function getloader(filename string) { const fileextension = filename split(' ') pop()? tolowercase(); switch (fileextension) { case 'glb' case 'gltf' return new gltfloader(); case 'png' case 'jpg' case 'jpeg' return new textureloader(); case 'mp3' case 'wav' return new audioloader(); // other asset types default throw new error(`no loader available for extension ${fileextension}`); } } asset cache the asset cache is a central repository that stores loaded assets in memory, preventing redundant loading of the same asset in the ir engine, this cache is implemented using hyperflux state management // simplified from src/assets/state/assetcachestate ts import { definestate } from '@ir engine/hyperflux'; import { entity } from '@ir engine/ecs'; export enum resourcestatus { unloaded, loading, loaded, error } export const assetcachestate = definestate({ name 'assetcachestate', initial {} as record\<string, { asset unknown; // the actual loaded data status resourcestatus; // current loading status references entity\[]; // entities using this asset // other metadata }> }); the cache provides several key benefits performance assets are loaded only once, even if used by multiple entities memory efficiency assets can be shared rather than duplicated reference tracking the system knows which entities are using each asset status tracking the loading state of each asset is monitored implementation loading assets the primary interface for requesting assets is through functions like loadresource // simplified concept from src/assets/functions/resourceloaderfunctions ts function loadresource( url string, // path to the asset resourcetype assettype, // type of asset expected entity entity, // entity requesting the asset onload (asset any) => void, // callback when loading succeeds onprogress? (event progressevent) => void, // optional progress tracking onerror? (error error) => void, // optional error handling abortsignal? abortsignal // optional cancellation ) void { // check if the asset is already in the cache const cachestate = getstate(assetcachestate); const cachedasset = cachestate\[url]; if (cachedasset) { // asset is in the cache if (cachedasset status === resourcestatus loaded) { // asset is already loaded add the entity to references and return the asset addentitytoassetreferences(url, entity); onload(cachedasset asset); return; } else if (cachedasset status === resourcestatus loading) { // asset is currently loading add the entity to references and wait addentitytoassetreferences(url, entity); // set up a listener for when loading completes // return; } } // asset is not in the cache or needs to be reloaded // mark as loading in the cache const mutablecache = getmutablestate(assetcachestate); mutablecache\[url] set({ asset null, status resourcestatus loading, references \[entity] }); // get the appropriate loader for this asset type const loader = getloader(url); // start loading the asset loader load( url, (loadedasset) => { // update cache with the loaded asset mutablecache\[url] set({ asset loadedasset, status resourcestatus loaded, references \[entity] }); // notify the requester onload(loadedasset); }, onprogress, (error) => { // update cache with error status mutablecache\[url] set({ asset null, status resourcestatus error, references \[entity], error error message }); // notify the requester of the error if (onerror) onerror(error); } ); } this function checks if the asset is already in the cache if it's loaded, returns it immediately if it's loading, adds the entity to the references and sets up a callback if it's not in the cache, starts loading it with the appropriate loader updates the cache with the loaded asset or error information unloading assets when an entity no longer needs an asset, the system must be notified to update reference counts // simplified concept from src/assets/functions/resourceloaderfunctions ts function unloadresource(url string, entity entity) void { const mutablecache = getmutablestate(assetcachestate); const cachedasset = mutablecache\[url] value; if (!cachedasset) return; // asset not in cache // remove the entity from references const newreferences = cachedasset references filter(ref => ref !== entity); if (newreferences length === 0) { // no more references asset can be fully unloaded // perform any cleanup needed for this asset type // // remove from cache mutablecache\[url] set(undefined); } else { // update references mutablecache\[url] merge({ references newreferences }); } } this function removes the entity from the asset's reference list if no entities are using the asset anymore, performs cleanup and removes it from the cache otherwise, updates the reference list in the cache asset loading workflow the process of loading an asset follows this general workflow sequencediagram participant gamelogic as "game logic" participant assetsystem as "asset management system" participant assetcache as "asset cache (assetcachestate)" participant specificloader as "asset loader (e g , gltfloader)" participant filesystem as "file system / network" gamelogic >>assetsystem loadresource("models/spaceship glb", entity) assetsystem >>assetcache check if "models/spaceship glb" is cached alt asset in cache and loaded assetcache >>assetsystem return cached asset assetsystem >>assetcache add entity to references assetsystem >>gamelogic provide asset immediately else asset not in cache or not loaded assetcache >>assetsystem asset not available assetsystem >>assetcache mark "models/spaceship glb" as loading assetsystem >>specificloader use appropriate loader for glb specificloader >>filesystem fetch file data filesystem >>specificloader return raw file data specificloader >>specificloader parse data into usable format specificloader >>assetsystem return parsed asset assetsystem >>assetcache store asset and mark as loaded assetsystem >>gamelogic provide loaded asset via callback end this diagram illustrates the two main paths cache hit the asset is already loaded, so it's returned immediately cache miss the asset needs to be loaded, which involves fetching the file and processing it practical examples loading a 3d model for an entity let's consider a practical example of loading a 3d model for a spaceship entity // define a component to specify which model an entity should use import { definecomponent, s } from '@ir engine/ecs'; const modelcomponent = definecomponent({ name 'modelcomponent', schema s object({ src s string() // url or path to the model file }) }); // create a spaceship entity and specify its model const spaceshipentity = createentity(); setcomponent(spaceshipentity, modelcomponent, { src "models/spaceship glb" }); // a system that processes entities with modelcomponent const modelsystem = definesystem({ uuid 'game modelsystem', execute () => { // find all entities with modelcomponent const entities = modelquery(); for (const entity of entities) { const modelcomp = getcomponent(entity, modelcomponent); // check if this entity already has its model loaded if (!hascomponent(entity, loadedmodelcomponent)) { // request the model to be loaded loadresource( modelcomp src, assettype model, entity, (loadedmodel) => { // model loaded successfully // attach the loaded model to the entity setcomponent(entity, loadedmodelcomponent, { model loadedmodel }); }, undefined, // no progress tracking (error) => { console error(`failed to load model ${modelcomp src} `, error); } ); } } } }); this example shows how a component ( modelcomponent ) specifies which model an entity should use a system processes entities with this component the system requests the model through the asset management system when the model is loaded, it's attached to the entity through another component managing asset dependencies many assets have dependencies on other assets for example, a 3d model might reference texture files the asset management system handles these dependencies automatically // simplified concept of handling asset dependencies function loadmodelwithdependencies(modelurl string, entity entity) void { loadresource( modelurl, assettype model, entity, (loadedmodel) => { // model loaded successfully // extract texture dependencies from the model const texturedependencies = extracttexturedependencies(loadedmodel); // load each texture dependency for (const textureurl of texturedependencies) { loadresource( textureurl, assettype image, entity, (loadedtexture) => { // assign the loaded texture to the model assigntexturetomodel(loadedmodel, textureurl, loadedtexture); } ); } // now the model with all its textures is ready to use } ); } this example demonstrates how the system can load a primary asset (the model) identify its dependencies (textures) load those dependencies connect the dependencies to the primary asset benefits of the asset management system the asset management system provides several key benefits efficiency assets are loaded only once and shared between entities that need them abstraction game logic doesn't need to know the details of how assets are loaded organization assets are categorized and managed in a structured way memory management reference counting ensures assets are unloaded when no longer needed asynchronous loading assets can be loaded in the background without blocking the main application error handling loading failures are managed gracefully with appropriate error reporting these benefits make the asset management system a critical component of the ir engine, enabling efficient resource handling for complex applications next steps with an understanding of how the asset management system loads and manages resources, the next chapter explores how these assets are organized in a scene and rendered to the screen next scene graph & rendering abstraction docid\ ggulfozuy2 vcoandnpfx