mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
M12.8: the app no longer freezes every 10 s on Windows: the system memory is read in the background
Every 10 seconds the UI thread re-read the system's memory for the cache budgets, and on Windows that starts PowerShell (`Get-CimInstance Win32_OperatingSystem`) and waits for it: about a second, sometimes two, during which the whole app froze. The reading now runs on a background thread (`sysinfo::MemoryWatch`); `Session::memory_tick` takes the last reading and starts the next one, never waiting. Console programs started for it no longer open a console window (`CREATE_NO_WINDOW`). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4b37443481
commit
2d4a0902ea
4 files changed
+77
-5
No files matched your search
@@ -373,6 +373,8 @@ pub struct Session {
|
||||
/// The last physical-memory reading (Settings ▸ Memory & CPU budgets); `None` = unknown
|
||||
/// (headless sessions don't query it, see [`Session::memory_tick`]).
|
||||
pub sys_memory: Option<sysinfo::SysMemory>,
|
||||
/// Reads the system's memory in the background for [`Session::memory_tick`].
|
||||
pub memory_watch: sysinfo::MemoryWatch,
|
||||
/// Switches Affect Nested Comps as last applied (a change empties the layer cache).
|
||||
pub applied_nested_switches: Option<bool>,
|
||||
/// Runs renders and analyses off the UI thread where there are no threads (the web app's
|
||||
@@ -465,6 +467,7 @@ impl Default for Session {
|
||||
plugin_loader: None,
|
||||
script_ui: scriptui::ScriptUi::default(),
|
||||
sys_memory: None,
|
||||
memory_watch: Default::default(),
|
||||
applied_nested_switches: None,
|
||||
offload: None,
|
||||
offloaded: vec![],
|
||||
|
||||
@@ -75,11 +75,13 @@ impl Session {
|
||||
self.footage.set_cache_budget(b.media);
|
||||
}
|
||||
|
||||
/// Re-read the system's memory (desktop frontends call this every few seconds) and re-apply
|
||||
/// the cache budgets when the reading changes them. Returns the effective budgets.
|
||||
/// Take the latest reading of the system's memory and start the next one in the background
|
||||
/// (desktop frontends call this every few seconds; it never waits for the system), then
|
||||
/// re-apply the cache budgets when the reading changes them. Returns the effective budgets.
|
||||
pub fn memory_tick(&mut self) -> crate::prefs::CacheBudgets {
|
||||
let before = self.prefs.cache_budgets(self.sys_memory);
|
||||
self.sys_memory = crate::sysinfo::memory().or(self.sys_memory);
|
||||
self.sys_memory = self.memory_watch.latest().or(self.sys_memory);
|
||||
self.memory_watch.poll();
|
||||
let after = self.prefs.cache_budgets(self.sys_memory);
|
||||
if after != before {
|
||||
self.apply_cache_budgets();
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
//! Pure Rust and dependency-free: the values come from the operating system's own reporting
|
||||
//! (`/proc/meminfo` on Linux, `sysctl`/`vm_stat` on macOS, `wmic`/PowerShell on Windows). Every
|
||||
//! query may fail (sandboxes, the web): callers treat `None` as "unknown" and keep their
|
||||
//! configured budgets.
|
||||
//! configured budgets. The periodic reading behind the cache budgets runs on a background
|
||||
//! thread ([`MemoryWatch`]): on Windows it starts PowerShell, which takes about a second.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -23,10 +27,51 @@ impl SysMemory {
|
||||
}
|
||||
}
|
||||
|
||||
/// The system's memory, read in the background: [`MemoryWatch::poll`] starts a reading (unless
|
||||
/// one is running) and [`MemoryWatch::latest`] returns the last one without waiting.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MemoryWatch {
|
||||
latest: Arc<Mutex<Option<SysMemory>>>,
|
||||
busy: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl MemoryWatch {
|
||||
/// Start a new reading in the background (no threads on the web: read inline, it is
|
||||
/// unknown there anyway).
|
||||
pub fn poll(&self) {
|
||||
if self.busy.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
let (latest, busy) = (self.latest.clone(), self.busy.clone());
|
||||
let read = move || {
|
||||
if let Some(m) = memory() {
|
||||
*latest.lock().unwrap_or_else(PoisonError::into_inner) = Some(m);
|
||||
}
|
||||
busy.store(false, Ordering::Release);
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if std::thread::Builder::new().name("ec-memory".into()).spawn(read).is_err() {
|
||||
self.busy.store(false, Ordering::Release);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
read();
|
||||
}
|
||||
|
||||
/// The last reading (`None` until one finished).
|
||||
pub fn latest(&self) -> Option<SysMemory> {
|
||||
*self.latest.lock().unwrap_or_else(PoisonError::into_inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(dead_code)]
|
||||
fn run(cmd: &str, args: &[&str]) -> Option<String> {
|
||||
let out = std::process::Command::new(cmd).args(args).output().ok()?;
|
||||
let mut c = std::process::Command::new(cmd);
|
||||
c.args(args);
|
||||
// A console program started from the windowed app would flash a console window.
|
||||
#[cfg(target_os = "windows")]
|
||||
std::os::windows::process::CommandExt::creation_flags(&mut c, 0x0800_0000); // CREATE_NO_WINDOW
|
||||
let out = c.output().ok()?;
|
||||
out.status.success().then(|| String::from_utf8_lossy(&out.stdout).to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -464,3 +464,25 @@ fn open_recent_increment_and_revert() {
|
||||
assert_eq!(autosave::increment_path("/x/v1.ecproj", |p| p.ends_with("v1 2.ecproj")), "/x/v1 3.ecproj");
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_tick_never_waits_for_the_system() {
|
||||
let mut s = Session::default();
|
||||
let t0 = std::time::Instant::now();
|
||||
s.memory_tick();
|
||||
s.memory_tick();
|
||||
// Reading the system's memory starts PowerShell on Windows (about a second): the UI thread
|
||||
// only takes the last reading.
|
||||
assert!(t0.elapsed() < std::time::Duration::from_millis(200), "{:?}", t0.elapsed());
|
||||
if crate::sysinfo::memory().is_some() {
|
||||
let mut got = None;
|
||||
for _ in 0..200 {
|
||||
got = s.memory_watch.latest();
|
||||
if got.is_some() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
assert!(got.is_some_and(|m| m.total > 0), "the reading arrives in the background");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user