verde 0.2.2

A refreshingly simple incremental computation library
Documentation
use std::sync::atomic::{AtomicBool, Ordering};

use verde::{db, query, storage, test::TestDatabase, Ctx, Db, Id, Pushable, Tracked};

#[derive(Copy, Clone, Eq, PartialEq, Hash, Tracked)]
#[cfg_attr(feature = "serde", derive(::verde::serde::Serialize, ::verde::serde::Deserialize))]
struct TrackedStruct {
	#[id]
	id: u32,
	value: u32,
}

#[derive(Clone, Pushable)]
#[cfg_attr(feature = "serde", derive(::verde::serde::Serialize, ::verde::serde::Deserialize))]
struct Accum;

#[query]
fn double(db: &Ctx, id: Id<TrackedStruct>) -> TrackedStruct {
	db.push(Accum);
	let s = db.get(id);
	TrackedStruct {
		id: s.id,
		value: s.value * 2,
	}
}

#[query]
fn sum(db: &Ctx, id: u32, ids: Vec<Id<TrackedStruct>>) -> TrackedStruct {
	db.push(Accum);
	let mut value = 0;
	for x in ids.into_iter().map(|x| db.get(x)) {
		value += x.value;
	}
	TrackedStruct { id, value }
}

#[test]
fn correct_result() {
	#[storage]
	struct Storage(TrackedStruct, Accum, double, sum);

	#[db]
	struct Database(Storage);

	let mut db = Database::default();
	let db = &mut db as &mut dyn Db;
	let init: Vec<_> = (1..=100)
		.map(|x| db.set_input(TrackedStruct { id: x, value: x }))
		.collect();

	db.execute(|ctx| {
		let doubled: Vec<_> = init.iter().map(|x| double(ctx, *x)).collect();
		let first_double_then_sum = sum(ctx, 0, doubled);

		let first_sum = sum(ctx, 1, init);
		let first_sum_then_double = double(ctx, first_sum);

		let val1 = db.get(first_double_then_sum).value;
		let val2 = db.get(first_sum_then_double).value;
		assert_eq!(val1, val2);
		assert_eq!(val1, 5050 * 2);

		let accums = db.get_all::<Accum>();
		assert_eq!(accums.count(), 103);
		let double_accums = db.get_query::<double, Accum>();
		assert_eq!(double_accums.count(), 101);
		let sum_accums = db.get_query::<sum, Accum>();
		assert_eq!(sum_accums.count(), 2);
	});
}

#[test]
fn simple_memoize() {
	static EXECUTED: AtomicBool = AtomicBool::new(false);

	#[query]
	fn double(ctx: &Ctx, id: Id<TrackedStruct>) -> TrackedStruct {
		if EXECUTED.load(Ordering::Relaxed) {
			panic!("double() was executed twice");
		}
		EXECUTED.store(true, Ordering::Relaxed);

		let s = ctx.get(id);
		TrackedStruct {
			id: s.id,
			value: s.value * 2,
		}
	}

	let mut db = TestDatabase::new();
	let db = &mut db as &mut dyn Db;
	let id = db.set_input(TrackedStruct { id: 0, value: 1 });

	db.execute(|ctx| {
		let check = || {
			let doubled = double(ctx, id);
			assert_eq!(db.get(doubled).value, 2);
		};

		check();
		check();
	});
}

#[test]
fn memoizes_after_old_dependency() {
	static EXECUTED: AtomicBool = AtomicBool::new(false);
	#[query]
	fn triple(ctx: &Ctx, id: Id<TrackedStruct>) -> TrackedStruct {
		if EXECUTED.load(Ordering::Relaxed) {
			panic!("triple() was executed twice");
		}
		EXECUTED.store(true, Ordering::Relaxed);

		let s = ctx.get(id);
		TrackedStruct {
			id: s.id,
			value: s.value * 3,
		}
	}

	let mut db = TestDatabase::new();
	let db = &mut db as &mut dyn Db;

	let input = db.set_input(TrackedStruct { id: 0, value: 1 });

	let first = db.execute(|ctx| double(ctx, input));
	let trip = db.execute(|ctx| triple(ctx, first));
	assert_eq!(db.get(trip).value, 6);
	assert_eq!(input, db.set_input(TrackedStruct { id: 0, value: 2 }));

	let dub = || db.execute(|ctx| double(ctx, input));
	let second = dub();
	let third = dub();
	assert_eq!(db.get(second).value, db.get(third).value);

	EXECUTED.store(false, Ordering::Relaxed);
	let check = || {
		let trip = db.execute(|ctx| triple(ctx, third));
		assert_eq!(db.get(trip).value, 12);
	};
	check();
	check();
}

#[test]
fn executes_after_old_dependency() {
	let mut db = TestDatabase::new();
	let db = &mut db as &mut dyn Db;

	let mut input1 = db.set_input(TrackedStruct { id: 1, value: 1 });
	let mut doubled = db.execute(|ctx| double(ctx, input1));

	let mut input2 = db.set_input(TrackedStruct { id: 2, value: 2 });
	let mut s = db.execute(|ctx| sum(ctx, 0, vec![doubled, input2]));
	assert_eq!(db.get(s).value, 4);

	for value in 2..=10 {
		input1 = db.set_input(TrackedStruct { id: 1, value });
		doubled = db.execute(|ctx| double(ctx, input1));
		s = db.execute(|ctx| sum(ctx, 0, vec![doubled, input2]));
		assert_eq!(db.get(s).value, value * 2 + 2);
	}

	input2 = db.set_input(TrackedStruct { id: 2, value: 1 });
	let trip = db.execute(|ctx| sum(ctx, 0, vec![doubled, input2]));
	assert_eq!(db.get(trip).value, 21);
}

#[test]
fn interning() {
	let mut db = TestDatabase::new();
	let db = &mut db as &mut dyn Db;

	let id = db.add("string".to_string());
	let id2 = db.add("string".to_string());
	let id3 = db.add("string2".to_string());
	let id4 = db.add_ref("string");

	assert_eq!(id, id2);
	assert_eq!(id, id4);
	assert_ne!(id, id3);
}