defmodule Raxol.Terminal.Emulator.OptimizedInputProcessor do @moduledoc """ Optimized input processing for the terminal emulator. This module provides performance-optimized versions of input processing functions with the following improvements: - Removed debug IO.puts statements - Optimized string concatenation using iolists - Reduced function calls and pattern matching - Implemented caching for charset commands - Minimized cursor position checks """ # Performance.Optimizer lives in main raxol; guarded at runtime @compile {:no_warn_undefined, [ Raxol.Performance.Optimizer, Raxol.Performance.Profiler, Raxol.Performance.Memoization.MemoizationServer ]} import :erlang, only: [iolist_to_binary: 1] alias Raxol.Terminal.{Input.CoreHandler, ScreenBuffer} # Cache charset commands for faster lookup @charset_commands %{ "\e)0" => {:g1, :dec_special_graphics}, "\e(B" => {:g0, :us_ascii}, "\e*0" => {:g2, :dec_special_graphics}, "\x0E" => {:gl, :g1}, "\x0F" => {:gl, :g0}, "\en" => {:gl, :g2}, "\eo" => {:gl, :g3}, "\e~" => {:gr, :g2}, "\e}" => {:gr, :g1}, "\e|" => {:gr, :g3} } @doc """ Optimized version of process_input that minimizes allocations and function calls. """ def process_input(emulator, input) do do_process_input(emulator, input) end defp do_process_input(emulator, input) do # Fast path for common single character input case byte_size(input) do 1 -> process_single_char(emulator, input) _ -> process_multi_char(emulator, input) end end defp process_single_char(emulator, <>) when char >= 32 and char <= 126 do # Fast path for printable ASCII CoreHandler.process_terminal_input(emulator, <>) end defp process_single_char(emulator, input) do process_multi_char(emulator, input) end defp process_multi_char(emulator, input) do # Check for mouse events using binary pattern matching case input do <<0x1B, ?[, ?M, button, x, y, rest::binary>> -> handle_mouse_event(emulator, button, x, y, rest) _ -> handle_non_mouse_input(emulator, input) end end defp handle_mouse_event(emulator, button, x, y, rest) do output = get_mouse_output(emulator.mode_manager.mouse_report_mode, button, x, y) handle_remaining_input(emulator, rest, output) end defp handle_non_mouse_input(emulator, input) do case Map.get(@charset_commands, input) do {field, value} -> handle_charset_command(emulator, field, value) nil -> handle_parser_input(emulator, input) end end defp handle_charset_command(emulator, field, value) do updated_charset = Map.put(emulator.charset_state, field, value) {%{emulator | charset_state: updated_charset}, ""} end defp handle_parser_input(emulator, input) do {updated_emulator, output} = CoreHandler.process_terminal_input(emulator, input) # Always apply cursor check - the emulator struct doesn't track last_operation final_emulator = ensure_cursor_visible_optimized(updated_emulator) {final_emulator, output} end @doc """ Optimized cursor visibility check that minimizes repeated calculations. """ def ensure_cursor_visible_optimized(emulator) do # Early return for autowrap flag case emulator do %{cursor_handled_by_autowrap: true} -> %{emulator | cursor_handled_by_autowrap: false} _ -> # Use lazy evaluation for expensive operations Stream.unfold(emulator, &unfold_cursor_check/1) |> Enum.reduce(emulator, fn _, acc -> acc end) end end defp unfold_cursor_check(emu) do if cursor_needs_scroll?(emu) do handle_cursor_scroll(true, emu) else handle_cursor_scroll(false, emu) end end defp cursor_needs_scroll?(emulator) do # Memoize cursor position within the same operation cursor_y = memoize_cursor_y(emulator) cursor_y >= get_buffer_height(emulator) end defp memoize_cursor_y(emulator) do case Raxol.Performance.Memoization.MemoizationServer.get_memoized({:get_cursor_y, [emulator]}) do nil -> result = case emulator.cursor do pid when is_pid(pid) -> {_, y} = Raxol.Terminal.Cursor.Manager.get_position(pid) y %{row: y} -> y _ -> 0 end Raxol.Performance.Memoization.MemoizationServer.memoize( {:get_cursor_y, [emulator]}, result ) result cached -> cached end end defp get_buffer_height(emulator) do # Cache buffer height as it rarely changes case Raxol.Performance.Memoization.MemoizationServer.get_memoized( {:buffer_height, emulator.active_buffer} ) do nil -> height = ScreenBuffer.get_height(emulator.active_buffer) Raxol.Performance.Memoization.MemoizationServer.memoize( {:buffer_height, emulator.active_buffer}, height ) height height -> height end end defp scroll_once(emulator) do # Delegate to scroll operations Raxol.Terminal.Emulator.ScrollOperations.scroll_up(emulator, 1) end @doc """ Batch process multiple input chunks for better performance. """ def batch_process_inputs(emulator, inputs) when is_list(inputs) do # Process inputs in chunks for better performance batch_size = 10 inputs |> Enum.chunk_every(batch_size) |> Enum.reduce({emulator, []}, fn batch, {emu, all_outputs} -> {new_emu, batch_outputs} = Enum.reduce(batch, {emu, []}, fn input, {current_emu, outputs} -> {updated_emu, output} = process_input(current_emu, input) {updated_emu, [output | outputs]} end) {new_emu, [Enum.reverse(batch_outputs) | all_outputs]} end) |> then(fn {final_emu, outputs} -> {final_emu, outputs |> Enum.reverse() |> List.flatten() |> iolist_to_binary()} end) end @doc """ Precompile common escape sequences for faster matching. """ def precompile_sequences do # Generate optimized pattern matching functions at compile time sequences = [ {~r/^\e\[(\d+)A/, :cursor_up}, {~r/^\e\[(\d+)B/, :cursor_down}, {~r/^\e\[(\d+)C/, :cursor_forward}, {~r/^\e\[(\d+)D/, :cursor_back}, {~r/^\e\[(\d+);(\d+)H/, :cursor_position}, {~r/^\e\[2J/, :clear_screen}, {~r/^\e\[K/, :clear_line} ] :persistent_term.put(:compiled_sequences, sequences) end @doc """ Profile input processing performance. """ def profile_input_processing(emulator, sample_inputs) do if Code.ensure_loaded?(Raxol.Performance.Profiler) do Raxol.Performance.Profiler.compare(:input_processing, old: fn -> Enum.each(sample_inputs, fn input -> Raxol.Terminal.Emulator.process_input(emulator, input) end) end, new: fn -> Enum.each(sample_inputs, fn input -> process_input(emulator, input) end) end ) else :ok end end # Helper functions for pattern matching instead of if statements defp get_mouse_output(:none, _button, _x, _y), do: [] defp get_mouse_output(_mode, button, x, y), do: ["\e[M", button, x, y] defp handle_remaining_input(emulator, <<>>, output) do {emulator, iolist_to_binary(output)} end defp handle_remaining_input(emulator, rest, output) do {final_emulator, remaining_output} = process_input(emulator, rest) {final_emulator, iolist_to_binary([output | remaining_output])} end defp handle_cursor_scroll(true, emu) do scrolled_emu = scroll_once(emu) {scrolled_emu, emu} end defp handle_cursor_scroll(false, emu), do: {nil, emu} end