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}