pie_core 0.2.14

A high-performance, index-based data structure toolkit. Provides an arena allocator (ElemPool) used to build a cache-friendly PieList (doubly-linked list) and FibHeap (priority queue).
Documentation
#![cfg(feature = "serde")]

use pie_core::{ElemPool, FibHeap, PieList};
use serde::{Deserialize, Serialize};

/// A wrapper struct simulating a game state or document model.
/// This is necessary for PieList because the Pool and List are separate.
#[derive(Serialize, Deserialize)]
struct WorldState {
    pool: ElemPool<String>,
    active_quests: PieList<String>,
    completed_quests: PieList<String>,
}

#[test]
fn test_pielist_roundtrip() {
    let mut pool = ElemPool::new();
    let mut list = PieList::new(&mut pool);
    let compl = PieList::new(&mut pool);

    list.push_back("Alpha".to_string(), &mut pool).unwrap();
    list.push_back("Beta".to_string(), &mut pool).unwrap();
    list.push_back("Gamma".to_string(), &mut pool).unwrap();

    // Create a struct to hold both
    let mut original_state = WorldState {
        pool: pool, // Ownership moves here
        active_quests: list,
        completed_quests: compl,
    };
    // Fix the hacky init above for the test logic:
    // (We need a fresh empty list for the second field)
    // Let's rebuild strictly:
    let mut pool = ElemPool::new();
    let mut l1 = PieList::new(&mut pool);
    l1.push_back("Alpha".to_string(), &mut pool).unwrap();
    l1.push_back("Beta".to_string(), &mut pool).unwrap();

    let mut l2 = PieList::new(&mut pool);
    l2.push_back("Done".to_string(), &mut pool).unwrap();

    let mut state = WorldState {
        pool,
        active_quests: l1,
        completed_quests: l2,
    };

    // 1. Serialize to JSON
    let json = serde_json::to_string(&state).expect("Failed to serialize");

    // 2. Deserialize
    let mut loaded_state: WorldState = serde_json::from_str(&json).expect("Failed to deserialize");

    // 3. Verify Data
    let active: Vec<&String> = loaded_state.active_quests.iter(&loaded_state.pool).collect();
    assert_eq!(active, vec!["Alpha", "Beta"]);

    let completed: Vec<&String> = loaded_state.completed_quests.iter(&loaded_state.pool).collect();
    assert_eq!(completed, vec!["Done"]);

    // 4. Verify Operational Integrity (Can we modify the loaded state?)
    loaded_state.active_quests.push_back("Delta".to_string(), &mut loaded_state.pool).unwrap();
    assert_eq!(loaded_state.active_quests.len(), 3);
    assert_eq!(*loaded_state.active_quests.back(&loaded_state.pool).unwrap(), "Delta");

    // Since we're dropping the pools anyway, ignore the leak checks
    original_state.active_quests.set_leak_check(false);
    state.active_quests.set_leak_check(false);
    state.completed_quests.set_leak_check(false);
    loaded_state.active_quests.set_leak_check(false);
    loaded_state.completed_quests.set_leak_check(false);
}

#[test]
fn test_fibheap_roundtrip() {
    // FibHeap is self-contained (owns its pool), so we can serialize it directly.
    let mut heap = FibHeap::new();
    heap.push(10, "Low Priority");
    heap.push(1, "High Priority");
    heap.push(5, "Medium Priority");

    // 1. Serialize
    let json = serde_json::to_string(&heap).expect("Failed to serialize heap");

    // 2. Deserialize
    let mut loaded_heap: FibHeap<i32, String> = serde_json::from_str(&json).expect("Failed to deserialize heap");

    // 3. Verify Integrity
    assert_eq!(loaded_heap.len(), 3);

    // Pop elements to ensure the tree structure and priority logic were preserved
    let (k1, v1) = loaded_heap.pop().unwrap();
    assert_eq!(k1, 1);
    assert_eq!(v1, "High Priority");

    let (k2, v2) = loaded_heap.pop().unwrap();
    assert_eq!(k2, 5);
    assert_eq!(v2, "Medium Priority");

    // 4. Verify we can add new items to the loaded heap
    loaded_heap.push(2, "New Item".to_string());
    assert_eq!(loaded_heap.peek().unwrap().0, &2);
}

