Builtin Library
- ..
- README.md6.0 KB
- about.yaml280 B
- behavior.luau11.3 KB
- inspector.luau1.8 KB
- template
- type.yaml2.6 KB
README.md
procPack — a family of procedural ops as content
A .procPack is a FAMILY of procedural operations authored as content: a
folder whose init.luau returns a pack table. When the asset registers,
every def in its ops map joins the proc op registry under the stable string
id the pack names it with, so a graph calls it by that id (g.op.<id>). Discover the
available packs with asset.list("procPack"), and the ops any of them
contributed with procgen.searchNodes("", { pack = "<name>" }).
This is the one path an op family reaches the registry through. Procgen's own
families are packs — procPacks/ in this package holds one per family,
abi.procPack beside it holds the ops every node is built from, and
terrain's terrain.procPack sits in its own package the same way — so the
packs shipped here are worked examples of exactly what you are writing, not a
separate mechanism the built-ins are exempt from.
MyPack.procPack/
init.luau -- returns the pack table
README.md
.metadata
The pack table
local proc = require("@builtin::systems.procgen.proc")
local function transformed(ctx: any, geometry: any, params: { [string]: any }): any
return proc.registry.get("geometry.transform").eval(ctx, { geometry = geometry }, params)
end
return {
name = "myPack", -- the asset's own name when omitted
description = "Moves and turns whole geometries.",
ops = {
["myPack.lift"] = {
description = "Lift a Geometry by a height.",
inputs = { geometry = { type = "Geometry" } },
params = { height = { type = "Float", default = 1.0 } },
outputs = { geometry = { type = "Geometry", default = true } },
eval = function(ctx, inputs, params)
return transformed(ctx, inputs.geometry, { translate = { x = 0, y = params.height, z = 0 } })
end,
},
["myPack.spin"] = {
description = "Turn a Geometry about the up axis.",
inputs = { geometry = { type = "Geometry" } },
params = { degrees = { type = "Float", default = 45 } },
outputs = { geometry = { type = "Geometry", default = true } },
eval = function(ctx, inputs, params)
return transformed(ctx, inputs.geometry, { rotate = { x = 0, y = params.degrees, z = 0 } })
end,
},
},
}
Each def is the same table every op in the registry is described by — typed
input sockets, params, typed output sockets, and an eval(ctx, inputs, params)
returning the outputs. ctx carries seed / nodeId / compositePath / hash /
checkpoint. Validation is the same for every pack, so a malformed socket or an
undeclared param type is refused here exactly as it is for the ones shipped in
this package.
Beside name, description and ops, the pack table takes:
context: the context every op of the family works in ("geometry","field","sim", ...), for each def that states none of its own. A name that is no registered context is refused.aliases: former op ids mapped onto current ones ({ ["myPack.raise"] = "myPack.lift" }). A graph naming a former id cooks the current op, while listings and schemas carry the current id. An alias onto an id that is no registered op is refused, and so is an alias whose former id is itself a registered op.
A graph calls a pack's op as g.op.<id>, with one table naming its inputs and
params:
return proc.define(function(g)
local box = g.mesh.box({})
local lifted = g.op.myPack.lift({ geometry = box, height = 2 })
return { mesh = g.op.myPack.spin({ geometry = lifted }) }
end)
procPack or procNode
Both contribute nodes that a graph calls. They differ in how a node is written and addressed:
.procNode | .procPack | |
|---|---|---|
| contributes | one node, written as a graph (proc.define(function(g) ... end)) | a family of ops, each a def table |
| addressed by | the asset's identity (g.points.disc, a path, a guid or a short name), recorded in the graph by guid | the string id the pack names it with |
| a graph writes | g.<family>.<name>({ ... }) | g.op.myPack.lift({ ... }) |
Reach for a .procPack when the ops belong together and you want to read the
family's name at every call site; reach for a .procNode when one operation
stands on its own.
Finding a pack's ops
Search embeds a pack's README.md and init.luau (the indexing block of type.yaml), so the README is
where a search by what an op does reaches it: list every op of the pack as a line of its own, the op's id and
what it does, and keep the line true when the op changes.
- `myPack.lift` — Lift a Geometry by a height.
- `myPack.spin` — Turn a Geometry about the up axis.
procgen.searchNodes asks for .procNode and .procPack assets together. A hit that is a pack is listed as the
ops of that pack that answer the query's words (op.myPack.lift, with matched.pack naming the pack), or as the
pack itself when none of them does.
Registration lifecycle
init.luau is compiled from its own source on every registration rather than
required, so an edit lands immediately. Another module is reached by its
absolute name (@builtin::systems.procgen.proc), and a file of the pack's own
by a relative require (require(".helpers")). Every op's version is derived
from the pack's source, unless its def declares a version of its own, so
editing the file re-keys the node caches of every graph that reaches any op in
the family — those graphs recook.
Editing the pack to REMOVE an op unregisters it. A graph built before the edit
that still names the op errors loudly at eval, naming the op that is not
registered, and a graph source that calls it fails to build, naming the ops
the pack still holds. Deleting the .procPack folder unregisters the whole
family the same way.
In the Inspector
Operations: how many ops the pack registers, and each op's id, kind and what it does.