fast_mesh_scene/report.rs
1//! Change reports: what happened to each mesh since a subscriber last took
2//! its changes.
3
4use fast_mesh_store::{ChangeSet, VertexId};
5
6use crate::MeshId;
7
8/// A consumer of change reports, from [`Scene::subscribe`](crate::Scene::subscribe).
9///
10/// Every subscriber has its own pending reports, merged the same way, and
11/// drains them with
12/// [`Scene::take_changes_for`](crate::Scene::take_changes_for). Ids are never
13/// reused within a scene.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct SubscriberId(pub(crate) u64);
16
17impl SubscriberId {
18 /// The default subscriber, behind
19 /// [`Scene::take_changes`](crate::Scene::take_changes). It always exists.
20 pub(crate) const DEFAULT: SubscriberId = SubscriberId(0);
21}
22
23/// A field registered on a mesh of the scene.
24///
25/// Opaque: field ids are handed out by the scene and only compared.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct FieldId(pub(crate) u32);
28
29impl FieldId {
30 /// The raw value, for passing the id across an FFI boundary. Turn it
31 /// back into an id with [`Scene::field_id`](crate::Scene::field_id).
32 pub fn raw(self) -> u32 {
33 self.0
34 }
35}
36
37/// A plane section of the scene, from
38/// [`Scene::add_section`](crate::Scene::add_section).
39///
40/// Opaque: section ids are handed out by the scene, never reused, and only
41/// compared.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct SectionId(pub(crate) u32);
44
45impl SectionId {
46 /// The raw value, for passing the id across an FFI boundary. Turn it
47 /// back into an id with [`Scene::section_id`](crate::Scene::section_id).
48 pub fn raw(self) -> u32 {
49 self.0
50 }
51}
52
53/// What happened to one mesh since the subscriber's last
54/// [`Scene::take_changes_for`](crate::Scene::take_changes_for) (or
55/// [`Scene::take_changes`](crate::Scene::take_changes) for the default
56/// subscriber).
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct MeshChanges {
59 /// The mesh. For a removed mesh this id no longer matches.
60 pub id: MeshId,
61 /// The merged changes of every commit, undo and redo since the last
62 /// call. `None` means "treat everything as changed": the mesh was added
63 /// or compacted, or an undo or redo touching it failed with
64 /// [`SceneError::Corrupted`](crate::SceneError::Corrupted).
65 pub changes: Option<ChangeSet>,
66 /// True if the placement changed at any point since the last call, even
67 /// if it was changed back.
68 pub placement_changed: bool,
69 /// The mesh was added since the last call.
70 pub added: bool,
71 /// The mesh was removed since the last call.
72 pub removed: bool,
73 /// The vertices whose colours changed since the last call, ascending
74 /// without duplicates. `Some` of an empty list when no colour changed;
75 /// `None` means "every colour may have changed" (redraw them all).
76 pub colors_dirty: Option<Vec<VertexId>>,
77 /// The vertices whose normals changed since the last call, with the
78 /// same semantics as [`MeshChanges::colors_dirty`]: ascending without
79 /// duplicates, `None` for "every normal may have changed". Only
80 /// reported while normals are on (see
81 /// [`Scene::set_normals`](crate::Scene::set_normals)), apart from the
82 /// `None` of an added or fully changed mesh.
83 pub normals_dirty: Option<Vec<VertexId>>,
84 /// The fields on this mesh whose values changed since the last call,
85 /// each once.
86 pub fields_changed: Vec<FieldId>,
87 /// The sections whose result for this mesh changed since the last call
88 /// (recomputed to a different result, removed, or no longer selecting
89 /// the mesh), each once, in the order first reported.
90 pub sections_changed: Vec<SectionId>,
91 /// The channels written through
92 /// [`Scene::derived_channel_mut`](crate::Scene::derived_channel_mut),
93 /// added with [`Scene::add_channel`](crate::Scene::add_channel) or
94 /// removed with [`Scene::remove_channel`](crate::Scene::remove_channel),
95 /// or dropped or restored as a whole by an edit, its undo or redo
96 /// ([`ChangeSet::channels_toggled`]),
97 /// since the last call, each name once, in the order first reported. A
98 /// `derived_channel_mut` call counts as a write whether or not the slice
99 /// is then written.
100 pub channels_written: Vec<String>,
101}
102
103/// The pending report of a live mesh, for one subscriber.
104#[derive(Clone)]
105pub(crate) struct Report {
106 /// Merged changes; `None` means "everything".
107 pub(crate) changes: Option<ChangeSet>,
108 pub(crate) placement_changed: bool,
109 pub(crate) added: bool,
110 /// Recoloured vertices; `None` means "all".
111 pub(crate) colors_dirty: Option<Vec<VertexId>>,
112 /// Vertices with new normals; `None` means "all".
113 pub(crate) normals_dirty: Option<Vec<VertexId>>,
114 pub(crate) fields_changed: Vec<FieldId>,
115 pub(crate) sections_changed: Vec<SectionId>,
116 pub(crate) channels_written: Vec<String>,
117}
118
119impl Report {
120 /// The report of a mesh that was just added: everything is new,
121 /// colours and normals included.
122 pub(crate) fn added() -> Report {
123 Report {
124 changes: None,
125 added: true,
126 colors_dirty: None,
127 normals_dirty: None,
128 ..Report::clean()
129 }
130 }
131
132 /// A report with nothing in it.
133 pub(crate) fn clean() -> Report {
134 Report {
135 changes: Some(ChangeSet::default()),
136 placement_changed: false,
137 added: false,
138 colors_dirty: Some(Vec::new()),
139 normals_dirty: Some(Vec::new()),
140 fields_changed: Vec::new(),
141 sections_changed: Vec::new(),
142 channels_written: Vec::new(),
143 }
144 }
145
146 /// True when there is nothing to report.
147 pub(crate) fn is_clean(&self) -> bool {
148 !self.added
149 && !self.placement_changed
150 && self.changes.as_ref().is_some_and(ChangeSet::is_empty)
151 && self.colors_dirty.as_ref().is_some_and(Vec::is_empty)
152 && self.normals_dirty.as_ref().is_some_and(Vec::is_empty)
153 && self.fields_changed.is_empty()
154 && self.sections_changed.is_empty()
155 && self.channels_written.is_empty()
156 }
157
158 /// Folds the changes of a later commit into the report. A report that
159 /// already says "everything" stays that way.
160 ///
161 /// `ChangeSet::merge` cannot fail: its allocations abort the process on
162 /// exhaustion rather than return an error, so this never falls back to
163 /// `None`. That only happens on `add_mesh`, `compact` and a `Corrupted`
164 /// undo or redo.
165 pub(crate) fn merge(&mut self, later: &ChangeSet) {
166 if let Some(c) = &mut self.changes {
167 c.merge(later);
168 }
169 }
170
171 /// Notes a write to channel `name`, once per name.
172 pub(crate) fn channel_written(&mut self, name: &str) {
173 if !self.channels_written.iter().any(|n| n == name) {
174 self.channels_written.push(name.to_owned());
175 }
176 }
177
178 /// Notes a change of field `id`'s values, once per field.
179 pub(crate) fn field_changed(&mut self, id: FieldId) {
180 if !self.fields_changed.contains(&id) {
181 self.fields_changed.push(id);
182 }
183 }
184
185 /// Notes a change of section `id`'s result for the mesh, once per
186 /// section.
187 pub(crate) fn section_changed(&mut self, id: SectionId) {
188 if !self.sections_changed.contains(&id) {
189 self.sections_changed.push(id);
190 }
191 }
192
193 /// Notes recoloured vertices (`ids` ascending without duplicates), or
194 /// every vertex for `None`.
195 pub(crate) fn colors_changed(&mut self, ids: Option<&[VertexId]>) {
196 merge_dirty(&mut self.colors_dirty, ids);
197 }
198
199 /// Notes vertices with new normals (`ids` ascending without
200 /// duplicates), or every vertex for `None`.
201 pub(crate) fn normals_changed(&mut self, ids: Option<&[VertexId]>) {
202 merge_dirty(&mut self.normals_dirty, ids);
203 }
204
205 /// Turns the report into its public form and resets it to clean.
206 pub(crate) fn take(&mut self, id: MeshId, removed: bool) -> MeshChanges {
207 let r = std::mem::replace(self, Report::clean());
208 MeshChanges {
209 id,
210 changes: r.changes,
211 placement_changed: r.placement_changed,
212 added: r.added,
213 removed,
214 colors_dirty: r.colors_dirty,
215 normals_dirty: r.normals_dirty,
216 fields_changed: r.fields_changed,
217 sections_changed: r.sections_changed,
218 channels_written: r.channels_written,
219 }
220 }
221}
222
223/// Folds dirty vertex ids (ascending without duplicates, `None` for all)
224/// into a dirty list that stays ascending without duplicates. A list that
225/// already says "all" stays that way.
226fn merge_dirty(dirty: &mut Option<Vec<VertexId>>, ids: Option<&[VertexId]>) {
227 match (ids, dirty) {
228 (_, None) => {}
229 (None, dirty) => *dirty = None,
230 (Some(ids), Some(dirty)) => {
231 if dirty.is_empty() {
232 dirty.extend_from_slice(ids);
233 } else if !ids.is_empty() {
234 dirty.extend_from_slice(ids);
235 dirty.sort_unstable();
236 dirty.dedup();
237 }
238 }
239 }
240}