)]}'
{
  "commit": "3330baec48641c87330b24268e403349f02c38fe",
  "tree": "552ec3ed8b9873cb5b1bbcaa0183de5555c79ffa",
  "parents": [
    "bbf3c4d8ba7971ab3b5b453d148b5bba9ddf6eb6"
  ],
  "author": {
    "name": "bodymovin",
    "email": "hello@rive.app",
    "time": "Thu Oct 01 04:02:36 2026 +0000"
  },
  "committer": {
    "name": "bodymovin",
    "email": "hello@rive.app",
    "time": "Thu Oct 01 04:02:36 2026 +0000"
  },
  "message": "perf(runtime): Idle state machines stop redoing work that can\u0027t change anything (#14508) 30c30acec9\n* perf(runtime): skip transition searches on settled state machine layers\n\nEvery layer of every state machine searched all of its transitions at least\ntwice a frame, even when nothing its conditions read had changed. In an idle\n400-row component list with view model conditions, that search was ~40% of\nthe frame.\n\nA layer whose search finds nothing now settles: later searches return at once\nuntil its state changes or something its conditions read does. Only states\nwhose transitions use view model conditions alone can settle (classified once\nper state in StateMachine::onAddedClean). State machine inputs, random states,\nand component, artboard, focus and scripted conditions keep the full search.\nSettled layers wake on any dirtied state machine data bind, on data context\nchanges (new DataBindContainer::dataContextChanged), and on main view model\nswaps; none settles while a dirtied bind is still unapplied. Editor builds\nnever settle, since transitions change there after import.\n\nMemory: StateMachineLayerInstance stays 80 B (its flags are now bit-fields)\nand StateMachineInstance 288 B (two bools in existing padding). LayerState\ngrows 104 -\u003e 112 B per state definition.\n\nUnder TESTING every skipped search is re-run and aborts if it would have\ntransitioned: the full suite checks 1.7M skips with no mismatch.\nArtboardFrame benches: a 400-tile list scene -33% idle (-22% with the\nRiveRenderer), bidirectional_binding_idle -85%, the rest neutral.\n\n* perf(runtime): freeze settled layers in states that key nothing\n\nA settled layer still advanced and applied its current state every frame.\nFor entry, exit and any states, and for animation states whose animation\nkeys nothing (including the empty animation that stands in for a missing\none), that work only moves a clock and applies no values. In an idle\n400-row component list it was ~30% of the frame.\n\nSuch a layer is now frozen: once settled, fully mixed and no longer keeping\nitself going, its advance returns what it would have without advancing,\nmixing, applying or searching. The one reader of that clock is exit time,\nso a state with any enabled exit-time transition never freezes\n(LayerState::transitionsIgnoreTime, classified in onAddedClean). Whether a\nstate keys anything is checked on the live instance instead: an artboard\u0027s\nlast layer only resolves its animations once the next layer is read, which\ncan be after the artboard initializes.\n\nNo size change: the new LayerState flag sits in existing padding.\nIdle-frame benches vs settled layers alone: a 400-tile list scene -31%\nsteady-state advance, -22% advance + draw (-47% vs master);\nbidirectional_binding_idle -24%; the rest neutral.\n\n* perf(runtime): add state machine bind clones once their target is set\n\nStateMachineInstance added each cloned data bind before pointing it at its\nper-instance bindable. addDataBind then saw no target, decided a toSource\nbind couldn\u0027t push, and put it on the polling list, so every listener bind\nin every state machine was re-read on each updateDataBinds pass. The state\nmachine never applies target to source in those passes (it always passes\nfalse; listener actions push their value explicitly), so the polling did\nnothing but keep updateDataBinds from returning early.\n\nAdding the clone after its target is set puts such binds on the push path:\nthey are processed only when the listener\u0027s bindable actually changes.\n\n* perf(runtime): skip the per-frame work of quiet list rows\n\nEvery row of an ArtboardComponentList advanced its state machine, searched\nits transitions, updated its data binds, advanced its artboard and reset it\nseveral times a frame, even when none of that did anything. With 400 settled,\nfrozen tile rows that bookkeeping was ~95% of the frame.\n\nA row whose per-frame work would do nothing is now marked quiet and skipped by\nevery pass (advance, settle, updateDataBinds, reset, and the update pass)\nuntil something wakes it. Quiet means: its state machine\u0027s layers are all\nfrozen with nothing pending (inputs, binds, reports, focus and semantic\nqueues, draw order), and its artboard has no dirt, bind work, layout or host\ntransform change, and only advancing components that report being idle\n(AdvancingComponent::quietState). Rows with scripts, nested artboards,\njoysticks, resettables, text inputs, scroll constraints or converters that\nadvance never go quiet, and neither do rows of lists with duplicate items.\nUnrealized rows count as quiet: they have no work until realized.\n\nRows are woken through Artboard::wakeIfQuietRow, from the artboard\u0027s dirt,\ndata binds, data context, layout and host transform, and from its state\nmachine (markNeedsAdvance, focus and semantic queues, reports, unsettling,\nreset) and every listener\u0027s performChanges, which keyboard and text\nlisteners otherwise use without marking anything. Rows are reset before\ntheir instances change (updateList, setRowsForItem, addArtboardAt, clear).\nThe list keeps one bit per row, so passes step over quiet rows 64 at a time.\n\nItem view models of number-bound lists are still advanced for every row:\nskipping that needs a changed flag on view model instances.\n\nMemory: Artboard gains a row index in existing padding (size unchanged);\neach list gains two bit vectors.\n\nUnder TESTING every skipped row pass still runs after the pass and aborts\nif the row stopped being quiet without waking or did any work; the suite\nchecks 22,847 of them. Idle-frame benches vs frozen layers: a 400-tile list\nscene -92% steady-state advance (68 -\u003e 5.6 us), -60% advance + draw;\ncomponent_list_virtualized_long -16%, its scroll -4%; the rest neutral.\n\n* fix(runtime): don\u0027t wake a quiet row from a state machine\u0027s destructor\n\nA state machine\u0027s destructor unbinds its data context, which unsettles its\nlayers and so woke its artboard\u0027s quiet list row, reading the artboard. When\nan ArtboardComponentList cleared its pools it destroyed the pooled artboards\nbefore the pooled state machines, so that read hit freed memory (caught by\nASan in \"Artboard lists with child artboard origin offset\"). A host can also\ndestroy an artboard before its state machine.\n\nThe state machine now stops waking its row once its destructor starts, and\nthe list destroys pooled state machines before pooled artboards, as it\nalready does for its realized rows. m_bindsPending, m_anySettled and the new\nm_destroying are bit-fields sharing the byte after m_needsAdvance, so\nStateMachineInstance stays 288 B.\n\nCo-authored-by: hernan \u003chernan@rive.app\u003e\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "2ef8e90f764db09452930081afc34acd95e6fb7e",
      "old_mode": 33188,
      "old_path": ".rive_head",
      "new_id": "1b52e3eb744045dcbb3bdeee966522be6915645f",
      "new_mode": 33188,
      "new_path": ".rive_head"
    },
    {
      "type": "modify",
      "old_id": "dc04653fdfab011f6683236a27127c93756720d4",
      "old_mode": 33188,
      "old_path": "include/rive/advancing_component.hpp",
      "new_id": "7c1fe7d9c7ca1cc4a87a6713e4cea6149e9b82e2",
      "new_mode": 33188,
      "new_path": "include/rive/advancing_component.hpp"
    },
    {
      "type": "modify",
      "old_id": "7d6daabdaa1c56c4f7b2219a8b685bb8f7f484e0",
      "old_mode": 33188,
      "old_path": "include/rive/animation/layer_state.hpp",
      "new_id": "a1ace7de539e55cf57d3166d523fc08e4b7fdbf3",
      "new_mode": 33188,
      "new_path": "include/rive/animation/layer_state.hpp"
    },
    {
      "type": "modify",
      "old_id": "387c1e29910d67e5b04d7ce56e8e3c4cbd4f076a",
      "old_mode": 33188,
      "old_path": "include/rive/animation/state_machine_instance.hpp",
      "new_id": "cf905db64bf679c7184702ee803d452c07e70ff8",
      "new_mode": 33188,
      "new_path": "include/rive/animation/state_machine_instance.hpp"
    },
    {
      "type": "modify",
      "old_id": "b692c612f9ed8c9896a580940601e68b627d7731",
      "old_mode": 33188,
      "old_path": "include/rive/artboard.hpp",
      "new_id": "9db88eda50e020d42c46605851f20827d3b8ed83",
      "new_mode": 33188,
      "new_path": "include/rive/artboard.hpp"
    },
    {
      "type": "modify",
      "old_id": "48269b2c7ea783caa79f1d80e7666f22182e515c",
      "old_mode": 33188,
      "old_path": "include/rive/artboard_component_list.hpp",
      "new_id": "efa5d3a2f052dac5943f2597a8f8e2cda2d559bc",
      "new_mode": 33188,
      "new_path": "include/rive/artboard_component_list.hpp"
    },
    {
      "type": "modify",
      "old_id": "8cbf3701bd577694765beebb8bffe4bb127acdb7",
      "old_mode": 33188,
      "old_path": "include/rive/artboard_host.hpp",
      "new_id": "7b2fb52af56bb09425b62147d52e22be53d9226b",
      "new_mode": 33188,
      "new_path": "include/rive/artboard_host.hpp"
    },
    {
      "type": "modify",
      "old_id": "86e3db82ee4cb7199bdfd16425a078c36eb97442",
      "old_mode": 33188,
      "old_path": "include/rive/data_bind/converters/data_converter.hpp",
      "new_id": "10516bb3a608d252bab35c128e219cccd0a33b0a",
      "new_mode": 33188,
      "new_path": "include/rive/data_bind/converters/data_converter.hpp"
    },
    {
      "type": "modify",
      "old_id": "b6829a758c5e1ff43a0fdc4987f9071a36e9c2bc",
      "old_mode": 33188,
      "old_path": "include/rive/data_bind/converters/data_converter_group.hpp",
      "new_id": "b7144309cef615d2a187f973df080678e5bf7e22",
      "new_mode": 33188,
      "new_path": "include/rive/data_bind/converters/data_converter_group.hpp"
    },
    {
      "type": "modify",
      "old_id": "2ccb0086fd6b615598383e3577a1e6e071afacff",
      "old_mode": 33188,
      "old_path": "include/rive/data_bind/converters/data_converter_interpolator.hpp",
      "new_id": "963c35ec3e7d951e4a75e9f04f1304e44ede0f9f",
      "new_mode": 33188,
      "new_path": "include/rive/data_bind/converters/data_converter_interpolator.hpp"
    },
    {
      "type": "modify",
      "old_id": "34da9e63293e6dd56dbfe76c95b7fca2b14e2e40",
      "old_mode": 33188,
      "old_path": "include/rive/data_bind/data_bind_container.hpp",
      "new_id": "f5264accbab2908309db1e619d78e4560a458830",
      "new_mode": 33188,
      "new_path": "include/rive/data_bind/data_bind_container.hpp"
    },
    {
      "type": "modify",
      "old_id": "e376a4f0658e9686e76d6ebf376c887bb533e603",
      "old_mode": 33188,
      "old_path": "include/rive/layout/layout_participant.hpp",
      "new_id": "303c783ecb5e2d45ebe83e0b4684033705bcd3ee",
      "new_mode": 33188,
      "new_path": "include/rive/layout/layout_participant.hpp"
    },
    {
      "type": "modify",
      "old_id": "1120ad1a3bbca3aeaf04dfacf1e5e62c905015c9",
      "old_mode": 33188,
      "old_path": "include/rive/layout_component.hpp",
      "new_id": "f94f2cdfd53f7c8a9822fadc3b00b070e756e8c7",
      "new_mode": 33188,
      "new_path": "include/rive/layout_component.hpp"
    },
    {
      "type": "modify",
      "old_id": "adc8bce8f21c833d9176186d59167cfccc0c9128",
      "old_mode": 33188,
      "old_path": "include/rive/scripted/scripted_data_converter.hpp",
      "new_id": "fe78d52e930fe42c2204c5ead98772be58f6f67e",
      "new_mode": 33188,
      "new_path": "include/rive/scripted/scripted_data_converter.hpp"
    },
    {
      "type": "modify",
      "old_id": "4025e2d5bb4d8273b2528ada1c7987575c99a9ce",
      "old_mode": 33188,
      "old_path": "src/animation/state_machine.cpp",
      "new_id": "2d468b522a0ac0ef3cd92450d2fff777611f32d1",
      "new_mode": 33188,
      "new_path": "src/animation/state_machine.cpp"
    },
    {
      "type": "modify",
      "old_id": "9ede6c41fb422f4b6654f1720eab61e2654b65bb",
      "old_mode": 33188,
      "old_path": "src/animation/state_machine_instance.cpp",
      "new_id": "a81c5c4200d0631451c61cb0c00b03496052d031",
      "new_mode": 33188,
      "new_path": "src/animation/state_machine_instance.cpp"
    },
    {
      "type": "modify",
      "old_id": "a31086f8ad286b6b806a57101812272f789933ad",
      "old_mode": 33188,
      "old_path": "src/animation/state_machine_listener.cpp",
      "new_id": "5fddcdc8308b820f7de3111bef3345cafcabc302",
      "new_mode": 33188,
      "new_path": "src/animation/state_machine_listener.cpp"
    },
    {
      "type": "modify",
      "old_id": "1b0ae5a4f973d014f6c878f725dc86f911fb574b",
      "old_mode": 33188,
      "old_path": "src/artboard.cpp",
      "new_id": "9f6b17f61733cbb094ce1a2d59760f2e598db007",
      "new_mode": 33188,
      "new_path": "src/artboard.cpp"
    },
    {
      "type": "modify",
      "old_id": "7365d99436356bbf064542761f781bc9732737d8",
      "old_mode": 33188,
      "old_path": "src/artboard_component_list.cpp",
      "new_id": "32a7fb4b57243f80b9ffa6cd1d8151fee9264012",
      "new_mode": 33188,
      "new_path": "src/artboard_component_list.cpp"
    },
    {
      "type": "modify",
      "old_id": "0244d5661e53ed7a9809a8c726e2203315d5de2d",
      "old_mode": 33188,
      "old_path": "src/data_bind/converters/data_converter_group.cpp",
      "new_id": "dab77c0971e176ed1ded0cb0d9efe326a07892b7",
      "new_mode": 33188,
      "new_path": "src/data_bind/converters/data_converter_group.cpp"
    },
    {
      "type": "modify",
      "old_id": "643f3913983230008c7e1981f2e5225253c9fda0",
      "old_mode": 33188,
      "old_path": "src/data_bind/data_bind_container.cpp",
      "new_id": "2a4a47991cf3da470ed5e62386cffab296689eb6",
      "new_mode": 33188,
      "new_path": "src/data_bind/data_bind_container.cpp"
    },
    {
      "type": "modify",
      "old_id": "fb4ca9be1b81c3a2e46a69af41f56c34c832ac70",
      "old_mode": 33188,
      "old_path": "src/layout/layout_participant.cpp",
      "new_id": "00524ef083a59c8e1d8112a750deb837bfbccb65",
      "new_mode": 33188,
      "new_path": "src/layout/layout_participant.cpp"
    },
    {
      "type": "modify",
      "old_id": "cc3ecbcbe39b6520657cf2dfa456874942b2e4eb",
      "old_mode": 33188,
      "old_path": "src/layout_component.cpp",
      "new_id": "9b682f7ee69db5c5272275382e05a0beb2bd0cad",
      "new_mode": 33188,
      "new_path": "src/layout_component.cpp"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "87167aafcd3786b99abeddd3955271d83dd40ba1",
      "new_mode": 33188,
      "new_path": "tests/unit_tests/runtime/quiet_rows_test.cpp"
    },
    {
      "type": "add",
      "old_id": "0000000000000000000000000000000000000000",
      "old_mode": 0,
      "old_path": "/dev/null",
      "new_id": "302e5d62249d6c52c32c6b4395b3cea79c0aa33f",
      "new_mode": 33188,
      "new_path": "tests/unit_tests/runtime/settled_layers_test.cpp"
    }
  ]
}
