~cytrogen/gstack

ref: aa7daf052ece077ab3d05da3834ad7a029b79bc9 gstack/browse/src/buffers.ts -rw-r--r-- 4.2 KiB
aa7daf05 — Garry Tan fix: Codex description limit + wrong-repo bug (v0.11.19.0) (#471) a month ago
                                                                                
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
/**
 * Shared buffers and types — extracted to break circular dependency
 * between server.ts and browser-manager.ts
 *
 * CircularBuffer<T>: O(1) insert ring buffer with fixed capacity.
 *
 *   ┌───┬───┬───┬───┬───┬───┐
 *   │ 3 │ 4 │ 5 │   │ 1 │ 2 │  capacity=6, head=4, size=5
 *   └───┴───┴───┴───┴─▲─┴───┘
 *                      │
 *                    head (oldest entry)
 *
 *   push() writes at (head+size) % capacity, O(1)
 *   toArray() returns entries in insertion order, O(n)
 *   totalAdded keeps incrementing past capacity (flush cursor)
 */

// ─── CircularBuffer ─────────────────────────────────────────

export class CircularBuffer<T> {
  private buffer: (T | undefined)[];
  private head: number = 0;
  private _size: number = 0;
  private _totalAdded: number = 0;
  readonly capacity: number;

  constructor(capacity: number) {
    this.capacity = capacity;
    this.buffer = new Array(capacity);
  }

  push(entry: T): void {
    const index = (this.head + this._size) % this.capacity;
    this.buffer[index] = entry;
    if (this._size < this.capacity) {
      this._size++;
    } else {
      // Buffer full — advance head (overwrites oldest)
      this.head = (this.head + 1) % this.capacity;
    }
    this._totalAdded++;
  }

  /** Return entries in insertion order (oldest first) */
  toArray(): T[] {
    const result: T[] = [];
    for (let i = 0; i < this._size; i++) {
      result.push(this.buffer[(this.head + i) % this.capacity] as T);
    }
    return result;
  }

  /** Return the last N entries (most recent first → reversed to oldest first) */
  last(n: number): T[] {
    const count = Math.min(n, this._size);
    const result: T[] = [];
    const start = (this.head + this._size - count) % this.capacity;
    for (let i = 0; i < count; i++) {
      result.push(this.buffer[(start + i) % this.capacity] as T);
    }
    return result;
  }

  get length(): number {
    return this._size;
  }

  get totalAdded(): number {
    return this._totalAdded;
  }

  clear(): void {
    this.head = 0;
    this._size = 0;
    // Don't reset totalAdded — flush cursor depends on it
  }

  /** Get entry by index (0 = oldest) — used by network response matching */
  get(index: number): T | undefined {
    if (index < 0 || index >= this._size) return undefined;
    return this.buffer[(this.head + index) % this.capacity];
  }

  /** Set entry by index (0 = oldest) — used by network response matching */
  set(index: number, entry: T): void {
    if (index < 0 || index >= this._size) return;
    this.buffer[(this.head + index) % this.capacity] = entry;
  }
}

// ─── Entry Types ────────────────────────────────────────────

export interface LogEntry {
  timestamp: number;
  level: string;
  text: string;
}

export interface NetworkEntry {
  timestamp: number;
  method: string;
  url: string;
  status?: number;
  duration?: number;
  size?: number;
}

export interface DialogEntry {
  timestamp: number;
  type: string;        // 'alert' | 'confirm' | 'prompt' | 'beforeunload'
  message: string;
  defaultValue?: string;
  action: string;      // 'accepted' | 'dismissed'
  response?: string;   // text provided for prompt
}

// ─── Buffer Instances ───────────────────────────────────────

const HIGH_WATER_MARK = 50_000;

export const consoleBuffer = new CircularBuffer<LogEntry>(HIGH_WATER_MARK);
export const networkBuffer = new CircularBuffer<NetworkEntry>(HIGH_WATER_MARK);
export const dialogBuffer = new CircularBuffer<DialogEntry>(HIGH_WATER_MARK);

// ─── Convenience add functions ──────────────────────────────

export function addConsoleEntry(entry: LogEntry) {
  consoleBuffer.push(entry);
}

export function addNetworkEntry(entry: NetworkEntry) {
  networkBuffer.push(entry);
}

export function addDialogEntry(entry: DialogEntry) {
  dialogBuffer.push(entry);
}