Builtin Library
README.md
vfs_async_read
Pure-Luau wrapper that grafts a yielding vfs.read over the engine's
sync vfs.read binding. The vfs API module
(modules/api/engine/vfs) calls M.installInto(vfs) on itself as it
loads, so every VM holding vfs (user VMs and the trusted VM alike)
reads through it; user code never requires this module directly: it
just calls vfs.read(path) and the wrapper handles cache misses by
yielding the running coroutine until the bytes resolve.
Exports
M.installInto(vfs: VfsNamespace): replacevfs.read(andvfs.readBoundedandvfs.list) with the yielding wrapper. No-op when the target lacksreadandreadAsyncas functions.M.CANNOT_WAIT:"VFS_READ_CANNOT_WAIT", the code a read raises when it names a known file whose bytes have not arrived and the reading code cannot yield.
Types:
VfsNamespace = { read?, readAsync?, readBounded?, list? }: the engine'svfsglobal shape.
Usage
-- The `vfs` API module installs the wrapper onto itself as it loads:
require("@builtin::modules.vfs_async_read").installInto(vfs)
-- After install, every `vfs.read` call yields on cache miss, and a read
-- that finds no bytes answers nil and the reason:
local bytes, why = vfs.read("/zero/source/some.asset")
Where a read can wait
- On a miss the wrapper schedules
vfs.readAsync(path, opts), which firesBlobProvider::fetchunder the hood, andtask.awaits the promise. A wait that resolves without bytes answers nil and the reason: the sentence the engine's read gave beside its nil, or, for bytes that were on their way, the one it gives once the wait ends. - Waiting takes a yield, so it happens where
coroutine.isyieldable()is true: a task, the component hooks the engine runs on a coroutine (awake,start,update,fixedUpdate,onEnable,onOwnerChanged,onSyncReceived), a replicated event's handlers, and a world or scene entrypoint's hooks. - Where it is false (
onDisable,onDestroy,onPropertyChanged, a received synced-function call, avfs.watchcallback, a module's top level whilerequireruns it, a metamethod, atable.sortcomparator, a VM's main thread) a miss the engine's read answersnil, "pending"for (a filevfs.existsknows that is no directory and whose bytes can still arrive, a render surface being captured, a/runtime/syncnode whose current reading the next frame assembles) raisesVFS_READ_CANNOT_WAIT: vfs.read("<path>") ..., naming the path and the places it can be read from. Every other miss answers nil and the reason there as everywhere else. vfs.listanswers a directory whose current listing the next frame assembles (a/runtime/syncdirectory nothing has read lately) after waiting that frame where the code can yield, and raisesVFS_READ_CANNOT_WAIT: vfs.list("<path>") ...where it cannot.installIntois idempotent in the trivial sense: calling it twice re-wrapsvfs.readaround the already-wrapped function, which is fine but pointless.