ferroui_base/rendering/composition/render_interface_feature.rs
1//! A feature of the render interface, as a caller holds it.
2//!
3//! Upstream `Compositor.TryGetRenderInterfaceFeature` hands the feature
4//! object itself to the UI thread, which keeps it while the render thread
5//! keeps the map it came from; the garbage collector shares the object. Here
6//! a feature is an `Rc` in the map of the server compositor, and a second
7//! handle to it held by another thread would change the same count without
8//! synchronisation. So the caller gets a [`RenderInterfaceFeature`]: the
9//! handle stays inside the compositor lock (a [`LockBound`]), and the feature
10//! is lent from there for the length of a call.
11
12use super::server::{LockBound, ServerCompositor};
13use std::any::Any;
14use std::rc::Rc;
15
16/// A feature of the platform render interface
17/// ([`Compositor::try_get_render_interface_feature`](super::Compositor::try_get_render_interface_feature)).
18///
19/// The handle keeps the feature alive, as the object upstream does: a caller
20/// that holds it still reaches the feature of a context that has since been
21/// replaced. It may be kept, moved and dropped anywhere; the feature behind
22/// it is only reached inside the compositor lock, with
23/// [`with`](Self::with) from outside or with [`get`](Self::get) by a caller
24/// that already has the server compositor in hand (a job).
25///
26/// A feature is registered under the type it is asked for by. By the
27/// convention of the optional features a feature asked for as a trait object
28/// (`TypeId::of::<dyn IFoo>()`) is registered as an `Rc<dyn IFoo>`: `with`
29/// and `get` lend the `dyn IFoo`, never the `Rc`.
30pub struct RenderInterfaceFeature {
31 feature: LockBound<Rc<dyn Any>>,
32}
33
34impl RenderInterfaceFeature {
35 /// `feature` is a handle cloned from the map of features inside the
36 /// lock, bound to the lock there.
37 pub(crate) fn new(feature: LockBound<Rc<dyn Any>>) -> Self {
38 Self { feature }
39 }
40
41 fn lend<T: ?Sized + 'static>(feature: &Rc<dyn Any>) -> Option<&T> {
42 feature.downcast_ref::<Rc<T>>().map(|feature| &**feature)
43 }
44
45 /// Runs `f` with the feature, registered as an `Rc<T>`, inside the
46 /// compositor lock, which the calling thread enters for the call.
47 ///
48 /// `None` when the feature is not registered as an `Rc<T>`, and once the
49 /// compositor has released its server compositor.
50 pub fn with<T: ?Sized + 'static, R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
51 self.feature.with(|_, feature| Self::lend::<T>(feature).map(f)).flatten()
52 }
53
54 /// The feature, registered as an `Rc<T>`, for a caller that is inside
55 /// the compositor lock: `server` is what a job receives and what
56 /// `Compositor::with_server` hands out. `None` when the feature is not
57 /// registered as an `Rc<T>`.
58 ///
59 /// # Panics
60 ///
61 /// When `server` is not the server compositor the feature came from.
62 pub fn get<'a, T: ?Sized + 'static>(&'a self, server: &'a ServerCompositor) -> Option<&'a T> {
63 Self::lend::<T>(self.feature.get(server))
64 }
65
66 /// Whether the feature is registered as a value of type `T` (for a
67 /// feature asked for as a trait object, `Rc<dyn IFoo>`). `false` once
68 /// the compositor has released its server compositor.
69 pub fn is<T: 'static>(&self) -> bool {
70 self.feature.with(|_, feature| feature.is::<T>()).unwrap_or(false)
71 }
72}
73
74const _: fn() = || {
75 // The handle is what crosses out of the lock, so it may be held by any
76 // thread.
77 fn assert_send_sync<T: Send + Sync>() {}
78 assert_send_sync::<RenderInterfaceFeature>();
79};