perf(lotus): call_state field-wise dedupe, 500 ms trailing throttle (max 2/s)
Fixes #20 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5
parent
501e3fb5ac
commit
1c1394b6ef
@@ -27,6 +27,29 @@ interface ParticipantState {
|
||||
videoEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* [lotus #20] Field-wise equality for `ParticipantState[]`, used in place of
|
||||
* `JSON.stringify` comparison: cheaper (no serialisation of every
|
||||
* participant on every emission) and just as correct, since array order here
|
||||
* is stable (it mirrors `members` from `userMedia$`).
|
||||
*/
|
||||
function participantsEqual(
|
||||
a: ParticipantState[],
|
||||
b: ParticipantState[],
|
||||
): boolean {
|
||||
return (
|
||||
a.length === b.length &&
|
||||
a.every(
|
||||
(p, i) =>
|
||||
p.id === b[i].id &&
|
||||
p.userId === b[i].userId &&
|
||||
p.speaking === b[i].speaking &&
|
||||
p.audioEnabled === b[i].audioEnabled &&
|
||||
p.videoEnabled === b[i].videoEnabled,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream per-participant speaking / mute / camera state to the Lotus host
|
||||
* (cinny) over the widget API, so the host can drive speaking rings, mute
|
||||
@@ -73,11 +96,19 @@ export function startLotusCallState(vm: CallViewModel): () => void {
|
||||
),
|
||||
),
|
||||
// `speaking` flips rapidly; drop no-op repeats BEFORE throttling so
|
||||
// the throttle window isn't spent re-emitting an unchanged value, then
|
||||
// cap the send rate. 250ms is plenty for speaking rings / mute badges
|
||||
// and keeps the request/response widget traffic modest.
|
||||
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
|
||||
throttleTime(250, undefined, { leading: true, trailing: true }),
|
||||
// the throttle window isn't spent re-emitting an unchanged value.
|
||||
// Field-wise (cheaper than JSON.stringify, and correct: array order is
|
||||
// stable since it mirrors `members` from userMedia$).
|
||||
distinctUntilChanged(participantsEqual),
|
||||
// [lotus #20] `speaking` is the field that flips constantly in an active
|
||||
// conversation; mute/camera toggles are rare and user-intentional and
|
||||
// would ideally stay prompt, but a single combined stream is far
|
||||
// simpler than splitting it, and the leading+trailing 250ms window
|
||||
// previously allowed ~8 sends/sec (each re-serialising every
|
||||
// participant) in a busy call. Trailing-only + a longer window caps
|
||||
// that to 2/sec while still reflecting mute/camera changes within
|
||||
// 500ms.
|
||||
throttleTime(500, undefined, { leading: false, trailing: true }),
|
||||
)
|
||||
.subscribe((participants) => {
|
||||
lotusSendToHost(LotusWidgetActions.CallState, { participants });
|
||||
|
||||
Reference in New Issue
Block a user