Resource saving and loading

All resource save and load operations are managed through the Resources module, accessible through GetResources().

Saving resources

Once a resource has been imported you can save it into a package for later use. The advantage of saving a resource (instead of importing it every time) is performance - resource import is usually a costly operation. Saved resources remain in an engine-friendly format and can be quickly loaded later.

Resources are saved into packages, which are container files that can hold one or more resources. To save a resource, you use Resources::SaveAsSinglePackage, which creates a package containing a single resource:

// Import a texture named "myTexture.jpg" from the disk
HTexture texture = GetImporter().Import<Texture>("myTexture.jpg");

// Save the texture into a package
GetResources().SaveAsSinglePackage(texture, "D:/MyGame/Assets/", "BrickTexture");

This will create a package file at D:/MyGame/Assets/BrickTexture.b3d containing the texture resource.

Note that resources can also be created within the engine programmatically, and don't necessarily have to be imported. For example, you can create textures with custom pixel data or meshes with procedural geometry, then save the resource in this same manner.

Save options

You can customize the save operation by providing ResourceSaveOptions:

ResourceSaveOptions saveOptions;
saveOptions.Overwrite = true;  // Overwrite if file exists
saveOptions.Compress = true;   // Use compression
saveOptions.VirtualPathPrefix = "/Game/Textures/";  // Virtual path prefix

GetResources().SaveAsSinglePackage(texture, "D:/MyGame/Assets/", "BrickTexture", saveOptions);

The VirtualPathPrefix is important - it allows you to load the resource using a virtual path instead of the physical file path (more on this in the packages manual).

Loading resources

Once a resource has been saved into a package, you can load it at any time using Resources::Load. Resources can be loaded using either a physical path or a virtual path.

Loading by physical path

A physical path is the actual file system path to the package and resource:

// Load texture by physical path: packagePath/resourceName
HTexture texture = GetResources().Load<Texture>("D:/MyGame/Assets/BrickTexture.b3d/BrickTexture");

Loading by virtual path

If you saved the resource with a virtual path prefix, you can load it using the virtual path:

// Load texture by virtual path (if saved with /Game/Textures/ prefix)
HTexture texture = GetResources().Load<Texture>("/Game/Textures/BrickTexture");

Load options

You can customize the load operation using ResourceLoadOptions:

ResourceLoadOptions loadOptions;
loadOptions.AsynchronousLoad = true;       // Load asynchronously
loadOptions.LoadDependencies = true;        // Load resource dependencies
loadOptions.KeepInternalReference = true;   // Keep internal reference

HTexture texture = GetResources().Load<Texture>("D:/MyGame/Assets/BrickTexture.b3d/BrickTexture", loadOptions);

If you attempt to load a resource that has already been loaded, the system will return the existing resource instead of loading it again.

Asynchronous loading

Resources can be loaded asynchronously (in the background) by setting the AsynchronousLoad option to true (which is the default). The returned handle will be valid immediately, but the resource data may not be loaded yet.

You can check if a resource is loaded by calling ResourceHandle::IsLoaded:

ResourceLoadOptions loadOptions;
loadOptions.AsynchronousLoad = true;
HMesh mesh = GetResources().Load<Mesh>("myMesh.b3d/Mesh", loadOptions);

if (mesh.IsLoaded())
{
	// Resource is loaded and ready to use
}

You can block the current thread until the resource is loaded by calling ResourceHandle::BlockUntilLoaded:

mesh.BlockUntilLoaded();
// Mesh is now guaranteed to be loaded

Note that not-yet-loaded resource handles can be provided to some engine systems. Generally a system will note in its documentation if it works with such resource handles. For example, you can assign a not-yet-loaded texture to a material, and it will automatically be used once loaded.

Resource lifetime

When you load a resource, that resource will be kept loaded until all references to it are lost. Each resource handle (e.g. HMesh) represents a single reference. Additionally, an "internal" reference is automatically created and held by the resource system (if KeepInternalReference is true, which is the default).

This internal reference ensures the resource stays loaded even when all external handles are destroyed. This is useful for resources you want to keep in memory for quick access.

Releasing internal references

To allow a resource to be unloaded when all external handles are destroyed, you must release the internal reference by calling Resources::ReleaseInternalReference:

HMesh mesh = GetResources().Load<Mesh>("myMesh.b3d/Mesh");

// Use the mesh...

// Release the internal reference
GetResources().ReleaseInternalReference(mesh);

// Now when 'mesh' goes out of scope, the resource will be unloaded

Note that if you call Resources::Load() multiple times for the same resource, you must also call Resources::ReleaseInternalReference() the same number of times to fully release the internal reference.

Alternatively, you can load the resource without creating an internal reference in the first place:

ResourceLoadOptions loadOptions;
loadOptions.KeepInternalReference = false;

HMesh mesh = GetResources().Load<Mesh>("myMesh.b3d/Mesh", loadOptions);
// Resource will be unloaded when all handles are destroyed

Unloading all unused resources

Instead of manually releasing internal references, you can unload all resources that aren't externally referenced by calling Resources::UnloadAllUnused:

// Unload all resources that don't have external references
GetResources().UnloadAllUnused();

This is useful for cleaning up resources after a level change or when you want to free memory.

Weak handles

In case you want to keep a reference to a resource without incrementing the reference count, you can use a weak handle. Weak handles are represented by the TWeakResourceHandle class and can be retrieved from normal handles by calling the GetWeak() method:

// Load a mesh and store a handle as normal
HMesh mesh = GetResources().Load<Mesh>("myMesh.b3d/Mesh");

// Create a weak handle
TWeakResourceHandle<Mesh> weakMesh = mesh.GetWeak();

// Weak handle doesn't prevent the resource from being unloaded
// To use the resource, you need to lock the weak handle back to a strong handle:
HMesh strongMesh = weakMesh.Lock();
if (strongMesh != nullptr)
{
	// Resource is still loaded, safe to use
}

Resource dependencies

When you load a resource, the system will automatically enumerate all dependencies of that resource and attempt to load them as well. For example, when loading a Material it will automatically load its Shader and any referenced Texture resources.

This behavior is controlled by the LoadDependencies option (enabled by default):

ResourceLoadOptions loadOptions;
loadOptions.LoadDependencies = false;  // Don't load dependencies

HMaterial material = GetResources().Load<Material>("myMaterial.b3d/Material", loadOptions);
// Material is loaded, but its textures and shader are not

Checking resource existence

You can check if a resource exists at a given path without loading it using Resources::Exists:

if (GetResources().Exists("myMesh.b3d/Mesh"))
{
	// Resource exists, safe to load
	HMesh mesh = GetResources().Load<Mesh>("myMesh.b3d/Mesh");
}

Resource events

The Resources system provides events you can subscribe to for monitoring resource state:

// Called when a resource has been loaded
GetResources().OnResourceLoaded.Connect([](const HResource& resource)
{
	B3D_LOG(Info, LogGeneric, "Resource loaded: {0}", resource.GetId());
});

// Called when a resource has been destroyed
GetResources().OnResourceDestroyed.Connect([](const UUID& resourceId)
{
	B3D_LOG(Info, LogGeneric, "Resource destroyed: {0}", resourceId);
});

// Called when a resource has been modified
GetResources().OnResourceModified.Connect([](const HResource& resource)
{
	B3D_LOG(Info, LogGeneric, "Resource modified: {0}", resource.GetId());
});