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
33	Increment:f64,
34}
35
36struct ProgressEmitBatch {
37	Pending:Mutex<HashMap<String, ProgressAccumulator>>,
38
39	FlushScheduled:AtomicBool,
40}
41
42static PROGRESS_EMIT_BATCH:OnceLock<Arc<ProgressEmitBatch>> = OnceLock::new();
43
44fn EnqueueProgressEmit(Handle:&AppHandle, ProgressHandle:String, Message:String, Increment:f64) {
45	let Batch = PROGRESS_EMIT_BATCH.get_or_init(|| {
46		Arc::new(ProgressEmitBatch { Pending:Mutex::new(HashMap::new()), FlushScheduled:AtomicBool::new(false) })
47	});
48
49	{
50		let mut Guard = Batch.Pending.lock().unwrap();
51
52		let Entry = Guard.entry(ProgressHandle).or_default();
53
54		// VS Code semantics: `message` replaces (latest wins); empty
55		// message means "keep previous". `increment` is per-call delta;
56		// accumulate so the final emit carries the same total movement.
57		if !Message.is_empty() {
58			Entry.Message = Message;
59		}
60
61		Entry.Increment += Increment;
62	}
63
64	if !Batch.FlushScheduled.swap(true, Ordering::AcqRel) {
65		let BatchClone = Batch.clone();
66
67		let HandleClone = Handle.clone();
68
69		tokio::spawn(async move {
70			tokio::time::sleep(Duration::from_millis(16)).await;
71			let Drained:HashMap<String, ProgressAccumulator> = {
72				let mut Guard = BatchClone.Pending.lock().unwrap();
73				std::mem::take(&mut *Guard)
74			};
75			BatchClone.FlushScheduled.store(false, Ordering::Release);
76			for (ProgressHandleId, Accumulator) in Drained {
77				if let Err(Error) = HandleClone.emit(
78					"sky://notification/progress-update",
79					json!({
80						"id": ProgressHandleId,
81						"message": Accumulator.Message,
82						"increment": Accumulator.Increment,
83					}),
84				) {
85					dev_log!(
86						"grpc",
87						"warn: [MountainVinegRPCService] sky://notification/progress-update emit failed: {}",
88						Error
89					);
90				}
91			}
92		});
93	}
94}
95
96pub async fn ProgressReport(Service:&MountainVinegRPCService, Parameter:&Value) {
97	let ProgressHandle = Parameter.get("handle").and_then(Value::as_str).unwrap_or("").to_string();
98
99	let Message = Parameter.get("message").and_then(Value::as_str).unwrap_or("").to_string();
100
101	let Increment = Parameter.get("increment").and_then(Value::as_f64).unwrap_or(0.0);
102
103	EnqueueProgressEmit(Service.ApplicationHandle(), ProgressHandle, Message, Increment);
104}