Skip to main content

Mountain/Vine/Server/Notification/
ProgressReport.rs

1#![allow(non_snake_case)]
2//! Cocoon → Mountain `progress.report` notification.
3//! Fires on every `Progress.report({ message, increment })` callback
4//! within a `vscode.window.withProgress(...)` run. The git extension
5//! alone fires 6000+ of these per session during repository scans;
6//! emitting one Tauri event per call saturates the WKWebView IPC
7//! channel that also delivers keystrokes. Each event is coalesced
8//! into a 16ms (one frame) window per Progress handle, accumulating
9//! `increment` deltas and keeping the most recent non-empty
10//! `message`. Sky sees one update per frame per progress operation
11//! instead of dozens, with the same final cumulative state.
12
13use std::{
14	collections::HashMap,
15	sync::{
16		Arc,
17		Mutex,
18		OnceLock,
19		atomic::{AtomicBool, Ordering},
20	},
21	time::Duration,
22};
23
24use serde_json::{Value, json};
25use tauri::{AppHandle, Emitter};
26
27use crate::{Vine::Server::MountainVinegRPCService::MountainVinegRPCService, dev_log};
28
29#[derive(Default)]
30struct ProgressAccumulator {
31	Message:String,
32	Increment:f64,
33}
34
35struct ProgressEmitBatch {
36	Pending:Mutex<HashMap<String, ProgressAccumulator>>,
37	FlushScheduled:AtomicBool,
38}
39
40static PROGRESS_EMIT_BATCH:OnceLock<Arc<ProgressEmitBatch>> = OnceLock::new();
41
42fn EnqueueProgressEmit(Handle:&AppHandle, ProgressHandle:String, Message:String, Increment:f64) {
43	let Batch = PROGRESS_EMIT_BATCH.get_or_init(|| {
44		Arc::new(ProgressEmitBatch { Pending:Mutex::new(HashMap::new()), FlushScheduled:AtomicBool::new(false) })
45	});
46
47	{
48		let mut Guard = Batch.Pending.lock().unwrap();
49		let Entry = Guard.entry(ProgressHandle).or_default();
50		// VS Code semantics: `message` replaces (latest wins); empty
51		// message means "keep previous". `increment` is per-call delta;
52		// accumulate so the final emit carries the same total movement.
53		if !Message.is_empty() {
54			Entry.Message = Message;
55		}
56		Entry.Increment += Increment;
57	}
58
59	if !Batch.FlushScheduled.swap(true, Ordering::AcqRel) {
60		let BatchClone = Batch.clone();
61		let HandleClone = Handle.clone();
62		tokio::spawn(async move {
63			tokio::time::sleep(Duration::from_millis(16)).await;
64			let Drained:HashMap<String, ProgressAccumulator> = {
65				let mut Guard = BatchClone.Pending.lock().unwrap();
66				std::mem::take(&mut *Guard)
67			};
68			BatchClone.FlushScheduled.store(false, Ordering::Release);
69			for (ProgressHandleId, Accumulator) in Drained {
70				if let Err(Error) = HandleClone.emit(
71					"sky://notification/progress-update",
72					json!({
73						"id": ProgressHandleId,
74						"message": Accumulator.Message,
75						"increment": Accumulator.Increment,
76					}),
77				) {
78					dev_log!(
79						"grpc",
80						"warn: [MountainVinegRPCService] sky://notification/progress-update emit failed: {}",
81						Error
82					);
83				}
84			}
85		});
86	}
87}
88
89pub async fn ProgressReport(Service:&MountainVinegRPCService, Parameter:&Value) {
90	let ProgressHandle = Parameter.get("handle").and_then(Value::as_str).unwrap_or("").to_string();
91	let Message = Parameter.get("message").and_then(Value::as_str).unwrap_or("").to_string();
92	let Increment = Parameter.get("increment").and_then(Value::as_f64).unwrap_or(0.0);
93	EnqueueProgressEmit(Service.ApplicationHandle(), ProgressHandle, Message, Increment);
94}