Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

vfs Module

Public Luau surface over the __vfs Internal FFI namespace — the engine's virtual filesystem.

Purpose

Wrap the raw __vfs.* FFI namespace in a typed Luau table that gets auto-injected as _G.vfs via the prelude. The standard read / write / move / remove / mkdir / list / exists / readAsync / reload / evict surface, plus trusted-only watch / unwatch for the bootstrap VM.

vfs.read yields on a byte-cache miss: the module's last step installs the vfs_async_read fallback over the synchronous read, so every VM that holds this namespace, the trusted VM included, reads a path through the same two steps and gets the same bytes for it.

Usage

-- Read / write
local src = vfs.read("@builtin/components/Camera.luau")
vfs.write("/zero/source/notes.md", body)

-- What a write landed: where the bytes went, and what the static passes
-- read in the Luau among them
local ok, report = vfs.write("/zero/source/game/Vent.component/init.luau", code)
if report.diagnostics then
    for _, d in report.diagnostics do
        print(d.severity, d.path, d.line, d.message)
    end
end
if not report.durable then
    log.warn(report.warning)
end

-- Listing
for _, e in ipairs(vfs.list("/zero/source")) do
    print(e.isDirectory and "[d] " or "    ", e.name)
end

-- Async fetch
local png = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))

-- Reclaim RAM after processing a big binary
vfs.evict("/zero/source/textures/imported_huge.png")

-- Reload a module after editing its source on disk
if vfs.reload("@mylib/utils.helpers") then
    local m = require("@mylib/utils.helpers")  -- sees the new source
end

vfs.reload(identity) answers whether a module was cached under that name and has now been dropped. A false says the name matched nothing — either a misspelled identity or a module this VM never required — so the next require() returns whatever it would have returned anyway.

A module that caches state derived from OTHER files keeps serving the old value when those files are rewritten, since only its own source is watched. Reload it by identity to make the next require() recompute.

How a write reaches disk

A native engine's vfs.write returns once the engine holds the bytes. The blob a binary write stores by content and the journal record of the mutation are written by a worker thread (vfs-io) in the order the writes were made, so the blob is on disk before the record that names it, and a script that writes a thousand files pays for handing them over, not for the flush of each. A read of a blob still waiting for its file answers from memory, so a write reads back the moment it returns. A queue holding more than its byte budget (256 MiB) makes the next write wait for it and write inline, which bounds the memory the queue pins. The queue is flushed when the engine exits normally; a process killed in the milliseconds between a write and its flush loses that write. world.syncStatus() reports nothing owed once the writes have also reached the world.

Exports

  • vfs.read(path, opts?) -> string?
  • vfs.write(path, content, opts?) -> (boolean, WriteReport | string)
  • vfs.move(src, dst, opts?) -> (boolean, WriteReport | string)
  • vfs.copy(src, dst) -> (boolean, WriteReport | string)
  • vfs.remove(path, opts?) -> (boolean, string?)
  • vfs.mkdir(path, opts?) -> boolean
  • vfs.list(path?) -> { VfsListEntry }
  • vfs.exists(path, opts?) -> boolean
  • vfs.readAsync(path, opts?) -> promiseId
  • vfs.reload(modulePath?) -> boolean
  • vfs.evict(path, opts?) -> boolean
  • vfs.mutationSeq() -> number
  • Trusted-only: vfs.watch(path, callback) -> number / vfs.unwatch(watcherId) -> boolean.