euv-engine 0.28.21

A high-performance 2D game engine built on the euv framework, featuring ECS, fixed-timestep game loop, canvas rendering, physics, collision detection, sprite animation, and audio.
Documentation
use super::*;

#[test]
fn the_time_source_reports_zero_off_wasm() {
    assert_eq!(
        SchedulerState::current_time(),
        0.0,
        "performance.now() does not exist off wasm, and the guard must report \
         0.0 rather than panicking or returning garbage"
    );
}

#[test]
fn the_time_source_is_monotonic_within_a_run() {
    let first: f64 = SchedulerState::current_time();
    let second: f64 = SchedulerState::current_time();
    assert!(second >= first, "time must never go backwards");
}

#[test]
fn the_three_body_types_are_mutually_distinct() {
    let all: [BodyType; 3] = [BodyType::Static, BodyType::Dynamic, BodyType::Kinematic];
    for i in 0..all.len() {
        for j in (i + 1)..all.len() {
            assert_ne!(all[i], all[j]);
        }
    }
}

#[test]
fn a_body_defaults_to_static_so_it_never_moves_unless_asked() {
    assert_eq!(
        BodyType::default(),
        BodyType::Static,
        "the inert body must be the default, not the simulated one"
    );
}

#[test]
fn a_ray_clones_itself_with_a_new_depth() {
    let ray: Ray = Ray::new(Vector3D::new(1.0, 2.0, 3.0), Vector3D::new(0.0, 0.0, 1.0));
    let deeper: Ray = ray.with_depth(3);
    let rendered: String = format!("{deeper:?}");
    assert!(
        rendered.contains("depth: 3"),
        "with_depth must replace the recursion budget, got: {rendered}"
    );
    assert!(
        rendered.contains("origin: Vector3D { x: 1.0, y: 2.0, z: 3.0 }"),
        "the clone must keep the original origin, got: {rendered}"
    );
    assert!(
        rendered.contains("direction: Vector3D { x: 0.0, y: 0.0, z: 1.0 }"),
        "the clone must keep the original direction, got: {rendered}"
    );
}

#[test]
fn deepening_a_ray_leaves_the_original_untouched() {
    let ray: Ray = Ray::new(Vector3D::zero(), Vector3D::new(1.0, 0.0, 0.0));
    let _: Ray = ray.with_depth(5);
    let rendered: String = format!("{ray:?}");
    assert!(
        !rendered.contains("depth: 5"),
        "with_depth returns a clone, it must not mutate in place, got: {rendered}"
    );
}

#[test]
fn a_ray_starts_at_a_known_recursion_depth() {
    let ray: Ray = Ray::new(Vector3D::zero(), Vector3D::new(1.0, 0.0, 0.0));
    let rendered: String = format!("{ray:?}");
    assert!(
        rendered.contains("depth: 0"),
        "a fresh ray should report the initial budget so a zero-depth bug is visible"
    );
}

#[test]
fn a_pool_exposes_its_free_list_for_bulk_seeding() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(Vec::new());
    let free: &mut Vec<u32> = pool.get_mut_free();
    free.push(1);
    free.push(2);
    assert_eq!(pool.get_free().len(), 2);
    assert_eq!(pool.get_free(), &vec![1_u32, 2]);
}

#[test]
fn a_pool_reports_how_many_values_it_can_hand_out() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(Vec::new());
    assert_eq!(pool.available(), 0);
    pool.get_mut_free().push(7);
    assert_eq!(
        pool.available(),
        1,
        "available is the free-list length, so seeding must be visible immediately"
    );
}

#[test]
fn a_seeded_pool_hands_out_exactly_what_was_put_back() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(Vec::new());
    pool.get_mut_free().extend([1, 2, 3]);
    assert_eq!(
        pool.acquire(),
        Some(3),
        "the free list is most-recently-released first"
    );
    assert_eq!(pool.available(), 2);
    assert_eq!(pool.acquire(), Some(2));
    assert_eq!(pool.acquire(), Some(1));
    assert_eq!(pool.available(), 0);
}

#[test]
fn a_pool_with_an_exhausted_free_list_reports_none_rather_than_inventing_a_value() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1]);
    assert_eq!(pool.acquire(), Some(1));
    assert_eq!(
        pool.acquire(),
        None,
        "an empty free list must read as None so the caller can decide whether to grow"
    );
}

#[test]
fn an_exhausted_pool_can_still_build_a_value_on_demand() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1]);
    let _: Option<u32> = pool.acquire();
    let made: u32 = pool.acquire_with(|| 99);
    assert_eq!(
        made, 99,
        "acquire_with falls back to the factory once the pool runs dry"
    );
}

#[test]
fn releasing_a_value_puts_it_back_on_the_free_list() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![42]);
    let value: u32 = pool.acquire().expect("the seeded pool still has a value");
    assert_eq!(value, 42);
    assert_eq!(
        pool.available(),
        0,
        "checking a value out removes it from the free list"
    );
    pool.release(value);
    assert_eq!(
        pool.available(),
        1,
        "a released value is immediately reusable"
    );
}

