package miaou-driver-matrix

  1. Overview
  2. Docs
Miaou high-performance terminal driver with diff rendering

Install

dune-project
 Dependency

Authors

Maintainers

Sources

v0.5.2.tar.gz
md5=60a3b9f181f24572a06a9492532bfdda
sha512=fcc35a275066be2900e6201782faf47503076fa4640f08cf78067835a6f447b74613009e55b2ac799adb7ca46f1bffa261fc5971753f2cc3c6bef327511c7ef6

doc/src/miaou-driver-matrix.driver/matrix_diff.ml.html

Source file matrix_diff.ml

1
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