Packages
Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/emulator/optimized_input_processor.ex
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, <<char>>)
when char >= 32 and char <= 126 do
# Fast path for printable ASCII
CoreHandler.process_terminal_input(emulator, <<char>>)
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