use std::{cell::Cell, hash::Hash, rc::Rc};
use super::{GroupScopeEntry, PendingMovable, SlotHostPassGuard};
use crate::{
BodyRun, Composer, Key, RecomposeOptions, RecomposeScope, RetentionMode, SlotsHost,
explicit_group_key_seed, slot::GroupStart,
};
#[doc(hidden)]
pub struct ComposableGroup<'a> {
composer: &'a Composer,
host: Rc<SlotsHost>,
scope: RecomposeScope,
pass: Option<SlotHostPassGuard>,
runs_body: bool,
completed: bool,
body: Cell<Body>,
}
#[doc(hidden)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ParameterChange {
Unchanged,
Changed,
}
impl From<bool> for ParameterChange {
fn from(changed: bool) -> Self {
if changed {
Self::Changed
} else {
Self::Unchanged
}
}
}
#[derive(Clone, Copy)]
enum Body {
Runs,
Skipped,
Paused,
}
impl ComposableGroup<'_> {
pub fn scope(&self) -> &RecomposeScope {
&self.scope
}
#[inline]
pub fn set_recompose_fn(&self, body: fn(&Composer)) {
self.scope.set_recompose_fn(body);
}
pub fn skip(&self) {
self.body.set(Body::Skipped);
}
pub fn should_execute(&self, change: ParameterChange, recompose: fn(&Composer)) -> bool {
self.scope.set_recompose_fn(recompose);
match self.body.get() {
Body::Paused => false,
_ if change == ParameterChange::Unchanged && self.scope.has_composed_once() => {
self.body.set(Body::Skipped);
false
}
_ => true,
}
}
#[cold]
#[inline(never)]
fn close_paused(&self) {
self.scope.end_reads(BodyRun::Skipped);
self.composer
.close_group_in_active_pass(&self.host, &self.scope);
self.composer.hold_paused(&self.scope);
}
#[inline]
pub fn close(mut self) {
self.completed = true;
}
}
impl Drop for ComposableGroup<'_> {
fn drop(&mut self) {
match self.body.get() {
Body::Runs => {
if self.completed {
self.scope.mark_composed_once();
}
self.scope
.end_reads(BodyRun::from(self.completed & self.runs_body));
self.composer
.close_group_in_active_pass(&self.host, &self.scope);
}
Body::Skipped => {
if self.completed {
self.scope.mark_composed_once();
}
self.scope.end_reads(BodyRun::Skipped);
self.composer
.close_skipped_group_in_active_pass(&self.host, &self.scope);
}
Body::Paused => self.close_paused(),
}
if let Some(mut pass) = self.pass.take() {
if self.completed
&& let Err(err) = self.composer.finish_slot_host_pass(&pass.host)
{
log::error!("slot host pass finalization failed: {err}");
}
pass.close();
}
}
}
#[expect(clippy::multiple_inherent_impl, reason = "one file per concern")]
impl Composer {
fn scope_for_started_group(
&self,
host: &SlotsHost,
group: crate::slot::ActiveGroupId,
scope: Option<RecomposeScope>,
) -> RecomposeScope {
if let Some(scope) = scope {
return scope;
}
let scope = RecomposeScope::new(self.runtime_handle());
host.with_write_session(|slots| slots.set_group_scope(group, scope.clone()));
if self.core.shared_state.pausing.is_holding() {
self.hold_scope(&scope);
}
scope
}
#[inline(never)]
fn open_group_in_active_pass(
&self,
host: &Rc<SlotsHost>,
key: crate::slot::GroupKeySeed,
) -> (RecomposeScope, bool) {
let options = self.core.pending_scope_options.take().unwrap_or_default();
let parent_node = self.current_parent_hint();
let (
GroupStart {
group,
anchor,
scope,
kind,
},
placeholder_for,
) = self.start_group(host, key, parent_node);
let scope_ref = self.scope_for_started_group(host, group, scope);
if let Some(movable_key) = placeholder_for {
self.core.shared_state.record_pending_movable(
host,
PendingMovable {
key: movable_key,
placeholder: anchor,
parent_scope: self
.core
.scope_stack
.borrow()
.last()
.map(RecomposeScope::downgrade),
},
);
}
self.enter_group_scope(
&scope_ref,
GroupScopeEntry {
options,
start_kind: kind,
host,
group,
parent_hint: parent_node,
},
);
(scope_ref, placeholder_for.is_none())
}
#[inline(never)]
fn close_group_in_active_pass(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
let result = host.with_write_session(|slots| {
let result = slots.finish_group_body_changes();
slots.end_group();
result
});
self.close_finished_group(host, scope, result);
self.settle_closed_group(scope);
}
#[inline(never)]
fn close_skipped_group_in_active_pass(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
host.with_write_session(|slots| slots.skip_and_end_group(|id| self.attach_to_parent(id)));
self.pop_closed_scope(scope);
self.settle_closed_group(scope);
}
fn settle_closed_group(&self, scope: &RecomposeScope) {
scope.mark_recomposed();
if let Err(err) = self.flush_pending_commands_if_large() {
log::error!("mid-composition command flush failed: {err}");
}
}
#[inline(never)]
fn open_group(&self, key: crate::slot::GroupKeySeed) -> ComposableGroup<'_> {
let host = self.active_slots_host();
let (host, pass) = if host.has_active_pass() {
(host, None)
} else {
let pass = self.begin_slot_host_pass(&host, crate::slot::SlotPassMode::Compose);
(self.active_slots_host(), Some(pass))
};
let (scope, runs_body) = self.open_group_in_active_pass(&host, key);
scope.begin_reads();
ComposableGroup {
composer: self,
host,
scope,
pass,
runs_body,
completed: false,
body: Cell::new(Body::Runs),
}
}
#[doc(hidden)]
pub fn __open_composable_group(&self, key: Key) -> ComposableGroup<'_> {
self.open_group(crate::slot::GroupKeySeed::unkeyed(key))
}
#[doc(hidden)]
pub fn __open_pausable_group(&self, key: Key) -> ComposableGroup<'_> {
let group = self.open_group(crate::slot::GroupKeySeed::unkeyed(key));
if self.core.shared_state.pausing.is_holding() && self.pauses_first_body(&group.scope) {
group.body.set(Body::Paused);
}
group
}
pub(crate) fn with_group_seed<R>(
&self,
key: crate::slot::GroupKeySeed,
f: impl FnOnce(&Self) -> R,
) -> R {
let group = self.open_group(key);
debug_assert!(group.runs_body, "only movable content waits for a body");
let result = f(self);
group.close();
result
}
pub(crate) fn with_movable_group(&self, id: Key, f: impl FnOnce(&Self)) {
let group = self.open_group(crate::slot::GroupKeySeed::movable(id));
if group.runs_body {
f(self);
}
group.close();
}
pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Self) -> R) -> R {
self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
}
pub fn cranpose_with_reuse<R>(
&self,
key: Key,
mut options: RecomposeOptions,
f: impl FnOnce(&Self) -> R,
) -> R {
options.retention = RetentionMode::RetainWhenInactive;
self.core.pending_scope_options.set(Some(options));
self.with_group(key, f)
}
#[track_caller]
pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Self) -> R) -> R {
let seed = explicit_group_key_seed(key, std::panic::Location::caller());
self.with_group_seed(seed, f)
}
}