atmos/os_lib/web_engine/anim_budget.rs
1//! アニメーション由来の再レイアウトを、実測コストに応じて間引くための予算判定。
2//!
3//! ## 背景
4//! CSS アニメーションは現状 1 フレームごとに `parse_and_layout`(HTML 再パース +
5//! 全ルール再カスケード)を丸ごと呼んでいる。実サイト規模(2403 ルール)では
6//! 1 回あたり数秒〜数十秒かかり、再レイアウトが描画を占有して
7//! 「描いた内容が画面に出る前に次のレイアウトが始まる」状態に陥る。
8//!
9//! 本モジュールは「直近の再レイアウト所要時間」と「前回終了からの経過時間」だけから
10//! 今フレームの再レイアウトを飛ばすかを決める純粋関数を提供する。
11//! ハードウェアにもグローバル状態にも依存しないため、単体試験で契約を固定できる。
12//!
13//! なお本来の解はアニメ値の差し替えのみで再ペイントする軽量経路であり、
14//! 本モジュールはそれが入るまでの緩和策(`spec/TODO.md` 参照)。
15
16/// この時間を超える再レイアウトを「高コスト」とみなし、間引き対象にする [ms]。
17/// これ未満なら毎フレーム再レイアウトしてもフレーム時間を食い潰さない。
18pub const COST_THRESHOLD_MS: f32 = 500.0;
19
20/// 高コスト時のクールダウン倍率。所要時間のこの倍数だけ間隔を空ける。
21/// 3.0 なら再レイアウトの占有率(デューティ比)は 1/(1+3) = 25% に収まり、
22/// 残り 75% を描画と画面転送に回せる。
23pub const COOLDOWN_FACTOR: f32 = 3.0;
24
25/// 予算判定の引数が不正だった場合のエラー。
26#[derive(Debug, PartialEq, Clone, Copy)]
27pub enum BudgetError {
28 /// 直近の再レイアウト所要時間が負・NaN・無限大。
29 InvalidLayoutMs(f32),
30 /// 前回終了からの経過時間が負・NaN・無限大。
31 InvalidSinceMs(f32),
32}
33
34/// 今フレームのアニメーション再レイアウトを飛ばすべきか判定する。
35///
36/// - `last_layout_ms`: 直近のアニメ由来再レイアウトの所要時間 [ms]。未計測なら `0.0`。
37/// - `since_ms`: 直近のアニメ由来再レイアウト完了からの経過時間 [ms]。
38///
39/// 戻り値 `Ok(true)` なら再レイアウトを飛ばす(アニメーション自体は継続)。
40///
41/// 引数が有効範囲外(負・NaN・無限大)のときは黙って戻らず、
42/// エラーログを出したうえで `Err` を返す。呼び出し側は再レイアウトを
43/// 飛ばさない(= 従来動作)方向にフォールバックすること。
44pub fn should_skip_relayout(last_layout_ms: f32, since_ms: f32) -> Result<bool, BudgetError> {
45 if !last_layout_ms.is_finite() || last_layout_ms < 0.0 {
46 crate::error!(
47 "[ANIM][BUDGET] last_layout_ms が有効範囲外です: {} (期待: 0 以上の有限値)",
48 last_layout_ms
49 );
50 return Err(BudgetError::InvalidLayoutMs(last_layout_ms));
51 }
52 if !since_ms.is_finite() || since_ms < 0.0 {
53 crate::error!(
54 "[ANIM][BUDGET] since_ms が有効範囲外です: {} (期待: 0 以上の有限値)",
55 since_ms
56 );
57 return Err(BudgetError::InvalidSinceMs(since_ms));
58 }
59
60 // 低コストなら間引かない。アニメーションを滑らかに保つ。
61 if last_layout_ms <= COST_THRESHOLD_MS {
62 return Ok(false);
63 }
64
65 Ok(since_ms < last_layout_ms * COOLDOWN_FACTOR)
66}