Refactor subscription table logic and enhance Lua logging and debounce functionality
This commit is contained in:
parent
05123a5989
commit
edb2ba338a
3 changed files with 145 additions and 2 deletions
|
|
@ -35,7 +35,6 @@ impl SubscriptionTable {
|
||||||
// swap_remove moves the last element into `idx`. We need to update by_id
|
// swap_remove moves the last element into `idx`. We need to update by_id
|
||||||
// for that element. But first, remove its stale entry (it was at the last
|
// for that element. But first, remove its stale entry (it was at the last
|
||||||
// position before the swap); then re-insert it at the new position.
|
// position before the swap); then re-insert it at the new position.
|
||||||
let _last_idx = self.entries.len() - 1;
|
|
||||||
self.entries.swap_remove(idx);
|
self.entries.swap_remove(idx);
|
||||||
|
|
||||||
if idx < self.entries.len() {
|
if idx < self.entries.len() {
|
||||||
|
|
|
||||||
|
|
@ -313,9 +313,67 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_filter(event_name: &str, pattern: &str) -> bool {
|
fn matches_filter(event_name: &str, pattern: &str) -> bool {
|
||||||
|
// Delegate to the same glob logic used by the subscription table so that
|
||||||
|
// `bread events --filter "bread.device.**"` behaves identically to
|
||||||
|
// `bread.on("bread.device.**", ...)` in Lua.
|
||||||
if pattern.ends_with(".*") {
|
if pattern.ends_with(".*") {
|
||||||
let prefix = &pattern[..pattern.len() - 1];
|
let prefix = &pattern[..pattern.len() - 1];
|
||||||
return event_name.starts_with(prefix);
|
return event_name.starts_with(prefix);
|
||||||
}
|
}
|
||||||
event_name == pattern
|
|
||||||
|
if let Some(prefix) = pattern.strip_suffix(".**") {
|
||||||
|
if event_name == prefix || event_name.starts_with(&format!("{prefix}.")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches_glob_filter(pattern.as_bytes(), event_name.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_glob_filter(pattern: &[u8], text: &[u8]) -> bool {
|
||||||
|
if pattern.is_empty() {
|
||||||
|
return text.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if pattern.len() >= 2 && pattern[0] == b'*' && pattern[1] == b'*' {
|
||||||
|
let rest = &pattern[2..];
|
||||||
|
if rest.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for offset in 0..=text.len() {
|
||||||
|
if matches_glob_filter(rest, &text[offset..]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match pattern[0] {
|
||||||
|
b'*' => {
|
||||||
|
let mut offset = 0;
|
||||||
|
loop {
|
||||||
|
if matches_glob_filter(&pattern[1..], &text[offset..]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if offset == text.len() || text[offset] == b'.' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
b'?' => {
|
||||||
|
if text.is_empty() || text[0] == b'.' {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
matches_glob_filter(&pattern[1..], &text[1..])
|
||||||
|
}
|
||||||
|
ch => {
|
||||||
|
if text.first().copied() != Some(ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
matches_glob_filter(&pattern[1..], &text[1..])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -840,6 +840,8 @@ impl LuaEngine {
|
||||||
globals.set("bread", bread)?;
|
globals.set("bread", bread)?;
|
||||||
self.install_require_loader()?;
|
self.install_require_loader()?;
|
||||||
self.install_wait_helper()?;
|
self.install_wait_helper()?;
|
||||||
|
self.install_log_helpers()?;
|
||||||
|
self.install_debounce()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1188,6 +1190,90 @@ impl LuaEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn install_log_helpers(&self) -> Result<()> {
|
||||||
|
// bread.log(msg) → tracing::info
|
||||||
|
// bread.warn(msg) → tracing::warn
|
||||||
|
// bread.error(msg) → tracing::error
|
||||||
|
//
|
||||||
|
// Each accepts any Lua value and coerces it to a string via tostring()
|
||||||
|
// so callers can do bread.log(some_table) without a crash.
|
||||||
|
self.lua.load(r#"
|
||||||
|
local _bread = bread
|
||||||
|
|
||||||
|
local function stringify(v)
|
||||||
|
if type(v) == "string" then
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return tostring(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _bread.log(msg)
|
||||||
|
_bread.__log_info(stringify(msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
function _bread.warn(msg)
|
||||||
|
_bread.__log_warn(stringify(msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
function _bread.error(msg)
|
||||||
|
_bread.__log_error(stringify(msg))
|
||||||
|
end
|
||||||
|
"#).exec()?;
|
||||||
|
|
||||||
|
// Register the raw Rust-backed log functions that the Lua wrappers call.
|
||||||
|
let globals = self.lua.globals();
|
||||||
|
let bread: mlua::Table = globals.get("bread")?;
|
||||||
|
|
||||||
|
let info_fn = self.lua.create_function(|_, msg: String| {
|
||||||
|
tracing::info!(target: "bread.lua", "{}", msg);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
bread.set("__log_info", info_fn)?;
|
||||||
|
|
||||||
|
let warn_fn = self.lua.create_function(|_, msg: String| {
|
||||||
|
tracing::warn!(target: "bread.lua", "{}", msg);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
bread.set("__log_warn", warn_fn)?;
|
||||||
|
|
||||||
|
let error_fn = self.lua.create_function(|_, msg: String| {
|
||||||
|
tracing::error!(target: "bread.lua", "{}", msg);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
bread.set("__log_error", error_fn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_debounce(&self) -> Result<()> {
|
||||||
|
// bread.debounce(delay_ms, fn) → wrapped_fn
|
||||||
|
//
|
||||||
|
// Returns a new function. When that function is called, it resets a
|
||||||
|
// timer. The original function is only called once the timer expires
|
||||||
|
// without being reset. Useful for rapid hardware events (e.g. monitor
|
||||||
|
// topology changes that fire multiple events in quick succession).
|
||||||
|
//
|
||||||
|
// Because the Lua runtime is single-threaded, we implement this in
|
||||||
|
// pure Lua using bread.cancel / bread.after.
|
||||||
|
self.lua.load(r#"
|
||||||
|
function bread.debounce(delay_ms, fn)
|
||||||
|
local timer_id = nil
|
||||||
|
return function(...)
|
||||||
|
local args = { ... }
|
||||||
|
if timer_id then
|
||||||
|
bread.cancel(timer_id)
|
||||||
|
timer_id = nil
|
||||||
|
end
|
||||||
|
timer_id = bread.after(delay_ms, function()
|
||||||
|
timer_id = nil
|
||||||
|
fn(table.unpack(args))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"#).exec()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn scan_module_decl(&self, path: &Path) -> Result<ModuleDecl> {
|
fn scan_module_decl(&self, path: &Path) -> Result<ModuleDecl> {
|
||||||
const MODULE_DECL_ABORT: &str = "__bread_module_decl__";
|
const MODULE_DECL_ABORT: &str = "__bread_module_decl__";
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue