Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions apps/mofa-asr/src/screen/log_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,10 @@ impl MoFaASRScreen {

/// Add a log entry (throttled)
pub(super) fn add_log(&mut self, cx: &mut Cx, entry: &str) {
self.log_entries.push(entry.to_string());
self.log_entries.push_back(entry.to_string());

if self.log_entries.len() > MAX_LOG_ENTRIES {
let excess = self.log_entries.len() - MAX_LOG_ENTRIES;
self.log_entries.drain(0..excess);
while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

self.mark_log_dirty(cx);
Expand All @@ -233,12 +232,11 @@ impl MoFaASRScreen {
}

for log_msg in logs {
self.log_entries.push(log_msg.format());
self.log_entries.push_back(log_msg.format());
}

if self.log_entries.len() > MAX_LOG_ENTRIES {
let excess = self.log_entries.len() - MAX_LOG_ENTRIES;
self.log_entries.drain(0..excess);
while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

self.mark_log_dirty(cx);
Expand Down
3 changes: 2 additions & 1 deletion apps/mofa-asr/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use mofa_ui::{LedMeterWidgetExt, MicButtonWidgetExt, AecButtonWidgetExt};
use mofa_settings::data::Preferences;
use crate::dora_integration::{AsrEngineId, DoraIntegration, DoraEvent};
use std::collections::HashMap;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use moly_kit::prelude::*;
Expand Down Expand Up @@ -139,7 +140,7 @@ pub struct MoFaASRScreen {
#[rust]
log_node_filter: usize,
#[rust]
log_entries: Vec<String>,
log_entries: VecDeque<String>,
#[rust]
log_display_dirty: bool,
#[rust]
Expand Down
4 changes: 2 additions & 2 deletions apps/mofa-debate/src/screen/audio_controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ impl MoFaDebateScreen {
/// Initialize log entries with a startup message
pub(super) fn init_demo_logs(&mut self, cx: &mut Cx) {
// Start with empty logs - real logs will come from log_bridge
self.log_entries = vec![
self.log_entries = std::collections::VecDeque::from(vec![
"[INFO] [App] MoFA FM initialized".to_string(),
"[INFO] [App] System log ready - Rust logs will appear here".to_string(),
];
]);

// Update the log display
self.update_log_display(cx);
Expand Down
15 changes: 13 additions & 2 deletions apps/mofa-debate/src/screen/log_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use makepad_widgets::*;

use super::MoFaDebateScreen;

const MAX_LOG_ENTRIES: usize = 5000;

impl MoFaDebateScreen {
/// Toggle log panel visibility
pub(super) fn toggle_log_panel(&mut self, cx: &mut Cx) {
Expand Down Expand Up @@ -221,7 +223,12 @@ impl MoFaDebateScreen {

/// Add a log entry
pub(super) fn add_log(&mut self, cx: &mut Cx, entry: &str) {
self.log_entries.push(entry.to_string());
self.log_entries.push_back(entry.to_string());

while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

self.update_log_display(cx);
}

Expand All @@ -233,7 +240,11 @@ impl MoFaDebateScreen {
}

for log_msg in logs {
self.log_entries.push(log_msg.format());
self.log_entries.push_back(log_msg.format());
}

while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

// Only update display if we got new logs
Expand Down
3 changes: 2 additions & 1 deletion apps/mofa-debate/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use mofa_ui::{
};
use mofa_widgets::participant_panel::ParticipantPanelWidgetExt;
use mofa_widgets::{StateChangeListener, TimerControl};
use std::collections::VecDeque;
use std::path::PathBuf;

/// Register live design for this module
Expand Down Expand Up @@ -82,7 +83,7 @@ pub struct MoFaDebateScreen {
#[rust]
log_node_filter: usize, // 0=ALL, 1=ASR, 2=TTS, 3=LLM, 4=Bridge, 5=Monitor, 6=App
#[rust]
log_entries: Vec<String>, // Raw log entries for filtering
log_entries: VecDeque<String>,

// Dropdown width caching for popup menu sync
#[rust]
Expand Down
4 changes: 2 additions & 2 deletions apps/mofa-fm/src/screen/audio_controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ impl MoFaFMScreen {
/// Initialize log entries with a startup message
pub(super) fn init_demo_logs(&mut self, cx: &mut Cx) {
// Start with empty logs - real logs will come from log_bridge
self.log_entries = vec![
self.log_entries = std::collections::VecDeque::from(vec![
"[INFO] [App] MoFA FM initialized".to_string(),
"[INFO] [App] System log ready - Rust logs will appear here".to_string(),
];
]);

// Update the log display
self.update_log_display(cx);
Expand Down
110 changes: 100 additions & 10 deletions apps/mofa-fm/src/screen/log_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,10 @@ impl MoFaFMScreen {

/// Add a log entry (throttled - doesn't immediately update display)
pub(super) fn add_log(&mut self, cx: &mut Cx, entry: &str) {
self.log_entries.push(entry.to_string());
self.log_entries.push_back(entry.to_string());

// Prune oldest entries if over limit
if self.log_entries.len() > MAX_LOG_ENTRIES {
let excess = self.log_entries.len() - MAX_LOG_ENTRIES;
self.log_entries.drain(0..excess);
while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

// Mark dirty for throttled update (don't update immediately)
Expand All @@ -274,13 +272,11 @@ impl MoFaFMScreen {
}

for log_msg in logs {
self.log_entries.push(log_msg.format());
self.log_entries.push_back(log_msg.format());
}

// Prune oldest entries if over limit
if self.log_entries.len() > MAX_LOG_ENTRIES {
let excess = self.log_entries.len() - MAX_LOG_ENTRIES;
self.log_entries.drain(0..excess);
while self.log_entries.len() > MAX_LOG_ENTRIES {
self.log_entries.pop_front();
}

// Mark dirty for throttled update (don't update immediately)
Expand All @@ -295,3 +291,97 @@ impl MoFaFMScreen {
self.update_log_display_now(cx);
}
}

#[cfg(test)]
mod tests {
use std::collections::VecDeque;

const MAX_LOG_ENTRIES: usize = super::MAX_LOG_ENTRIES;

fn push_and_prune(buf: &mut VecDeque<String>, entry: String) {
buf.push_back(entry);
while buf.len() > MAX_LOG_ENTRIES {
buf.pop_front();
}
}

#[test]
fn stays_within_capacity() {
let mut buf = VecDeque::new();
for i in 0..(MAX_LOG_ENTRIES + 500) {
push_and_prune(&mut buf, format!("log {i}"));
}
assert_eq!(buf.len(), MAX_LOG_ENTRIES);
}

#[test]
fn oldest_entries_evicted_first() {
let mut buf = VecDeque::new();
for i in 0..(MAX_LOG_ENTRIES + 3) {
push_and_prune(&mut buf, format!("log {i}"));
}
// first 3 entries should be evicted
assert_eq!(buf.front().unwrap(), "log 3");
assert_eq!(buf.back().unwrap(), &format!("log {}", MAX_LOG_ENTRIES + 2));
}

#[test]
fn below_cap_no_eviction() {
let mut buf = VecDeque::new();
for i in 0..100 {
push_and_prune(&mut buf, format!("log {i}"));
}
assert_eq!(buf.len(), 100);
assert_eq!(buf.front().unwrap(), "log 0");
assert_eq!(buf.back().unwrap(), "log 99");
}

#[test]
fn exactly_at_cap() {
let mut buf = VecDeque::new();
for i in 0..MAX_LOG_ENTRIES {
push_and_prune(&mut buf, format!("log {i}"));
}
assert_eq!(buf.len(), MAX_LOG_ENTRIES);
assert_eq!(buf.front().unwrap(), "log 0");
// One more triggers first eviction
push_and_prune(&mut buf, "overflow".to_string());
assert_eq!(buf.len(), MAX_LOG_ENTRIES);
assert_eq!(buf.front().unwrap(), "log 1");
assert_eq!(buf.back().unwrap(), "overflow");
}

#[test]
fn clear_resets_buffer() {
let mut buf = VecDeque::new();
for i in 0..100 {
push_and_prune(&mut buf, format!("log {i}"));
}
buf.clear();
assert_eq!(buf.len(), 0);
assert!(buf.is_empty());
// Can continue adding after clear
push_and_prune(&mut buf, "after clear".to_string());
assert_eq!(buf.len(), 1);
assert_eq!(buf.front().unwrap(), "after clear");
}

#[test]
fn batch_insert_pruning() {
// poll_rust_logs
let mut buf = VecDeque::new();
for i in 0..MAX_LOG_ENTRIES {
buf.push_back(format!("log {i}"));
}
// Batch of 10
for i in 0..10 {
buf.push_back(format!("batch {i}"));
}
while buf.len() > MAX_LOG_ENTRIES {
buf.pop_front();
}
assert_eq!(buf.len(), MAX_LOG_ENTRIES);
assert_eq!(buf.front().unwrap(), "log 10");
assert_eq!(buf.back().unwrap(), "batch 9");
}
}
3 changes: 2 additions & 1 deletion apps/mofa-fm/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::dora_integration::{DoraIntegration, DoraCommand};
use mofa_widgets::participant_panel::ParticipantPanelWidgetExt;
use mofa_widgets::{StateChangeListener, TimerControl};
use mofa_ui::{LedMeterWidgetExt, MicButtonWidgetExt, AecButtonWidgetExt};
use std::collections::VecDeque;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
Expand Down Expand Up @@ -92,7 +93,7 @@ pub struct MoFaFMScreen {
#[rust]
log_node_filter: usize, // 0=ALL, 1=ASR, 2=TTS, 3=LLM, 4=Bridge, 5=Monitor, 6=App
#[rust]
log_entries: Vec<String>, // Raw log entries for filtering
log_entries: VecDeque<String>,
#[rust]
log_display_dirty: bool, // Flag to track if log display needs update
#[rust]
Expand Down