Skip to main content

tilo_app/framework/
kinds.rs

1//! A framework app's resource type, by the kind its records name.
2
3use std::sync::{Mutex, PoisonError};
4
5use tilo_resource::{Doc, Kinds, Resource, ResourceKind};
6
7use super::app::{ResourceApp, SeedCx, apply_seed};
8
9/// `A`'s resource type as a kind a host can look up: hosted by `A`, and an
10/// open resource named as `A` names it ([`ResourceApp::label`]). Every
11/// framework app gets its kind from here, so a host working with whatever a
12/// room holds reads, publishes and replays it without any of the app's own
13/// code.
14#[must_use]
15pub const fn resource_kind<A: ResourceApp>() -> ResourceKind {
16    ResourceKind::of::<A::Resource>()
17        .hosted_by(A::ID, A::TITLE)
18        .named(name::<A>)
19}
20
21fn name<A: ResourceApp>(state: &dyn std::any::Any) -> Option<String> {
22    state
23        .downcast_ref::<<A::Resource as Resource>::State>()
24        .map(A::label)
25}
26
27/// Applies an app's seed to a new record, created by `me` on `device`.
28type Seed = fn(&mut Doc, &str, &str, Option<&str>) -> Result<(), String>;
29
30/// Every framework app's kind whose definition this process created, and
31/// its seed.
32static HOSTED: Mutex<(Kinds, Vec<(&'static str, Seed)>)> = Mutex::new((Kinds::new(), Vec::new()));
33
34/// Remember `A`'s kind and seed; its definition is being created.
35pub(crate) fn register<A: ResourceApp>() {
36    let mut hosted = HOSTED.lock().unwrap_or_else(PoisonError::into_inner);
37    hosted.0.insert(resource_kind::<A>());
38    let kind = <A::Resource as Resource>::KIND;
39    hosted.1.retain(|(known, _)| *known != kind);
40    hosted.1.push((kind, seed::<A>));
41}
42
43fn seed<A: ResourceApp>(
44    doc: &mut Doc,
45    me: &str,
46    device: &str,
47    room: Option<&str>,
48) -> Result<(), String> {
49    let seed = A::seed(&SeedCx {
50        me,
51        device,
52        host: None,
53        room,
54    });
55    apply_seed::<A>(doc, me, &seed)
56}
57
58/// Seed a record of `kind` its genesis just created, as `me` creating it on
59/// `device` without a window: the app's [`ResourceApp::seed`] with no host,
60/// applied as in a window. A kind no app registered has nothing to seed.
61///
62/// # Errors
63/// Returns why a seed action did not apply.
64pub fn seed_record(kind: &str, doc: &mut Doc, me: &str, device: &str) -> Result<(), String> {
65    seed_scoped(kind, doc, me, device, None)
66}
67
68/// Seed a new room record in its host-selected creation scope, without lending
69/// ambient device services. The route supplies context, not permission.
70///
71/// # Errors
72/// Returns why a seed action did not apply.
73pub fn seed_record_in_room(
74    kind: &str,
75    doc: &mut Doc,
76    me: &str,
77    device: &str,
78    room: &str,
79) -> Result<(), String> {
80    seed_scoped(kind, doc, me, device, Some(room))
81}
82
83fn seed_scoped(
84    kind: &str,
85    doc: &mut Doc,
86    me: &str,
87    device: &str,
88    room: Option<&str>,
89) -> Result<(), String> {
90    let seed = HOSTED
91        .lock()
92        .unwrap_or_else(PoisonError::into_inner)
93        .1
94        .iter()
95        .find(|(known, _)| *known == kind)
96        .map(|(_, seed)| *seed);
97    seed.map_or(Ok(()), |seed| seed(doc, me, device, room))
98}
99
100/// The resource type of every framework app this process has the definition
101/// of ([`definition`](super::definition)): what a host that holds records
102/// of those apps without mounting them — an agent, a room service — reads,
103/// publishes and edits them by. Look one up by the kind a record names
104/// ([`Kinds::get`]) or by its app ([`Kinds::hosted_by`]).
105#[must_use]
106pub fn hosted_kinds() -> Kinds {
107    HOSTED
108        .lock()
109        .unwrap_or_else(PoisonError::into_inner)
110        .0
111        .clone()
112}