Repository navigation
Filesystem
Runtime asset bytes, shader-archive records, pipeline-cache persistence, and asset gathering all go through NWB::Core::Filesystem::IFilesystem. The default implementation is VolumeFileSystem, which reads and writes the engine's segmented volume format.
Host source-file discovery and compiler/cache scratch files continue to use the platform-independent helpers under global/filesystem. Those helpers are not virtual runtime asset namespaces.
core/filesystem/filesystem.h defines the interface and resource-free FileCursor helpers. core/filesystem/factory.h owns composition:
FilesystemFactory = Function<UniquePtr<IFilesystem>(GlobalArena&, const VolumeMountDesc&)>
CreateFilesystem(arena, desc, factory = {})
An empty factory selects VolumeFileSystem. A custom factory that returns null is a failure; CreateFilesystem does not fall back to local files. The factory is invoked once per mount, including the read-only graphics namespace and writable pipeline-cache namespaces.
CreateFilesystem creates and mounts the filesystem. It returns an empty UniquePtr if either step fails.
Projects configure graphics and storage before device initialization through the loader adapter NWB::ConfigureProjectRuntime. Keep policy and filesystem implementation in the application namespace; the adapter delegates to it. This Testbed-shaped example assumes the project-owned project_patch_filesystem.h declares Testbed::ProjectPatchFilesystem, an IFilesystem implementation, and the local namespace.h defines TESTBED_BEGIN/TESTBED_END independently of the engine namespace header:
#include "namespace.h"
#include "project_patch_filesystem.h"
#include <loader/project_entry.h>
#include <core/graphics/runtime/runtime.h>
#include <core/filesystem/volume_file_system.h>
#include <impl/assets/graphics/bindless/runtime_abi.h>
TESTBED_BEGIN
namespace __hidden_testbed_startup{
static bool ConfigureRuntime(NWB::ProjectStartupContext& context){
if(!context.graphics.setBindlessHeapAbi(NWB::Impl::AssetsGraphicsBindless::MakeGpuDescriptorHeapAbi()))
return false;
context.filesystemFactory = [](NWB::Core::Alloc::GlobalArena& arena, const NWB::Core::Filesystem::VolumeMountDesc& desc)
-> UniquePtr<NWB::Core::Filesystem::IFilesystem>{
if(desc.volumeName.view() == AStringView("graphics"))
return MakeUnique<ProjectPatchFilesystem>(arena);
return MakeUnique<NWB::Core::Filesystem::VolumeFileSystem>(arena);
};
return true;
}
};
TESTBED_END
bool NWB::ConfigureProjectRuntime(NWB::ProjectStartupContext& context){
return Testbed::__hidden_testbed_startup::ConfigureRuntime(context);
}Other applications use their own namespace and wrappers. See the source filesystem guide for the complete factory and lifetime contract.
Leaving filesystemFactory empty selects VolumeFileSystem for every mount. Testbed and the shared smoke graphics ABI currently leave it empty. The engine copies that factory into graphics device configuration, so captured services must remain valid until graphics shutdown. Do not capture startup-local references. GraphicsRuntime::setFilesystemFactory fails if a device already exists.
ProjectRuntimeContext::filesystem exposes the mounted asset filesystem to project runtime code. The loader keeps it alive until project callbacks, asset loading, and the project world have shut down, then unmounts it explicitly.
VolumeMountDesc names a logical storage namespace using volumeName, provides a mountDirectory hint, and specifies usage and createIfMissing. The segmented backend also consumes segmentSize, metadataSize, and maxSegments. Custom backends can ignore segmentation hints.
| Usage | Writes | Typical caller |
|---|---|---|
RuntimeReadOnly |
rejected | loader graphics volume, pipeline-cache load |
RuntimeReadWrite |
allowed | pipeline-cache save |
CookWrite |
allowed | asset gatherer staged publication |
Creation is explicit: mounting does not erase an existing namespace. The asset gatherer stages a new volume in its own empty directory and only publishes it after successful finalization.
Paths are canonical engine Name keys, including in optimized builds where exact source strings may be absent. A remote backend should map those keys to its own URLs while preserving the asset identities produced by the builder.
-
writeFileandwriteFileDeferredreplace the complete file and copy payload bytes before returning. Empty files are valid (nullptrwith zero bytes). Callers may reuse their buffer immediately. -
writeFileDeferredcan postpone metadata publication untilflushorunmount. -
flushpersists pending writes. The volume backend also compact-trims its segments. -
unmountpersists pending metadata before releasing the mount and reports failure. Callers that need to act on persistence failures must callflushorunmountbefore destroying the object. -
readFile(path, offset, buffer, capacity)returnsExpected<usize>with the copied byte count, without shared cursor state. A successful read can be shorter than requested at EOF. Reading at EOF succeeds with zero bytes; reading beyond EOF returns an unexpected failure. Check admission before consuming the count. -
openFile(path)returnsExpected<FileCursor>bound to that filesystem. Cursor reads returnExpected<usize>and advance only after success. Cursors own no backend resources and are invalid after unmount, remount, or destruction.
fileSize(path) returns Expected<u64>, with zero valid for an empty file. Whole-container reads and IAssetBinarySource::readAssetBinary retain caller-owned buffers and their supplied arena/capacity. Host helpers such as FileExists and FileSize use Expected<bool, ErrorCode> and Expected<u64, ErrorCode>; successful missing-path false is distinct from a host error. See Diagnostics and Invariants.
The default backend serializes individual operations with an internal Futex. A cursor is caller-owned and requires caller synchronization when shared between threads. Runtime asset volumes are normally immutable while mounted.
The loader resolves res, then mounts graphics as RuntimeReadOnly through CreateFilesystem. FilesystemAssetBinarySource implements IAssetBinarySource by forwarding readFile. The same factory is used for pipeline-cache namespaces: load is read-only and skipped when no volume exists, while save uses RuntimeReadWrite with createIfMissing. Custom factories are not recreated through local segment files after a failed save mount.
Cook publication does not use the project factory. asset_gatherer constructs VolumeFileSystem directly, mounts it as CookWrite in an empty staging directory, writes pack-manifest payloads, flushes, unmounts, and then calls PublishStagedVolume. Validation or packing failure leaves the previously published volume in place.
See Cooked Volumes for segment naming, object cache, and staged promotion, and Project API for ConfigureProjectRuntime.
- Architecture
- Engine Concepts
- Asset Flow
- Frame Life Cycle
- Runtime Boot
- Project API
- Runtime and ECS
- CPU Task Scheduler
- Asset Pipeline
- Texture Conversion
- FBX to NWB Conversion
- Asset Types
- Cooked Volumes
- Filesystem
- Shader and Material System
- Mesh Model and Skinning