send method
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();
}