Skip to main content

Vine/Server/Notification/
RegisterCommand.rs

1//! Cocoon → `registerCommand` notification.
2//! Two side effects per call:
3//! 1. Insert a `Proxied` handler into the embedder's command dispatch registry
4//!    via `VineHost::RegisterCommandInRegistry` (synchronous; allows
5//!    `commands.executeCommand` to route back to Cocoon immediately).
6//! 2. Push the command descriptor into a channel-drain coalescer that emits one
7//!    `sky://command/register` batch per 16 ms frame, avoiding 1000+ individual
8//!    renderer events during extension boot.
9//!
10//! The coalescer holds a captured `Arc<dyn RendererEmitter>` so the drain
11//! task never borrows the full host across await points.
12
13use std::sync::{Arc, OnceLock};
14
15use serde_json::{Value, json};
16use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
17
18use crate::{
19	Host::{RendererEmitter, VineHost},
20	dev_log,
21};
22
23struct CommandBatchChannel {
24	Sender:UnboundedSender<Value>,
25}
26
27static CMD_CHANNEL:OnceLock<CommandBatchChannel> = OnceLock::new();
28
29fn GetOrInitChannel(Emitter:Arc<dyn RendererEmitter>) -> &'static CommandBatchChannel {
30	CMD_CHANNEL.get_or_init(|| {
31		let (Tx, mut Rx) = unbounded_channel::<Value>();
32
33		tokio::spawn(async move {
34			let mut Buf:Vec<Value> = Vec::with_capacity(128);
35
36			loop {
37				// Block until at least one item arrives.
38				match Rx.recv().await {
39					None => break,
40					Some(V) => Buf.push(V),
41				}
42
43				// Drain everything already queued without blocking.
44				Rx.recv_many(&mut Buf, 4096).await;
45
46				// One frame - let stragglers accumulate.
47				tokio::time::sleep(std::time::Duration::from_millis(16)).await;
48
49				// Drain again after the frame window.
50				Rx.recv_many(&mut Buf, 4096).await;
51
52				if Buf.is_empty() {
53					continue;
54				}
55
56				let Count = Buf.len();
57
58				let Commands:Vec<Value> = Buf.drain(..).collect();
59
60				Emitter.Emit("sky://command/register", json!({ "commands": Commands }));
61
62				dev_log!("commands", "[RegisterCommand] batch={}", Count);
63			}
64		});
65
66		CommandBatchChannel { Sender:Tx }
67	})
68}
69
70pub async fn RegisterCommand(Host:&dyn VineHost, Parameter:&Value) {
71	let CommandId = Parameter.get("commandId").and_then(Value::as_str).unwrap_or("");
72
73	dev_log!("command-register", "[RegisterCommand] id={}", CommandId);
74
75	if CommandId.is_empty() {
76		return;
77	}
78
79	let Kind = Parameter.get("kind").and_then(Value::as_str).unwrap_or("command").to_string();
80
81	// Synchronous registry insert so executeCommand works immediately.
82	Host.RegisterCommandInRegistry(CommandId, "cocoon-main");
83
84	// Queue for batched Sky emit.
85	let Ch = GetOrInitChannel(Host.RendererEmitter());
86
87	let _ = Ch.Sender.send(json!({ "id": CommandId, "commandId": CommandId, "kind": Kind }));
88}