euv-engine 0.28.2

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 seeded_pool_reports_three_available_zero_active_and_len_three() {
    let pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2, 3]);
    assert_eq!(pool.available(), 3, "seeded pool must hold 3 free values");
    assert_eq!(pool.get_active(), 0, "a fresh pool has no checkouts");
    assert_eq!(pool.len(), 3, "len counts active plus free");
    assert!(!pool.is_empty(), "a seeded pool is not empty");
}

#[test]
fn acquire_returns_values_in_lifo_release_order() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2, 3]);
    let first: u32 = pool.acquire().unwrap();
    let second: u32 = pool.acquire().unwrap();
    let third: u32 = pool.acquire().unwrap();
    assert_eq!((first, second, third), (3, 2, 1), "checkout is LIFO");
    assert_eq!(pool.available(), 0, "free list drains after 3 acquires");
    assert_eq!(pool.get_active(), 3, "3 values are checked out");
    assert_eq!(pool.len(), 3, "len is stable across checkouts");
}

#[test]
fn acquire_on_empty_free_list_reports_none() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1]);
    let taken: Option<u32> = pool.acquire();
    assert_eq!(taken, Some(1), "the only seeded value is served");
    let miss: Option<u32> = pool.acquire();
    assert_eq!(miss, None, "a 4th acquire must miss");
    assert_eq!(pool.get_active(), 1, "a miss must not count a checkout");
    assert_eq!(pool.len(), 1, "a miss must not grow the pool");
}

#[test]
fn acquire_with_calls_factory_only_when_free_list_is_empty() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![7]);
    let mut builds: u32 = 0;
    let recycled: u32 = pool.acquire_with(|| {
        builds += 1;
        99
    });
    assert_eq!(recycled, 7, "a pooled value beats the factory");
    assert_eq!(builds, 0, "the factory must not run on a hit");
    let built: u32 = pool.acquire_with(|| {
        builds += 1;
        99
    });
    assert_eq!(built, 99, "a miss returns the factory value");
    assert_eq!(builds, 1, "the factory runs exactly once on a miss");
    assert_eq!(pool.len(), 2, "a factory build grows the pool by one");
    assert_eq!(
        pool.get_active(),
        2,
        "a fresh build still counts as a checkout"
    );
}

#[test]
fn release_increases_available_and_leaves_len_unchanged() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2, 3]);
    let taken: u32 = pool.acquire().unwrap();
    let len_before: usize = pool.len();
    assert_eq!(pool.available(), 2, "one value is checked out");
    pool.release(taken);
    assert_eq!(
        pool.available(),
        3,
        "release returns the value to the free list"
    );
    assert_eq!(
        pool.len(),
        len_before,
        "len is total tracked values and is unchanged"
    );
    assert_eq!(pool.get_active(), 0, "release closes the checkout");
}

#[test]
fn acquire_after_release_returns_the_exact_same_value() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![42]);
    let taken: u32 = pool.acquire().unwrap();
    assert_eq!(taken, 42, "seeded value is served");
    pool.release(taken);
    let again: u32 = pool.acquire().unwrap();
    assert_eq!(again, 42, "the very same value comes back");
    assert_eq!(pool.len(), 1, "a round trip never grows the pool");
}

#[test]
fn round_trip_acquire_release_acquire_is_stable() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![5, 6]);
    for _ in 0..100 {
        let taken: u32 = pool.acquire().unwrap();
        pool.release(taken);
    }
    assert_eq!(
        pool.available(),
        2,
        "a balanced round trip restores the free list"
    );
    assert_eq!(
        pool.get_active(),
        0,
        "a balanced round trip leaves no checkouts"
    );
    assert_eq!(pool.len(), 2, "a balanced round trip keeps len constant");
}

#[test]
fn prewarm_makes_exactly_count_values_available() {
    let mut pool: ObjectPool<u32> = ObjectPool::empty();
    let available: usize = pool.prewarm(4, || 0);
    assert_eq!(available, 4, "prewarm of 4 leaves 4 available");
    assert_eq!(pool.available(), 4, "available matches the prewarm result");
    assert_eq!(pool.len(), 4, "prewarm grows the tracked set by 4");
    assert_eq!(
        pool.get_active(),
        0,
        "prewarmed values are free, not checked out"
    );
}

