fj_core/operations/replace/
mod.rs

1//! # Operations to Replace Objects
2//!
3//! Most objects reference other objects, which can in turn reference even more
4//! objects. All of these objects form a graph. Objects are immutable, which
5//! means changing one in any way means creating a new version of the object,
6//! and hence a new version of the objects that reference it, all the way to the
7//! root of the graph.
8//!
9//! Replace operations replace objects within the object graph, solving a few
10//! problems that would otherwise occur here:
11//!
12//! 1. They take care of *finding* the object. A replace operation for a given
13//!    object can be called on any object that references it, directly or
14//!    indirectly, so the caller does not need to know which objects reference
15//!    the called object directly.
16//! 2. They take care of creating new versions of all objects referencing the
17//!    replaced object in the whole object graph defined by the object that the
18//!    replace operation is called on.
19//! 3. They *only* create a new version of an object, if anything has actually
20//!    been replaced.
21//!
22//!
23//! ## Structure
24//!
25//! All replace operations follow the same structure:
26//!
27//! - They are implemented for `Handle<T>`, where `T` is a bare object type.
28//! - They have an associated type to identify `T`. See implementation note
29//!   below.
30//! - They take a reference to the [`Handle`] of the original object that
31//!   should be replaced.
32//! - Based on the specific replace operations, they take the [`Handle`] of the
33//!   replacement, or multiple handles that replace the object. (Depending on
34//!   the arity of the reference.)
35//! - If the original object is referenced (directly or indirectly) by the
36//!   object the operation is called on, it is replaced with the replacement. If
37//!   not, nothing happens.
38//! - They return an enum that indicates whether an object was actually
39//!   replaced. If it was, it contains the [`Handle`] to the new version of the
40//!   object the operation was called on. If it wasn't, it contains the original
41//!   handle.
42//!
43//!
44//! ## Comparison to Update Operations
45//!
46//! There is another type of operation, [update operations], which has some
47//! conceptual overlap with replace operations. There are some differences
48//! though:
49//!
50//! - Each update operation is only implemented for one type of object
51//!   respectively, the one it updates.
52//! - Update operations cover changes to attributes that are not references to
53//!   other objects.
54//! - Update operations cover changes to references that are not replacements,
55//!   like adding more references.
56//! - Update operations might provide more convenient methods to replace an
57//!   object, if the object they are implemented on references only one such
58//!   object. In such a case, the update operation does not need to take a
59//!   reference to the object being updated, while the respective replace
60//!   operation still does.
61//!
62//!
63//! ## Implementation Notes
64//!
65//! Only a few replace operations are implemented so far. More can be added, as
66//! the need arises.
67//!
68//! As of this writing, replace operations are generally implemented in the most
69//! simple and naive way possible: Iterating over all referenced objects and
70//! calling the replace operation recursively. This might have performance
71//! implications for large object graphs.
72//!
73//! There are some update operations that are straight-up redundant with what
74//! replace operations are doing. Some of the methods even have the same names.
75//! Those haven't been removed yet, as update operations generally require a
76//! reference to a bare object, while replace operations require a `Handle`.
77//! There are some open questions about how operations in general should deal
78//! with objects being inserted or not, so it's probably not worth addressing
79//! this before doing a general revamp of how operations deal with inserting.
80//!
81//! All replace traits have an associated type. This is required, because the
82//! traits are implemented for `Handle<T>`, but they must refer to the bare
83//! object type (`T`) in their method return values. It should be possible to
84//! avoid this additional complexity by implementing the traits for `T`
85//! directly, but then we would need arbitrary self types to keep the current
86//! level of convenience:
87//! <https://doc.rust-lang.org/beta/unstable-book/language-features/arbitrary-self-types.html>
88//!
89//!
90//! [`Handle`]: crate::storage::Handle
91//! [update operations]: crate::operations::update
92
93mod curve;
94mod half_edge;
95mod vertex;
96
97pub use self::{
98    curve::ReplaceCurve, half_edge::ReplaceHalfEdge, vertex::ReplaceVertex,
99};
100
101/// The output of a replace operation
102///
103/// See [module documentation] for more information.
104///
105/// [module documentation]: self
106pub enum ReplaceOutput<Original, Updated> {
107    /// The original object that the replace operation was called on
108    ///
109    /// If this variant is returned, the object to be replaced was not
110    /// referenced, and no replacement happened.
111    Original(Original),
112
113    /// The updated version of the object that the operation was called on
114    ///
115    /// If this variant is returned, a replacement happened, and this is the new
116    /// version of the object that reflects that.
117    ///
118    /// This is a bare object, not a `Handle`, to leave the decision of whether
119    /// to insert the object to the caller. This might be relevant, if the
120    /// result of the replacement is an invalid intermediate step in the
121    /// modeling process. The validation infrastructure currently provides no
122    /// good ways to deal with invalid intermediate results, even if the end
123    /// result ends up valid.
124    Updated(Updated),
125}
126
127impl<Original, Updated> ReplaceOutput<Original, Updated> {
128    /// Indicate whether the original object was updated
129    pub fn was_updated(&self) -> bool {
130        matches!(self, ReplaceOutput::Updated(_))
131    }
132
133    /// Map the `Original` variant using the provided function
134    pub fn map_original<T>(
135        self,
136        f: impl FnOnce(Original) -> T,
137    ) -> ReplaceOutput<T, Updated> {
138        match self {
139            Self::Original(original) => ReplaceOutput::Original(f(original)),
140            Self::Updated(updated) => ReplaceOutput::Updated(updated),
141        }
142    }
143
144    /// Map the `Updated` variant using the provided function
145    pub fn map_updated<T>(
146        self,
147        f: impl FnOnce(Updated) -> T,
148    ) -> ReplaceOutput<Original, T> {
149        match self {
150            Self::Original(original) => ReplaceOutput::Original(original),
151            Self::Updated(updated) => ReplaceOutput::Updated(f(updated)),
152        }
153    }
154}
155
156impl<T> ReplaceOutput<T, T> {
157    /// Return the wrapped object, whether it's the original or was updated
158    pub fn into_inner(self) -> T {
159        match self {
160            Self::Original(inner) => inner,
161            Self::Updated(inner) => inner,
162        }
163    }
164}