use crate::app; use gpui::{AppContext, BorrowAppContext}; use std::any::Any; pub trait QueryFn: Clone + 'static where C: Context + 'static, { type Data: 'static; type Error: 'static; fn key(&self) -> &'static str; async fn run(&self, c: &C) -> Result; } pub enum QueryStatus<'a, Data, Error> { Loading, Loaded(&'a Data), Err(&'a Error), } pub fn use_query(query_fn: F, cx: &mut gpui::Context) where C: Context + 'static, F: QueryFn, T: 'static, Store: gpui::Global, { let ent = cx.update_global::, _>(|store, cx| store.ensure_query_data(&query_fn, cx)); cx.observe(&ent, |_, _, cx| { cx.notify(); }) .detach(); let query_context = cx.global::>().query_context.clone(); cx.observe(&query_context, move |_, _, cx| { cx.update_global::, _>(|store, cx| { store.invalidate_query(&query_fn, cx); store.ensure_query_data(&query_fn, cx); }) }) .detach(); } // ================= Store ================== pub(crate) struct QueryState { data: QueryData, } pub(crate) enum QueryData { Pending, Loading, Stale, Some(Box), Err(Box), } pub(crate) type Entity = gpui::Entity; pub(crate) trait Context {} pub struct Store where C: Context, { query_data: std::collections::HashMap, query_context: gpui::Entity, } impl Store where C: Context + 'static, { pub fn new(ctx: C, cx: &mut gpui::Context) -> Self { Self { query_data: std::collections::HashMap::new(), query_context: cx.new(|_| ctx), } } fn entity_for(&mut self, query: &Q, cx: &mut gpui::Context) -> Entity where Q: QueryFn, T: 'static, { self.query_data .entry(query.key().into()) .or_insert_with(|| { cx.new(|_| QueryState { data: QueryData::Pending, }) }) .clone() } fn ensure_query_data(&mut self, query: &Q, cx: &mut gpui::Context) -> Entity where T: 'static, Q: QueryFn, { let entity = self.entity_for(query, cx); let should_execute = entity.read_with(cx, |state, _| { matches!(state.data, QueryData::Pending | QueryData::Stale) }); if should_execute { self.execute_query(query, cx).detach(); } entity } fn execute_query( &mut self, query: &Q, cx: &mut gpui::Context, ) -> gpui::Task> where Q: QueryFn, T: 'static, { let entity = self.entity_for(query, cx); entity.update(cx, |state, cx| { state.data = QueryData::Loading; cx.notify(); }); cx.spawn(async move |_, cx| { let query_context = self.query_context.read(cx); let result = query.run(query_context).await; entity.update(cx, |state, cx| { state.data = match result { Ok(data) => QueryData::Some(Box::new(data)), Err(err) => QueryData::Err(Box::new(err)), }; cx.notify(); })?; anyhow::Ok(()) }) } fn invalidate_query(&mut self, query: &Q, cx: &mut gpui::Context) where Q: QueryFn, { if let Some(entity) = self.query_data.get(query.key()) { entity.update(cx, |query, cx| { if !matches!(query.data, QueryData::Loading) { query.data = QueryData::Stale; cx.notify() } }) } } pub(crate) fn read_query<'a, Q, E>( &self, query_fn: Q, cx: &'a gpui::Context, ) -> QueryStatus<'a, Q::Data, Q::Error> where Q: QueryFn, { let Some(ent) = self.query_data.get(query_fn.key()) else { return QueryStatus::Loading; }; let QueryState { data } = ent.read(cx); match data { QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading, QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::().unwrap()), QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::().unwrap()), } } }