mumuav/share/handler_arg.rs
1// src/share/handler_arg.rs
2//
3// Helper types and functions used by AV bridges to normalize a “handler”
4// argument passed from MuMu code. Users may pass either:
5// • Ref(KeyedArray) — the original reference cell returned by av:out_open
6// • KeyedArray — an auto-dereferenced snapshot (when a bare ident is used)
7//
8// This module extracts the numeric `id` in both cases. If the original `Ref`
9// cell is available, it is surfaced so bridges can update the live handler
10// map in place (state, frames_written, draining flags). When only a plain
11// KeyedArray is provided, bridges should treat it as read-only and operate
12// by `id` via the internal registry.
13
14use std::sync::{Arc, Mutex};
15use mumu::parser::types::Value;
16
17use crate::share::{get_handler_cell, register_handler_cell};
18
19/// Normalized handler argument extracted from a MuMu Value.
20///
21/// - `id`: numeric identifier of the native output handle.
22/// - `cell`: the original Ref cell (when the caller passed Ref(KeyedArray));
23/// `None` when the caller passed a plain KeyedArray snapshot.
24#[derive(Clone)]
25pub struct HandlerArg {
26 pub id: i64,
27 pub cell: Option<Arc<Mutex<Value>>>,
28}
29
30/// Walk through nested `Ref(...)` cells (if any) and return the **final**
31/// cell whose **value** should be a `KeyedArray` (handler map). This helper
32/// drops the lock guard before reassigning the current cell to avoid E0506.
33fn unwrap_ref_chain(mut cell: Arc<Mutex<Value>>) -> Result<Arc<Mutex<Value>>, String> {
34 loop {
35 // Lock the current cell and inspect its value.
36 // Collect the "next" ref (if any), but **drop** the guard before reassigning.
37 let next_opt = {
38 let guard = cell
39 .lock()
40 .map_err(|_| "av: handler ref lock poisoned".to_string())?;
41 match &*guard {
42 Value::Ref(next) => Some(next.clone()),
43 _ => None,
44 }
45 };
46
47 if let Some(next) = next_opt {
48 cell = next;
49 continue;
50 }
51 return Ok(cell);
52 }
53}
54
55/// Extract a numeric id from a `KeyedArray` map (int/long).
56fn id_from_map(map: &indexmap::IndexMap<String, Value>) -> Option<i64> {
57 match map.get("id") {
58 Some(Value::Long(l)) => Some(*l),
59 Some(Value::Int(i)) => Some(*i as i64),
60 _ => None,
61 }
62}
63
64/// Parse a handler argument that can be either a `Ref(KeyedArray)` or a plain
65/// `KeyedArray`. Returns the normalized `HandlerArg` or a descriptive error.
66///
67/// Extra behavior:
68/// • When a `Ref(KeyedArray)` is provided, the (top-level) `Ref` cell is
69/// registered internally by `id` so later snapshots can be mapped back.
70/// • When a plain `KeyedArray` is provided, we try to recover a live cell
71/// from the registry using the `id`; if found, `cell` is returned.
72pub fn parse_handler_arg(v: Value) -> Result<HandlerArg, String> {
73 // Path 1: Ref(KeyedArray) — unwrap through any nested Ref(...) cells
74 if let Value::Ref(top_cell) = v.clone() {
75 let resolved = unwrap_ref_chain(top_cell.clone())?;
76 let guard = resolved
77 .lock()
78 .map_err(|_| "av: handler lock poisoned".to_string())?;
79
80 if let Value::KeyedArray(map) = &*guard {
81 let id = id_from_map(map)
82 .ok_or_else(|| "av: handler missing valid 'id'".to_string())?;
83
84 // Remember the top-level Ref cell for this id, so snapshots can be upgraded later.
85 register_handler_cell(id, top_cell.clone());
86
87 return Ok(HandlerArg {
88 id,
89 cell: Some(top_cell),
90 });
91 } else {
92 return Err("av: handler Ref did not resolve to KeyedArray".to_string());
93 }
94 }
95
96 // Path 2: plain KeyedArray (auto-dereferenced snapshot)
97 if let Value::KeyedArray(map) = v {
98 let id = id_from_map(&map)
99 .ok_or_else(|| "av: handler missing valid 'id'".to_string())?;
100
101 // If we have a live Ref cell registered for this id, return it; else snapshot-only.
102 if let Some(cell) = get_handler_cell(id) {
103 Ok(HandlerArg { id, cell: Some(cell) })
104 } else {
105 Ok(HandlerArg { id, cell: None })
106 }
107 } else {
108 Err("av: handler must be Ref(KeyedArray) or KeyedArray".to_string())
109 }
110}