Skip to main content

ferroui_splat/builder/
app_builder.rs

1//! `IAppBuilder`, `IAppInstance`, `IModule`, `AppBuilder` and `SplatBuilderExtensions`.
2
3use crate::service_location::{AppLocator, IMutableDependencyResolver, IReadonlyDependencyResolver};
4use std::cell::{Cell, RefCell};
5use std::rc::Rc;
6
7/// A module that registers its services with a resolver (`IModule`).
8pub trait IModule {
9    /// Registers the services of the module with `resolver` (`Configure`).
10    fn configure(&self, resolver: &dyn IMutableDependencyResolver);
11}
12
13/// A built application: the resolvers of its services (`IAppInstance`).
14pub trait IAppInstance {
15    /// The resolver the application resolves its services from, if any (`Current`).
16    fn current(&self) -> Option<Rc<dyn IReadonlyDependencyResolver>>;
17
18    /// The resolver the application registers its services with (`CurrentMutable`).
19    fn current_mutable(&self) -> Rc<dyn IMutableDependencyResolver>;
20}
21
22/// Configures the services of an application, then builds it (`IAppBuilder`). Each method returns
23/// the builder, to chain the calls.
24pub trait IAppBuilder {
25    /// Applies the registrations and returns the application (`Build`).
26    fn build(&self) -> &dyn IAppInstance;
27
28    /// Applies the registrations to the current resolver of [`AppLocator`] at build time instead
29    /// of the builder's (`UseCurrentSplatLocator`).
30    fn use_current_splat_locator(&self) -> &dyn IAppBuilder;
31
32    /// Adds a module, whose registrations are applied at build time (`UsingModule<T>`).
33    fn using_module(&self, registration_module: Rc<dyn IModule>) -> &dyn IAppBuilder;
34
35    /// Adds the core services (`WithCoreServices`).
36    fn with_core_services(&self) -> &dyn IAppBuilder;
37
38    /// Adds registrations, applied at build time (`WithCustomRegistration`).
39    fn with_custom_registration(
40        &self,
41        configure_action: Rc<dyn Fn(&dyn IMutableDependencyResolver)>,
42    ) -> &dyn IAppBuilder;
43}
44
45// Deviation (DEVIATIONS.md, Splat): `HasBeenBuilt` and `UsingBuilder` are static properties of the
46// process upstream; here they are per thread, as the locator is.
47ferroui_system::ferro_thread_local! {
48    static HAS_BEEN_BUILT: Cell<bool> = const { Cell::new(false) };
49    static USING_BUILDER: Cell<bool> = const { Cell::new(false) };
50}
51
52type Registration = Rc<dyn Fn(&dyn IMutableDependencyResolver)>;
53type ResolverProvider = Rc<dyn Fn() -> Rc<dyn IMutableDependencyResolver>>;
54type ServiceProvider = Rc<dyn Fn() -> Option<Rc<dyn IReadonlyDependencyResolver>>>;
55
56/// The builder of Splat (`AppBuilder`): it collects modules and registrations and applies them
57/// once, when the application is built for the first time.
58///
59/// Upstream subclasses override `WithCoreServices`; a builder built on this one wraps it and
60/// implements [`IAppBuilder`], adding its core services before it builds the inner builder.
61pub struct AppBuilder {
62    registrations: RefCell<Vec<Registration>>,
63    resolver_provider: RefCell<ResolverProvider>,
64    service_provider: RefCell<ServiceProvider>,
65    current: RefCell<Option<Rc<dyn IReadonlyDependencyResolver>>>,
66    current_mutable: Rc<dyn IMutableDependencyResolver>,
67}
68
69impl AppBuilder {
70    /// A builder over `resolver` (`AppBuilder(IMutableDependencyResolver)`).
71    pub fn new(resolver: Rc<dyn IMutableDependencyResolver>) -> Self {
72        Self::with_current(resolver, None)
73    }
74
75    /// A builder over `resolver`, with `current` as the resolver of the application
76    /// (`AppBuilder(IMutableDependencyResolver, IReadonlyDependencyResolver?)`).
77    pub fn with_current(
78        resolver: Rc<dyn IMutableDependencyResolver>,
79        current: Option<Rc<dyn IReadonlyDependencyResolver>>,
80    ) -> Self {
81        USING_BUILDER.with(|flag| flag.set(true));
82        let provided_mutable = resolver.clone();
83        let provided_current = current.clone();
84        Self {
85            registrations: RefCell::new(Vec::new()),
86            resolver_provider: RefCell::new(Rc::new(move || provided_mutable.clone())),
87            service_provider: RefCell::new(Rc::new(move || provided_current.clone())),
88            current: RefCell::new(current),
89            current_mutable: resolver,
90        }
91    }
92
93    /// A builder over the current resolver of [`AppLocator`] (`CreateSplatBuilder`).
94    pub fn create_splat_builder() -> Self {
95        Self::with_current(AppLocator::current_mutable(), Some(AppLocator::current()))
96    }
97
98    /// Whether an application was built (`HasBeenBuilt`).
99    pub fn has_been_built() -> bool {
100        HAS_BEEN_BUILT.with(|flag| flag.get())
101    }
102
103    /// Whether a builder was created (`UsingBuilder`).
104    pub fn using_builder() -> bool {
105        USING_BUILDER.with(|flag| flag.get())
106    }
107
108    /// Resets [`has_been_built`](Self::has_been_built) and [`using_builder`](Self::using_builder)
109    /// (`ResetBuilderStateForTests`).
110    pub fn reset_builder_state_for_tests() {
111        Self::restore_state(false, false);
112    }
113
114    /// `GetState`. For the tests of the crate.
115    #[doc(hidden)]
116    pub fn get_state() -> (bool, bool) {
117        (Self::has_been_built(), Self::using_builder())
118    }
119
120    /// `RestoreState`. For the tests of the crate.
121    #[doc(hidden)]
122    pub fn restore_state(has_been_built: bool, using_builder: bool) {
123        HAS_BEEN_BUILT.with(|flag| flag.set(has_been_built));
124        USING_BUILDER.with(|flag| flag.set(using_builder));
125    }
126
127    /// `ResetState`. For the tests of the crate.
128    #[doc(hidden)]
129    pub fn reset_state() {
130        Self::restore_state(false, false);
131    }
132}
133
134impl IAppInstance for AppBuilder {
135    fn current(&self) -> Option<Rc<dyn IReadonlyDependencyResolver>> {
136        self.current.borrow().clone()
137    }
138
139    fn current_mutable(&self) -> Rc<dyn IMutableDependencyResolver> {
140        self.current_mutable.clone()
141    }
142}
143
144impl IAppBuilder for AppBuilder {
145    fn build(&self) -> &dyn IAppInstance {
146        // An application is built once.
147        if Self::has_been_built() {
148            return self;
149        }
150
151        HAS_BEEN_BUILT.with(|flag| flag.set(true));
152
153        // The core services are always registered.
154        let _ = self.with_core_services();
155
156        // The registrations are applied to the resolver the builder provides now.
157        let registrations: Vec<Registration> = self.registrations.borrow().clone();
158        for registration in registrations {
159            let provider = self.resolver_provider.borrow().clone();
160            let target_resolver = provider();
161            registration(&*target_resolver);
162        }
163
164        let provider = self.service_provider.borrow().clone();
165        *self.current.borrow_mut() = provider();
166        self
167    }
168
169    fn use_current_splat_locator(&self) -> &dyn IAppBuilder {
170        *self.resolver_provider.borrow_mut() = Rc::new(AppLocator::current_mutable);
171        *self.service_provider.borrow_mut() = Rc::new(|| Some(AppLocator::current()));
172        self
173    }
174
175    fn using_module(&self, registration_module: Rc<dyn IModule>) -> &dyn IAppBuilder {
176        self.registrations
177            .borrow_mut()
178            .push(Rc::new(move |resolver: &dyn IMutableDependencyResolver| registration_module.configure(resolver)));
179        self
180    }
181
182    fn with_core_services(&self) -> &dyn IAppBuilder {
183        self
184    }
185
186    fn with_custom_registration(
187        &self,
188        configure_action: Rc<dyn Fn(&dyn IMutableDependencyResolver)>,
189    ) -> &dyn IAppBuilder {
190        self.registrations.borrow_mut().push(configure_action);
191        self
192    }
193}
194
195/// `Apply` of a module (`SplatBuilderExtensions`).
196pub trait ModuleExt {
197    /// Registers the services of the module with the current resolver of [`AppLocator`]
198    /// (`Apply`).
199    fn apply(&self);
200}
201
202impl<M: IModule + ?Sized> ModuleExt for M {
203    fn apply(&self) {
204        self.configure(&*AppLocator::current_mutable());
205    }
206}
207
208/// `CreateSplatBuilder` of a resolver (`SplatBuilderExtensions`).
209pub trait SplatBuilderExt {
210    /// A builder over the resolver (`CreateSplatBuilder()`).
211    fn create_splat_builder(&self) -> AppBuilder;
212
213    /// A builder over the resolver, with `configure_action` as its first registration
214    /// (`CreateSplatBuilder(Action<IMutableDependencyResolver>)`).
215    fn create_splat_builder_with(
216        &self,
217        configure_action: impl Fn(&dyn IMutableDependencyResolver) + 'static,
218    ) -> AppBuilder;
219}
220
221impl<R: IMutableDependencyResolver + 'static> SplatBuilderExt for Rc<R> {
222    fn create_splat_builder(&self) -> AppBuilder {
223        AppBuilder::new(self.clone())
224    }
225
226    fn create_splat_builder_with(
227        &self,
228        configure_action: impl Fn(&dyn IMutableDependencyResolver) + 'static,
229    ) -> AppBuilder {
230        let builder = AppBuilder::new(self.clone());
231        let _ = builder.with_custom_registration(Rc::new(configure_action));
232        builder
233    }
234}
235
236impl SplatBuilderExt for Rc<dyn IMutableDependencyResolver> {
237    fn create_splat_builder(&self) -> AppBuilder {
238        AppBuilder::new(self.clone())
239    }
240
241    fn create_splat_builder_with(
242        &self,
243        configure_action: impl Fn(&dyn IMutableDependencyResolver) + 'static,
244    ) -> AppBuilder {
245        let builder = AppBuilder::new(self.clone());
246        let _ = builder.with_custom_registration(Rc::new(configure_action));
247        builder
248    }
249}