cranpose-core 0.9.35

Composition runtime and reactive state for Cranpose
Documentation
use std::{
    any::Any,
    cell::{Cell, RefCell},
    ops::Deref,
    rc::Rc,
};

use crate::{
    CommandQueue, ComposerCore, HotOrigin, Key,
    collections::{FixedHashMap, FixedHashSet, fixed_hash_map, fixed_hash_set},
    runtime::{DeferredStateRelease, RuntimeHandle, RuntimeId, StateHandleLease},
    snapshot_id_set::SnapshotId,
    snapshot_pinning::PinningTable,
    snapshot_v2::{
        AnySnapshot, ApplyObserver, GlobalSnapshot, ObserverId, SnapshotRuntime, StateObjectId,
    },
    snapshot_weak_set::SnapshotWeakSet,
    state::{ObjectId, RecordValue},
};

pub(crate) const CALL_SITE_KEY_SLOTS: usize = 1024;
pub(crate) const FILE_HASH_SLOTS: usize = 64;

pub(crate) struct ThreadCells {
    pub(crate) call_site_keys: [Cell<(usize, Key)>; CALL_SITE_KEY_SLOTS],
    pub(crate) file_hashes: [Cell<(usize, usize, u64)>; FILE_HASH_SLOTS],
    pub(crate) hot_origin: Cell<HotOrigin>,
    pub(crate) in_event_handler: Cell<bool>,
    pub(crate) in_applied_snapshot: Cell<bool>,
    pub(crate) scope_activity_epoch: Cell<u64>,
    pub(crate) thread_exiting: Cell<bool>,
    #[cfg(any(test, debug_assertions))]
    pub(crate) location_key_collisions: Cell<usize>,
    #[cfg(all(test, debug_assertions))]
    pub(crate) scope_tracking_override: Cell<Option<bool>>,
    #[cfg(all(feature = "inspection", debug_assertions))]
    pub(crate) track_recompositions: Cell<bool>,
    #[cfg(all(feature = "inspection", debug_assertions))]
    pub(crate) next_recomposition_instance: Cell<u64>,
}

const _: () = assert!(!std::mem::needs_drop::<ThreadCells>());

impl ThreadCells {
    const fn new() -> Self {
        Self {
            call_site_keys: [const { Cell::new((0, 0)) }; CALL_SITE_KEY_SLOTS],
            file_hashes: [const { Cell::new((0, 0, 0)) }; FILE_HASH_SLOTS],
            hot_origin: Cell::new(HotOrigin::NONE),
            in_event_handler: Cell::new(false),
            in_applied_snapshot: Cell::new(false),
            scope_activity_epoch: Cell::new(1),
            thread_exiting: Cell::new(false),
            #[cfg(any(test, debug_assertions))]
            location_key_collisions: Cell::new(0),
            #[cfg(all(test, debug_assertions))]
            scope_tracking_override: Cell::new(None),
            #[cfg(all(feature = "inspection", debug_assertions))]
            track_recompositions: Cell::new(false),
            #[cfg(all(feature = "inspection", debug_assertions))]
            next_recomposition_instance: Cell::new(0),
        }
    }
}

pub(crate) struct SnapshotThreadState {
    pub(crate) runtime: RefCell<Option<SnapshotRuntime>>,
    pub(crate) runtime_lock_depth: Cell<usize>,
    pub(crate) global: RefCell<Option<Rc<GlobalSnapshot>>>,
    pub(crate) current: RefCell<Option<AnySnapshot>>,
    pub(crate) apply_observers: RefCell<FixedHashMap<ObserverId, ApplyObserver>>,
    pub(crate) last_writes: RefCell<FixedHashMap<StateObjectId, SnapshotId>>,
    pub(crate) extra_state_objects: RefCell<SnapshotWeakSet>,
    pub(crate) last_unused_record_cleanup: Cell<SnapshotId>,
    pub(crate) pinning: RefCell<PinningTable>,
    pub(crate) active_updates: RefCell<FixedHashSet<ObjectId>>,
    pub(crate) pending_writes: RefCell<FixedHashSet<ObjectId>>,
    pub(crate) retired_record_values: RefCell<Vec<Box<dyn RecordValue>>>,
    pub(crate) retire_depth: Cell<usize>,
}

impl Drop for SnapshotThreadState {
    fn drop(&mut self) {
        crate::runtime::forget_registered_runtimes();
    }
}

