Skip to content

Commit

Permalink
feat: add dynamic capsule support (#10)
Browse files Browse the repository at this point in the history
Fixes #9
  • Loading branch information
GregoryConrad authored Dec 6, 2023
1 parent ca3e17a commit 1d336b5
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 121 deletions.
20 changes: 20 additions & 0 deletions examples/fibonacci/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "example-fibonacci"
version = "0.4.0"
publish = false
edition.workspace = true
license.workspace = true
description.workspace = true
homepage.workspace = true
documentation.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
authors.workspace = true
readme.workspace = true

[lints]
workspace = true

[dependencies]
rearch = { workspace = true }
41 changes: 41 additions & 0 deletions examples/fibonacci/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use rearch::{Capsule, CapsuleHandle, CapsuleKey, Container};

struct FibonacciCapsule(u8);
impl Capsule for FibonacciCapsule {
type Data = u128;

fn build(&self, CapsuleHandle { mut get, .. }: CapsuleHandle) -> Self::Data {
let Self(n) = self;
match n {
0 => 0,
1 => 1,
n => get.get(Self(n - 1)) + get.get(Self(n - 2)),
}
}

fn eq(old: &Self::Data, new: &Self::Data) -> bool {
old == new
}

fn key(&self) -> CapsuleKey {
let Self(id) = self;
id.to_le_bytes().as_ref().to_owned().into()
}
}

fn main() {
let container = Container::new();
println!(
"The 100th fibonacci number is {}",
container.read(FibonacciCapsule(100)),
);
}

#[test]
fn fib_number_is_correct() {
let container = Container::new();
assert_eq!(
container.read(FibonacciCapsule(100)),
354_224_848_179_261_915_075
);
}
55 changes: 28 additions & 27 deletions rearch/src/capsule_reader.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use std::{
any::{Any, TypeId},
collections::HashMap,
};
use std::{any::Any, collections::HashMap};

use crate::{Capsule, CapsuleData, ContainerWriteTxn};
use crate::{Capsule, CapsuleData, ContainerWriteTxn, CreateId, Id};

/// Allows you to read the current data of capsules based on the given state of the container txn.
pub enum CapsuleReader<'scope, 'total> {
// For normal operation
Normal {
id: TypeId,
txn: &'scope mut ContainerWriteTxn<'total>,
},
Normal(NormalCapsuleReader<'scope, 'total>),
// To enable easy mocking in testing
Mock {
mocks: HashMap<TypeId, Box<dyn CapsuleData>>,
},
Mock(MockCapsuleReader),
}
#[allow(clippy::module_name_repetitions)]
pub struct NormalCapsuleReader<'scope, 'total> {
id: Id,
txn: &'scope mut ContainerWriteTxn<'total>,
}
#[allow(clippy::module_name_repetitions)]
pub struct MockCapsuleReader {
mocks: HashMap<Id, Box<dyn CapsuleData>>,
}

impl<'scope, 'total> CapsuleReader<'scope, 'total> {
pub(crate) fn new(id: TypeId, txn: &'scope mut ContainerWriteTxn<'total>) -> Self {
Self::Normal { id, txn }
pub(crate) fn new(id: Id, txn: &'scope mut ContainerWriteTxn<'total>) -> Self {
Self::Normal(NormalCapsuleReader { id, txn })
}

/// Reads the current data of the supplied capsule, initializing it if needed.
Expand All @@ -31,16 +32,16 @@ impl<'scope, 'total> CapsuleReader<'scope, 'total> {
/// Panics when a capsule attempts to read itself in its first build.
pub fn get<C: Capsule>(&mut self, capsule: C) -> C::Data {
match self {
CapsuleReader::Normal { id, txn } => {
let (this, other) = (*id, TypeId::of::<C>());
if this == other {
CapsuleReader::Normal(NormalCapsuleReader { ref id, txn }) => {
let (this, other) = (id, capsule.id());
if this == &other {
return txn.try_read(&capsule).unwrap_or_else(|| {
let name = std::any::type_name::<C>();
panic!(
"Capsule {name} tried to read itself on its first build! {} {} {}",
"{name} ({id:?}) tried to read itself on its first build! {} {} {}",
"This is disallowed since the capsule doesn't have data to read yet.",
"To avoid this issue, wrap the `read({name})` call in an if statement",
"with the builtin \"is first build\" side effect."
"To avoid this issue, wrap the `get()` call in an if statement",
"with the builtin \"is_first_build\" side effect."
);
});
}
Expand All @@ -50,13 +51,13 @@ impl<'scope, 'total> CapsuleReader<'scope, 'total> {
txn.add_dependency_relationship(other, this);
data
}
CapsuleReader::Mock { mocks } => {
let id = TypeId::of::<C>();
CapsuleReader::Mock(MockCapsuleReader { mocks }) => {
let id = capsule.id();
let any: Box<dyn Any> = mocks
.get(&id)
.unwrap_or_else(|| {
panic!(
"Mock CapsuleReader was used to read {} {}",
"Mock CapsuleReader was used to read {} ({id:?}) {}",
std::any::type_name::<C>(),
"when it was not included in the mock!"
);
Expand Down Expand Up @@ -85,7 +86,7 @@ impl<A: Capsule> FnMut<(A,)> for CapsuleReader<'_, '_> {
}

#[derive(Clone, Default)]
pub struct MockCapsuleReaderBuilder(HashMap<TypeId, Box<dyn CapsuleData>>);
pub struct MockCapsuleReaderBuilder(HashMap<Id, Box<dyn CapsuleData>>);

impl MockCapsuleReaderBuilder {
#[must_use]
Expand All @@ -94,13 +95,13 @@ impl MockCapsuleReaderBuilder {
}

#[must_use]
pub fn set<C: Capsule>(mut self, _capsule: &C, data: C::Data) -> Self {
self.0.insert(TypeId::of::<C>(), Box::new(data));
pub fn set<C: Capsule>(mut self, capsule: &C, data: C::Data) -> Self {
self.0.insert(capsule.id(), Box::new(data));
self
}

#[must_use]
pub fn build(self) -> CapsuleReader<'static, 'static> {
CapsuleReader::Mock { mocks: self.0 }
CapsuleReader::Mock(MockCapsuleReader { mocks: self.0 })
}
}
Loading

0 comments on commit 1d336b5

Please sign in to comment.