Skip to content

The scene's JSON tree is re-deserialized on every frame (no cache between frames) #159

Description

@LeadcodeDev

Found while verifying a round-4 fix, outside the audit corpus.

The call chain

render_frame_task (per frame)
render_scene_frame_scaled_with_prev_bg
prepare_scene (engine/render/scene.rs:549)
deserialize_children (engine/render/scene.rs:534)
→ for each child: serde_json::from_value::<ChildComponent>(v.clone())

prepare_scene is three lines long and has no cache. Its result depends only on scene, never on frame_in_scene — it is identical for every frame of a scene, and recomputed in full for each one.

The cost

Per frame, per child:

  1. a .clone() of the whole serde_json subtree;
  2. a deserialization into ChildComponent, whose component field is an untagged enum with 57 variants — serde tries them in order, re-parsing the same object on every failed attempt.

On a 1200-frame render with 50 nodes, that is 60,000 clones and 60,000 deserializations of a 57-variant enum, for a strictly constant result.

Unmeasured. I am not quoting a figure because I have not profiled it — but the order of magnitude deserves profiling, especially since the round-3 paint workstream (PR #154) already found a comparable factor on another per-frame path (unbounded save_layer: 42-60s → 0.5s over 60 frames).

Direction

Deserialize once per scene, ahead of the frame loop, and pass the Vec<ChildComponent> to the render functions. The render_frame_v2 / render_frame_v2_scaled entry points already take &[ChildComponent]; it is the render_scene_frame* wrappers that call prepare_scene each time. The fix is mostly hoisting the call one level, not re-architecting.

One detail to watch: deserialize_children emits a stderr warning per unreadable child. Today that fires once per frame; hoisting it naturally reduces it to once per scene, which is also the desirable behaviour.

Suggested verification

A before/after benchmark on a realistic scenario (examples/mega-showcase.json), comparing total render time. Plus a test asserting prepare_scene is no longer called from the per-frame path.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions