Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commitdd348ac6c3

README.md

Generator

A live procedural generator: it binds a .procGraph, plays the graph's timeline and stands each of the graph's outputs in the scene as children of the entity it is on. Change a param and the output is made again in place; in edit and in play alike, nothing it makes is written to the world's source.

local graph = asset.create("procGraph", "crate")   -- a box with a `size` input; edit its init.luau
local e = entity.spawn("crate")
e.component.add("Generator", { graph = graph })
e.component.get("Generator").params = { size = 2 }

Fields

FieldWhat it does
graphThe .procGraph it plays.
paramsOverrides for the graph's declared inputs, by name.
outputOne declared output to stand; empty, the default, stands every output. A graph of the call form stands what its io.output calls show (the ones the named output reaches, when one is named); an output it names that no io.output of the graph shows is shown by the Generator itself, under the output's name, the way an io.output of it would show it. A graph of the older node form stands each output it declares, one root each.
playOnWhat starts its clock in play mode: "auto" (default) plays a timeline with no end as soon as it binds and holds any other on its first frame; "load" plays on bind; "manual" waits for play(). In edit mode a Generator is not set to play, whatever playOn says: it shows the frame it is on until it is played.
ending"graph" (default) does what the timeline declares; "loop" plays the range round and round; "hold" plays it once and stays on its last frame.
speedHow fast its clock runs: 1 (default) its own rate, 0 still, negative plays a timeline with an end backwards.
diskCacheWhether the outputs the graph caches load from, and are written to, the world's disk cache. On by default.
bakedSnapshotThe bundle an earlier engine stored with the scene for what the generator realized. In play it stands in place of cooking while the disk cache does not hold every output; the first commit that writes the disk cache for all of them clears it. Empty by default.
cookBudgetMsMilliseconds of cook work a frame before a cook yields to the next frame. 5 by default.

Methods

play(), pause(), seek(seconds), seekFrame(frame), loop(), hold(), setFps(fps), setRange(first, last) and setWallTime(on) drive its clock, on every peer. getFrame(), getTime(), describeGraph() and playback() read it; playback() also says what running the graph costs and how each output is drawn: cost.cooks counts the cooks it presented and cost.cookMs the CPU milliseconds they ran, the frames they waited out apart, for a graph of the call form as for one of the node form. describeGraph() describes the graph bound, of either form. cancelCook() stops a cook in flight and leaves the last output standing. apply() swaps the generator for the asset it makes: the frame being presented becomes a bundle, and an Asset component standing it takes the Generator's place; for a graph of the call form, the bundle holds what its io.output calls show, each under the name it was shown under. The onFinished event fires when a range played once reaches its end.

A timeline that runs in real time (realTime = true) has an end when its graph states frames beside it (g:timeline({ realTime = true, fps = 60, frames = 180 })) or when setRange gives it one; with neither it runs on and never finishes. When the playhead of one with an end reaches its last frame, ending applies as it does to any timeline: "graph" loops where the graph declares loop and otherwise holds on the last frame and fires onFinished once; "loop" plays it again from the start, a fresh simulation each time, without firing it; "hold" stays on the last frame. play() plays a held one again from its start. playback().ends is whether the timeline has an end, and last is its last frame. A real-time timeline begins again, by seek(0), a loop or play(), in the runs its solvers hold on the device: a restart costs a rewrite like any other present, and opens no cook.

What it cooks again

A Generator cooks again, in edit mode and in play alike, when its graph's source is edited, when its graph, params, output or diskCache change, and when anything its graph was built from changes: a node the graph reaches (edited, or no longer loading), a module the graph or one of those nodes requires while it builds, or a file their builds read (vfs.read in a node or graph source, proc.read in a module they call). A Generator that does not reach the change cooks nothing for it. Every node of one cook is built from one version of each module and file.

A graph that does not build (its source, or a node it calls, raises while it is built) leaves what the Generator stands standing: playback().error says why, the cook history holds a cook of target source closed failed with the error, and once what it failed on is fixed the Generator cooks again on its own.

What playback() answers

One table, typed PlaybackState. The tables in it keyed by name are maps, read with pairs, never ipairs:

for output, d in pairs(generator:playback().drawn) do
    print(output, d.as, d.count)   -- "instances" or "entities", and how many
end
FieldWhat it holds
playing, heldByPause, held, realTime, wallTimeThe clock's state: running, held by a pause, held at the end of its range, a real-time timeline, wall time on.
frame, time, first, last, frames, fps, loop, endsWhere the playhead stands and the range it plays; ends is whether the timeline has an end.
cookedThroughThe last frame cooked ahead; reaching last means every frame is cooked.
cooked, hits, steps, restarts, replacingNode cooks, cache hits, solver steps, solver restarts, and outputs built again whose earlier state still stands.
error, cookErrorWhy the graph's source does not load, and why its latest cook failed (led by the line of the call that failed, path:line: g.<call> failed: <why>), while they do.
drawnBy output name: { as, count, because?, pieces? }, how the output's elements reach the screen.
cost{ frames, frameMs, cooks, cookMs, devicePresents, deviceMs, memoPresents, framePresents, realizeMs, drainMs, rewriteMs }: frameMs + cookMs over frames is the script cost of a frame.
nodes, msBy node id: how many times each node cooked, and its milliseconds.
placement, placementByGraphBy node id: where its cooks ran (runs "device", "cpu", "mixed" or "values"); and per node a graph used as a node stands as, the sums of its inner nodes.
onDevice, offDevice, cpuNodesWhether a new time is presented on the device, what holds it to cooking when it is not, and how many nodes run on the CPU every frame.
heldFrames, notHeldFramesWhether presents stand on the frames the engine holds, and why not.
diskCacheBy output name: { key, frames, static, complete, hits }, or { reason } where the cache is not used.
cacheBytes, heaviestEntriesWhat the node cache holds, and its largest entries.
callFor a graph in the call form, the report of its play.
ownerThe name the generator's device runs are held under.

What stands while it cooks again

What a Generator stands stays standing while it cooks again, and a present replaces all of it at once: the shows a present makes stand together in the frame its cook finishes, and what they replace goes in that frame. So no frame stands nothing, and none stands part of one cook beside part of another. Each output stands once: a param set it cooked before shows that set's output again, and params set while a cook runs are cooked by the cook after it. A cook that fails, or is cancelled, leaves what stood standing.

Under an effect

An effect's play runs each graph of its entry on a Generator, a child of the play's entity. effectRef:play(entry, opts) and the Effect component make them and drive their clocks together, so a shot, an impact or a cast is a play of an effect (guides { path = "systems/effects/documentation/visual-effects" }). The play's own playback() adds its Generators' costs together.

Every play of one graph with the same params plays the frames the first one cooked. A Generator of a graph with playOn = "manual" cooks every frame of the timeline while it waits, and playback().cookedThrough reaching playback().last says every frame is cooked.

Related

  • guides { path = "systems/procgen/documentation/procgen" }: graphs, ops, timelines, effects and the live generator in full.
  • The procgen toolbox: spawn, adjust, apply, cancel, generators, cooks, frames and onPresent work on generators by entity, and play plays a graph on the preview stage.