#[test]
fn an_active_count_can_be_overridden_for_bulk_operations() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2, 3]);
    assert_eq!(pool.len(), 3);
    pool.set_active(0);
    assert_eq!(
        pool.get_active(),
        0,
        "the caller owns the count, so set_active must take effect verbatim"
    );
}

#[test]
fn a_uv_rect_holds_normalised_texture_coordinates() {
    let rect: UvRect = UvRect::new(0.0, 0.0, 0.5, 0.5);
    let rendered: String = format!("{rect:?}");
    assert!(
        rendered.contains("u0: 0.0") && rendered.contains("v1: 0.5"),
        "a uv rect must round-trip its four normalised edges, got: {rendered}"
    );
    let other: UvRect = UvRect::new(0.5, 0.5, 1.0, 1.0);
    assert_ne!(rect, other, "two different sub-rects must stay distinct");
}

#[test]
fn a_uv_rect_is_copy_so_it_can_be_returned_by_value() {
    let rect: UvRect = UvRect::new(0.1, 0.2, 0.3, 0.4);
    let copied: UvRect = rect;
    assert_eq!(copied, rect);
}

#[test]
fn nine_slice_insets_are_four_independent_edge_widths() {
    let insets: NineSliceInsets = NineSliceInsets::new(1.0, 2.0, 3.0, 4.0);
    let rendered: String = format!("{insets:?}");
    for edge in ["left", "top", "right", "bottom"] {
        assert!(
            rendered.contains(edge),
            "a nine-slice needs all four edge insets, {edge} missing from: {rendered}"
        );
    }
    assert_ne!(insets, NineSliceInsets::new(4.0, 3.0, 2.0, 1.0));
}

#[test]
fn a_tween_remembers_the_delay_it_was_given() {
    let mut tween: Tween<f64> = Tween::create(0.0, 1.0, 1.0);
    tween.set_delay(0.25);
    assert!(
        format!("{tween:?}").contains("delay: 0.25"),
        "set_delay must store the value verbatim, got: {tween:?}"
    );
}

#[test]
fn a_tween_delay_of_zero_is_still_a_delay() {
    let mut tween: Tween<f64> = Tween::create(0.0, 1.0, 1.0);
    tween.set_delay(0.5);
    tween.set_delay(0.0);
    assert!(
        format!("{tween:?}").contains("delay: 0.0"),
        "clearing the delay must be possible, got: {tween:?}"
    );
}

#[test]
fn a_component_handle_is_a_shared_cell_around_a_trait_object() {
    struct Probe;
    impl Lifecycle for Probe {
        fn on_update(&mut self, _: f64) {}
    }
    impl Component for Probe {
        fn on_start(&mut self) {}
        fn on_render(&self, _: &mut DrawList, _: &Transform2D) {}
        fn on_destroy(&mut self) {}
        fn name(&self) -> &str {
            "probe"
        }
    }
    let handle: ComponentRc = Rc::new(EngineCell::new(Probe));
    let cloned: ComponentRc = Rc::clone(&handle);
    assert!(
        Rc::ptr_eq(&handle, &cloned),
        "the handle is an std::rc::Rc alias"
    );
}

#[test]
fn an_entity_handle_is_a_shared_cell_around_an_entity() {
    let handle: EntityRc = Rc::new(EngineCell::new(Entity::create("probe")));
    let cloned: EntityRc = Rc::clone(&handle);
    assert!(
        Rc::ptr_eq(&handle, &cloned),
        "the handle is an std::rc::Rc alias"
    );
    assert_eq!(
        cloned.get().get_name(),
        "probe",
        "the handle must expose the very entity it was built from"
    );
}

#[test]
fn one_entity_event_can_have_several_handlers() {
    let seen: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let first: Rc<Cell<u32>> = seen.clone();
    let second: Rc<Cell<u32>> = seen.clone();
    let mut handlers: EventHandlers = EventHandlers::new();
    let entry: &mut Vec<EventHandler> = handlers.entry(String::from("hit")).or_default();
    entry.push(Rc::new(move |_: &EntityEvent| {
        first.set(first.get() + 1);
    }));
    entry.push(Rc::new(move |_: &EntityEvent| {
        second.set(second.get() + 10);
    }));
    assert_eq!(handlers.len(), 1, "one event name, one bucket");
    let bucket: &Vec<EventHandler> = handlers.get("hit").expect("the bucket was just created");
    for handler in bucket {
        handler(&EntityEvent::Collision {
            other_id: 1,
            normal: Vector2D::new(0.0, 1.0),
            depth: 0.5,
        });
    }
    assert_eq!(seen.get(), 11, "both handlers must run, in bucket order");
}

#[test]
fn a_handler_that_was_never_registered_has_no_bucket() {
    let handlers: EventHandlers = EventHandlers::new();
    assert!(handlers.is_empty());
    assert!(
        !handlers.contains_key("never-emitted"),
        "an unheard event must not have a bucket at all"
    );
}