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;
4
5use tilo_resource::{Doc, Kinds, Resource, ResourceKind};
6
7use super::app::{ResourceApp, SeedCx, apply_seed};
8use tilo_lock::MutexRecover as _;
9
10/// `A`'s resource type as a kind a host can look up: hosted by `A`, and an
11/// open resource named as `A` names it ([`ResourceApp::label`]). Every
12/// framework app gets its kind from here, so a host working with whatever a
13/// workspace holds reads, publishes and replays it without any of the app's own
14/// code.
15#[must_use]
16pub const fn resource_kind<A: ResourceApp>() -> ResourceKind {
17    ResourceKind::of::<A::Resource>()
18        .hosted_by(A::ID, A::TITLE)
19        .named(name::<A>)
20}
21
22fn name<A: ResourceApp>(state: &dyn std::any::Any) -> Option<String> {
23    state
24        .downcast_ref::<<A::Resource as Resource>::State>()
25        .map(A::label)
26}
27
28/// Applies an app's seed to a new record, created by `me` on `device`.
29type Seed = fn(&mut Doc, &str, &str, Option<&str>) -> Result<(), String>;
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    // The parts of the chains it keeps beside its record are its kinds too:
37    // a host reads a part (a chat's messages) as the app's window does.
38    let mut declared = super::Records::declaring();
39    A::records(&mut declared);
40    // A runtime that runs a package hosts the kinds under the package's app.
41    let app = super::app::hooks::<A>();
42    // What the app is called, for every resource its types publish: its
43    // title as people read it, matched to the kinds its first part names. A
44    // published resource then says "Tube" and "tubes" the way a tilolang
45    // package's does, with nothing the app or its types write for it.
46    tilo_resource::words::name_app(app.title(), <A::Resource as Resource>::KIND);
47    let mut hosted = HOSTED.lock_recover();
48    hosted.0.insert(
49        ResourceKind::of::<A::Resource>()
50            .hosted_by(app.id(), app.title())
51            .named(name::<A>),
52    );
53    for chain in declared.into_chains() {
54        hosted.0.insert(chain.hosted_by(app.id(), app.title()));
55    }
56    let kind = <A::Resource as Resource>::KIND;
57    hosted.1.retain(|(known, _)| *known != kind);
58    hosted.1.push((kind, seed::<A>));
59}
60
61fn seed<A: ResourceApp>(
62    doc: &mut Doc,
63    me: &str,
64    device: &str,
65    workspace: Option<&str>,
66) -> Result<(), String> {
67    let seed = A::seed(&SeedCx {
68        me,
69        device,
70        host: None,
71        workspace,
72    });
73    apply_seed::<A>(doc, me, &seed)
74}
75
76/// Seed a record of `kind` its genesis just created, as `me` creating it on
77/// `device` without a window: the app's [`ResourceApp::seed`] with no host,
78/// applied as in a window. A kind no app registered has nothing to seed.
79pub fn seed_record(kind: &str, doc: &mut Doc, me: &str, device: &str) -> Result<(), String> {
80    seed_scoped(kind, doc, me, device, None)
81}
82
83/// Seed a new workspace record in its host-selected creation scope, without lending
84/// ambient device services. The route supplies context, not permission.
85pub fn seed_record_in_workspace(
86    kind: &str,
87    doc: &mut Doc,
88    me: &str,
89    device: &str,
90    workspace: &str,
91) -> Result<(), String> {
92    seed_scoped(kind, doc, me, device, Some(workspace))
93}
94
95fn seed_scoped(
96    kind: &str,
97    doc: &mut Doc,
98    me: &str,
99    device: &str,
100    workspace: Option<&str>,
101) -> Result<(), String> {
102    let seed = HOSTED
103        .lock_recover()
104        .1
105        .iter()
106        .find(|(known, _)| *known == kind)
107        .map(|(_, seed)| *seed);
108    seed.map_or(Ok(()), |seed| seed(doc, me, device, workspace))
109}
110
111/// The resource type of every framework app this process has the definition
112/// of ([`definition`](super::definition)): what a host that holds records
113/// of those apps without mounting them — an agent, a workspace service — reads,
114/// publishes and edits them by. Look one up by the kind a record names
115/// ([`Kinds::get`]) or by its app ([`Kinds::hosted_by`]).
116#[must_use]
117pub fn hosted_kinds() -> Kinds {
118    HOSTED.lock_recover().0.clone()
119}