cranpose-core 0.1.127

Core runtime for a Jetpack Compose inspired UI framework in Rust
Documentation
use std::{
    cell::Cell,
    rc::Rc,
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
    },
};

use crate::{Composition, MemoryApplier, frame_clock::FrameClock};

#[test]
fn a_frame_callback_fires_once_for_the_frame_it_registered_for() {
    let composition = Composition::new(MemoryApplier::new());
    let clock = FrameClock::new(composition.runtime_handle());
    let millis = Arc::new(AtomicUsize::new(0));
    let nanos = Arc::new(AtomicUsize::new(0));

    let millis_sink = Arc::clone(&millis);
    let _millis = clock.with_frame_millis(move |_| {
        millis_sink.fetch_add(1, Ordering::SeqCst);
    });
    let nanos_sink = Arc::clone(&nanos);
    let _nanos = clock.with_frame_nanos(move |_| {
        nanos_sink.fetch_add(1, Ordering::SeqCst);
    });

    clock.runtime_handle().drain_frame_callbacks(16_000_000);
    assert_eq!(millis.load(Ordering::SeqCst), 1);
    assert_eq!(nanos.load(Ordering::SeqCst), 1);

    clock.runtime_handle().drain_frame_callbacks(32_000_000);
    assert_eq!(millis.load(Ordering::SeqCst), 1);
    assert_eq!(nanos.load(Ordering::SeqCst), 1);
}

#[test]
fn a_perpetual_frame_keeps_asking_after_every_dispatch() {
    let composition = Composition::new(MemoryApplier::new());
    let clock = FrameClock::new(composition.runtime_handle());
    let seen = Rc::new(Cell::new(0usize));

    for frame in 1..=2u64 {
        let mut next = Box::pin(clock.next_perpetual_frame());
        let waker = std::task::Waker::noop().clone();
        let mut context = std::task::Context::from_waker(&waker);
        assert!(
            std::future::Future::poll(next.as_mut(), &mut context).is_pending(),
            "a frame was answered before it was dispatched"
        );
        clock
            .runtime_handle()
            .drain_frame_callbacks(frame * 16_000_000);
        if std::future::Future::poll(next.as_mut(), &mut context).is_ready() {
            seen.set(seen.get() + 1);
        }
    }
    assert_eq!(seen.get(), 2, "the perpetual frame stopped answering");
}

#[test]
fn a_snapshot_id_range_adds_every_id_in_it_and_nothing_outside() {
    use crate::snapshot_id_set::SnapshotIdSet;

    let set = SnapshotIdSet::EMPTY.add_range(3, 7);
    assert_eq!(set.to_list(), vec![3, 4, 5, 6], "the range is half-open");

    assert_eq!(set.add_range(5, 5).to_list(), set.to_list());
    assert_eq!(set.add_range(9, 2).to_list(), set.to_list());

    let widened = set.add_range(5, 9);
    assert_eq!(widened.to_list(), vec![3, 4, 5, 6, 7, 8]);
}

#[test]
fn a_callback_holder_forwards_to_whichever_closure_was_last_stored() {
    use std::{cell::Cell, rc::Rc};

    use crate::callbacks::CallbackHolder;

    let holder = CallbackHolder::new();
    let calls = Rc::new(Cell::new(0u32));

    let forward = holder.clone_rc();
    forward();
    assert_eq!(calls.get(), 0);

    let first = Rc::clone(&calls);
    holder.update_boxed(Box::new(move || first.set(first.get() + 1)));
    forward();
    assert_eq!(calls.get(), 1);

    let second = Rc::clone(&calls);
    holder.update_boxed(Box::new(move || second.set(second.get() + 10)));
    forward();
    assert_eq!(calls.get(), 11);
}

#[test]
fn a_ui_task_label_is_taken_by_the_next_task_and_not_the_one_after() {
    use std::sync::{
        Arc,
        atomic::{AtomicU32, Ordering},
    };

    use crate::runtime::label_next_ui_task;

    let composition = Composition::new(MemoryApplier::new());
    let runtime = composition.runtime_handle();
    let order = Arc::new(AtomicU32::new(0));

    label_next_ui_task("labelled");
    let first = Arc::clone(&order);
    runtime.post_ui(move || {
        first.fetch_add(1, Ordering::SeqCst);
    });
    let second = Arc::clone(&order);
    runtime.post_ui(move || {
        second.fetch_add(10, Ordering::SeqCst);
    });

    runtime.drain_ui();
    assert_eq!(
        order.load(Ordering::SeqCst),
        11,
        "a labelled task changed what ran"
    );
}

#[test]
fn a_subcompose_state_takes_a_policy_and_pool_limits_without_disturbing_its_slots() {
    use crate::subcompose::{ContentTypeReusePolicy, SubcomposeState};

    let mut state = SubcomposeState::new(Box::new(ContentTypeReusePolicy::default()));
    assert_eq!(
        state.was_last_slot_reused(),
        None,
        "a state that has composed nothing cannot have reused anything"
    );
    assert_eq!(state.active_slots_count(), 0);

    state.set_policy(Box::new(ContentTypeReusePolicy::default()));
    state.set_reusable_pool_limits(2, 3);
    assert_eq!(
        state.active_slots_count(),
        0,
        "changing the policy disturbed the slots it was only meant to govern"
    );
    assert_eq!(state.was_last_slot_reused(), None);
}