Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Scene Management

Viso’s structural state lives in molex::Assembly, which is owned by your application — not by viso. Viso is a pure consumer: you push the latest Arc<Assembly> to the engine via [VisoEngine::set_assembly], and the engine drains the snapshot on the next sync tick and rederives its render state.

There is no “group” abstraction in viso — every entity lives directly in the Assembly and is identified by an opaque EntityId.

Pushing Assembly Snapshots

┌──────────────────────┐                          ┌─────────────────┐
│   your application   │   Arc<Assembly>          │   VisoEngine    │
│                      │ ─engine.set_assembly──►  │                 │
│  molex::Assembly     │                          │  pending slot   │
│  (mutated freely)    │                          │  → Scene+derived│
└──────────────────────┘                          └─────────────────┘
  • Your application mutates its molex::Assembly (using molex’s APIs) and calls engine.set_assembly(Arc::new(self.assembly.clone())).
  • VisoEngine::update(dt) drains the pending snapshot; if its generation differs from the last applied one, the engine rederives its per-entity state and submits a full-rebuild request to the background mesh processor.

That’s the entire structural ingest contract for library users. There is no viso-defined channel, publisher, or consumer in the public API.

Mutating the Scene

You mutate molex::Assembly through molex’s own APIs and re-publish to viso after each batch of changes:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use molex::Assembly;
use viso::{Transition, VisoEngine};

// 1. Mutate the assembly however you like.
let mut assembly = /* your owned Assembly */;
assembly.add_entity(new_entity);
assembly.update_positions(eid, &new_coords);
// ... add/remove/update as needed ...

// 2. Push the new snapshot. Cheap: Arc<Assembly> is shared
//    by reference.
engine.set_assembly(Arc::new(assembly.clone()));

// 3. (Optional) For entities whose positions changed, queue a
//    per-entity transition so the next sync animates instead of
//    snapping. Without this, the engine snaps to the new state.
engine.set_entity_behavior(entity_id, Transition::smooth());
}

The next engine.update(dt) drains the pending snapshot, rederives the scene, and submits a full-rebuild to the background mesh processor. Mesh generation happens off-thread, so the main thread is not blocked.

Note. If you’re embedding viso as a library, ignore VisoApp entirely. VisoApp is the standalone-app helper that viso uses to be its own host when run via cargo run -p viso (or the viewer / gui / web features). Library consumers own their own Assembly and don’t need or want the convenience wrapper.

Engine-Side Annotations

Some per-entity state is purely a viso concern (it doesn’t belong on the molecular structure itself). Those live on EntityAnnotations, mutated through engine methods:

#![allow(unused)]
fn main() {
// Animation behavior overrides (keyed by EntityId, not raw u32).
let eid = engine.entity_id(raw_id).expect("known entity");
engine.set_entity_behavior(eid, Transition::collapse_expand(/* ... */));
engine.clear_entity_behavior(eid);

// Per-entity appearance overrides (drawing mode, color scheme,
// helix/sheet style, surface kind, palette, etc.).
let mut overrides = DisplayOverrides::default();
overrides.color_scheme = Some(ColorScheme::SecondaryStructure);
engine.set_entity_appearance(eid, overrides);
engine.clear_entity_appearance(eid);
}

set_entity_appearance diffs against the previous overrides and dispatches only the invalidations that matter — a surface_kind change triggers surface regeneration, a color_scheme change triggers color recomputation, and so on.

Looking Up Entities

The engine exposes a small read-only surface for looking entities up:

#![allow(unused)]
fn main() {
// Translate a raw u32 (from IPC, TOML, CLI) to an opaque EntityId.
let eid: Option<EntityId> = engine.entity_id(raw_id);

// Walk the current Assembly snapshot directly.
for entity in engine.assembly().entities() {
    println!("{:?}: {}", entity.id(), entity.molecule_type());
}

// Total entity count.
let n = engine.entity_count();
}

entity_id is the canonical “boundary translator” — wire formats carry raw u32 ids; viso-internal APIs use EntityId. Translate once at the boundary and pass EntityId through.

Focus

Focus determines what the camera follows and what the user is “working on”. It cycles through entities with Tab:

#![allow(unused)]
fn main() {
pub enum Focus {
    Session,             // All visible entities (default)
    Entity(EntityId),    // A specific entity
}
}
#![allow(unused)]
fn main() {
// Cycle: Session → Entity₁ → … → EntityN → Session
engine.execute(VisoCommand::CycleFocus);

// Focus a specific entity by raw id.
engine.execute(VisoCommand::FocusEntity { id });

// Reset to session-wide focus.
engine.execute(VisoCommand::ResetFocus);

// Read current focus state.
let focus: Focus = engine.focus();
let focused_entity: Option<EntityId> = engine.focused_entity();
}

What Happens During Sync

When a new Assembly snapshot arrives:

  1. Rederive per-entity state. For each entity, the engine builds render-ready derived data (backbone chains, sidechain topology, SS types, residue color metadata).
  2. Submit a FullRebuild. The background processor receives a Vec<FullRebuildEntity> plus the active display, color, and geometry options. Per-entity mesh caching means only entities whose mesh_version changed are regenerated.
  3. Triple-buffer the result. When the processor finishes, the resulting PreparedRebuild is written to a triple buffer.
  4. Apply on the next frame. engine.update(dt) calls apply_pending_scene, which uploads the GPU buffers in a memcpy and rebuilds picking bind groups.

The main thread never blocks. If the new meshes aren’t ready by the next frame, the previous frame’s data continues to render until they are.