Implement watch command #65
6 changed files with 422 additions and 64 deletions
110
Cargo.lock
generated
110
Cargo.lock
generated
|
@ -525,6 +525,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.15"
|
||||
|
@ -749,6 +759,7 @@ dependencies = [
|
|||
"glob",
|
||||
"libloading",
|
||||
"nanorand",
|
||||
"notify",
|
||||
"oodle-sys",
|
||||
"path-clean",
|
||||
"path-slash",
|
||||
|
@ -878,6 +889,18 @@ dependencies = [
|
|||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
|
@ -949,6 +972,15 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.26"
|
||||
|
@ -1358,6 +1390,26 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
|
@ -1461,6 +1513,26 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.9.1"
|
||||
|
@ -1649,6 +1721,24 @@ dependencies = [
|
|||
"nom 7.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -2277,6 +2367,15 @@ version = "1.0.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -2920,6 +3019,17 @@ dependencies = [
|
|||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -31,6 +31,7 @@ zip = "0.6.3"
|
|||
path-clean = "1.0.1"
|
||||
path-slash = "0.2.1"
|
||||
async-recursion = "1.0.2"
|
||||
notify = "5.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
|
|
|
@ -260,26 +260,16 @@ pub(crate) async fn read_project_config(dir: Option<PathBuf>) -> Result<ModConfi
|
|||
Ok(cfg)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
unsafe {
|
||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||
}
|
||||
|
||||
let cfg = read_project_config(matches.get_one::<PathBuf>("directory").cloned()).await?;
|
||||
|
||||
let game_dir = matches
|
||||
.get_one::<PathBuf>("deploy")
|
||||
.map(|p| p.join("bundle"));
|
||||
|
||||
let out_path = matches
|
||||
.get_one::<PathBuf>("out")
|
||||
.expect("parameter should have default value");
|
||||
|
||||
tracing::debug!(?cfg, ?game_dir, ?out_path);
|
||||
|
||||
let game_dir = Arc::new(game_dir);
|
||||
let cfg = Arc::new(cfg);
|
||||
pub(crate) async fn build<P1, P2>(
|
||||
cfg: &ModConfig,
|
||||
out_path: P1,
|
||||
game_dir: Arc<Option<P2>>,
|
||||
) -> Result<()>
|
||||
where
|
||||
P1: AsRef<Path>,
|
||||
P2: AsRef<Path>,
|
||||
{
|
||||
let out_path = out_path.as_ref();
|
||||
|
||||
fs::create_dir_all(out_path)
|
||||
.await
|
||||
|
@ -340,7 +330,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
|
|||
.wrap_err_with(|| format!("Failed to write bundle to '{}'", path.display()))?;
|
||||
|
||||
if let Some(game_dir) = game_dir.as_ref() {
|
||||
let path = game_dir.join(&name);
|
||||
let path = game_dir.as_ref().join(&name);
|
||||
|
||||
tracing::trace!(
|
||||
"Deploying bundle {} to '{}'",
|
||||
|
@ -393,8 +383,33 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
|
|||
tracing::info!("Compiled bundles written to '{}'", out_path.display());
|
||||
|
||||
if let Some(game_dir) = game_dir.as_ref() {
|
||||
tracing::info!("Deployed bundles to '{}'", game_dir.display());
|
||||
tracing::info!("Deployed bundles to '{}'", game_dir.as_ref().display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
unsafe {
|
||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||
}
|
||||
|
||||
let cfg = read_project_config(matches.get_one::<PathBuf>("directory").cloned()).await?;
|
||||
|
||||
let game_dir = matches
|
||||
.get_one::<PathBuf>("deploy")
|
||||
.map(|p| p.join("bundle"));
|
||||
|
||||
let out_path = matches
|
||||
.get_one::<PathBuf>("out")
|
||||
.expect("parameter should have default value");
|
||||
|
||||
tracing::debug!(?cfg, ?game_dir, ?out_path);
|
||||
|
||||
let game_dir = Arc::new(game_dir);
|
||||
|
||||
build(&cfg, out_path, game_dir).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{value_parser, Arg, ArgMatches, Command};
|
||||
use color_eyre::eyre::{Context, Result};
|
||||
use color_eyre::Help;
|
||||
use path_slash::PathBufExt;
|
||||
use dtmt_shared::ModConfig;
|
||||
use path_slash::{PathBufExt, PathExt};
|
||||
use tokio::fs;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_stream::wrappers::ReadDirStream;
|
||||
|
@ -41,18 +42,28 @@ pub(crate) fn command_definition() -> Command {
|
|||
Arg::new("out")
|
||||
.long("out")
|
||||
.short('o')
|
||||
.default_value(".")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help("The path to write the packaged file to. May be a directory or a file name."),
|
||||
.help(
|
||||
"The path to write the packaged file to. Will default to a file in the \
|
||||
current working directory",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[async_recursion::async_recursion]
|
||||
async fn process_directory<W: std::io::Write + std::io::Seek + std::marker::Send>(
|
||||
async fn process_directory<P1, P2, W>(
|
||||
zip: Arc<Mutex<ZipWriter<W>>>,
|
||||
path: PathBuf,
|
||||
prefix: PathBuf,
|
||||
) -> Result<()> {
|
||||
path: P1,
|
||||
prefix: P2,
|
||||
) -> Result<()>
|
||||
where
|
||||
P1: AsRef<Path> + std::marker::Send,
|
||||
P2: AsRef<Path> + std::marker::Send,
|
||||
W: std::io::Write + std::io::Seek + std::marker::Send,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
let prefix = prefix.as_ref();
|
||||
|
||||
zip.lock()
|
||||
.await
|
||||
.add_directory(prefix.to_slash_lossy(), Default::default())?;
|
||||
|
@ -88,33 +99,18 @@ async fn process_directory<W: std::io::Write + std::io::Seek + std::marker::Send
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
let cfg = read_project_config(matches.get_one::<PathBuf>("project").cloned()).await?;
|
||||
|
||||
let dest = {
|
||||
let mut path = matches
|
||||
.get_one::<PathBuf>("out")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
if path.extension().is_none() {
|
||||
path.push(format!("{}.zip", cfg.id))
|
||||
}
|
||||
|
||||
path
|
||||
};
|
||||
pub(crate) async fn package<P1, P2>(cfg: &ModConfig, path: P1, dest: P2) -> Result<()>
|
||||
where
|
||||
P1: AsRef<Path>,
|
||||
P2: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
let dest = dest.as_ref();
|
||||
|
||||
let data = Cursor::new(Vec::new());
|
||||
let zip = ZipWriter::new(data);
|
||||
let zip = Arc::new(Mutex::new(zip));
|
||||
|
||||
let path = cfg.dir.join(
|
||||
matches
|
||||
.get_one::<PathBuf>("directory")
|
||||
.expect("parameter has default value"),
|
||||
);
|
||||
|
||||
process_directory(zip.clone(), path, PathBuf::from(&cfg.id))
|
||||
.await
|
||||
.wrap_err("Failed to add directory to archive")?;
|
||||
|
@ -135,7 +131,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
|
|||
|
||||
let data = zip.finish()?;
|
||||
|
||||
fs::write(&dest, data.into_inner())
|
||||
fs::write(dest, data.into_inner())
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to write mod archive to '{}'", dest.display()))
|
||||
.with_suggestion(|| "Make sure that parent directories exist.".to_string())?;
|
||||
|
@ -143,3 +139,21 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
|
|||
tracing::info!("Mod archive written to {}", dest.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
let cfg = read_project_config(matches.get_one::<PathBuf>("project").cloned()).await?;
|
||||
|
||||
let dest = matches
|
||||
.get_one::<PathBuf>("out")
|
||||
.map(path_clean::clean)
|
||||
.unwrap_or_else(|| PathBuf::from(format!("{}.zip", cfg.id)));
|
||||
|
||||
let path = cfg.dir.join(
|
||||
matches
|
||||
.get_one::<PathBuf>("directory")
|
||||
.expect("parameter has default value"),
|
||||
);
|
||||
|
||||
package(&cfg, path, dest).await
|
||||
}
|
||||
|
|
|
@ -1,24 +1,242 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{value_parser, Arg, ArgMatches, Command};
|
||||
use color_eyre::eyre::Result;
|
||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||
use color_eyre::eyre::{Context, Result};
|
||||
use dtmt_shared::ModConfig;
|
||||
use notify::{Event, Watcher};
|
||||
|
||||
pub(crate) fn _command_definition() -> Command {
|
||||
use crate::cmd::build::{build, read_project_config};
|
||||
|
||||
use super::package::package;
|
||||
|
||||
pub(crate) fn command_definition() -> Command {
|
||||
Command::new("watch")
|
||||
.about("Re-build the given directory on file changes.")
|
||||
.about("Watch for file system changes and re-build the mod archive.")
|
||||
.arg(
|
||||
Arg::new("debounce")
|
||||
.default_value("150")
|
||||
.value_parser(value_parser!(u64))
|
||||
.help(
|
||||
"The delay to debounce events by. This avoids continously \
|
||||
rebuilding on rapid file changes, such as version control checkouts.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("directory")
|
||||
.required(false)
|
||||
.default_value(".")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help(
|
||||
"The path to the project to build. \
|
||||
If omitted, the current working directory is used.",
|
||||
If omitted, the current working directory is used.",
|
||||
),
|
||||
)
|
||||
.arg(Arg::new("oodle").long("oodle").help(
|
||||
"The oodle library to load. This may either be:\n\
|
||||
- A library name that will be searched for in the system's default paths.\n\
|
||||
- A file path relative to the current working directory.\n\
|
||||
- An absolute file path.",
|
||||
))
|
||||
.arg(
|
||||
Arg::new("out")
|
||||
.long("out")
|
||||
.short('o')
|
||||
.default_value("out")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help("The directory to write output files to."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("deploy")
|
||||
.long("deploy")
|
||||
.short('d')
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help(
|
||||
"If the path to the game (without the trailing '/bundle') is specified, \
|
||||
deploy the newly built bundles. \
|
||||
This will not adjust the bundle database or package files, so if files are \
|
||||
added or removed, you will have to import into DTMM and re-deploy there.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("archive")
|
||||
.long("archive")
|
||||
.short('a')
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help(
|
||||
"The path to write the packaged file to. Will default to a file in the \
|
||||
current working directory",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ignore")
|
||||
.long("ignore")
|
||||
.short('i')
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.action(ArgAction::Append)
|
||||
.help(
|
||||
"A directory or file path to ignore. May be specified multiple times. \
|
||||
The values of 'out' and 'archive' are ignored automatically.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, _matches: &ArgMatches) -> Result<()> {
|
||||
unimplemented!()
|
||||
async fn compile<P1, P2, P3>(
|
||||
cfg: &ModConfig,
|
||||
out_path: P1,
|
||||
archive_path: P2,
|
||||
game_dir: Arc<Option<P3>>,
|
||||
) -> Result<()>
|
||||
where
|
||||
P1: AsRef<Path> + std::marker::Copy,
|
||||
P2: AsRef<Path>,
|
||||
P3: AsRef<Path>,
|
||||
{
|
||||
build(cfg, out_path, game_dir)
|
||||
.await
|
||||
.wrap_err("Failed to build bundles")?;
|
||||
package(cfg, out_path, archive_path)
|
||||
.await
|
||||
.wrap_err("Failed to package bundles")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
unsafe {
|
||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||
}
|
||||
|
||||
let cfg = read_project_config(matches.get_one::<PathBuf>("directory").cloned())
|
||||
.await
|
||||
.wrap_err("failed to load project config")?;
|
||||
tracing::debug!(?cfg);
|
||||
let cfg = Arc::new(cfg);
|
||||
|
||||
let game_dir = matches
|
||||
.get_one::<PathBuf>("deploy")
|
||||
.map(path_clean::clean)
|
||||
.map(|p| if p.is_absolute() { p } else { cfg.dir.join(p) })
|
||||
.map(|p| p.join("bundle"));
|
||||
|
||||
let out_path = matches
|
||||
.get_one::<PathBuf>("out")
|
||||
.map(path_clean::clean)
|
||||
.map(|p| if p.is_absolute() { p } else { cfg.dir.join(p) })
|
||||
.expect("parameter should have default value");
|
||||
|
||||
let archive_path = matches
|
||||
.get_one::<PathBuf>("archive")
|
||||
.map(path_clean::clean)
|
||||
.map(|p| if p.is_absolute() { p } else { cfg.dir.join(p) })
|
||||
.unwrap_or_else(|| cfg.dir.join(format!("{}.zip", cfg.id)));
|
||||
|
||||
let ignored = {
|
||||
let mut ignored: Vec<_> = matches
|
||||
.get_many::<PathBuf>("ignore")
|
||||
.unwrap_or_default()
|
||||
.map(path_clean::clean)
|
||||
.map(|p| if p.is_absolute() { p } else { cfg.dir.join(p) })
|
||||
.collect();
|
||||
|
||||
ignored.push(out_path.clone());
|
||||
ignored.push(archive_path.clone());
|
||||
|
||||
ignored
|
||||
};
|
||||
|
||||
if tracing::enabled!(tracing::Level::INFO) {
|
||||
let list = ignored.iter().fold(String::new(), |mut s, p| {
|
||||
s.push_str("\n - ");
|
||||
s.push_str(&p.display().to_string());
|
||||
s
|
||||
});
|
||||
|
||||
tracing::info!("Ignoring:{}", list);
|
||||
}
|
||||
|
||||
let game_dir = Arc::new(game_dir);
|
||||
|
||||
let duration =
|
||||
Duration::from_millis(matches.get_one::<u64>("debounce").copied().unwrap_or(150));
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let mut watcher = notify::recommended_watcher(move |res: Result<Event, _>| {
|
||||
let ignored = match &res {
|
||||
Ok(evt) => evt.paths.iter().any(|p1| {
|
||||
let p1 = path_clean::clean(p1);
|
||||
ignored.iter().any(|p2| p1.starts_with(p2))
|
||||
}),
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
tracing::trace!(?res, ignored, "Received file system event");
|
||||
|
||||
if !ignored {
|
||||
if let Err(err) = tx.send(res) {
|
||||
tracing::error!("Failed to send file system event: {:?}", err);
|
||||
}
|
||||
}
|
||||
})
|
||||
.wrap_err("failed to create file system watcher")?;
|
||||
|
||||
tracing::info!("Starting file watcher on '{}'", cfg.dir.display());
|
||||
|
||||
let path = cfg.dir.clone();
|
||||
watcher
|
||||
.watch(&path, notify::RecursiveMode::Recursive)
|
||||
.wrap_err_with(|| {
|
||||
format!(
|
||||
"failed to watch directory for file changes: {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::trace!("Starting debounce loop");
|
||||
|
||||
let mut dirty = false;
|
||||
loop {
|
||||
// While we could just always await on the timeout, splitting things like this
|
||||
// optimizes the case when no events happen for a while. Rather than being woken every
|
||||
// `duration` just to do nothing, this way we always wait for a new event first until
|
||||
// we start the debounce timeouts.
|
||||
if dirty {
|
||||
match tokio::time::timeout(duration, rx.recv()).await {
|
||||
// The error is the wanted case, as it signals that we haven't received an
|
||||
// event within `duration`, which es what the debounce is supposed to wait for.
|
||||
Err(_) => {
|
||||
tracing::trace!("Received debounce timeout, running build");
|
||||
if let Err(err) =
|
||||
compile(&cfg, &out_path, &archive_path, game_dir.clone()).await
|
||||
{
|
||||
tracing::error!("Failed to build mod archive: {:?}", err);
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
Ok(None) => {
|
||||
break;
|
||||
}
|
||||
// We received a value before the timeout, so we reset it
|
||||
Ok(_) => {
|
||||
tracing::trace!("Received value before timeout, resetting");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match rx.recv().await {
|
||||
Some(_) => {
|
||||
tracing::trace!("Received event, starting debounce");
|
||||
dirty = true;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("Event channel closed");
|
||||
if let Err(err) = compile(&cfg, &out_path, &archive_path, game_dir.clone()).await {
|
||||
tracing::error!("Failed to build mod archive: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ async fn main() -> Result<()> {
|
|||
.subcommand(cmd::murmur::command_definition())
|
||||
.subcommand(cmd::new::command_definition())
|
||||
.subcommand(cmd::package::command_definition())
|
||||
// .subcommand(cmd::watch::command_definition())
|
||||
.subcommand(cmd::watch::command_definition())
|
||||
.get_matches();
|
||||
|
||||
dtmt_shared::create_tracing_subscriber();
|
||||
|
|
Loading…
Add table
Reference in a new issue