#[test]
fn prewarm_on_a_populated_pool_adds_exactly_count() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2]);
    let available: usize = pool.prewarm(3, || 0);
    assert_eq!(available, 5, "prewarm adds to the existing free list");
    assert_eq!(pool.len(), 5, "the tracked set grows by exactly 3");
}

#[test]
fn prewarm_of_zero_changes_nothing() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1]);
    let available: usize = pool.prewarm(0, || 0);
    assert_eq!(available, 1, "prewarming 0 is a no-op");
    assert_eq!(pool.len(), 1, "prewarming 0 does not grow the pool");
}

#[test]
fn len_is_stable_across_one_thousand_acquire_release_cycles() {
    let mut pool: ObjectPool<u32> = ObjectPool::empty();
    pool.prewarm(4, || 0);
    let high_water: usize = pool.len();
    for _ in 0..1000 {
        let taken: u32 = pool.acquire().unwrap();
        pool.release(taken);
    }
    assert_eq!(
        pool.len(),
        high_water,
        "recycling never reallocates the tracked set"
    );
    assert_eq!(
        pool.get_active(),
        0,
        "1000 balanced cycles leave no checkouts"
    );
    assert_eq!(
        pool.available(),
        4,
        "1000 balanced cycles restore the free list"
    );
}

#[test]
fn clear_discards_free_values_and_resets_the_checkout_count() {
    let mut pool: ObjectPool<u32> = ObjectPool::new(vec![1, 2, 3]);
    let taken: u32 = pool.acquire().unwrap();
    let discarded: usize = pool.clear();
    assert_eq!(
        discarded, 2,
        "clear reports only the free values it dropped"
    );
    assert_eq!(pool.available(), 0, "the free list is empty after clear");
    assert_eq!(pool.get_active(), 0, "clear resets the checkout count");
    assert!(pool.is_empty(), "the pool is empty after clear");
    pool.release(taken);
    assert_eq!(
        pool.available(),
        1,
        "a post-clear release repopulates the free list"
    );
}

#[test]
fn empty_pool_is_empty_and_serves_no_value() {
    let mut pool: ObjectPool<u32> = ObjectPool::empty();
    assert!(pool.is_empty(), "a default pool tracks nothing");
    assert_eq!(pool.len(), 0, "len is 0 on an empty pool");
    assert_eq!(pool.get_active(), 0, "an empty pool has no checkouts");
    let miss: Option<u32> = pool.acquire();
    assert_eq!(miss, None, "an empty pool serves no value");
}

#[test]
fn non_copy_values_survive_a_release_round_trip() {
    let mut pool: ObjectPool<String> = ObjectPool::new(vec![String::from("alpha")]);
    let taken: String = pool.acquire().unwrap();
    assert_eq!(taken, "alpha", "the seeded String is served whole");
    pool.release(taken);
    let again: String = pool.acquire().unwrap();
    assert_eq!(again, "alpha", "the same String value returns, not a copy");
    assert_eq!(pool.len(), 1, "a String round trip never grows the pool");
}

#[test]
fn capacity_and_len_hold_after_peak_then_drain() {
    let mut pool: ObjectPool<u32> = ObjectPool::empty();
    let mut peak: Vec<u32> = Vec::new();
    for value in 0..8 {
        let built: u32 = pool.acquire_with(|| value);
        peak.push(built);
    }
    assert_eq!(pool.len(), 8, "8 concurrent checkouts track 8 values");
    assert_eq!(pool.available(), 0, "none are free at peak");
    for value in peak {
        pool.release(value);
    }
    assert_eq!(
        pool.len(),
        8,
        "draining back to zero keeps the high-water mark"
    );
    assert_eq!(pool.available(), 8, "every value returned to the free list");
    assert_eq!(pool.get_active(), 0, "all checkouts are closed");
}

