perf: reusing compiled config to save performance and time

This commit is contained in:
Byson94
2025-08-22 22:31:15 +05:30
parent 100a97e27b
commit e1387caad4
6 changed files with 53 additions and 36 deletions

View File

@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Better dynamic system which can handle dyn_id issues and reodering.
- Added `std::math` module for mathematics related tasks.
- Added `propagate_natural_height` property to scroll widget.
- Faster re-evaluation of configuration by reusing compiled configuration.
### Changed

View File

@@ -343,6 +343,7 @@ impl<B: DisplayBackend> App<B> {
// listening/polling
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
let config_path = self.paths.get_rhai_path();
let compiled_ast = self.ewwii_config.get_owned_compiled_ast();
let store = iirhai::updates::handle_state_changes(self.ewwii_config.get_root_node()?, tx);
glib::MainContext::default().spawn_local(async move {
@@ -350,7 +351,7 @@ impl<B: DisplayBackend> App<B> {
log::debug!("Received update for var: {}", var_name);
let vars = store.read().unwrap().clone();
match generate_new_widgetnode(&vars, &config_path).await {
match generate_new_widgetnode(&vars, &config_path, compiled_ast.as_ref()).await {
Ok(new_widget) => {
let _ = widget_reg_store.update_widget_tree(new_widget);
}
@@ -559,7 +560,11 @@ fn initialize_window<B: DisplayBackend>(
})
}
async fn generate_new_widgetnode(all_vars: &HashMap<String, String>, code_path: &Path) -> Result<WidgetNode> {
async fn generate_new_widgetnode(
all_vars: &HashMap<String, String>,
code_path: &Path,
compiled_ast: Option<&rhai::AST>,
) -> Result<WidgetNode> {
let mut scope = Scope::new();
for (name, val) in all_vars {
scope.set_value(name.clone(), Dynamic::from(val.clone()));
@@ -570,7 +575,8 @@ async fn generate_new_widgetnode(all_vars: &HashMap<String, String>, code_path:
}
let mut reeval_parser = iirhai::parser::ParseConfig::new();
let new_root_widget = reeval_parser.eval_file_with(code_path, scope, None);
let rhai_code = reeval_parser.code_from_file(&code_path)?;
let new_root_widget = reeval_parser.eval_code_with(&rhai_code, Some(scope), compiled_ast);
Ok(config::EwwiiConfig::get_windows_root_widget(new_root_widget?)?)
}

View File

@@ -13,7 +13,7 @@ use crate::{
use iirhai::{parser::ParseConfig, widgetnode::WidgetNode};
use rhai::Map;
use rhai::{Map, AST};
// use tokio::{net::UnixStream, runtime::Runtime, sync::mpsc};
@@ -28,6 +28,7 @@ pub fn read_from_ewwii_paths(eww_paths: &EwwPaths) -> Result<EwwiiConfig> {
pub struct EwwiiConfig {
windows: HashMap<String, WindowDefinition>,
root_node: Option<WidgetNode>,
compiled_ast: Option<AST>,
}
#[derive(Debug, Clone)]
@@ -46,9 +47,16 @@ impl EwwiiConfig {
bail!("The configuration file `{}` does not exist", rhai_path.display());
}
// get the iirhai widget tree
// initialize configuration parser
let mut config_parser = ParseConfig::new();
let config_tree = config_parser.eval_file(rhai_path)?;
// get code from file
let rhai_code = config_parser.code_from_file(&rhai_path)?;
// get the iirhai widget tree
let compiled_ast = config_parser.compile_code(&rhai_code)?;
let poll_listen_scope = ParseConfig::initial_poll_listen_scope(&rhai_code)?;
let config_tree = config_parser.eval_code_with(&rhai_code, Some(poll_listen_scope), Some(&compiled_ast))?;
let mut window_definitions = HashMap::new();
@@ -68,7 +76,7 @@ impl EwwiiConfig {
bail!("Expected root node to be `Enter`, but got something else.");
}
Ok(EwwiiConfig { windows: window_definitions, root_node: Some(config_tree) })
Ok(EwwiiConfig { windows: window_definitions, root_node: Some(config_tree), compiled_ast: Some(compiled_ast) })
}
pub fn get_windows(&self) -> &HashMap<String, WindowDefinition> {
@@ -101,4 +109,8 @@ impl EwwiiConfig {
bail!("Expected root node to be `Enter`, but got something else.");
}
}
pub fn get_owned_compiled_ast(&self) -> Option<AST> {
self.compiled_ast.clone()
}
}

View File

@@ -146,7 +146,9 @@ fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()>
ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
tokio::spawn(async move {
match daemon_resp_response.recv().await {
Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
Some(daemon_response::DaemonResponse::Success(_)) => {
log::info!("Reloaded config successfully")
}
Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
None => log::error!("No response to reload configuration-reload request"),
}

View File

@@ -48,7 +48,9 @@ impl From<&MonitorIdentifier> for DynVal {
impl fmt::Display for MonitorIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::List(l) => write!(f, "[{}]", l.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(" ")),
Self::List(l) => {
write!(f, "[{}]", l.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(" "))
}
Self::Numeric(n) => write!(f, "{}", n),
Self::Name(n) => write!(f, "{}", n),
Self::Primary => write!(f, "<primary>"),

View File

@@ -26,36 +26,22 @@ impl ParseConfig {
}
pub fn eval_code(&mut self, code: &str) -> Result<WidgetNode> {
// Setting the initial value of poll/listen
let mut scope = Scope::new();
for (var, initial) in extract_poll_and_listen_vars(code)? {
let value = match initial {
Some(val) => Dynamic::from(val),
None => Dynamic::UNIT,
};
scope.set_value(var, value);
}
self.engine
.eval_with_scope::<WidgetNode>(&mut scope, code)
.map_err(|e| anyhow!(format_rhai_error(&e, code, &self.engine)))
}
pub fn eval_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<WidgetNode> {
let code = fs::read_to_string(&file_path).map_err(|e| anyhow!("Failed to read {:?}: {}", file_path.as_ref(), e))?;
self.eval_code(&code)
}
pub fn compile_code(&mut self, code: &str) -> Result<AST> {
Ok(self.engine.compile(code)?)
}
pub fn compile_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<AST> {
let code = fs::read_to_string(&file_path).map_err(|e| anyhow!("Failed to read {:?}: {}", file_path.as_ref(), e))?;
self.compile_code(&code)
}
pub fn eval_code_with(&mut self, code: &str, rhai_scope: Option<Scope>, compiled_ast: Option<&AST>) -> Result<WidgetNode> {
let mut scope = match rhai_scope {
Some(s) => s,
None => Scope::new(),
};
pub fn eval_code_with(&mut self, code: &str, mut scope: Scope, compiled_ast: Option<AST>) -> Result<WidgetNode> {
match compiled_ast {
Some(ast) => self
.engine
@@ -68,13 +54,21 @@ impl ParseConfig {
}
}
pub fn eval_file_with<P: AsRef<Path>>(
&mut self,
file_path: P,
scope: Scope,
compiled_ast: Option<AST>,
) -> Result<WidgetNode> {
let code = fs::read_to_string(&file_path).map_err(|e| anyhow!("Failed to read {:?}: {}", file_path.as_ref(), e))?;
self.eval_code_with(&code, scope, compiled_ast)
pub fn code_from_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<String> {
Ok(fs::read_to_string(&file_path).map_err(|e| anyhow!("Failed to read {:?}: {}", file_path.as_ref(), e))?)
}
pub fn initial_poll_listen_scope(code: &str) -> Result<Scope> {
// Setting the initial value of poll/listen
let mut scope = Scope::new();
for (var, initial) in extract_poll_and_listen_vars(code)? {
let value = match initial {
Some(val) => Dynamic::from(val),
None => Dynamic::UNIT,
};
scope.set_value(var, value);
}
Ok(scope)
}
}