How the parts work together

Undo

Undo belongs to the document. kinogaki-document records each edit as a step between two snapshots of the document, which share everything the edit left alone, and a gesture makes a drag or a live tool's run one step. This page says what a snapshot holds, how steps are recorded and restored, how gestures are opened and joined, and what stays out of undo.

On this page

Snapshots

A Snapshot (studio/core/document/src/document/history.rs) is what undo restores:

PartWhat it holds
scenethe objects, their bodies and networks, the project settings and asset definitions
locationwhere the user was inside an object; an object level is kept as the root
objectsthe objects selected
picksthe nodes picked on the open level
slotsevery package's session slot values
rangethe frame range

A snapshot costs a map of pointers. The scene is a map of shared objects, each object shares its body and its network, and the slot table holds one shared pointer a value. An edit copies only the object, network or slot value it writes, while the snapshots keep the versions they hold.

Where the user stands at an object level is navigation: a restore keeps the object level shown and puts back a place inside an object. The current time and playback are navigation too; the frame range is in the snapshot.

Steps

Document::recorded is the one place a step is recorded. It takes a snapshot, runs the change, takes a second snapshot and compares them: when they are equal nothing is recorded; otherwise the redo steps are dropped and a Step (its label, the snapshot before, the snapshot after, its bytes) goes onto the undo steps. Three methods record through it:

A failed edit or check leaves the document as it was, so the two snapshots are equal and no step is recorded. The step's label is what Edit ▸ Undo and Redo show ("Undo Transform objects"), read with Document::undo_label and Document::redo_label.

differs recorded Undo puts back before Snapshot beforescene, place, selections The changetransact, edit_slot Snapshot aftercompared with before Steplabel, before, after, bytes Undo steps128, soft 64 MB Redo stepsdropped by a new step

Undo and redo

Document::undo first puts back an open gesture, whoever opened it, recording nothing. It then takes the newest step, restores its snapshot before, and moves the step onto the redo steps. Document::redo does the same with the newest redo step and restores its snapshot after: the scene as the edit left it, the location inside an object where the edit was made, the selection and picks the edit left, the slots and the frame range. A new step drops every redo step.

A restore (Document::restore):

  1. replaces the scene and moves the document's revisions, Document::revision among them when a network differs, so the scene cook is asked for the restored scene;
  2. puts back the place inside an object that the snapshot holds, while a snapshot taken at an object level keeps the object level shown now; when the restored scene lacks the place, it moves up to the nearest place the scene can show (Location::kept, studio/core/document/src/document/location.rs);
  3. puts back the selection and the picks, clearing the picks when their place is gone, and drops selected objects that are gone or locked;
  4. puts back the slots, then asks each slot to bring its value in line with the open level (Slot::restored);
  5. puts back the frame range, keeping the current time inside it.

Whether the document has unsaved changes is a comparison with what the file holds, so undoing back to the saved state leaves the document clean.

In the editor (kinogaki-editor), Undo and Redo first cancel the open context's live tools with Ended::Undo (studio/core/editor/src/frame/layer.rs), then call the document. A package that asks for an undo through the host has it queued until its own call returns. A drag whose gesture an undo ended ends with it (gesture::Held::live). After the restore, the cook finds the networks' compiled topologies kept (each network keeps its last two), so undoing and redoing the newest step compile nothing again (studio/core/network/tests/walk.rs).

History limits

The undo steps keep the newest 128 within a soft budget of 64 MB. A step's bytes are estimated once, when it is made: the objects that differ between its two sides (Scene::changed_bytes), both sides' slot values (Slot::estimated_bytes) and the label. The total is kept as steps come and go. When a new step passes either limit, the oldest steps go, and the newest step always stays. Starting or opening a document starts an empty history.

Gestures

A gesture makes every edit until it ends one step. Document::begin_gesture(label) opens one and returns its GestureToken (sdk/crates/api/src/session.rs), or None while a gesture is open. The token is the opener's alone and ends the gesture once: finishing or cancelling takes it by value, and it is the only way to do either. Its number comes from one count for the whole process, so a token held across a new document never names that document's gesture.

Inside a gesture, Document::recorded takes no snapshot; each edit is checked for what it changed, and nothing is recorded until the end.

Packages reach these through HostUndo in kinogaki-studio-ui-api (sdk/crates/ui-api/src/host/capabilities.rs): begin_gesture, finish_gesture, cancel_gesture, back_to, in_gesture, undo, redo and the two labels. With back_to(token.start()), a live tool that computes each step from the gesture's start stays one undo step.

drag inside it Escape release, Enter Escape begin_gesturetoken to the opener Edits in the gestureno snapshot each A drag joinsmark() back_to(mark)gesture stays open finish_gesturewhole check, one step cancel_gestureeverything put back

Drags and live tools

Every part of the editor that drags holds a gesture::Held (studio/core/editor/src/gesture.rs): the views (the gizmo, handles, the open context's selection, and a view steering a Camera object), the network pane's node moves, the outliner's flag drags, the timeline's frame range, the inspector's sheet and the canvas. A drag asks it to begin, finish (release or Enter) or cancel (Escape or focus lost):

Mesh's live tools (plugins/mesh, plugins/mesh/src/ui/tools.rs) show how a run joins this. Running a tool opens a gesture named for the operation, adds the tool's node (or a step of the displayed Edit Mesh) and keeps the token. While the run lasts, its card's rows take the inspector's path, its handles are dragged and templated branches are picked into its node, each joining the tool's gesture. The card's Done, Enter, a click, running another tool, or going on to something else (the location leaving the context, a node picked or edited in the network) keeps the run and finishes the gesture as one step. The card's ×, Escape, Undo and Redo put the run back, and Undo or Redo then takes its step.

Selections, picks and slots

These stay out of undo: a package's UI state (the state type the host lends to each call of its UI parts); the object level shown, the current time and playback; each view's camera and display options, saved with the project's view (studio/core/editor/src/view/cameras.rs); the user's settings; and the network's stacking and its nodes' views (Document::raise_nodes, Document::set_node_view), saved with the document and recorded as no step, as is Reload (studio/core/document/src/document/edits/nodes.rs).

Held edits across snapshots

An Edit Mesh node (plugins/mesh/src/nodes/edit_mesh) holds its steps as a delta over its captured input. Core keeps the mechanism in kinogaki-cook and kinogaki-network: the node's HeldNode holds the delta and a Held state (studio/core/cook/src/held.rs), a base captured by the first cook that reaches the node and a result filled by the first cook that finishes. The network is shared by every snapshot that holds it, and so is the held state with its result.

Written in the tree as docs/how/undo.md.

← An edit becomes a frame · The render path →