impl SnapshotThreadState {
    const fn new() -> Self {
        Self {
            runtime: RefCell::new(None),
            runtime_lock_depth: Cell::new(0),
            global: RefCell::new(None),
            current: RefCell::new(None),
            apply_observers: RefCell::new(fixed_hash_map()),
            last_writes: RefCell::new(fixed_hash_map()),
            extra_state_objects: RefCell::new(SnapshotWeakSet::new()),
            last_unused_record_cleanup: Cell::new(0),
            pinning: RefCell::new(PinningTable::new()),
            active_updates: RefCell::new(fixed_hash_set()),
            pending_writes: RefCell::new(fixed_hash_set()),
            retired_record_values: RefCell::new(Vec::new()),
            retire_depth: Cell::new(0),
        }
    }

    fn release(&self) -> Self {
        let released = Self::new();
        let Self {
            runtime,
            runtime_lock_depth: _,
            global,
            current,
            apply_observers,
            last_writes,
            extra_state_objects,
            last_unused_record_cleanup,
            pinning,
            active_updates,
            pending_writes,
            retired_record_values,
            retire_depth: _,
        } = self;
        move_into(runtime, &released.runtime);
        move_into(global, &released.global);
        move_into(current, &released.current);
        move_into(apply_observers, &released.apply_observers);
        move_into(last_writes, &released.last_writes);
        move_into(extra_state_objects, &released.extra_state_objects);
        move_into(pinning, &released.pinning);
        move_into(active_updates, &released.active_updates);
        move_into(pending_writes, &released.pending_writes);
        move_into(retired_record_values, &released.retired_record_values);
        last_unused_record_cleanup.set(0);
        released
    }
}

pub(crate) struct RuntimeThreadState {
    pub(crate) next_runtime_id: Cell<u32>,
    pub(crate) active_runtimes: RefCell<Vec<RuntimeHandle>>,
    pub(crate) last_runtime: RefCell<Option<RuntimeHandle>>,
    pub(crate) registered_runtimes: RefCell<FixedHashMap<RuntimeId, RuntimeHandle>>,
    pub(crate) next_task_label: RefCell<Option<String>>,
    pub(crate) state_owners: RefCell<Vec<Vec<Rc<StateHandleLease>>>>,
    pub(crate) state_teardown_depth: Cell<usize>,
    pub(crate) deferred_state_releases: RefCell<Vec<DeferredStateRelease>>,
    pub(crate) current_composer: RefCell<Option<Rc<ComposerCore>>>,
    pub(crate) spare_commands: RefCell<Vec<CommandQueue>>,
    pub(crate) bridges: RefCell<FixedHashMap<u64, Rc<dyn Any>>>,
    #[cfg(any(test, debug_assertions))]
    pub(crate) location_keys: RefCell<FixedHashMap<Key, crate::LocationKeyDebugInfo>>,
    #[cfg(debug_assertions)]
    pub(crate) scope_labels: RefCell<FixedHashMap<usize, &'static str>>,
    #[cfg(debug_assertions)]
    pub(crate) scope_invalidation_sources:
        RefCell<FixedHashMap<usize, crate::collections::map::HashSet<String>>>,
    #[cfg(all(feature = "inspection", debug_assertions))]
    pub(crate) recomposition_counters: RefCell<Vec<std::rc::Weak<crate::RecomposeScopeInner>>>,
    #[cfg(feature = "inspection")]
    pub(crate) source_stack: RefCell<Vec<crate::source_trace::SourceLocation>>,
}

impl RuntimeThreadState {
    const fn new() -> Self {
        Self {
            next_runtime_id: Cell::new(1),
            active_runtimes: RefCell::new(Vec::new()),
            last_runtime: RefCell::new(None),
            registered_runtimes: RefCell::new(fixed_hash_map()),
            next_task_label: RefCell::new(None),
            state_owners: RefCell::new(Vec::new()),
            state_teardown_depth: Cell::new(0),
            deferred_state_releases: RefCell::new(Vec::new()),
            current_composer: RefCell::new(None),
            spare_commands: RefCell::new(Vec::new()),
            bridges: RefCell::new(fixed_hash_map()),
            #[cfg(any(test, debug_assertions))]
            location_keys: RefCell::new(fixed_hash_map()),
            #[cfg(debug_assertions)]
            scope_labels: RefCell::new(fixed_hash_map()),
            #[cfg(debug_assertions)]
            scope_invalidation_sources: RefCell::new(fixed_hash_map()),
            #[cfg(all(feature = "inspection", debug_assertions))]
            recomposition_counters: RefCell::new(Vec::new()),
            #[cfg(feature = "inspection")]
            source_stack: RefCell::new(Vec::new()),
        }
    }

    fn has_live_runtime(&self) -> bool {
        self.state_teardown_depth.get() > 0 || !self.registered_runtimes.borrow().is_empty()
    }

