From 9cf41ad6ec6ec66d5fcf3689b88086b11e8aa1e9 Mon Sep 17 00:00:00 2001 From: Joe Bellus Date: Tue, 18 Oct 2022 01:15:36 +0000 Subject: [PATCH] Output Threading (#3) Output processing in the app layer is now done via commands. The script parsing takes place in a separate thread to prevent UI blocking. Output::Scalar was changed to a string. This value was always convereted to a string and rhai::Dynamic was not Send. Since the output is passed between threads now it was convereted to a string. --- abacus-core/src/dataframe.rs | 2 +- abacus-core/src/engine.rs | 8 +++-- abacus-ui/src/app_delegate.rs | 55 ++++++++++++++++++++++++++++++++--- abacus-ui/src/app_header.rs | 4 ++- abacus-ui/src/commands.rs | 3 ++ abacus-ui/src/output_block.rs | 34 +++++++++------------- 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/abacus-core/src/dataframe.rs b/abacus-core/src/dataframe.rs index d8edf37..cae4c7d 100644 --- a/abacus-core/src/dataframe.rs +++ b/abacus-core/src/dataframe.rs @@ -583,7 +583,7 @@ mod tests { #[test] pub fn series_index() { let s = process(r#"series("ages", [18, 21, 25, 35]).sort(true)[1]"#).into_scalar(); - assert_eq!(s.cast::(), 25); + assert_eq!(s, "25"); } #[test] diff --git a/abacus-core/src/engine.rs b/abacus-core/src/engine.rs index 6735121..3e89a38 100644 --- a/abacus-core/src/engine.rs +++ b/abacus-core/src/engine.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::dataframe; #[derive(Debug)] @@ -34,7 +36,7 @@ impl<'a> Engine<'a> { let series = rhai::Dynamic::cast::(res); Output::Series(series) } - Ok(res) => Output::Scalar(res), + Ok(res) => Output::Scalar(res.to_string()), Err(e) => Output::Error(e.to_string()), } } @@ -43,7 +45,7 @@ impl<'a> Engine<'a> { #[derive(Debug, Clone)] pub enum Output { None, - Scalar(rhai::Dynamic), + Scalar(String), DataFrame(dataframe::DataFrame), Series(dataframe::Series), Error(String), @@ -80,7 +82,7 @@ impl Output { } panic!("Not a series"); } - pub fn into_scalar(self) -> rhai::Dynamic { + pub fn into_scalar(self) -> String { if let Self::Scalar(v) = self { return v; } diff --git a/abacus-ui/src/app_delegate.rs b/abacus-ui/src/app_delegate.rs index 3739476..8f84505 100644 --- a/abacus-ui/src/app_delegate.rs +++ b/abacus-ui/src/app_delegate.rs @@ -1,4 +1,5 @@ -use druid::AppDelegate; +use abacus_core::Engine; +use druid::{AppDelegate, Target}; use crate::{ commands, @@ -10,7 +11,7 @@ pub struct Delegate; impl AppDelegate for Delegate { fn command( &mut self, - _ctx: &mut druid::DelegateCtx, + ctx: &mut druid::DelegateCtx, _target: druid::Target, cmd: &druid::Command, data: &mut AppData, @@ -43,13 +44,59 @@ impl AppDelegate for Delegate { } if cmd.is(commands::PROCESS_WORKBOOK) { - data.process(); + let scripts = data + .blocks + .iter() + .enumerate() + .map(|(idx, b)| (idx, b.editor_data.content.to_string())) + .collect::>(); + + let sink = ctx.get_external_handle(); + std::thread::spawn(move || { + for (idx, script) in scripts.iter() { + let mut engine = Engine::default(); + let output = engine.process_script(script); + let _ = sink.submit_command( + crate::commands::BLOCK_PROCESSED, + (*idx, output), + Target::Auto, + ); + } + }); + return druid::Handled::Yes; } if cmd.is(commands::PROCESS_BLOCK) { if let Some(index) = cmd.get(commands::PROCESS_BLOCK) { - data.process_block(*index); + let index = *index; + let script = data + .blocks + .get(index) + .map(|b| b.editor_data.content.to_string()); + + if let Some(script) = script { + let sink = ctx.get_external_handle(); + std::thread::spawn(move || { + let mut engine = Engine::default(); + let output = engine.process_script(&script); + let _ = sink.submit_command( + crate::commands::BLOCK_PROCESSED, + (index, output), + Target::Auto, + ); + }); + } + + return druid::Handled::Yes; + } + } + + if cmd.is(commands::BLOCK_PROCESSED) { + if let Some((idx, output)) = cmd.get(commands::BLOCK_PROCESSED) { + if let Some(block) = data.blocks.get_mut(*idx) { + block.output = output.clone(); + } return druid::Handled::Yes; } } diff --git a/abacus-ui/src/app_header.rs b/abacus-ui/src/app_header.rs index dbbb2bc..a8ecdfd 100644 --- a/abacus-ui/src/app_header.rs +++ b/abacus-ui/src/app_header.rs @@ -114,7 +114,9 @@ pub fn app_header_ui() -> impl Widget { .with_child( Container::new(Padding::new(10.0, Svg::new(run_svg).fix_width(10.0))) .controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50))) - .on_click(|_ctx, data: &mut AppData, _env| data.process()), + .on_click(|ctx, _data: &mut AppData, _env| { + ctx.submit_command(crate::commands::PROCESS_WORKBOOK); + }), ) .with_spacer(20.0) .expand_width(), diff --git a/abacus-ui/src/commands.rs b/abacus-ui/src/commands.rs index a12184e..4ee700c 100644 --- a/abacus-ui/src/commands.rs +++ b/abacus-ui/src/commands.rs @@ -1,3 +1,4 @@ +use abacus_core::Output; use druid::Selector; pub const PROCESS_WORKBOOK: Selector<()> = Selector::new("process-workbook"); @@ -7,3 +8,5 @@ pub const DELETE_BLOCK: Selector = Selector::new("delete-block"); pub const RENAME_BLOCK: Selector = Selector::new("rename-block"); pub const CLOSE_MODAL: Selector<()> = Selector::new("close-modal"); + +pub const BLOCK_PROCESSED: Selector<(usize, Output)> = Selector::new("block-processed"); diff --git a/abacus-ui/src/output_block.rs b/abacus-ui/src/output_block.rs index 04661bf..8d433f5 100644 --- a/abacus-ui/src/output_block.rs +++ b/abacus-ui/src/output_block.rs @@ -12,26 +12,18 @@ pub fn output_block() -> impl Widget { ViewSwitcher::new( |data: &Block, _env| data.clone(), |selector: &Block, _data, _env| match &selector.output { - Output::Scalar(v) => { - let str = match v { - _ if v.is::() => v.clone().cast::(), - _ if v.is::<&str>() => v.clone().cast::<&str>().to_string(), - v => v.to_string(), - }; - Box::new(Padding::new( - 25.0, - Label::new(str) - .with_font( - FontDescriptor::new(FontFamily::MONOSPACE) - .with_weight(FontWeight::BOLD), - ) - .with_text_size(OUTPUT_FONT_SIZE) - .padding(10.0) - .background(Color::rgb8(30, 30, 30)) - .rounded(4.0) - .expand_width(), - )) - } + Output::Scalar(str) => Box::new(Padding::new( + 25.0, + Label::new(str.clone()) + .with_font( + FontDescriptor::new(FontFamily::MONOSPACE).with_weight(FontWeight::BOLD), + ) + .with_text_size(OUTPUT_FONT_SIZE) + .padding(10.0) + .background(Color::rgb8(30, 30, 30)) + .rounded(4.0) + .expand_width(), + )), Output::Error(e) => Box::new(Padding::new( 25.0, Label::new(e.to_string()) @@ -87,7 +79,7 @@ pub fn output_block() -> impl Widget { .background(Color::rgb8(40, 40, 40)), ); - for v in series.iter() { + for v in series.iter().take(20) { col.add_child( Label::new(format_dataframe_value(v)) .with_font(