Skip to content

Updating

Randall C. O'Reilly edited this page Jan 25, 2024 · 1 revision

Updating

First, all updates that might affect GUI display should be bracketed by the Ki standard update calls:

    updt := nb.UpdateStart()
    // ... do updates
    nb.UpdateEnd(updt)

The UpdateStart automatically marks the entire tree down from given node with the ki.Updating flag, if it doesn't already have that flag set by virtue of some higher-level (or prior) UpdateStart call. The updt return value is true if this represents a novel update start for this node, and false if it is already within another update window. The assumption here is that updates are almost always nested structurally and over time, so that the first to call UpdateStart will also be the last to call UpdateEnd.

If the UpdateEnd can happen as the last call at the end of a method, it is a good idea to use the defer syntax so you don't forget:

    updt := nb.UpdateStart()
    defer nb.UpdateEnd(updt)
    // ... do updates

When UpdateEnd is called, it emits an Updated signal on its NodeSig signal, if updt == true (and resets all the Updating flags before that too).

Thus, to actually have the update do something, someone must be listening to that NodeSig signal!

Viewport updating

The someone who is listening for updates is typically the Viewport2D for standard 2D nodes (e.g., Widgets). When a node is Render2Dd, it connects to the Viewport, and if it is not visible, it disconnects from the Viewport, so that only visible nodes can trigger updates.

Thus, all of the logic for how to update a node is contained in the Viewport, which makes sense because nodes render into the Viewport which has shared RenderState and the image on which to render, so the viewport can regulate these updates and serialize them etc.

The function triggered by the update signal is: SignalViewport2D in gi/viewport.go, which then figures out what kind of update is required:

  • A full re-render of the tree (i.e., re-sizing, layout, and then render) for structural changes or if the node has been marked as requiring a full re-render using the NeedsFullReRender flag. If it has an anchor parent flagged with ReRenderAnchor flag (e.g., a Frame or a SplitView) then re-rendering occurs just under the level of that anchor node -- otherwise it is the entire tree under the viewport (see below for more info).

  • Just a basic Render2D update of the node itself, e.g., if its state has changed. This is what happens when you mouse over a button for example -- it is very efficient and localized.

  • Although most nodes should already know when they need a full re-render, you can manually call SetFullReRender() to ensure a full re-render if the automatic detection is not working properly -- after trying various more automated ways of doing this, it seems better to just manually add these SetFullReRender() calls in the relatively few cases where they are needed -- automation can only be so smart..

If you are likely to be driving a large number of repeated updates in a short period of time ("spamming" the updates) then it is a good idea to check the IsUpdatingNode() method on the parent viewport, and don't do the update if it is already in the middle of an update. This will increase the overall responsiveness of the interface.

ReRenderAnchor

The ReRenderAnchor flag is almost always used on a gi.Frame object, when it is a container of potentially dynamically-changing content that requires full re-rendering. By default any Frame in a SplitView or a TabView frame is marked as a ReRenderAnchor. If you have further more fine-grained areas in the display that contain dynamic content, definitely mark them with SetReRenderAnchor() method.

Multiple goroutines

For most cases, the GUI updates all occur within the same goroutine as the gi.Window event loop (see Events), and thus there is generally no need to do anything special beyond the basic update blocking shown above.

However, if you do have another goroutine that is driving structural updates to the scenegraph, such that an update triggered on the main update thread might attempt to render a partially-formed scenegraph, then it it may be useful to call the BlockUpdates and UnblockUpdates methods on the parent Viewport2D around these structural changes. This prevents the viewport from updating during this time. Care must also be taken not to have these blocks be called when updating during an existing viewport update, as that will then block indefinitely waiting to lock the mutex that has already been locked at the start of the update, so you need to distinguish between those two contexts.

Window Updating

The gi.Window has a special ki.OnlySelfUpdate flag set, which means that it only deals with itself for UpdateStart / End calls and does not propagate that state down the whole tree. Furthermore, the Update signal on the window is what triggers the final Publish that blits the updated pixels out to the actual OS window so the user can see them. The Viewport typically manages the job of triggering that update.

Multiple parallel-level updates: block window updating

If you are doing something that will trigger multiple Update actions on different nodes at the same level, which is not covered by a higher-level UpdateStart (e.g., updating the state of multiple actions in a toolbar), then it is much faster to block the final window-level Publish updates during those individual updates, by adding an UpdateStart / End bracket around the full set of updates at the top-level:

	wupdt := tv.TopUpdateStart()
	defer tv.TopUpdateEnd(wupdt)
Clone this wiki locally