Skip to main content

foldit_plugin_sdk/
decode.rs

1//! Proto-to-native conversion for the protocol types.
2
3use std::collections::HashMap;
4
5use crate::proto::plugin as proto;
6use crate::protocol::{DispatchContext, ParamValue, ResidueRef};
7
8/// Decode a `proto::DispatchContext` into the native [`DispatchContext`].
9/// A `None` input yields the default (no focus, empty selection).
10// The wire carries entity ids as u64, but molex::EntityId is u32. These ids
11// are host-minted and fit u32 by construction, so the narrowing is sound.
12#[allow(clippy::cast_possible_truncation)]
13#[must_use]
14pub fn dispatch_context_from_proto(p: Option<proto::DispatchContext>) -> DispatchContext {
15    let to_native = |refs: Vec<proto::ResidueRef>| -> Vec<ResidueRef> {
16        refs.into_iter()
17            .map(|r| ResidueRef {
18                entity_id: molex::EntityId::from_raw(r.entity_id as u32),
19                residue_index: r.residue_index,
20            })
21            .collect()
22    };
23    match p {
24        Some(p) => DispatchContext {
25            focused_entity_id: p
26                .focused_entity_id
27                .map(|raw| molex::EntityId::from_raw(raw as u32)),
28            selection: to_native(p.selection),
29            designable: to_native(p.designable),
30        },
31        None => DispatchContext::default(),
32    }
33}
34
35/// Decode a proto param map into native [`ParamValue`]s. Entries whose
36/// `value` oneof is unset are dropped.
37#[must_use]
38pub fn params_from_proto<S: std::hash::BuildHasher + Default>(
39    p: HashMap<String, proto::ParamValue, S>,
40) -> HashMap<String, ParamValue, S> {
41    p.into_iter()
42        .filter_map(|(k, v)| {
43            let value = v.value?;
44            let native = match value {
45                proto::param_value::Value::IntValue(i) => ParamValue::Int(i),
46                proto::param_value::Value::FloatValue(f) => ParamValue::Float(f),
47                proto::param_value::Value::BoolValue(b) => ParamValue::Bool(b),
48                proto::param_value::Value::StringValue(s) => ParamValue::String(s),
49                proto::param_value::Value::Vec3Value(v3) => ParamValue::Vec3([v3.x, v3.y, v3.z]),
50            };
51            Some((k, native))
52        })
53        .collect()
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn dispatch_context_decodes_ids_and_refs() {
62        let ctx = dispatch_context_from_proto(Some(proto::DispatchContext {
63            focused_entity_id: Some(7),
64            selection: vec![proto::ResidueRef {
65                entity_id: 7,
66                residue_index: 3,
67            }],
68            designable: vec![proto::ResidueRef {
69                entity_id: 2,
70                residue_index: 0,
71            }],
72        }));
73        assert_eq!(ctx.focused_entity_id, Some(molex::EntityId::from_raw(7)));
74        assert_eq!(ctx.selection.len(), 1);
75        assert_eq!(ctx.selection[0].entity_id, molex::EntityId::from_raw(7));
76        assert_eq!(ctx.selection[0].residue_index, 3);
77        assert_eq!(ctx.designable[0].entity_id, molex::EntityId::from_raw(2));
78    }
79
80    #[test]
81    fn dispatch_context_none_is_default() {
82        let ctx = dispatch_context_from_proto(None);
83        assert!(ctx.focused_entity_id.is_none());
84        assert!(ctx.selection.is_empty());
85        assert!(ctx.designable.is_empty());
86    }
87
88    #[test]
89    fn params_decode_each_variant_and_drop_unset() {
90        let mut p: HashMap<String, proto::ParamValue> = HashMap::new();
91        let _ = p.insert(
92            "i".to_owned(),
93            proto::ParamValue {
94                value: Some(proto::param_value::Value::IntValue(5)),
95            },
96        );
97        let _ = p.insert(
98            "f".to_owned(),
99            proto::ParamValue {
100                value: Some(proto::param_value::Value::FloatValue(1.5)),
101            },
102        );
103        let _ = p.insert(
104            "b".to_owned(),
105            proto::ParamValue {
106                value: Some(proto::param_value::Value::BoolValue(true)),
107            },
108        );
109        let _ = p.insert(
110            "s".to_owned(),
111            proto::ParamValue {
112                value: Some(proto::param_value::Value::StringValue("x".to_owned())),
113            },
114        );
115        let _ = p.insert(
116            "v".to_owned(),
117            proto::ParamValue {
118                value: Some(proto::param_value::Value::Vec3Value(proto::Vec3 {
119                    x: 1.0,
120                    y: 2.0,
121                    z: 3.0,
122                })),
123            },
124        );
125        let _ = p.insert("unset".to_owned(), proto::ParamValue { value: None });
126
127        let native = params_from_proto(p);
128        assert_eq!(native.len(), 5);
129        assert_eq!(native.get("i"), Some(&ParamValue::Int(5)));
130        assert_eq!(native.get("f"), Some(&ParamValue::Float(1.5)));
131        assert_eq!(native.get("b"), Some(&ParamValue::Bool(true)));
132        assert_eq!(native.get("s"), Some(&ParamValue::String("x".to_owned())));
133        assert_eq!(native.get("v"), Some(&ParamValue::Vec3([1.0, 2.0, 3.0])));
134        assert!(!native.contains_key("unset"));
135    }
136}