#[test]
fn pooled_entity_creation_serves_a_new_entity_when_the_pool_is_empty() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let entity: Entity = Entity::create_pooled("first", &mut pool);
    assert_eq!(
        entity.get_name(),
        "first",
        "the pooled entity carries its name"
    );
    assert!(entity.get_active(), "a freshly pooled entity is active");
    assert_eq!(pool.get_active(), 1, "the checkout is tracked");
    assert_eq!(pool.len(), 1, "the pool grew to track the new entity");
}

#[test]
fn pooled_entity_creation_reuses_a_recycled_entity_with_a_fresh_id() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let mut first: Entity = Entity::create_pooled("first", &mut pool);
    let first_id: u64 = first.get_id();
    Entity::release_to_pool(&mut first, &mut pool);
    let second: Entity = Entity::create_pooled("second", &mut pool);
    assert_eq!(
        second.get_name(),
        "second",
        "the recycled entity takes the new name"
    );
    assert_ne!(
        second.get_id(),
        first_id,
        "a recycled entity must not reuse the previous identifier"
    );
    assert_eq!(pool.len(), 1, "the round trip never grows the entity pool");
}

#[test]
fn release_to_pool_clears_components_tags_and_transform() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let mut entity: Entity = Entity::create_pooled_at(Vector2D::new(3.0, 4.0), &mut pool);
    entity.add_tag(String::from("doomed"));
    Entity::release_to_pool(&mut entity, &mut pool);
    let recycled: Entity = pool.acquire().unwrap();
    assert_eq!(
        recycled.get_components().len(),
        0,
        "a recycled entity carries no components"
    );
    assert_eq!(
        recycled.get_tags().len(),
        0,
        "a recycled entity carries no tags"
    );
    assert_eq!(
        recycled.get_transform().get_position(),
        Vector2D::zero(),
        "a recycled entity is back at the identity position"
    );
    assert!(recycled.get_active(), "a recycled entity is active again");
}

#[test]
fn prewarm_pool_builds_exactly_count_entities() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let available: usize = Entity::prewarm_pool(3, &mut pool);
    assert_eq!(available, 3, "prewarm of 3 leaves 3 entities available");
    assert_eq!(pool.len(), 3, "the pool tracks 3 prewarmed entities");
    assert_eq!(
        pool.get_active(),
        0,
        "prewarmed entities are free, not checked out"
    );
}

#[test]
fn pooled_spawn_despawn_cycles_keep_the_entity_pool_size_flat() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    Entity::prewarm_pool(2, &mut pool);
    let high_water: usize = pool.len();
    for index in 0..1000 {
        let mut entity: Entity = Entity::create_pooled(format!("e{index}"), &mut pool);
        entity.add_tag(String::from("temp"));
        Entity::release_to_pool(&mut entity, &mut pool);
    }
    assert_eq!(
        pool.len(),
        high_water,
        "1000 spawn/despawn cycles never grow the pool"
    );
    assert_eq!(
        pool.get_active(),
        0,
        "1000 cycles leave no outstanding checkouts"
    );
    assert_eq!(
        pool.available(),
        2,
        "1000 cycles return every entity to the free list"
    );
}

#[test]
fn create_pooled_at_places_a_recycled_entity_at_the_requested_position() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let mut stale: Entity = Entity::create_pooled_at(Vector2D::new(9.0, 9.0), &mut pool);
    Entity::release_to_pool(&mut stale, &mut pool);
    let placed: Entity = Entity::create_pooled_at(Vector2D::new(1.0, 2.0), &mut pool);
    assert_eq!(
        placed.get_transform().get_position(),
        Vector2D::new(1.0, 2.0),
        "the pooled entity sits at the requested position, not the recycled one"
    );
}

#[test]
fn two_live_pooled_entities_never_share_an_identifier() {
    let mut pool: ObjectPool<Entity> = ObjectPool::empty();
    let first: Entity = Entity::create_pooled("a", &mut pool);
    let second: Entity = Entity::create_pooled("b", &mut pool);
    assert_ne!(
        first.get_id(),
        second.get_id(),
        "two live pooled entities must not share an identifier"
    );
    assert_eq!(
        pool.len(),
        2,
        "an empty pool builds one entity per checkout"
    );
    assert_eq!(pool.get_active(), 2, "both checkouts are outstanding");
}