1use std::marker::PhantomData;
10use std::sync::Arc;
11
12use key_paths_core::RefKpTrait;
13use parking_lot::Mutex;
14
15#[derive(Clone)]
17pub struct StateBinding<S: 'static> {
18 state: Arc<Mutex<S>>,
19}
20
21impl<S: 'static> StateBinding<S> {
22 pub(crate) fn new(state: Arc<Mutex<S>>) -> Self {
23 Self { state }
24 }
25
26 pub fn with<R>(&self, f: impl FnOnce(&S) -> R) -> R {
28 f(&*self.state.lock())
29 }
30
31 pub fn with_mut<R>(&self, f: impl FnOnce(&mut S) -> R) -> R {
33 f(&mut *self.state.lock())
34 }
35
36 pub fn project<V, SK>(&self, kp: SK) -> ProjectedBinding<S, V, SK>
38 where
39 SK: RefKpTrait<S, V>,
40 {
41 ProjectedBinding {
42 state: Arc::clone(&self.state),
43 kp,
44 _marker: PhantomData,
45 }
46 }
47}
48
49pub struct ProjectedBinding<Root: 'static, Focus: 'static, SK> {
51 state: Arc<Mutex<Root>>,
52 kp: SK,
53 _marker: PhantomData<Focus>,
54}
55
56impl<Root, Focus, SK> Clone for ProjectedBinding<Root, Focus, SK>
57where
58 Root: 'static,
59 Focus: 'static,
60 SK: RefKpTrait<Root, Focus> + Clone,
61{
62 fn clone(&self) -> Self {
63 Self {
64 state: Arc::clone(&self.state),
65 kp: self.kp.clone(),
66 _marker: PhantomData,
67 }
68 }
69}
70
71impl<Root, Focus, SK> ProjectedBinding<Root, Focus, SK>
72where
73 Root: 'static,
74 Focus: 'static,
75 SK: RefKpTrait<Root, Focus>,
76{
77 pub fn with<R>(&self, f: impl FnOnce(&Focus) -> R) -> Option<R> {
79 let guard = self.state.lock();
80 let focused = self.kp.focus(&*guard)?;
81 Some(f(focused))
82 }
83
84 pub fn with_mut<R>(&self, f: impl FnOnce(&mut Focus) -> R) -> Option<R> {
86 let mut guard = self.state.lock();
87 let focused = self.kp.focus_mut(&mut *guard)?;
88 Some(f(focused))
89 }
90
91 pub fn project<V, SubSK>(self, sub_kp: SubSK) -> ComposedBinding<Root, Focus, V, SK, SubSK>
93 where
94 SubSK: RefKpTrait<Focus, V>,
95 SK: Clone,
96 {
97 ComposedBinding {
98 state: self.state,
99 parent_kp: self.kp,
100 sub_kp,
101 _marker: PhantomData,
102 }
103 }
104}
105
106pub struct ComposedBinding<Root: 'static, Mid: 'static, Focus: 'static, ParentKP, SubKP> {
108 state: Arc<Mutex<Root>>,
109 parent_kp: ParentKP,
110 sub_kp: SubKP,
111 _marker: PhantomData<(Mid, Focus)>,
112}
113
114impl<Root, Mid, Focus, ParentKP, SubKP> ComposedBinding<Root, Mid, Focus, ParentKP, SubKP>
115where
116 Root: 'static,
117 Mid: 'static,
118 Focus: 'static,
119 ParentKP: RefKpTrait<Root, Mid>,
120 SubKP: RefKpTrait<Mid, Focus>,
121{
122 pub fn with<R>(&self, f: impl FnOnce(&Focus) -> R) -> Option<R> {
124 let guard = self.state.lock();
125 let mid = self.parent_kp.focus(&*guard)?;
126 let focused = self.sub_kp.focus(mid)?;
127 Some(f(focused))
128 }
129
130 pub fn with_mut<R>(&self, f: impl FnOnce(&mut Focus) -> R) -> Option<R> {
132 let mut guard = self.state.lock();
133 let mid = self.parent_kp.focus_mut(&mut *guard)?;
134 let focused = self.sub_kp.focus_mut(mid)?;
135 Some(f(focused))
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use key_paths_derive::{FieldDiff, Kp};
143 use rust_key_paths::Kp as KpPath;
144
145 #[derive(Debug, Kp, Clone, Hash, FieldDiff, PartialEq)]
146 struct Episode {
147 current_position: i32,
148 is_favorite: bool,
149 }
150
151 fn position_kp() -> KpPath<
152 Episode,
153 i32,
154 &'static Episode,
155 &'static i32,
156 &'static mut Episode,
157 &'static mut i32,
158 for<'b> fn(&'b Episode) -> Option<&'b i32>,
159 for<'b> fn(&'b mut Episode) -> Option<&'b mut i32>,
160 > {
161 fn get(e: &Episode) -> Option<&i32> {
162 Some(&e.current_position)
163 }
164 fn get_mut(e: &mut Episode) -> Option<&mut i32> {
165 Some(&mut e.current_position)
166 }
167 KpPath::new(get, get_mut)
168 }
169
170 fn favorite_kp() -> KpPath<
171 Episode,
172 bool,
173 &'static Episode,
174 &'static bool,
175 &'static mut Episode,
176 &'static mut bool,
177 for<'b> fn(&'b Episode) -> Option<&'b bool>,
178 for<'b> fn(&'b mut Episode) -> Option<&'b mut bool>,
179 > {
180 fn get(e: &Episode) -> Option<&bool> {
181 Some(&e.is_favorite)
182 }
183 fn get_mut(e: &mut Episode) -> Option<&mut bool> {
184 Some(&mut e.is_favorite)
185 }
186 KpPath::new(get, get_mut)
187 }
188
189 #[test]
190 fn binding_projects_fields_without_cloning_root() {
191 let state = Arc::new(Mutex::new(Episode {
192 current_position: 1,
193 is_favorite: false,
194 }));
195 let binding = StateBinding::new(state);
196
197 let position = binding.project(position_kp());
198 assert_eq!(position.with(|p| *p), Some(1));
199
200 position.with_mut(|p| *p = 2);
201 assert_eq!(binding.with(|e| e.current_position), 2);
202
203 let favorite = binding.project(favorite_kp());
204 favorite.with_mut(|f| *f = true);
205 assert!(binding.with(|e| e.is_favorite));
206 }
207
208 #[test]
209 fn projected_binding_returns_none_when_keypath_misses() {
210 #[derive(Kp, Clone, Hash, FieldDiff, PartialEq, Debug)]
211 struct Root {
212 child: Option<Episode>,
213 }
214
215 fn child_kp() -> KpPath<
216 Root,
217 Episode,
218 &'static Root,
219 &'static Episode,
220 &'static mut Root,
221 &'static mut Episode,
222 for<'b> fn(&'b Root) -> Option<&'b Episode>,
223 for<'b> fn(&'b mut Root) -> Option<&'b mut Episode>,
224 > {
225 fn get(r: &Root) -> Option<&Episode> {
226 r.child.as_ref()
227 }
228 fn get_mut(r: &mut Root) -> Option<&mut Episode> {
229 r.child.as_mut()
230 }
231 KpPath::new(get, get_mut)
232 }
233
234 let binding = StateBinding::new(Arc::new(Mutex::new(Root { child: None })));
235 let child = binding.project(child_kp());
236 assert!(child.with(|_| ()).is_none());
237 assert!(child.with_mut(|_| ()).is_none());
238 }
239}