51 lines
1.5 KiB
Python
51 lines
1.5 KiB
Python
"""Streaming text buffer — smooth reveal of bursty network chunks.
|
|
|
|
Accumulates text from the provider and reveals it at ~60fps with a
|
|
~200ms target reveal pace, so the UI sees a smooth stream regardless
|
|
of network burst patterns.
|
|
|
|
All methods must be called from the GTK main thread.
|
|
"""
|
|
|
|
from typing import Callable
|
|
|
|
from gi.repository import GLib
|
|
|
|
|
|
class StreamingTextBuffer:
|
|
TICK_MS = 16 # ~60fps
|
|
REVEAL_TARGET_MS = 200
|
|
|
|
def __init__(self, on_reveal: Callable[[str], None]):
|
|
self._pending = ""
|
|
self._on_reveal = on_reveal
|
|
self._timer_id: int | None = None
|
|
|
|
def push(self, text: str):
|
|
self._pending += text
|
|
if self._timer_id is None:
|
|
self._timer_id = GLib.timeout_add(self.TICK_MS, self._tick)
|
|
|
|
def _tick(self) -> bool:
|
|
if not self._pending:
|
|
self._timer_id = None
|
|
return False
|
|
n = max(1, len(self._pending) * self.TICK_MS // self.REVEAL_TARGET_MS)
|
|
reveal, self._pending = self._pending[:n], self._pending[n:]
|
|
self._on_reveal(reveal)
|
|
return True
|
|
|
|
def flush(self):
|
|
if self._pending:
|
|
self._on_reveal(self._pending)
|
|
self._pending = ""
|
|
if self._timer_id is not None:
|
|
GLib.source_remove(self._timer_id)
|
|
self._timer_id = None
|
|
|
|
def cancel(self):
|
|
self._pending = ""
|
|
if self._timer_id is not None:
|
|
GLib.source_remove(self._timer_id)
|
|
self._timer_id = None
|