Skip to main content

Shared line playback core

Connected Rust controllers, atomic material preparation and current script scope

The Rust map session now owns shared line controllers and reuses the common timing and style-track computation. Flow, dash, reveal, arrows, glow and image patterns overlay the display without writing sampled values into user paint or source data. Applications provide their own layer panels, playback buttons and layout.

This page retains the Rust core example. The common script facade and complete HTML are now available: shared line playback and style tracks. Matching versions expose sceneLinePlayback / sceneTracks as true.

The following example uses the current core repository's sqs_map::session::MapSession and scene JSON channel. It is a Rust core example, not a published npm interface or native binary release. getLayer retains MapLibre-style specification semantics; getLayers supplies small state snapshots with instance identities.

use serde_json::json;
use sqs_map::session::{MapSession, SessionError};

fn main() -> Result<(), SessionError> {
    let mut map = MapSession::new_headless(1, 256, 192)?;
    map.camera_call("jumpTo", &[json!({"center":[0,0],"zoom":12})])?;
    let owner = map.scene_call("create", &[json!({"id":"lines"})])?;
    let owner = json!({"id":owner["id"],"sceneInstanceId":owner["sceneInstanceId"]});
    let source = map.source_call(
        "addSource",
        &[
            json!("road"),
            json!({"type":"geojson","lineMetrics":true,
            "data":{"type":"LineString","coordinates":[[-0.003,0],[0.003,0]]}}),
        ],
    )?;
    let source = json!({"id":source["id"],"sourceInstanceId":source["sourceInstanceId"]});
    map.scene_call("attachSource", &[owner.clone(), source])?;
    map.layer_call(
        "addLayer",
        &[json!({"id":"road-line","type":"line","source":"road",
        "paint":{"line-color":"#ff0000","line-width":8}})],
    )?;
    // getLayer follows MapLibre and returns the style specification.
    // getLayers returns the small state snapshots that include instance identities.
    let layers = map.layer_call("getLayers", &[])?;
    let layer = &layers.as_array().unwrap()[0];
    let layer = json!({"id":layer["id"],"layerInstanceId":layer["layerInstanceId"]});
    let playback = map.scene_call(
        "animation.line",
        &[
            json!({"id":"flow","layer":layer,"timing":{"duration":3000,"loop":true},
            "effects":[{"type":"flow","spacing":0.3,"trailLength":0.2}],
            "tracks":{"widthScale":[{"offset":0,"value":1},{"offset":1,"value":2}]}}),
            json!({"owner":owner}),
        ],
    )?;
    map.scene_call("start", &[owner.clone()])?;
    let _rgba = map.render_rgba()?; // The host keeps rendering to advance playback.
    let identity = json!({"id":playback["id"],"playbackInstanceId":playback["playbackInstanceId"]});
    map.scene_call("playback.stop", &[identity])?;
    map.scene_call("remove", &[owner])?;
    Ok(())
}

The target must be an actual line layer bound to a loaded, static, nonclustered GeoJSON source with lineMetrics:true, explicitly attached to its owner scene. Shared lines currently accept only two-dimensional line coordinates with absolute latitude at most 85.0511287798066; three-dimensional and out-of-range coordinates are explicitly rejected. Limits are 4096 paths and 100,000 vertices per layer, 64 shared controllers per map, 32 simultaneously rendered animated line layers and 256 MiB of GPU resources. Logical-pixel path precision is also validated.

timing uses duration and delay in milliseconds, loop, direction (forward/reverse/alternate), easing (linear/ease-in-out), and fill (forwards/none). tracks supports color, opacity and widthScale curves, each with 2 to 32 strictly increasing offsets covering 0 through 1. Owner pause and manual controller pause are independent. Seeking a paused controller to its endpoint keeps it paused; resuming and accepting the endpoint completes the round. Finite playback retains the final overlay or restores static paint according to fill; looping playback does not complete on its own.

Patterns use {"type":"pattern","image":"registered-image-name"} and capture the current image instance and revision, rather than accepting a local file path as a shared interface. SDF line patterns are unavailable. Changes to a layer instance, source data/state, paint or registered image invalidate the old controller and preserve new external data. Removing a controller or its scene restores static paint while retaining the source and layer. Owner stop follows scene visibility rules.

Display preparation validates CPU geometry and aggregate budgets before allocating candidate GPU resources and publishing state. Failed proposals do not advance controllers or emit false completion. Ordinary frames update phases and material parameters without rebuilding path geometry. Actual Linux software Vulkan acceptance covers all six effects and their composition, 36 frames, foreign-output rollback and atomic rejection of a 33-layer start; removing one controller allows 32 layers to start. Ten new model checks, 16 route regressions, the existing line lifecycle check and four renderer checks pass. Common browser controllers, the same script across four hosts, GL controller integration, hardware, mobile devices, external operating systems and formal releases still require separate acceptance.

Run the complete Rust example in the core repository: cargo run --locked -p sqs-map --example managed_line_playback.