Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

resource_handle

Recognises a **live GPU resource handle** — the value `renderer.mesh.create`, `renderer.material.create` and `renderer.texture.create` answer with.

by◐lumi·posted 1mo ago
What it does

resource_handle

Recognises a live GPU resource handle — the value renderer.mesh.create, renderer.material.create and renderer.texture.create answer with.

A handle is a table of the shape { kind = "<Category>Handle", category, guid, name }, and its guid names a slot in the running process's GPU registry. It is minted when the resource is created and identifies that resource for as long as the registry lives. An AssetRef's guid is content identity — it names a stored asset, and any session that can reach the world's content resolves it.

The two look alike at a glance and answer differently at every session boundary, which is what this module exists to tell apart:

  • The replication wire. A peer runs its own GPU registry, so a handle's guid means nothing there. The sync layer drops such a value and the receiver falls back to the field's declared default.
  • Durable content. A scene record outlives the session that wrote it. The next session — a peer loading the world, or the same engine after a restart — mints its own registry, so a recorded handle names a resource that session never created.

asset.create(category, name, handle) is the crossing: it stores the resource and hands back a ref whose guid does survive both boundaries.

A record is also the medium a scene reloads through inside one session: a play flip unloads the live entities and rebuilds them from the same bytes a peer receives, and the handle is valid there. So a handle a record leaves out is parked in modules.session under the entity and component it came from — park as the record is written, withParked as it is applied. A load inside the minting session puts it back; every other session opens an empty store and the field takes its declared default.

API

local ResourceHandle = require("modules.resource_handle")

local mesh = renderer.mesh.create(geometry)

ResourceHandle.isLive(mesh)                     -- true
ResourceHandle.isLive(asset.resolve("wall.texture"))  -- false (an AssetRef)
ResourceHandle.isLive("cube")                   -- false

ResourceHandle.label(mesh)                      -- "mesh handle 'msh_grass_1'"

-- Component field maps: `data` unchanged when it holds no handle.
local data, dropped = ResourceHandle.withoutLive({ model = mesh, visible = true })
-- data    -> { visible = true }
-- dropped -> { model = "mesh handle 'msh_grass_1'" }

-- What a record leaves out, held for the rest of this session.
ResourceHandle.park(entityId, "@builtin::components.Model", { model = mesh })
local applyData, restored = ResourceHandle.withParked(data, entityId, "@builtin::components.Model")
-- applyData -> { visible = true, model = mesh }
-- restored  -> 1

withoutLive returns the same table when there is nothing to leave out, so the common case allocates nothing. When it does drop, the second return names each field and the handle it stood for, so the caller reports what happened at the point it happens rather than leaving the field to fail later.

park replaces whatever that entity-and-component pair held before, so a field that stops holding a handle stops being restored with the ones that do. withParked fills only the fields the record has no value for — a record that carries a value is the authority on it.

Consumers

scene_saver asks per field as it serialises an entity, reports the entity, component and field once per session, and parks the handles it left out. scene_loader asks per component as it applies a record: it reports the whole scene's count of recorded handles in one line — one generator run writes the same field on every entity it made, so a per-entity report would say the same thing hundreds of times — and puts the parked ones back. dirty_hot_reload applies a peer's body to a live entity and leaves the handles in it out.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

module ResourceHandle Recognises a live GPU resource handle — the `{ kind = "<Category>Handle", category, guid, name }` table `renderer.mesh.create`, `renderer.material.create` and `renderer.texture.create` return. Every boundary that carries a component field value out of the session that produced it consults this to tell a handle from an `AssetRef`.

parkKey(owner: string, componentType: string) → string

argtypedescription
ownerstring
componentTypestring

ownField(v: any, key: string) → any

A handle carries its fields itself: `renderer.*.create` returns the plain `{ kind = "<Category>Handle", category = ..., guid = ... }` table the Rust discriminator reads the same way. A component field can hold a PROXY instead — an entity ref, an asset — whose metatable answers for every name it carries and raises on one it does not, so reading a candidate field off one directly is how asking "is this a handle" becomes an error about the name asked for. `rawget` reads what the table holds itself and never reaches a metatable, so the question answers for every value a field can hold rather than only for the ones that tolerate being asked.

argtypedescription
vany
keystring

isLive(v: any) → boolean

Whether a value is a live GPU resource handle.

argtypedescription
vanyAny component field value.

examples

ResourceHandle.isLive(renderer.mesh.create(geometry)) -- true

label(v: any) → string

A short phrase naming a handle, for a message about the value. the `kind` when the handle carries no category or name.

argtypedescription
vanyA handle table.

examples

ResourceHandle.label(meshHandle) -- "mesh handle 'msh_grass_1'"

withoutLive(data: any) →

The component field map to apply, with any live-handle value left out — the form a value read from durable content takes in a session other than the one that minted it. The field falls back to the component's declared default, and the caller reports what it stood for. and a `{ [field] = label }` map of what was left out, or nil when none was.

argtypedescription
dataanyA component's `{ field = value }` map.

examples

ResourceHandle.withoutLive(record.data) -- data, nil

park(owner: string, componentType: string, dropped: { [string]: any }?) → void

Hold, for the rest of this session, the live handles one component's durable record left out — keyed by the entity and component they came from. Each call replaces what that pair had parked before. the component's record carries every field it holds.

argtypedescription
ownerstringThe entity id the component sits on.
componentTypestringThe component's type name.
dropped{ [string]: any }?The `{ field = handle }` map left out of the record, or nil when

examples

ResourceHandle.park(id, "@builtin::components.Model", { model = mesh })

withParked(data: any, owner: string, componentType: string) →

The component field map to apply, with every field this session parked for `owner`'s `componentType` and the record does not carry put back. carrying the parked fields too; and how many fields were put back.

argtypedescription
dataanyA component's `{ field = value }` map from a record.
ownerstringThe entity id the component is being applied to.
componentTypestringThe component's type name.

examples

ResourceHandle.withParked(record.data, id, "@builtin::components.Model")

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

5items
▣
module · born here
❒asset
# session Session-scoped key-value state. The store lives in the module's environment, which loads once per engine process, so values survive VM reloads (play flips, hot reloads) and die with the process. Use it for state whose lifetime must equal the session's runtime artifacts (GPU resources, runtime materials, bake outputs), which outlive a world save and would leave stale claims otherwise. ```lua local Session = require("modules.session") Session.set("my_system_" .. entityId, handle) local handle = Session.get("my_system_" .. entityId) ```
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.