The path
Each box names the step's type or function and links the crate that holds it.
One frame of the window
The app (kinogaki-studio, studio/apps/studio/src/main.rs) calls two methods of the editor (kinogaki-editor) each frame: Editor::poll, which takes what arrived since the last frame, then Editor::frame, which draws and does what the input asked (studio/core/editor/src/frame.rs). Inside Editor::frame the order is fixed:
- The panes and the views draw, reading the document, and each returns what the user asked of it as data: network edits, outliner edits, view edits, inspector edits.
- The frame applies those edits to the document, in one place.
- The views render (
Editor::render_views,studio/core/editor/src/frame/render.rs), after every pane and control of the frame has run, so what a control changed shows in the same frame. Editor::submitasks the scene cook for the document when what the cook reads changed.- The render sync brings render engines in step with the document (The render path).
The edit
Three common edits and the calls they become:
- A field of the inspector.
set_parameters(studio/core/editor/src/panes/inspector/edits.rs) callsDocument::edit_open_network, which runsDocument::transacton the network open at the location and sets the node's values at the current time (an animated row's value becomes its key there). - A wire drawn in the network pane.
wire(studio/core/editor/src/panes/network/edits.rs) callsedit_open_networkwithCoreNetwork::wire. - A drag of the gizmo at an object level. The views return
Edit::Transformseach frame of the drag, andDocument::set_transformswrites the objects' local transforms in onetransact(studio/core/editor/src/view/apply.rs).
A package's UI edits through the host interface of kinogaki-studio-ui-api (HostNetwork::edit_network, HostDoc::set_object_body), which the editor's HostCx implements with the same transact (studio/core/editor/src/host.rs).
Document::transact
The document (kinogaki-document) holds the scene, the undo history, where the user is, the selections and the packages' session slots. Document::transact (studio/core/document/src/document/transaction.rs) runs the edit on a Transaction, a copy of the scene. The scene is a map of shared objects, so the copy is a map of pointers: Transaction::object_mut copies an object when the edit first writes it, and refuses a locked one; Object::network_mut copies that object's network the same way. When the edit returns:
- Paths keep naming what they named through renames, main nodes write their asset definitions, and the instances follow.
Scene::validate_changes(studio/core/document/src/scene/check.rs) checks what differs from the scene before: the settings, the tree when it changed, each changed object, and each changed network against the one it was edited from.CoreNetwork::validate_since(studio/core/network/src/core.rs) checks only the nodes and frames the edit changed when no identity, wire, node type or port changed, and the whole network otherwise.Scene::check_guardedrefuses a change to an object that was locked.- The copy replaces the scene. A failed edit or check leaves the document as it was, and an edit that changed nothing records no step.
Two counters move. Document::scene_revision moves with every change to the scene. Document::revision moves when an object's network differs by allocation, or the body or display quality of an object whose drawing reads them; it is what the scene cook cooks. A moved object moves the first counter alone. Inside a network, CoreNetwork::set_levels keeps the topology's revision when only parameters, positions, views, held deltas over a captured base or the stacking changed, so the walker's compiled order stays valid; a new wire gives the topology a new revision.
The undo step this records, and what undo restores, are on Undo.
Asking the cook
Editor::submit hands CookState::submit (studio/core/editor/src/cook/mod.rs) the document and a Wanted: the open object and level, the picked nodes' results (what Freeze keeps), a final render's node, the nodes the views ghost or snap on, the network open inside a node, the look outside, and whether a view shades by curvature. It also passes the cache budget from the user's settings and whether playback is on. SceneCook::submit in kinogaki-scene (studio/core/scene/src/cook.rs) compares this with the last request: the document's revision, the scene time (only while a network reads the time), the wants, the project (kept while the project settings, the folder and the color service stay the same) and where the objects that path rows read, and their readers, are placed. When all of them are the same, it asks nothing. Otherwise it builds a SceneRequest around a DocumentScene, one snapshot of the scene that the worker and the UI share, and submits it.
Cooking on the worker
The scene cooks on one thread of kinogaki-cook, a LatestTaskService (studio/core/cook/src/latest.rs): one running request, one pending request that a newer one replaces, one result. Outside a gesture, a submit cancels the running cook through kinogaki-parallel's CancelToken and drops a result the UI has yet to adopt. Cancellation is cooperative, and the objects a cancelled cook finished keep their answers. SceneService (studio/core/cook/src/objects.rs) numbers the requests and marks the objects a request cooks anew; when the first of them turns busy, it wakes the window once, so a frame draws while the cook runs and the status line shows it under way.
The cook of one request (studio/core/scene/src/objects/cook.rs):
- The sources: nodes that other objects' path rows read, in dependency order.
- Every object whose last answer no longer answers the request: the open object alone first, then the rest in parallel. Objects showing one network allocation with the same path stamp, body and display quality cook once and share the answer.
- The nodes asked for besides, the nodes asked to be drawn, and last the network open inside a node.
- Every object's cache kept within the memory budget (
Tally::keep_within,studio/core/network/src/budget.rs).
Each object walks its network with its own NetCache (kinogaki-network, studio/core/network/src/net_cache.rs): its compiled topologies (the last two), a NodeCache and a state per node. The walker (studio/core/network/src/walk.rs) goes through the nodes in compiled order and, per node, takes a frozen node's kept outputs, passes a bypassed node's input on, takes a held node's result or applies its delta to its base, fails a value its parameter refuses, or cooks the node's type. A node's stamp (studio/core/network/src/stamp.rs) hashes its type, identity and parameters and everything it read: its inputs' stamps, the time when it reads the time, the project when it reads the project, what its path parameters resolve to, what the network it holds gave, what it reads outside. The stamp is made again only when the node's parameters (by allocation) or what it read changed, so an edit rehashes the edited node and what it feeds. Each node has one cache entry (studio/core/cook/src/cache.rs), overwritten by its next cook and reused while its stamp, its type and the outputs something reads stay the same; a reused node costs a compare and an Arc clone.
A held node, such as Mesh's Edit Mesh in plugins/mesh, keeps a Held state (studio/core/cook/src/held.rs): its base, captured by the first cook that reaches it, and its result, filled by the first cook that finishes and shared by every copy of that state. Once the base is captured, edits upstream leave the node as it is and nothing upstream cooks for it.
The shown value is drawn by its data type's Displayable::prepare, a facet of kinogaki-studio-api, on the cook's workers (studio/core/scene/src/objects/state.rs). The type is handed what it drew for the object before, so a value that changed a little is drawn by patching the last pieces, and a value that is the same allocation keeps its pieces. The result is a Drawn: pieces, each a packet (Arc<dyn SurfaceSource>) with its placements and its role. Core's polygons, curves and B-reps draw through kinogaki-geometry-view (GeometrySurface, BrepSurface). What a value holds of other values, such as an Instances value's prototypes, goes through one table of the cook's (HeldValues), so a prototype is drawn once and drawn instanced in the views. Where the scene rests, the cook also refits or builds the open object's picker on the worker. A package that panics in a cook fails that object alone.
Waking and adopting
The worker publishes the answer under its lock and calls Wake::wake (studio/core/cook/src/wake.rs). The editor's Wake asks the window for one frame (request_single_repaint, studio/core/editor/src/shell/mod.rs), so each result draws one frame.
At that frame's start, Editor::poll adopts the answer through CookState::adopt; SceneCook::adopt takes the worker's latest result and returns at once.
- An answer to the document's current revision is published on the
Board(studio/core/scene/src/board.rs): each object's last goodObjectCook(its walk, the value it shows, warnings, stamps and drawn pieces), failures kept beside the last good answers, and the nodes asked for besides. - A failed cook goes to the status line, and the board keeps what it showed.
- A refinement replaces the shown pieces with exact ones, and the cook is told it was shown.
- An answer to an earlier revision is dropped, except inside a gesture (below).
Editor::poll then runs the poll of each package that was woken or has a latest-wins job landed.
From the board to the GPU
ViewState::render (studio/core/editor/src/view/render.rs) reads the board. DrawnList::list (studio/core/editor/src/view/drawn.rs) makes the list of placed packets: every shown object's pieces, each placed by its object's world, in the look its role, its flags and the selection give it, with the colors and images its value's look gives (studio/core/editor/src/view/looks.rs). The list is kept while what it reads stays the same: the placements, each object's answer by allocation, the location, the selection, the hover, the edited surface, the templated branches and the materials' revision. When only placements or marks changed, the entries of the objects concerned are written in place. Inside an object whose context has levels, the open object's own piece is the edited surface, drawn with its component selection and hover.
Gpu::draw (studio/core/editor/src/view/gpu.rs) hands each view's list to the ViewportRenderer of lib-viewport (kinogaki-viewport-gpu, on wgpu), which renders into the view's texture; the view's pane paints that texture. The renderer keeps one target per view and one set of packet buffers for all views. A packet's allocation is its identity: its buffers stay while its topology_key stays, and a packet derived from another carries DrawUpdate ranges, so the upload writes those ranges alone. Objects are records in one storage buffer (libs/lib-viewport/crates/gpu/src/gpu/batches.rs), each written when its object changes; packets placed once are merged into batches, and packets placed several times draw instanced. ViewportRenderer::render_scene (libs/lib-viewport/crates/gpu/src/gpu/render.rs) draws nothing for a view whose objects, camera, options, selection and overlays are the ones it drew last.
A drag
A drag runs inside a gesture. The part that starts it holds a gesture::Held (studio/core/editor/src/gesture.rs), every edit until the release is one undo step, and each edit takes no snapshot of its own (Undo). The rest of the path changes in these ways:
SceneCook::submitqueues the request behind the running cook (submit_after): the running cook finishes and publishes, and the newest request runs next, so a cook slower than the pointer still shows.- The request is
interactive: data types prepare fast and leave finer display toDisplayable::refine, which runs as idle work once the gesture ends (hold_refinement). The open object's picker is made once the scene rests. SceneCook::adopttakes an answer to an earlier state of the gesture for every object but the open one (Adopted::Continuing), so what reads the edited object follows it while the edited object waits for the newest answer.- Node caches are overwritten in place, so a drag adds no cache entries, and each step's check covers what that step changed; the gesture's end checks the changed networks whole, once.
- A gizmo drag at an object level changes placements alone.
Document::revisionstays, so the cook is asked nothing unless a path row reads a moved object; the drawn list writes the moved objects' entries in place, and the renderer rewrites their records. - A drag of the open context's selection is previewed on the GPU. Mesh hands the views
Edited::movingeach frame (plugins/mesh/src/ui/transforms.rs); the editor'sPreviewing(studio/core/editor/src/view/moving.rs) patches the moved points' polygons over the shown surface through geometry-view'sDragPreview, and nothing cooks. On release Mesh commits one Edit Mesh step, and its cook's answer replaces the preview.
What wakes the window
The window draws a frame for input, for a Wake, and during playback for the next played frame, which Panes::frame_ended asks for as a frame ends (studio/core/editor/src/panes/timeline.rs). Wakes come from the scene cook (a result, or an object turning busy), packages' latest-wins jobs, the render engines, an open or save, file dialogs, the file-system watch over the files the document reads (studio/core/editor/src/frame/outside.rs), the settings writer, the clipboard and the Console. Each wake draws one frame, and a resting app draws none: studio/core/editor/tests/behaviour/idle.rs checks that a result draws exactly one frame, and that the stacked list, the Tab browser, a popover and the Settings window settle in a frame or two and then rest. The timers rule of tools/check_rules.py keeps timed repaints and sleeps out of UI code.
A frame that moves a camera
A press with Alt, or of the middle button, belongs to the camera for its whole drag. A view's camera is view state held by the editor's Views (studio/core/editor/src/view/cameras.rs), saved with the project's view and left out of undo, so navigating runs no transact. In a frame whose only change is the camera, SceneCook::submit finds the request unchanged and asks nothing, DrawnList::list compares in place and returns the last list, and render_scene sees the same objects under another camera: it writes the view's uniforms and draws again, uploading no packet and no object record. A view looking through a Camera object is the one case that edits: navigating it moves the object, one undo step in a gesture of its own. The test orbit_and_static_playback_upload_and_build_nothing (studio/core/editor/tests/view_latency.rs) checks such frames.
