1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Diff + fingerprint. Port of `ffi/diff.rs`.
//!
//! Omitted from port: `diff_free` — destructor is generated.
//! Omitted from port: `diff_free_glb` — buffer memory management is obsolete; the
//! overlay GLB crosses as a base64 string (`to_overlay_glb_b64`, PORTING rule 6).
#[diplomat::bridge]
pub mod ffi {
use super::super::schematic::ffi::Schematic;
use super::super::shared::ffi::NucleationError;
use diplomat_runtime::DiplomatWrite;
use std::fmt::Write;
/// Namespace type for the fingerprint free functions (PORTING rule 12).
#[diplomat::opaque]
pub struct Fingerprint;
impl Fingerprint {
fn spec(preset: &[u8]) -> Result<crate::fingerprint::FingerprintSpec, NucleationError> {
let preset =
std::str::from_utf8(preset).map_err(|_| NucleationError::InvalidArgument)?;
crate::fingerprint::FingerprintSpec::from_preset(preset)
.ok_or(NucleationError::InvalidArgument)
}
/// The fingerprint of a schematic for the given preset, as a hex string.
/// Errors with `InvalidArgument` on an unknown preset.
pub fn compute(
schematic: &Schematic,
preset: &DiplomatStr,
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
let spec = Self::spec(preset)?;
let hex = crate::fingerprint::fingerprint(&schematic.0, &spec).to_hex();
let _ = write!(out, "{}", hex);
Ok(())
}
/// The structural signature (JSON) of a schematic for the given preset.
pub fn signature_json(
schematic: &Schematic,
preset: &DiplomatStr,
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
let spec = Self::spec(preset)?;
let json = crate::fingerprint::signature(&schematic.0, &spec).to_json();
let _ = write!(out, "{}", json);
Ok(())
}
/// Translation-invariant fuzzy distance between two builds' footprints.
pub fn footprint_distance(
a: &Schematic,
b: &Schematic,
preset: &DiplomatStr,
) -> Result<f32, NucleationError> {
let spec = Self::spec(preset)?;
Ok(crate::fingerprint::footprint_distance(&a.0, &b.0, &spec))
}
/// The schematic's translation/scale-invariant FFT shape footprint as a
/// JSON array of floats.
pub fn footprint_json(
schematic: &Schematic,
preset: &DiplomatStr,
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
let spec = Self::spec(preset)?;
let v = crate::fingerprint::footprint(&schematic.0, &spec).0;
let json = serde_json::to_string(&v).map_err(|_| NucleationError::Serialize)?;
let _ = write!(out, "{}", json);
Ok(())
}
/// Whether two schematics share the same fingerprint for the given preset.
pub fn is_duplicate(
a: &Schematic,
b: &Schematic,
preset: &DiplomatStr,
) -> Result<bool, NucleationError> {
let spec = Self::spec(preset)?;
Ok(crate::fingerprint::is_duplicate(&a.0, &b.0, &spec))
}
}
/// A computed diff between two schematics.
#[diplomat::opaque]
pub struct Diff(pub(crate) crate::diff::Diff);
impl Diff {
/// Diff two schematics with the given preset (default cost model).
/// Errors with `InvalidArgument` on an unknown preset.
pub fn compute(
a: &Schematic,
b: &Schematic,
preset: &DiplomatStr,
) -> Result<Box<Diff>, NucleationError> {
Self::compute_with_opts(a, b, preset, -1, -1, -1, -1, b"")
}
/// Diff two schematics with optional cost/symmetry overrides. Negative
/// cost ints mean "unset" (use the preset default); an empty `symmetry`
/// string means "unset". Errors with `InvalidArgument` on an unknown
/// preset or symmetry name.
#[allow(clippy::too_many_arguments)]
pub fn compute_with_opts(
a: &Schematic,
b: &Schematic,
preset: &DiplomatStr,
cost_add: i32,
cost_delete: i32,
cost_change: i32,
cost_swap: i32,
symmetry: &DiplomatStr,
) -> Result<Box<Diff>, NucleationError> {
let preset =
std::str::from_utf8(preset).map_err(|_| NucleationError::InvalidArgument)?;
let symmetry =
std::str::from_utf8(symmetry).map_err(|_| NucleationError::InvalidArgument)?;
let mut overrides = crate::diff::SpecOverrides::default();
if cost_add >= 0 {
overrides.cost_add = Some(cost_add as u32);
}
if cost_delete >= 0 {
overrides.cost_delete = Some(cost_delete as u32);
}
if cost_change >= 0 {
overrides.cost_change = Some(cost_change as u32);
}
if cost_swap >= 0 {
overrides.cost_swap = Some(cost_swap as u32);
}
if !symmetry.is_empty() {
let sym = crate::fingerprint::symmetry::Symmetry::from_name(symmetry)
.ok_or(NucleationError::InvalidArgument)?;
overrides.symmetry = Some(sym);
}
let spec = crate::diff::DiffSpec::resolve(preset, &overrides)
.ok_or(NucleationError::InvalidArgument)?;
Ok(Box::new(Diff(crate::diff::diff(&a.0, &b.0, &spec))))
}
/// Reconstruct a diff from its JSON representation.
pub fn from_json(json: &DiplomatStr) -> Result<Box<Diff>, NucleationError> {
let json = std::str::from_utf8(json).map_err(|_| NucleationError::InvalidArgument)?;
crate::diff::Diff::from_json(json)
.map(|d| Box::new(Diff(d)))
.map_err(|_| NucleationError::Parse)
}
/// The edit distance of the diff.
pub fn distance(&self) -> u64 {
self.0.distance
}
/// The support (alignment confidence) of the diff.
pub fn support(&self) -> f32 {
self.0.support
}
/// Serialize the diff to its full JSON representation.
pub fn to_json(&self, out: &mut DiplomatWrite) {
let _ = write!(out, "{}", self.0.to_json());
}
/// Serialize the diff to its compact summary JSON.
pub fn summary_json(&self, out: &mut DiplomatWrite) {
let _ = write!(out, "{}", self.0.summary_json());
}
/// A new schematic containing only the blocks added in this diff.
pub fn added(&self) -> Box<Schematic> {
Box::new(Schematic(self.0.added()))
}
/// A new schematic containing only the blocks removed in this diff.
pub fn removed(&self) -> Box<Schematic> {
Box::new(Schematic(self.0.removed()))
}
/// A new schematic containing only the blocks changed in this diff.
pub fn changed(&self) -> Box<Schematic> {
Box::new(Schematic(self.0.changed()))
}
/// A new schematic containing only the blocks swapped in this diff.
pub fn swapped(&self) -> Box<Schematic> {
Box::new(Schematic(self.0.swapped()))
}
/// A new schematic with marker blocks summarizing this diff.
pub fn markers(&self) -> Box<Schematic> {
Box::new(Schematic(self.0.markers()))
}
/// Render a diff overlay on top of an "after" GLB buffer, returning the
/// new GLB as base64 (PORTING rule 6). Requires the `meshing` feature;
/// errors with `Mesh` when compiled without it.
pub fn to_overlay_glb_b64(
&self,
after_glb: &[u8],
out: &mut DiplomatWrite,
) -> Result<(), NucleationError> {
#[cfg(feature = "meshing")]
{
use base64::Engine;
let opts = crate::diff::OverlayOptions::default();
let data = self
.0
.to_overlay_glb(after_glb, &opts)
.map_err(|_| NucleationError::Mesh)?;
let _ = write!(
out,
"{}",
base64::engine::general_purpose::STANDARD.encode(&data)
);
Ok(())
}
#[cfg(not(feature = "meshing"))]
{
let _ = (after_glb, out);
Err(NucleationError::Mesh)
}
}
}
}