Skip to main content

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}