| Crates.io | daab |
| lib.rs | daab |
| version | 0.4.0 |
| created_at | 2020-01-03 02:18:53.790999+00 |
| updated_at | 2020-12-21 16:39:26.887806+00 |
| description | DAG aware artifact builder |
| homepage | |
| repository | https://github.com/CoffeJunkStudio/daab |
| max_upload_size | |
| id | 194704 |
| size | 306,589 |
Rust crate for managing the building and caching of artifacts which are connected in a directed acyclic graph (DAG) like manner, i.e. artifacts may depend on others.
The caching provided by this crate could be especially useful if the artifact builders use consumable resources, the building process is a heavyweight procedure, or a given DAG dependency structure among the builders shall be properly preserved among their artifacts.
Minimal Rust version: 1.40
The basic concept of daab revolves around Builders, which are user provided
structs that implement the Builder trait. That trait essentially has an
associated type Artifact and method build where the latter will
produce a value of the Artifact type, which will be subsequently be
referred to as Artifact. In order to be able to depend on the Artifact of
other Builders, the build method also gets a Resolver that allows
to retrieve the Artifacts of others.
In order to allow Builders and Artifacts to form a directed acyclic graph
this crate provides at its heart an Artifact Cache which keeps the
Artifacts of Builders in order to prevent the Builders to produce multiple
equal Artifacts. Thus different Builders may depend on same Builder and
getting the same Artifact from the Cache.
To be able to share Builders and Artifacts this crate also provides a
concept of Cans and Bins, which in the most basic case are simply an opaque
Rc<dyn Any> and a transparent Rc<T>, respectively. These are referred to
by the generic arguments of e.g. the Cache. For more details consult the
canning module.
Additional to the canning, the Cache expects Builders to wrapped in a
opaque Blueprint enforcing encapsulation, i.e. it prevents users from
accessing the inner struct (the one which implements the Builder trait),
while only allowing the Cache itself to call its build method.
For the basic concept (explained above) there exists simplified traits
which skip over the more
advanced features. One such simplified trait is the SimpleBuilder of the
rc module, which uses Rcs for canning and has simplified aliases
(minimal generic arguments) for all the above types. For getting started
that rc module is probably the best place to start.
use std::rc::Rc;
use daab::*;
// Simple artifact
#[derive(Debug)]
struct Leaf {
//...
}
// Simple builder
#[derive(Debug)]
struct BuilderLeaf {
// ...
}
impl BuilderLeaf {
pub fn new() -> Self {
Self {
// ...
}
}
}
impl rc::SimpleBuilder for BuilderLeaf {
type Artifact = Leaf;
fn build(&self, _resolver: &mut rc::Resolver) -> Self::Artifact {
Leaf{
// ...
}
}
}
// Composed artifact, linking to a Leaf
#[derive(Debug)]
struct Node {
leaf: Rc<Leaf>, // Dependency artifact
value: u8, // Some custom value
// ...
}
// Composed builder, depending on BuilderLeaf
#[derive(Debug)]
struct BuilderNode {
builder_leaf: rc::Blueprint<BuilderLeaf>, // Dependency builder
// ...
}
impl BuilderNode {
pub fn new(builder_leaf: rc::Blueprint<BuilderLeaf>) -> Self {
Self {
builder_leaf,
// ...
}
}
}
use std::any::Any;
impl rc::Builder for BuilderNode {
type Artifact = Node;
type DynState = u8;
type Err = Never;
fn build(&self, resolver: &mut rc::Resolver<Self::DynState>) -> Result<Rc<Self::Artifact>, Never> {
// Resolve Blueprint to its artifact
// Unpacking because the Err type is Never.
let leaf = resolver.resolve(&self.builder_leaf).unpack();
Ok(Node {
leaf,
value: *resolver.my_state(),
// ...
}.into())
}
fn init_dyn_state(&self) -> Self::DynState {
42
}
}
// The cache to storing already created artifacts
let mut cache = rc::Cache::new();
// Constructing builders
let leaf_builder = rc::Blueprint::new(BuilderLeaf::new());
let node_builder_1 = rc::Blueprint::new(BuilderNode::new(leaf_builder.clone()));
let node_builder_2 = rc::Blueprint::new(BuilderNode::new(leaf_builder.clone()));
// Using the cache to access the artifacts from the builders
// The same builder results in same artifact
assert!(Rc::ptr_eq(&cache.get(&node_builder_1).unpack(), &cache.get(&node_builder_1).unpack()));
// Different builders result in different artifacts
assert!( ! Rc::ptr_eq(&cache.get(&node_builder_1).unpack(), &cache.get(&node_builder_2).unpack()));
// Different artifacts may link the same dependent artifact
assert!(Rc::ptr_eq(&cache.get(&node_builder_1).unpack().leaf, &cache.get(&node_builder_2).unpack().leaf));
// Purge builder 2 to ensure the following does not affect it
cache.purge(&node_builder_2);
// Test dynamic state
assert_eq!(cache.get(&node_builder_1).unpack().value, 42);
// Change state
*cache.dyn_state_mut(&node_builder_1) = 127.into();
// Without invalidation, the cached artefact remains unchanged
assert_eq!(cache.dyn_state(&node_builder_1), &127);
// Invalidate node, and ensure it made use of the state
assert_eq!(cache.get(&node_builder_1).unpack().value, 127);
// State of node 2 remains unchanged
assert_eq!(cache.get_dyn_state(&node_builder_2), None);
assert_eq!(cache.get(&node_builder_2).unpack().value, 42);
daab comes with extensive debugging gear. However, in order to
keep the production impact as low as possible, the debugging facilities
are capsuled behind the diagnostics feature.
Of course, the debugging feature is for the user of this crate to
debug their graphs. Therefore, it is rather modelled as a
diagnostics feature (hence the name). The diagnosis
is carried out by a Doctor, which is a trait receiving various
internal events in order to record them, print them, or otherwise help
treating the bug.
Care has been taken to keep the diagnostics feature broadly applicable
as well as keeping the non-diagnostics API compatible with the
diagnostics-API, meaning that a project not using the
diagnostics feature can be easily converted to using
diagnostics, usually by just replacing Cache::new()
with Cache::new_with_doctor().
In order to store the Doctor the Cache is generic to a doctor,
which is important on its creation and for storing it by value.
The rest of the time the Cache uses dyn Doctor as its default
generic argument.
To ease conversion between them, all creatable Caches
(i.e. not Cache<dyn Doctor>) implement DerefMut to
&mut Cache<dyn Doctor> which has all the important methods
implemented.
This crate offers the following features:
diagnostics enables elaborate graph and cache interaction debugging.
It adds the new_with_doctor() function to the Cache and adds
the diagnostics module with the Doctor trait definition and some
default Doctors.
tynm enable the optional dependency on the tynm crate which adds
functionality to abbreviate type names, which are used by some default
Doctors, hence it is only useful in connection with the diagnostics
feature.
unsized enables better conversion between unsized Builders with
BlueprintUnsized::into_unsized. This feature requires Nightly
Rust.
Licensed under Apache License, Version 2.0 (LICENSE or https://www.apache.org/licenses/LICENSE-2.0).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.