    fn release(&self) -> Self {
        let released = Self::new();
        let Self {
            next_runtime_id: _,
            active_runtimes,
            last_runtime,
            registered_runtimes,
            next_task_label,
            state_owners,
            state_teardown_depth: _,
            deferred_state_releases,
            current_composer,
            spare_commands,
            bridges,
            #[cfg(any(test, debug_assertions))]
            location_keys,
            #[cfg(debug_assertions)]
            scope_labels,
            #[cfg(debug_assertions)]
            scope_invalidation_sources,
            #[cfg(all(feature = "inspection", debug_assertions))]
            recomposition_counters,
            #[cfg(feature = "inspection")]
            source_stack,
        } = self;
        move_into(active_runtimes, &released.active_runtimes);
        move_into(last_runtime, &released.last_runtime);
        move_into(registered_runtimes, &released.registered_runtimes);
        move_into(next_task_label, &released.next_task_label);
        move_into(state_owners, &released.state_owners);
        move_into(deferred_state_releases, &released.deferred_state_releases);
        move_into(current_composer, &released.current_composer);
        move_into(spare_commands, &released.spare_commands);
        move_into(bridges, &released.bridges);
        #[cfg(any(test, debug_assertions))]
        move_into(location_keys, &released.location_keys);
        #[cfg(debug_assertions)]
        move_into(scope_labels, &released.scope_labels);
        #[cfg(debug_assertions)]
        move_into(
            scope_invalidation_sources,
            &released.scope_invalidation_sources,
        );
        #[cfg(all(feature = "inspection", debug_assertions))]
        move_into(recomposition_counters, &released.recomposition_counters);
        #[cfg(feature = "inspection")]
        move_into(source_stack, &released.source_stack);
        released
    }
}

fn move_into<T>(from: &RefCell<T>, into: &RefCell<T>) {
    if let (Ok(mut from), Ok(mut into)) = (from.try_borrow_mut(), into.try_borrow_mut()) {
        std::mem::swap(&mut *from, &mut *into);
    }
}

#[cfg(not(target_os = "android"))]
pub(crate) struct Kept<T>(std::mem::ManuallyDrop<T>);

#[cfg(target_os = "android")]
pub(crate) struct Kept<T>(T);

impl<T> Kept<T> {
    #[cfg(not(target_os = "android"))]
    const fn new(value: T) -> Self {
        Self(std::mem::ManuallyDrop::new(value))
    }

    #[cfg(target_os = "android")]
    const fn new(value: T) -> Self {
        Self(value)
    }
}

impl<T> Deref for Kept<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

#[cfg(not(target_os = "android"))]
const _: () = assert!(
    !std::mem::needs_drop::<Kept<SnapshotThreadState>>()
        && !std::mem::needs_drop::<Kept<RuntimeThreadState>>()
);

#[cfg(not(target_os = "android"))]
struct ThreadExit;

#[cfg(not(target_os = "android"))]
impl Drop for ThreadExit {
    fn drop(&mut self) {
        CELLS.with(|cells| cells.thread_exiting.set(true));
        release_when_idle();
    }
}

thread_local! {
    static CELLS: ThreadCells = const { ThreadCells::new() };
    static SNAPSHOTS: Kept<SnapshotThreadState> = const { Kept::new(SnapshotThreadState::new()) };
    static RUNTIMES: Kept<RuntimeThreadState> = const { Kept::new(RuntimeThreadState::new()) };
}

#[cfg(not(target_os = "android"))]
thread_local! {
    static THREAD_EXIT: ThreadExit = const { ThreadExit };
}

pub(crate) fn watch_thread_exit() {
    #[cfg(not(target_os = "android"))]
    let _ = THREAD_EXIT.try_with(|_| ());
}

pub(crate) fn release_when_idle() {
    if !CELLS.with(|cells| cells.thread_exiting.get()) {
        return;
    }
    let Some(runtimes) = RUNTIMES
        .try_with(|state| (!state.has_live_runtime()).then(|| state.release()))
        .ok()
        .flatten()
    else {
        return;
    };
    let snapshots = SNAPSHOTS.try_with(|state| state.release()).ok();
    drop(runtimes);
    drop(snapshots);
}

pub(crate) fn cells<R>(f: impl FnOnce(&ThreadCells) -> R) -> R {
    CELLS.with(f)
}

pub(crate) fn snapshots<R>(f: impl FnOnce(&SnapshotThreadState) -> R) -> R {
    SNAPSHOTS.with(|state| f(state))
}

pub(crate) fn try_snapshots<R>(f: impl FnOnce(&SnapshotThreadState) -> R) -> Option<R> {
    SNAPSHOTS.try_with(|state| f(state)).ok()
}

pub(crate) fn runtimes<R>(f: impl FnOnce(&RuntimeThreadState) -> R) -> R {
    RUNTIMES.with(|state| f(state))
}

pub(crate) fn try_runtimes<R>(f: impl FnOnce(&RuntimeThreadState) -> R) -> Option<R> {
    RUNTIMES.try_with(|state| f(state)).ok()
}