defmodule Raxol.Terminal.Scroll.Optimizer do @moduledoc """ Handles scroll optimization for better performance. Dynamically adjusts batch size based on recent scroll patterns and (optionally) performance metrics. """ @history_size 10 @type scroll_event :: %{ direction: :up | :down, lines: non_neg_integer(), timestamp: integer() } @type t :: %__MODULE__{ batch_size: non_neg_integer(), last_optimization: non_neg_integer(), history: [scroll_event()] } defstruct batch_size: 10, last_optimization: 0, history: [] @doc """ Creates a new optimizer instance. """ @spec new() :: t() def new do %__MODULE__{ batch_size: 10, last_optimization: System.monotonic_time(), history: [] } end @doc """ Optimizes scroll operations for better performance. - Increases batch size for large/rapid scrolls. - Decreases batch size for small/precise or alternating scrolls. - Uses recent scroll history to adapt. """ @spec optimize(t(), :up | :down, non_neg_integer()) :: t() def optimize(%__MODULE__{} = optimizer, direction, lines) do now = System.monotonic_time() event = %{direction: direction, lines: lines, timestamp: now} history = [event | Enum.take(optimizer.history, @history_size - 1)] new_batch_size = calculate_batch_size(optimizer.batch_size, history) %{ optimizer | batch_size: new_batch_size, last_optimization: now, history: history } end defp calculate_batch_size(current_batch_size, history) do avg_lines = calculate_average_lines(history) alternation_ratio = calculate_alternation_ratio(history) calculate_batch_size_by_conditions( current_batch_size, avg_lines, alternation_ratio ) end # High alternation pattern - reduce batch size to handle direction changes defp calculate_batch_size_by_conditions( current_batch_size, _avg_lines, alternation_ratio ) when alternation_ratio > 0.5 do max(current_batch_size - 2, 1) end # Large scroll operations - increase batch size significantly defp calculate_batch_size_by_conditions( current_batch_size, avg_lines, _alternation_ratio ) when avg_lines >= 50 do min(current_batch_size + 10, 100) end # Medium scroll operations - moderate increase defp calculate_batch_size_by_conditions( current_batch_size, avg_lines, _alternation_ratio ) when avg_lines >= 20 do min(current_batch_size + 5, 50) end # Very small scrolls - reduce batch size for precision defp calculate_batch_size_by_conditions( current_batch_size, avg_lines, _alternation_ratio ) when avg_lines <= 2 do max(current_batch_size - 2, 1) end # Small scrolls - slight reduction defp calculate_batch_size_by_conditions( current_batch_size, avg_lines, _alternation_ratio ) when avg_lines <= 5 do max(current_batch_size - 1, 1) end # Default case - no changes needed defp calculate_batch_size_by_conditions( current_batch_size, _avg_lines, _alternation_ratio ) do current_batch_size end # history is always non-empty when called from calculate_batch_size # (optimize/3 prepends an event before calling) defp calculate_average_lines(history) do Enum.sum(Enum.map(history, & &1.lines)) / length(history) end defp calculate_alternation_ratio(history) when length(history) <= 1, do: 0 defp calculate_alternation_ratio(history) do alternations = history |> Enum.chunk_every(2, 1, :discard) |> Enum.count(fn [a, b] -> a.direction != b.direction end) alternations / (length(history) - 1) end end