foldit_plugin_sdk/protocol.rs
1//! Native-Rust mirrors of the `proto::plugin` protocol types used by the
2//! [`crate::Plugin`] trait surface.
3
4use std::collections::HashMap;
5
6#[cfg(feature = "python")]
7use pyo3::prelude::*;
8
9use crate::proto::plugin::ScoreReport;
10
11/// Reference to a specific residue inside an entity. Mirror of
12/// `proto::plugin::ResidueRef`.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(
15 feature = "python",
16 pyclass(name = "ResidueRef", module = "foldit_plugin_sdk", from_py_object)
17)]
18pub struct ResidueRef {
19 /// Entity the residue belongs to.
20 pub entity_id: molex::EntityId,
21 /// 0-indexed within entity.
22 pub residue_index: u32,
23}
24
25#[cfg(feature = "python")]
26#[pymethods]
27impl ResidueRef {
28 /// Build a residue ref from a raw entity id and 0-indexed residue
29 /// position. The host constructs these to populate a `DispatchContext`
30 /// on the receive path.
31 #[new]
32 #[must_use]
33 pub fn new(entity_id: u32, residue_index: u32) -> Self {
34 Self {
35 entity_id: molex::EntityId::from_raw(entity_id),
36 residue_index,
37 }
38 }
39
40 /// Raw entity id (`u32`) the residue belongs to.
41 #[must_use]
42 #[getter]
43 pub fn entity_id(&self) -> u32 {
44 self.entity_id.raw()
45 }
46
47 /// 0-indexed residue position within the entity.
48 #[must_use]
49 #[getter]
50 pub fn residue_index(&self) -> u32 {
51 self.residue_index
52 }
53}
54
55/// Captured by the orchestrator at op-trigger time and frozen on the wire.
56/// Streams do NOT receive an updated context mid-flight; only
57/// `UpdateStream(params)` can change values during a running stream.
58#[derive(Debug, Clone, Default)]
59#[cfg_attr(
60 feature = "python",
61 pyclass(name = "DispatchContext", module = "foldit_plugin_sdk", from_py_object)
62)]
63pub struct DispatchContext {
64 /// Entity the user has currently focused, or `None` for session
65 /// mode (no specific entity targeted).
66 pub focused_entity_id: Option<molex::EntityId>,
67 /// Residue selection at op-trigger time.
68 pub selection: Vec<ResidueRef>,
69 /// Residues the plugin may redesign (change identity at), the puzzle's
70 /// design mask. Carried alongside the selection so the engine can gate
71 /// identity changes; orthogonal to `selection`, which says where to
72 /// operate. Empty when the session gates no design.
73 pub designable: Vec<ResidueRef>,
74}
75
76#[cfg(feature = "python")]
77#[pymethods]
78impl DispatchContext {
79 /// Build a dispatch context from a raw focused entity id (or `None` for
80 /// session mode) plus the selection + design-mask residue refs. The host
81 /// constructs these on the receive path before dispatching an op.
82 #[new]
83 #[pyo3(signature = (focused_entity_id = None, selection = vec![], designable = vec![]))]
84 #[must_use]
85 pub fn new(
86 focused_entity_id: Option<u32>,
87 selection: Vec<ResidueRef>,
88 designable: Vec<ResidueRef>,
89 ) -> Self {
90 Self {
91 focused_entity_id: focused_entity_id.map(molex::EntityId::from_raw),
92 selection,
93 designable,
94 }
95 }
96
97 /// Raw id (`u32`) of the focused entity, or `None` in session mode.
98 #[must_use]
99 #[getter]
100 pub fn focused_entity_id(&self) -> Option<u32> {
101 self.focused_entity_id.map(molex::EntityId::raw)
102 }
103
104 /// Residue selection at op-trigger time.
105 #[must_use]
106 #[getter]
107 pub fn selection(&self) -> Vec<ResidueRef> {
108 self.selection.clone()
109 }
110
111 /// Residues the plugin may redesign (the puzzle's design mask).
112 #[must_use]
113 #[getter]
114 pub fn designable(&self) -> Vec<ResidueRef> {
115 self.designable.clone()
116 }
117}
118
119/// Native-Rust parameter value.
120///
121/// Mirrors `proto::plugin::ParamValue` (a oneof on the wire) but in
122/// ergonomic enum form for the `Plugin` trait surface. Wire-native
123/// conversion happens at the IPC boundary.
124#[derive(Debug, Clone, PartialEq)]
125pub enum ParamValue {
126 /// 32-bit signed integer.
127 Int(i32),
128 /// 32-bit float.
129 Float(f32),
130 /// Boolean.
131 Bool(bool),
132 /// Used for both `PARAM_TYPE_STRING` and `PARAM_TYPE_ENUM`.
133 String(String),
134 /// 3-component float vector (positions, axes, etc.).
135 Vec3([f32; 3]),
136}
137
138/// Outcome of a `PollStream` call. Native-Rust mirror of
139/// `proto::plugin::PollStreamResponse`'s oneof.
140#[derive(Debug, Clone)]
141pub enum PollOutcome {
142 /// Stream still running. `latest_assembly` is the working state at
143 /// poll time; not authoritative until promoted on `Final` /
144 /// `Cancelled`.
145 Pending {
146 /// Working assembly snapshot, if the plugin emits one.
147 latest_assembly: Option<Vec<u8>>,
148 /// Progress fraction in `[0.0, 1.0]`, if the plugin tracks it.
149 progress: Option<f32>,
150 /// Human-readable stage label, if provided.
151 stage: Option<String>,
152 /// Warm score of `latest_assembly`, if the plugin scores it.
153 score: Option<ScoreReport>,
154 },
155 /// Accepted intermediate the host should commit into canonical state
156 /// while the stream keeps running. Same payload as `Pending`, but the
157 /// host commits it rather than treating it as a discardable preview;
158 /// unlike a terminal it does not end the op (more checkpoints or a
159 /// terminal follow), so the poll loop keeps going.
160 Checkpoint {
161 /// Working assembly snapshot, if the plugin emits one.
162 latest_assembly: Option<Vec<u8>>,
163 /// Progress fraction in `[0.0, 1.0]`, if the plugin tracks it.
164 progress: Option<f32>,
165 /// Human-readable stage label, if provided.
166 stage: Option<String>,
167 /// Warm score of `latest_assembly`, if the plugin scores it.
168 score: Option<ScoreReport>,
169 },
170 /// Stream stopped at host request (the host sent `CancelStream` and
171 /// the plugin returned its working pose). Same downstream handling
172 /// as `Final`: the orchestrator promotes `assembly` into canonical
173 /// state. Distinguished from `Final` only so the host can tell
174 /// "the algorithm reached its endpoint" from "the user asked it
175 /// to stop"; for open-ended ops (wiggle, shake) this IS the
176 /// success terminal.
177 Cancelled {
178 /// Working assembly bytes for the orchestrator to promote.
179 assembly: Vec<u8>,
180 /// Warm score of `assembly`, if the plugin scores it.
181 score: Option<ScoreReport>,
182 },
183 /// Stream finished successfully. `assembly` is the definitive output
184 /// the orchestrator promotes into canonical state.
185 Final {
186 /// Definitive assembly bytes for the orchestrator to promote.
187 assembly: Vec<u8>,
188 /// Warm score of `assembly`, if the plugin scores it.
189 score: Option<ScoreReport>,
190 },
191 /// Op-level failure. Distinct from a transport-level error.
192 Error {
193 /// Machine-readable error code (e.g. `"STREAM_INTERNAL"`).
194 code: String,
195 /// Human-readable error message.
196 message: String,
197 /// Optional structured detail map.
198 details: HashMap<String, String>,
199 },
200}