#[test]
fn test_shrink_before_serialize() {
    // This tests the recommended workflow: Shrink -> Remap -> Save
    let mut pool = ElemPool::new();
    let mut list = PieList::new(&mut pool);

    // Create fragmentation
    list.push_back(10, &mut pool).unwrap();
    let _idx_remove = list.push_back(20, &mut pool).unwrap(); // This will be removed
    list.push_back(30, &mut pool).unwrap();

    // Remove item 20
    list.pop_front(&mut pool); // Removes 10
    // Actually let's use the specific removal logic to ensure a hole in the middle
    // Reset:
    list.clear(&mut pool);
    list.push_back(100, &mut pool).unwrap(); // Index 2 (1 is sentinel)
    let _idx_rm = list.push_back(200, &mut pool).unwrap(); // Index 3
    list.push_back(300, &mut pool).unwrap(); // Index 4

    // Pop 200 (Index 3). Now [100, (Free), 300]
    // To do this cleanly without a remove_at in public API, we swap remove logic:
    // (Simulate hole creation via pop for this test's simplicity)
    list.pop_front(&mut pool); // Removes 100. [ (Free), 200, 300 ]

    let before_cap = pool.capacity();

    // 1. Shrink & Remap
    let map = pool.shrink_to_fit();
    list.remap(&map);

    // 2. Serialize
    // We need a wrapper because PieList isn't self-contained
    #[derive(Serialize, Deserialize)]
    struct Container { p: ElemPool<i32>, l: PieList<i32> }

    let mut container = Container { p: pool, l: list };
    let json = serde_json::to_string(&container).unwrap();

    // 3. Deserialize
    let mut loaded: Container = serde_json::from_str(&json).unwrap();

    // 4. Verify
    // The loaded pool should be compact (capacity roughly equals len)
    assert!(loaded.p.capacity() < before_cap);

    let vec: Vec<_> = loaded.l.iter(&loaded.p).copied().collect();
    assert_eq!(vec, vec![200, 300]);
    
    // Since we're dropping the pools anyway, ignore the leak checks
    container.l.set_leak_check(false);
    loaded.l.set_leak_check(false);
}

#[test]
fn test_corrupted_freed_count() {
    // Build a valid pool, serialize it, tamper with `freed`, then deserialize.
    let mut pool = ElemPool::new();
    let mut list = PieList::new(&mut pool);
    list.push_back(1, &mut pool).unwrap();

    #[derive(Serialize, Deserialize)]
    struct Container { p: ElemPool<i32>, l: PieList<i32> }

    let mut c = Container { p: pool, l: list };
    c.l.set_leak_check(false);
    let mut json: serde_json::Value = serde_json::to_value(&c).unwrap();

    // Tamper: set freed to wrong value
    json["p"]["freed"] = serde_json::Value::from(999);
    let result: Result<Container, _> = serde_json::from_value(json);
    match result {
        Err(e) => assert!(e.to_string().contains("freed count mismatch"), "unexpected error: {e}"),
        Ok(_) => panic!("should reject mismatched freed count"),
    }
}

#[test]
fn test_corrupted_used_count() {
    let mut pool = ElemPool::new();
    let mut list = PieList::new(&mut pool);
    list.push_back(1, &mut pool).unwrap();

    #[derive(Serialize, Deserialize)]
    struct Container { p: ElemPool<i32>, l: PieList<i32> }

    let mut c = Container { p: pool, l: list };
    c.l.set_leak_check(false);
    let mut json: serde_json::Value = serde_json::to_value(&c).unwrap();

    // Tamper: set used to wrong value
    json["p"]["used"] = serde_json::Value::from(0);
    let result: Result<Container, _> = serde_json::from_value(json);
    match result {
        Err(e) => assert!(e.to_string().contains("used count mismatch"), "unexpected error: {e}"),
        Ok(_) => panic!("should reject mismatched used count"),
    }
}

#[test]
fn test_corrupted_broken_link() {
    // Serialize a valid pool, then corrupt a next-link so it points out of bounds.
    let mut pool = ElemPool::new();
    let mut list = PieList::new(&mut pool);
    list.push_back(1, &mut pool).unwrap();
    list.push_back(2, &mut pool).unwrap();

    #[derive(Serialize, Deserialize)]
    struct Container { p: ElemPool<i32>, l: PieList<i32> }

    let mut c = Container { p: pool, l: list };
    c.l.set_leak_check(false);
    let mut json: serde_json::Value = serde_json::to_value(&c).unwrap();

    // Tamper: set the first element's next-link to out-of-bounds
    // The elems array has entries [sentinel, list-sentinel, elem1, elem2]
    // Setting elem1's next to 999 should break bidirectional check
    if let Some(elems) = json["p"]["elems"].as_array_mut() {
        if let Some(elem) = elems.get_mut(2) {
            elem["next"] = serde_json::Value::from(999);
        }
    }
    let result: Result<Container, _> = serde_json::from_value(json);
    assert!(result.is_err(), "should reject broken links");
}