Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
matrix_diff.ml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174(******************************************************************************) (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2025 Nomadic Labs <contact@nomadic-labs.com> *) (* *) (******************************************************************************) type change = | MoveTo of int * int | SetStyle of Matrix_cell.style | WriteChar of string | WriteRun of string * int (* Shared diff computation logic. Takes callbacks for cell access to support both locked and unlocked variants. Returns changes in correct order. Parameters: - get_front: get cell from front buffer at (row, col) - get_back: get cell from back buffer at (row, col) - start_row, start_col: region start (0,0 for full buffer) - end_row, end_col: region end (exclusive) - cols: total columns (for cursor wrap calculation) Optimizations: - Emits WriteRun for consecutive identical characters (e.g., "────") - Only emits MoveTo when cursor position changes - Only emits SetStyle when style changes *) let compute_diff ~get_front ~get_back ~start_row ~start_col ~end_row ~end_col ~cols = let changes = ref [] in (* Track cursor position and style. Use -1 to force MoveTo on first change. *) let cursor_row = ref (-1) in let cursor_col = ref (-1) in let current_style = ref Matrix_cell.default_style in (* Pending run of identical characters *) let run_char = ref "" in let run_count = ref 0 in (* Emit a change, prepending to list (we'll reverse at the end) *) let emit change = changes := change :: !changes in (* Flush any pending character run *) let flush_run () = if !run_count > 0 then begin if !run_count = 1 then emit (WriteChar !run_char) else emit (WriteRun (!run_char, !run_count)) ; run_count := 0 ; run_char := "" end in (* Move cursor if not already at target position *) let move_to row col = if row <> !cursor_row || col <> !cursor_col then begin flush_run () ; emit (MoveTo (row, col)) ; cursor_row := row ; cursor_col := col end in (* Set style if different from current *) let set_style style = if not (Matrix_cell.style_equal style !current_style) then begin flush_run () ; emit (SetStyle style) ; current_style := style end in (* Write a character - batches consecutive identical chars into runs *) let write_char char = if !run_count > 0 && char = !run_char then begin (* Extend current run *) incr run_count end else begin (* Start new run *) flush_run () ; run_char := char ; run_count := 1 end ; incr cursor_col ; if !cursor_col >= cols then cursor_col := cols - 1 in (* Scan through buffer region *) for row = start_row to end_row - 1 do for col = start_col to end_col - 1 do let front = get_front ~row ~col in let back = get_back ~row ~col in if not (Matrix_cell.equal front back) then begin move_to row col ; set_style back.style ; write_char back.char end done done ; (* Flush final run *) flush_run () ; (* Return changes in correct order *) List.rev !changes (* Compute diff between front and back buffers - NOT thread-safe, use compute_atomic *) let compute buffer = let rows = Matrix_buffer.rows buffer in let cols = Matrix_buffer.cols buffer in compute_diff ~get_front:(fun ~row ~col -> Matrix_buffer.get_front buffer ~row ~col) ~get_back:(fun ~row ~col -> Matrix_buffer.get_back buffer ~row ~col) ~start_row:0 ~start_col:0 ~end_row:rows ~end_col:cols ~cols (* Compute diff atomically with buffer lock held - thread-safe for two-domain architecture. Also clears the dirty flag while holding the lock to prevent race conditions where new writes could set dirty=true between when we release the lock and when clear_dirty would have been called. *) let compute_atomic buffer = Matrix_buffer.with_read_lock buffer (fun () -> let rows = Matrix_buffer.rows_unlocked buffer in let cols = Matrix_buffer.cols_unlocked buffer in let changes = compute_diff ~get_front:(fun ~row ~col -> Matrix_buffer.get_front_unlocked buffer ~row ~col) ~get_back:(fun ~row ~col -> Matrix_buffer.get_back_unlocked buffer ~row ~col) ~start_row:0 ~start_col:0 ~end_row:rows ~end_col:cols ~cols in (* Swap buffers while still holding the lock *) Matrix_buffer.swap_unlocked buffer ; (* Clear dirty flag while still holding the lock to prevent race with new writes *) Matrix_buffer.clear_dirty_unlocked buffer ; changes) (* Compute diff for a specific region *) let compute_region buffer ~row ~col ~width ~height = let rows = Matrix_buffer.rows buffer in let cols = Matrix_buffer.cols buffer in let end_row = min (row + height) rows in let end_col = min (col + width) cols in compute_diff ~get_front:(fun ~row ~col -> Matrix_buffer.get_front buffer ~row ~col) ~get_back:(fun ~row ~col -> Matrix_buffer.get_back buffer ~row ~col) ~start_row:row ~start_col:col ~end_row ~end_col ~cols (* Count changed cells *) let count_changes buffer = let rows = Matrix_buffer.rows buffer in let cols = Matrix_buffer.cols buffer in let count = ref 0 in for row = 0 to rows - 1 do for col = 0 to cols - 1 do if Matrix_buffer.cell_changed buffer ~row ~col then incr count done done ; !count