send method

void send(
  1. Msg msg
)

Sends a message to the program.

The message will be processed through Model.update and the view will be re-rendered.

This can be called from outside the program to inject messages. Messages are queued and processed sequentially to prevent race conditions and ensure consistent state updates.

Implementation

void send(Msg msg) {
  if (!_running) return;

  final interceptor = _options.interceptor;
  if (interceptor != null) {
    final intercepted = interceptor.onSend(msg);
    if (intercepted == null) {
      if (TuiTrace.enabled) {
        _trace(
          'interceptor dropped ${msg.runtimeType}',
          tag: TraceTag.dispatch,
        );
      }
      return;
    }
    msg = intercepted;
  }

  // Coalesce: when a key arrives, drop all pending mouse messages.
  // Also drop pending frame ticks so input is not blocked by stale animation
  // updates. When a motion/wheel arrives, drop prior events of the same
  // action. Keep only the newest frame tick in the queue.
  // Use _coalesceQueue to avoid O(n) removeWhere on each send.
  if (msg is KeyMsg) {
    final dropped = _coalesceQueue(
      (m) => m is! MouseMsg && m is! FrameTickMsg,
    );
    if (TuiTrace.enabled && dropped > 0) {
      _trace('queue coalesce key dropped=$dropped', tag: TraceTag.queue);
    }
  } else if (msg is FrameTickMsg) {
    final dropped = _coalesceQueue((m) => m is! FrameTickMsg);
    if (TuiTrace.enabled && dropped > 0) {
      _trace(
        'queue coalesce frameTick dropped=$dropped',
        tag: TraceTag.queue,
      );
    }
  } else if (msg is MouseMsg &&
      msg.action == MouseAction.motion &&
      msg.button == MouseButton.none) {
    final dropped = _coalesceQueue(
      (m) => m is! MouseMsg || m.action != MouseAction.motion,
    );
    if (TuiTrace.enabled && dropped > 0) {
      _trace(
        'queue coalesce mouse-motion dropped=$dropped',
        tag: TraceTag.queue,
      );
    }
  } else if (msg is MouseMsg && msg.action == MouseAction.wheel) {
    final dropped = _coalesceQueue(
      (m) => m is! MouseMsg || m.action != MouseAction.wheel,
    );
    if (TuiTrace.enabled && dropped > 0) {
      _trace(
        'queue coalesce mouse-wheel dropped=$dropped',
        tag: TraceTag.queue,
      );
    }
  }
  if (TuiTrace.enabled && msg is KeyMsg) {
    final now = DateTime.now();
    final dtMs = _lastQueuedKeyAt == null
        ? -1
        : now.difference(_lastQueuedKeyAt!).inMicroseconds / 1000.0;
    _lastQueuedKeyAt = now;
    TuiTrace.log(
      'queue before key len=${_messageQueue.length} '
      'msg=${_traceMsgSummary(msg)} '
      'input_dt_ms=${dtMs < 0 ? 'n/a' : dtMs.toStringAsFixed(2)}',
      tag: TraceTag.queue,
    );
  }
  _messageQueue.add(msg);
  if (TuiTrace.enabled && msg is KeyMsg) {
    TuiTrace.log(
      'queue after key len=${_messageQueue.length}',
      tag: TraceTag.queue,
    );
  }
  _drainMessageQueue();
}