Skip to content

Commit

Permalink
fix: add debounce logic for better experience
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Nov 8, 2024
1 parent ecb4dde commit 2a0b965
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 43 deletions.
96 changes: 55 additions & 41 deletions bin/sozo/src/commands/dev.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::mpsc::channel;
use std::time::Duration;
use std::thread;
use std::time::{Duration, Instant};

use anyhow::Result;
use clap::Args;
Expand Down Expand Up @@ -53,32 +54,22 @@ pub struct DevArgs {
/// Specify packages to build.
#[command(flatten)]
pub packages: Option<PackagesFilter>,

#[arg(long)]
#[arg(help = "Poll interval in seconds to watch for changes.")]
#[arg(default_value = "3")]
pub poll_interval: u64,
}

impl DevArgs {
/// Watches the `src` directory that is found at the same level of the `Scarb.toml` manifest
/// of the project into the provided [`Config`].
///
/// When a change is detected, it rebuilds the project and applies the migrations.
pub fn run(self, config: &Config) -> Result<()> {
let (tx, rx) = channel();
let (file_tx, file_rx) = channel();
let (rebuild_tx, rebuild_rx) = channel();

Check warning on line 62 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L61-L62

Added lines #L61 - L62 were not covered by tests

let watcher_config =
notify::Config::default().with_poll_interval(Duration::from_secs(self.poll_interval));
notify::Config::default().with_poll_interval(Duration::from_millis(500));

Check warning on line 65 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L64-L65

Added lines #L64 - L65 were not covered by tests

let mut watcher = PollWatcher::new(tx, watcher_config)?;
let mut watcher = PollWatcher::new(file_tx, watcher_config)?;

Check warning on line 67 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L67

Added line #L67 was not covered by tests

let watched_directory = config.manifest_path().parent().unwrap().join("src");

watcher.watch(watched_directory.as_std_path(), RecursiveMode::Recursive).unwrap();

// Always build the project before starting the dev loop to make sure that the project is
// in a valid state. Devs may not use `build` anymore when using `dev`.
// Initial build and migrate
let build_args = BuildArgs {
typescript: self.typescript,
typescript_v2: self.typescript_v2,
Expand All @@ -105,48 +96,71 @@ impl DevArgs {
"Initial migration completed. Waiting for changes."
);

let mut e_handler = EventHandler;

loop {
// Issue with that currently is that we may receive other events
// and we can't cancel on-going tasks.
let is_rebuild_needed = match rx.recv() {
Ok(maybe_event) => match maybe_event {
Ok(event) => {
println!("Event: {:?}", event);
e_handler.process_event(event)
let e_handler = EventHandler;
let rebuild_tx_clone = rebuild_tx.clone();

// Independent thread to handle file events and trigger rebuilds.
config.tokio_handle().spawn(async move {
loop {
match file_rx.recv() {
Ok(maybe_event) => match maybe_event {
Ok(event) => {
trace!(?event, "Event received.");

Check warning on line 108 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L99-L108

Added lines #L99 - L108 were not covered by tests

if e_handler.process_event(event) && rebuild_tx_clone.send(()).is_err()

Check warning on line 110 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L110

Added line #L110 was not covered by tests
{
break;
}

Check warning on line 113 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L112-L113

Added lines #L112 - L113 were not covered by tests
}
Err(error) => {
error!(?error, "Processing event.");
break;

Check warning on line 117 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L115-L117

Added lines #L115 - L117 were not covered by tests
}
},
Err(error) => {
error!(?error, "Processing event.");
error!(?error, "Receiving event.");

Check warning on line 121 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L121

Added line #L121 was not covered by tests
break;
}
},
Err(error) => {
error!(?error, "Receiving event.");
break;
}
};

if is_rebuild_needed {
// Ignore the fails of those commands as the `run` function
// already logs the error.
// Currently, those two tasks are not cancellable since they are synchronous.
let _ = build_args.clone().run(config);
let _ = migrate_args.clone().run(config);
}
});

// Main thread handles the rebuilds.
let mut last_event_time = None;
let debounce_period = Duration::from_millis(1500);

Check warning on line 130 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L126-L130

Added lines #L126 - L130 were not covered by tests

loop {
match rebuild_rx.try_recv() {
Ok(()) => {
last_event_time = Some(Instant::now());
}

Check warning on line 136 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L133-L136

Added lines #L133 - L136 were not covered by tests
Err(std::sync::mpsc::TryRecvError::Empty) => {
if let Some(last_time) = last_event_time {
if last_time.elapsed() >= debounce_period {
let _ = build_args.clone().run(config);
let _ = migrate_args.clone().run(config);
last_event_time = None;
} else {
trace!("Change detected, waiting for debounce period.");

Check warning on line 144 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L138-L144

Added lines #L138 - L144 were not covered by tests
}
}
thread::sleep(Duration::from_millis(300));

Check warning on line 147 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L146-L147

Added lines #L146 - L147 were not covered by tests
}
Err(std::sync::mpsc::TryRecvError::Disconnected) => break,

Check warning on line 149 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L149

Added line #L149 was not covered by tests
}
}

Ok(())
}
}

#[derive(Debug, Default)]
#[derive(Debug)]
struct EventHandler;

impl EventHandler {
/// Processes a debounced event and return true if a rebuild is needed.
/// Only considers Cairo file and the Scarb.toml manifest.
fn process_event(&mut self, event: Event) -> bool {
fn process_event(&self, event: Event) -> bool {

Check warning on line 163 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L163

Added line #L163 was not covered by tests
trace!(?event, "Processing event.");

let paths = match event.kind {
Expand Down
4 changes: 2 additions & 2 deletions examples/simple/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ pub mod c1 {
use super::{MyInterface, M, E, EH, MValue};
use dojo::model::{ModelStorage, ModelValueStorage, Model};
use dojo::event::EventStorage;

fn dojo_init(self: @ContractState, v: felt252) {
let m = M { k: 0, v, };

let mut world = self.world_default();
world.write_model(@m);
}
}

#[abi(embed_v0)]
impl MyInterfaceImpl of MyInterface<ContractState> {
Expand Down

0 comments on commit 2a0b965

Please sign in to comment.