Add lazy-input (on-demand) example

This commit is contained in:
Jack Rickard 2022-09-03 22:56:08 +01:00
parent 5b8464c4f9
commit 609acc396c
No known key found for this signature in database
GPG key ID: 88084D7D08A72C8A
7 changed files with 165 additions and 0 deletions

View file

@ -37,4 +37,5 @@ members = [
"components/salsa-2022-macros",
"calc-example/calc",
"salsa-2022-tests",
"lazy-input",
]

14
lazy-input/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "lazy-input"
version = "0.1.0"
edition = "2021"
[dependencies]
salsa = { path = "../components/salsa-2022", package = "salsa-2022" }
ordered-float = "3.0"
dashmap = "5.4.0"
notify = "5.0.0"
crossbeam-channel = "0.5.6"
[dev-dependencies]
expect-test = "1.4.0"

2
lazy-input/inputs/a Normal file
View file

@ -0,0 +1,2 @@
2
./aa

1
lazy-input/inputs/aa Normal file
View file

@ -0,0 +1 @@
8

1
lazy-input/inputs/b Normal file
View file

@ -0,0 +1 @@
4

3
lazy-input/inputs/start Normal file
View file

@ -0,0 +1,3 @@
1
./a
./b

143
lazy-input/src/main.rs Normal file
View file

@ -0,0 +1,143 @@
use std::{path::PathBuf, sync::Mutex};
use crossbeam_channel::{unbounded, Sender};
use dashmap::DashMap;
use notify::{
event::ModifyKind, recommended_watcher, EventKind, RecommendedWatcher, RecursiveMode, Watcher,
};
use salsa::DebugWithDb;
fn main() {
let (tx, rx) = unbounded();
let mut db = Database::new(tx);
let initial = db.input("./lazy-input/inputs/start".parse().unwrap());
loop {
let parsed = parse(&db, initial);
let sum = sum(&db, parsed);
println!("{}", sum);
for log in db.logs.lock().unwrap().drain(..) {
println!("{}", log);
}
loop {
let event = rx.recv().unwrap().unwrap();
let paths: Vec<_> = if event.need_rescan() {
db.files.iter().map(|entry| entry.key().clone()).collect()
} else if matches!(
event.kind,
EventKind::Access(_) | EventKind::Modify(ModifyKind::Metadata(_))
) {
continue;
} else {
event.paths
};
if paths.is_empty() {
continue;
}
for path in paths {
let path = path.canonicalize().unwrap();
let file = match db.files.get(&path) {
Some(file) => *file,
None => continue,
};
file.set_contents(&mut db)
.to(std::fs::read_to_string(path).unwrap());
}
break;
}
}
}
#[salsa::jar(db = Db)]
struct Jar(File, ParsedFile, parse, sum);
trait Db: salsa::DbWithJar<Jar> {
fn input(&self, path: PathBuf) -> File;
}
#[salsa::db(Jar)]
struct Database {
storage: salsa::Storage<Self>,
logs: Mutex<Vec<String>>,
files: DashMap<PathBuf, File>,
file_watcher: Mutex<RecommendedWatcher>,
}
impl Database {
fn new(tx: Sender<notify::Result<notify::Event>>) -> Self {
let storage = Default::default();
Self {
storage,
logs: Default::default(),
files: DashMap::new(),
file_watcher: Mutex::new(recommended_watcher(tx).unwrap()),
}
}
}
impl salsa::Database for Database {
fn salsa_event(&self, event: salsa::Event) {
// don't log boring events
if let salsa::EventKind::WillExecute { .. } = event.kind {
self.logs
.lock()
.unwrap()
.push(format!("Event: {:?}", event.debug(self)));
}
}
}
impl Db for Database {
fn input(&self, path: PathBuf) -> File {
let path = path.canonicalize().unwrap();
*self.files.entry(path.clone()).or_insert_with(|| {
let watcher = &mut *self.file_watcher.lock().unwrap();
watcher.watch(&path, RecursiveMode::NonRecursive).unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
File::new(self, path, contents)
})
}
}
#[salsa::input]
struct File {
path: PathBuf,
#[return_ref]
contents: String,
}
#[salsa::tracked]
fn parse(db: &dyn Db, input: File) -> ParsedFile {
let mut lines = input.contents(db).lines();
let value = lines.next().unwrap().parse().unwrap();
let links = lines
.map(|path| {
let link_path = input
.path(db)
.parent()
.unwrap()
.join(path.parse::<PathBuf>().unwrap());
let file = db.input(link_path);
parse(db, file)
})
.collect();
ParsedFile::new(db, value, links)
}
#[salsa::tracked]
struct ParsedFile {
value: u32,
#[return_ref]
links: Vec<ParsedFile>,
}
#[salsa::tracked]
fn sum(db: &dyn Db, input: ParsedFile) -> u32 {
input.value(db)
+ input
.links(db)
.iter()
.map(|&file| sum(db, file))
.sum::<u32>()
}