use anymore::AnyDebug;
use hashbrown::{HashMap, hash_map::Entry};
use crate::{MessageContext, MessageResult, Mut, View, ViewId, ViewMarker, ViewPathTracker};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use core::{any::TypeId, marker::PhantomData};
#[derive(Debug)]
pub struct Rebuild;
#[derive(Debug)]
#[expect(missing_docs, reason = "Public on an interim basis")]
pub struct EnvironmentItem {
#[expect(missing_docs, reason = "Public on an interim basis")]
pub value: Box<dyn AnyDebug>,
change_listeners: Vec<Option<Arc<[ViewId]>>>,
}
#[derive(Debug)]
#[expect(missing_docs, reason = "Public on an interim basis")]
pub struct Slot {
#[expect(missing_docs, reason = "Public on an interim basis")]
pub item: Option<EnvironmentItem>,
ref_count: u32,
}
#[derive(Debug)]
pub struct Environment {
#[expect(missing_docs, reason = "Public on an interim basis")]
pub slots: Vec<Slot>,
free_slots: Vec<u32>,
types: HashMap<TypeId, u32>,
}
impl Environment {
pub fn new() -> Self {
Self {
slots: Vec::new(),
free_slots: Vec::new(),
types: HashMap::new(),
}
}
fn create_slot_for_type<Context>(&mut self) -> u32
where
Context: Resource,
{
match self.types.entry(TypeId::of::<Context>()) {
Entry::Occupied(occupied_entry) => *occupied_entry.get(),
Entry::Vacant(vacant_entry) => {
if let Some(slot) = self.free_slots.pop() {
debug_assert_eq!(
self.slots[usize::try_from(slot).unwrap()].ref_count,
0,
"Free slot should actually be set and unused"
);
vacant_entry.insert(slot);
slot
} else {
let slot: u32 = self
.slots
.len()
.try_into()
.expect("Should be fewer than 2.pow(32) resources/locals used.");
self.slots.push(Slot {
item: None,
ref_count: 0,
});
vacant_entry.insert(slot);
slot
}
}
}
}
#[expect(missing_docs, reason = "Public on an interim basis")]
pub fn get_slot_for_type<Context>(&mut self) -> Option<u32>
where
Context: Resource,
{
self.types.get(&TypeId::of::<Context>()).copied()
}
}
impl Default for Environment {
fn default() -> Self {
Self::new()
}
}
pub trait Resource: AnyDebug {}
pub fn provides<State, Action, Context, InitialContext, ChildView, Ctx>(
initial_context: InitialContext,
child: ChildView,
) -> Provides<State, Action, Context, InitialContext, ChildView>
where
InitialContext: Fn(&mut State) -> Context,
ChildView: View<State, Action, Ctx>,
Ctx: ViewPathTracker,
Context: Resource,
{
Provides {
initial_context,
child,
phantom: PhantomData,
}
}
#[derive(Debug)]
#[must_use = "View values do nothing unless provided to Xilem."]
pub struct Provides<State, Action, Context: Resource, InitialContext, ChildView> {
initial_context: InitialContext,
child: ChildView,
phantom: PhantomData<fn(State, Context) -> Action>,
}
#[derive(Debug)]
#[expect(
unnameable_types,
reason = "Implementation detail, public because of trait visibility rules"
)]
pub struct ProvidesState<ChildState> {
child_state: ChildState,
this_state: Option<EnvironmentItem>,
environment_slot: u32,
}
impl<State, Action, Context, InitialContext, ChildView> ViewMarker
for Provides<State, Action, Context, InitialContext, ChildView>
where
Context: Resource,
{
}
impl<State, Action, Context, InitialContext, Ctx: ViewPathTracker, ChildView>
View<State, Action, Ctx> for Provides<State, Action, Context, InitialContext, ChildView>
where
InitialContext: Fn(&mut State) -> Context,
ChildView: View<State, Action, Ctx>,
Context: Resource,
Self: 'static,
{
type Element = ChildView::Element;
type ViewState = ProvidesState<ChildView::ViewState>;
fn build(&self, ctx: &mut Ctx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
let value = (self.initial_context)(app_state);
let environment_item = EnvironmentItem {
change_listeners: Vec::new(),
value: Box::new(value),
};
let env = ctx.environment();
let pos = env.create_slot_for_type::<Context>();
let slot_idx = usize::try_from(pos).unwrap();
let slot = &mut env.slots[slot_idx];
slot.ref_count += 1;
let old_value = slot.item.replace(environment_item);
#[cfg(debug_assertions)]
if let Some(old_value) = old_value.as_ref() {
assert!(
old_value.value.is::<Context>(),
"In providing {}, the type of the old value didn't match. The old value was instead {:?}",
core::any::type_name::<Context>(),
old_value.value
);
}
let (child_element, child_state) = self.child.build(ctx, app_state);
let env = ctx.environment();
let slot = &mut env.slots[slot_idx];
let my_item = core::mem::replace(&mut slot.item, old_value);
let my_item =
my_item.expect("Child Views should not have deleted the environment item's value.");
debug_assert!(
my_item.value.is::<Context>(),
"Running a child build should have restored the same value"
);
let state = ProvidesState {
child_state,
this_state: Some(my_item),
environment_slot: pos,
};
(child_element, state)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut Ctx,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) {
let env = ctx.environment();
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
debug_assert!(
view_state.this_state.is_some(),
"`Provides` should be providing something."
);
core::mem::swap(&mut slot.item, &mut view_state.this_state);
self.child.rebuild(
&prev.child,
&mut view_state.child_state,
ctx,
element,
app_state,
);
let env = ctx.environment();
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
core::mem::swap(&mut slot.item, &mut view_state.this_state);
debug_assert!(
view_state.this_state.is_some(),
"`Provides` should get its value back."
);
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut Ctx,
element: Mut<'_, Self::Element>,
) {
let env = ctx.environment();
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
core::mem::swap(&mut slot.item, &mut view_state.this_state);
self.child
.teardown(&mut view_state.child_state, ctx, element);
let env = ctx.environment();
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
core::mem::swap(&mut slot.item, &mut view_state.this_state);
slot.ref_count -= 1;
if slot.ref_count == 0 {
assert!(
slot.item.is_none(),
"Ref count for {slot:?} was not properly managed."
);
env.free_slots.push(view_state.environment_slot);
env.types.remove(&TypeId::of::<Context>());
}
}
fn message(
&self,
view_state: &mut Self::ViewState,
message: &mut MessageContext,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
let slot =
&mut message.environment.slots[usize::try_from(view_state.environment_slot).unwrap()];
debug_assert!(
view_state.this_state.is_some(),
"`Provides` should be providing something."
);
core::mem::swap(&mut slot.item, &mut view_state.this_state);
let ret = self
.child
.message(&mut view_state.child_state, message, element, app_state);
let slot =
&mut message.environment.slots[usize::try_from(view_state.environment_slot).unwrap()];
core::mem::swap(&mut slot.item, &mut view_state.this_state);
debug_assert!(
view_state.this_state.is_some(),
"`Provides` should get its value back."
);
ret
}
}
pub fn with_context<State, Action, Context, Child, ChildView, Ctx>(
child: Child,
) -> WithContext<State, Action, Context, Child, ChildView>
where
Child: Fn(&mut Context, &mut State) -> ChildView,
ChildView: View<State, Action, Ctx>,
Ctx: ViewPathTracker,
Context: Resource,
{
WithContext {
child,
phantom: PhantomData,
}
}
#[derive(Debug)]
#[must_use = "View values do nothing unless provided to Xilem."]
pub struct WithContext<State, Action, Context: Resource, Child, ChildView> {
child: Child,
phantom: PhantomData<fn(State, Context) -> (Action, ChildView)>,
}
#[derive(Debug)]
#[expect(
unnameable_types,
reason = "Implementation detail, public because of trait visibility rules"
)]
pub struct WithContextState<ChildState, ChildView> {
prev: ChildView,
child_state: ChildState,
environment_slot: u32,
listener_index: Option<usize>,
}
const WITH_CONTEXT_CHILD: ViewId = ViewId::new(0);
impl<State, Action, Context, Child, ChildView> ViewMarker
for WithContext<State, Action, Context, Child, ChildView>
where
Context: Resource,
{
}
impl<State, Action, Context, Ctx: ViewPathTracker, Child, ChildView> View<State, Action, Ctx>
for WithContext<State, Action, Context, Child, ChildView>
where
Child: Fn(&mut Context, &mut State) -> ChildView,
ChildView: View<State, Action, Ctx>,
Context: Resource,
Self: 'static,
{
type Element = ChildView::Element;
type ViewState = WithContextState<ChildView::ViewState, ChildView>;
fn build(&self, ctx: &mut Ctx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
let path: Arc<[ViewId]> = ctx.view_path().into();
ctx.with_id(WITH_CONTEXT_CHILD, |ctx| {
let env = ctx.environment();
let pos = env.get_slot_for_type::<Context>();
let Some(pos) = pos else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been provided. Did you forget to wrap this view with `xilem_core::environment::provides`?",
core::any::type_name::<Context>()
);
};
let slot_idx = usize::try_from(pos).unwrap();
let slot = &mut env.slots[slot_idx];
let Some(value) = slot.item.as_mut() else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been `Provided`.",
core::any::type_name::<Context>()
);
};
let context = value
.value
.downcast_mut::<Context>()
.expect("Environment's slots should have the correct types.");
let mut first_empty = None;
let mut needs_storing = true;
for (idx, item) in value.change_listeners.iter().enumerate() {
if let Some(item) = item {
if **item == *path {
needs_storing = false;
break;
}
} else {
first_empty.get_or_insert(idx);
}
}
let listener_index = if needs_storing {
if let Some(first_empty) = first_empty {
value.change_listeners[first_empty] = Some(path);
Some(first_empty)
} else {
let idx = value.change_listeners.len();
value.change_listeners.push(Some(path));
Some(idx)
}
} else {
None
};
let child_view = (self.child)(context, app_state);
let (child_element, child_state) = child_view.build(ctx, app_state);
let state = WithContextState {
prev: child_view,
child_state,
environment_slot: pos,
listener_index,
};
(child_element, state)
})
}
fn rebuild(
&self,
_: &Self,
view_state: &mut Self::ViewState,
ctx: &mut Ctx,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) {
ctx.with_id(WITH_CONTEXT_CHILD, |ctx| {
let env = ctx.environment();
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
let Some(value) = slot.item.as_mut() else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been `Provided`.",
core::any::type_name::<Context>()
);
};
let context = value
.value
.downcast_mut::<Context>()
.expect("Environment's slots should have the correct types.");
let child_view = (self.child)(context, app_state);
child_view.rebuild(
&view_state.prev,
&mut view_state.child_state,
ctx,
element,
app_state,
);
view_state.prev = child_view;
});
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut Ctx,
element: Mut<'_, Self::Element>,
) {
if let Some(_listener_idx) = view_state.listener_index {
}
ctx.with_id(WITH_CONTEXT_CHILD, |ctx| {
view_state
.prev
.teardown(&mut view_state.child_state, ctx, element);
});
}
fn message(
&self,
view_state: &mut Self::ViewState,
message: &mut MessageContext,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
let Some(first) = message.take_first() else {
match message.take_message::<Rebuild>() {
Some(_) => return MessageResult::RequestRebuild,
None => {
tracing::warn!("Expected `Rebuild` in WithContext::Message, got {message:?}");
return MessageResult::Stale;
}
}
};
debug_assert_eq!(
first, WITH_CONTEXT_CHILD,
"Message should have been routed properly."
);
view_state
.prev
.message(&mut view_state.child_state, message, element, app_state)
}
}
#[derive(Debug)]
#[must_use = "View values do nothing unless provided to Xilem."]
pub struct OnActionWithContext<State, Action, Context, OnAction, Res, ChildView, ChildAction> {
child: ChildView,
on_action: OnAction,
phantom: PhantomData<fn(State, ChildAction, Context, Res) -> Action>,
}
pub fn on_action_with_context<State, Action, Context, OnAction, Res, ChildView, ChildAction>(
on_action: OnAction,
child: ChildView,
) -> OnActionWithContext<State, Action, Context, OnAction, Res, ChildView, ChildAction>
where
Context: ViewPathTracker,
OnActionWithContext<State, Action, Context, OnAction, Res, ChildView, ChildAction>:
View<State, Action, Context>,
OnAction: Fn(&mut State, &mut Res, ChildAction) -> Action,
{
OnActionWithContext {
child,
on_action,
phantom: PhantomData,
}
}
#[expect(
unnameable_types,
reason = "Implementation detail, public because of trait visibility rules"
)]
#[derive(Debug)]
pub struct OnActionWithContextState<ChildState> {
child_state: ChildState,
environment_slot: u32,
}
impl<State, Action, Context, OnAction, Res, ChildView, ChildAction> ViewMarker
for OnActionWithContext<State, Action, Context, OnAction, Res, ChildView, ChildAction>
{
}
impl<State, Action, Context, OnAction, Res, ChildView, ChildAction> View<State, Action, Context>
for OnActionWithContext<State, Action, Context, OnAction, Res, ChildView, ChildAction>
where
Context: ViewPathTracker,
Res: Resource,
Self: 'static,
ChildView: View<State, ChildAction, Context>,
OnAction: Fn(&mut State, &mut Res, ChildAction) -> Action,
{
type Element = ChildView::Element;
type ViewState = OnActionWithContextState<ChildView::ViewState>;
fn build(&self, ctx: &mut Context, app_state: &mut State) -> (Self::Element, Self::ViewState) {
let (element, child_state) = self.child.build(ctx, app_state);
let env = ctx.environment();
let pos = env.get_slot_for_type::<Res>();
let Some(pos) = pos else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been provided. Did you forget to wrap this view with `xilem_core::environment::provides`?",
core::any::type_name::<Context>()
);
};
(
element,
OnActionWithContextState {
child_state,
environment_slot: pos,
},
)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
ctx: &mut Context,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) {
self.child.rebuild(
&prev.child,
&mut view_state.child_state,
ctx,
element,
app_state,
);
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
ctx: &mut Context,
element: Mut<'_, Self::Element>,
) {
self.child
.teardown(&mut view_state.child_state, ctx, element);
}
fn message(
&self,
view_state: &mut Self::ViewState,
message: &mut MessageContext,
element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
let prev_res = self
.child
.message(&mut view_state.child_state, message, element, app_state);
let env = &mut message.environment;
let slot = &mut env.slots[usize::try_from(view_state.environment_slot).unwrap()];
let Some(value) = slot.item.as_mut() else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been `Provided`.",
core::any::type_name::<Res>()
);
};
let resource = value
.value
.downcast_mut::<Res>()
.expect("Environment's slots should have the correct types.");
prev_res.map(|child_action| (self.on_action)(app_state, resource, child_action))
}
}