diff --git a/Cargo.lock b/Cargo.lock index 0e9a1ac..efc2932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,7 @@ dependencies = [ "euclid", "femtovg", "flate2", + "futures", "geo", "geo-macros", "geo-types", @@ -519,6 +520,8 @@ dependencies = [ "proj5", "quadtree_rs", "radarg_plugin_interface", + "rayon", + "regex", "relm4", "relm4-components", "relm4-icons", @@ -526,6 +529,7 @@ dependencies = [ "serde", "serde_json", "shapefile", + "smallvec", "surfman", "svg", "thiserror", @@ -2160,9 +2164,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -3077,9 +3081,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -3087,14 +3091,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -3137,9 +3139,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -3148,9 +3162,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "relm4" @@ -3466,9 +3480,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "smithay-client-toolkit" diff --git a/Cargo.toml b/Cargo.toml index 0be5082..e29ff2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,10 @@ serde_json = "1.0.112" flate2 = "1.0.28" toml = "0.8.8" dirs = "5.0.1" +regex = "1.10.3" +smallvec = "1.13.1" +rayon = "1.8.1" +futures = "0.3.30" [build-dependencies] diff --git a/src/chart/mod.rs b/src/chart/mod.rs index 735ee91..ff7f51d 100644 --- a/src/chart/mod.rs +++ b/src/chart/mod.rs @@ -1,7 +1,7 @@ mod backend; mod imp; use self::backend::CairoBackend; -use crate::render::{Render, RenderConfig, RenderMotion}; +use crate::widgets::render::{Render, RenderConfig, RenderMotion}; use glib::{clone, ObjectExt}; use gtk::prelude::*; use gtk::{glib, AspectFrame}; diff --git a/src/components/app.rs b/src/components/app.rs index 3a9a5ab..8b199dc 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -1,7 +1,17 @@ +use std::collections::HashMap; + use crate::{ + coords::{proj::Mercator, Mapper}, data::{self, CoordType, Radar2d}, + pipeline::{ + offscreen_renderer::OffscreenRenderer, pool::Pool, render_pipeline::RenderResult, + utils::Pipeline, + }, plugin_system::init_plugin, - render::{predefined::color_mapper::BoundaryNorm, Layer}, + widgets::{ + render::{predefined::color_mapper::BoundaryNorm, Layer}, + CMS, + }, PLUGIN_MANAGER, }; @@ -22,7 +32,7 @@ use ndarray::{Array1, Array2, Array3}; use radarg_plugin_interface::{Block, DataShape, PluginId, VecResult}; use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup}; use relm4::*; -use relm4::{gtk, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; +use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; use relm4_components::open_dialog::{ OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings, }; @@ -42,12 +52,19 @@ pub enum AppMsg { pub struct AppModel { open_dialog: Controller, control: Controller, + target_pipeline: HashMap, render: Controller, setting: Controller, } +#[derive(Debug)] +pub enum AppCommand { + Prepare, +} + #[relm4::component(pub)] -impl SimpleComponent for AppModel { +impl Component for AppModel { + type CommandOutput = AppCommand; type Init = (); type Input = AppMsg; type Output = (); @@ -143,6 +160,7 @@ impl SimpleComponent for AppModel { let model = AppModel { open_dialog: dialog, + target_pipeline: HashMap::new(), control, render, setting, @@ -174,7 +192,6 @@ impl SimpleComponent for AppModel { let page_home = view_stack.add_titled(&page_home, Some("renderer"), "Render"); page_home.set_icon_name(Some("home-filled")); - let page_setting = model.setting.widget(); let page_setting = view_stack.add_titled(page_setting, Some("setting"), "Setting"); @@ -192,10 +209,39 @@ impl SimpleComponent for AppModel { ComponentParts { model, widgets } } - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { + fn update(&mut self, msg: Self::Input, _sender: ComponentSender, root: &Self::Root) { match msg { AppMsg::OpenFile((time, layer)) => { - self.render.sender().emit(MonitorInputMsg::AddLayer(layer)); + if self.target_pipeline.contains_key(&layer.name) { + } else { + let mut pipeline = Pipeline::new(10); + pipeline.add_task( + time.timestamp(), + Box::pin(async move { + let mut offscreen_render = OffscreenRenderer::new(3000, 3000).unwrap(); + let canvas_wrapper = offscreen_render.create_canvas(); + let canvas_mutex = + std::sync::Arc::new(std::sync::Mutex::new(canvas_wrapper)); + let f = { + let p = layer.get_prepare(); + let mut _p = p.lock().unwrap(); + _p.take() + }; + let target = if let Some(f) = f { + let imp = layer.get_imp().unwrap(); + let map: Mapper = Mercator::default().into(); + let cms = CMS::new(map, (3000.0, 3000.0)); + let canvas = canvas_mutex.clone(); + let c = f(imp, canvas, cms).await; + Some(c) + } else { + None + }; + RenderResult::new(canvas_mutex, target.unwrap(), time) + }), + ); + } + // self.render.sender().emit(MonitorInputMsg::AddLayer(layer)); self.control .sender() .emit(ControlPanelInputMsg::Selection(Some(time))); diff --git a/src/components/control_panel/control_panel.rs b/src/components/control_panel/control_panel.rs index 50cd62d..3f315c2 100644 --- a/src/components/control_panel/control_panel.rs +++ b/src/components/control_panel/control_panel.rs @@ -1,9 +1,9 @@ use super::messages::*; use crate::data::{CoordType, Radar2d, RadarData2d}; use crate::plugin_system::init_plugin; -use crate::render::predefined::color_mapper::BoundaryNorm; -use crate::render::Layer; -use crate::timeline::TimeLine; +use crate::widgets::render::predefined::color_mapper::BoundaryNorm; +use crate::widgets::render::Layer; +use crate::widgets::timeline::{Selection, TimeLine}; use abi_stable::std_types::RStr; use adw::prelude::*; use chrono::{prelude::*, DateTime, Duration, TimeZone, Utc}; @@ -145,7 +145,7 @@ impl SimpleComponent for ControlPanelModel { #[track = "model.changed(ControlPanelModel::timeline_start())"] set_time_start: model.timeline_start, #[track = "model.changed(ControlPanelModel::selection())"] - set_selection: model.selection.map(|p| crate::timeline::Selection::Point(p)), + set_selection: model.selection.map(|p| Selection::Point(p)), connect_start_time_notify[sender] => move |time| { let time = time.start_time(); sender.input( diff --git a/src/components/monitor/messages.rs b/src/components/monitor/messages.rs index b735513..70a3dcb 100644 --- a/src/components/monitor/messages.rs +++ b/src/components/monitor/messages.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::render::Layer; +use crate::widgets::render::Layer; pub enum MonitorInputMsg { AddLayer(Layer), @@ -18,7 +18,6 @@ impl Debug for MonitorInputMsg { MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"), } } - } #[derive(Debug)] diff --git a/src/components/monitor/monitor.rs b/src/components/monitor/monitor.rs index 8a22cfe..535846d 100644 --- a/src/components/monitor/monitor.rs +++ b/src/components/monitor/monitor.rs @@ -1,10 +1,10 @@ use super::messages::{MonitorInputMsg, MonitorOutputMsg}; use crate::pipeline::offscreen_renderer::OffscreenRenderer; -use crate::render::{RenderConfig, Target, CMS}; +use crate::widgets::render::{RenderConfig, Target, CMS}; use crate::{ coords::{proj::Mercator, Mapper}, - dynamic_col::DynamicCol, - render::{Layer, Render}, + widgets::dynamic_col::DynamicCol, + widgets::render::{Layer, Render}, }; use glib::clone; use std::sync::{Arc, Mutex}; diff --git a/src/components/monitor/sidebar/sidebar.rs b/src/components/monitor/sidebar/sidebar.rs index 174b8ce..f8df41a 100644 --- a/src/components/monitor/sidebar/sidebar.rs +++ b/src/components/monitor/sidebar/sidebar.rs @@ -12,7 +12,7 @@ use relm4::{ use crate::{ chart::Chart, data::Npz, - render::{predefined::color_mapper::BoundaryNorm, Layer}, + widgets::render::{predefined::color_mapper::BoundaryNorm, Layer}, }; use super::bottom_bar::BottomBarModel; diff --git a/src/components/setting/setting.rs b/src/components/setting/setting.rs index c55f82f..84192d9 100644 --- a/src/components/setting/setting.rs +++ b/src/components/setting/setting.rs @@ -1,6 +1,6 @@ use crate::{ data::{self, CoordType, Radar2d}, - render::{predefined::color_mapper::BoundaryNorm, Layer}, + widgets::render::{predefined::color_mapper::BoundaryNorm, Layer}, PLUGIN_MANAGER, }; use adw::prelude::*; @@ -29,7 +29,9 @@ impl SimpleComponent for SettingModel { #[wrap(Some)] set_sidebar=&adw::NavigationPage{ #[wrap(Some)] - set_child=>k::StackSidebar{} + set_child=>k::StackSidebar{ + set_stack:&stack, + } }, #[wrap(Some)] set_content=&adw::NavigationPage{ @@ -59,7 +61,19 @@ impl SimpleComponent for SettingModel { let general_page = gtk::Box::new(gtk::Orientation::Vertical, 10); let plugin_page = gtk::Box::new(gtk::Orientation::Vertical, 10); + let preferences = adw::PreferencesPage::new(); + let group = adw::PreferencesGroup::new(); + group.set_title("Plugins"); + let entry_row = adw::EntryRow::new(); + entry_row.set_title("Plugin Path"); + group.add(&entry_row); + preferences.add(&group); + preferences.set_parent(&plugin_page); + let widgets = view_output!(); + let stack = widgets.stack.clone(); + + stack.add_titled(&plugin_page, None, "Plugins"); ComponentParts { model, widgets } } diff --git a/src/coords/mapper.rs b/src/coords/mapper.rs index 4011998..e9dbc90 100644 --- a/src/coords/mapper.rs +++ b/src/coords/mapper.rs @@ -1,4 +1,4 @@ -use crate::render::WindowCoord; +use crate::widgets::render::WindowCoord; use std::borrow::ToOwned; use super::{proj::ProjectionS, Range}; @@ -13,7 +13,6 @@ pub struct Mapper { unsafe impl Send for Mapper {} unsafe impl Sync for Mapper {} - impl Clone for Mapper { fn clone(&self) -> Self { diff --git a/src/dealers/layer_handler.rs b/src/dealers/layer_handler.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/dealers/mod.rs b/src/dealers/mod.rs deleted file mode 100644 index f1b1cec..0000000 --- a/src/dealers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod layer_handler; diff --git a/src/errors.rs b/src/errors.rs index e15b887..549f22b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -47,3 +47,21 @@ pub enum PluginError { #[error("")] PluginError, } + + +#[derive(Debug, Error)] +pub enum PoolError { + #[error("")] + TimestampError, + #[error("Data Pool is full")] + PoolFull, + #[error("Data Pool is not initialized")] + PoolInitialized(&'static str), +} + + +#[derive(Debug, Error)] +pub enum RenderError { + #[error("")] + PreRenderError(#[from] femtovg::ErrorKind), +} diff --git a/src/main.rs b/src/main.rs index 25d71e8..04539c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,17 +11,13 @@ mod components; mod config; mod coords; mod data; -mod dealers; -mod dynamic_col; mod errors; mod pipeline; mod plugin_system; -mod render; -mod timeline; -mod window; use components::app::AppModel; use once_cell::sync::Lazy; use std::sync::Mutex; +mod widgets; const APP_ID: &str = "org.tsuki.radar_g"; diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index a63b625..02c95a4 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -6,7 +6,9 @@ use ndarray::parallel::prelude::*; use ndarray::{Array2, ArrayView2}; use num_traits::{Num, AsPrimitive, FromPrimitive}; pub mod offscreen_renderer; -mod utils; +pub mod utils; +pub mod pool; +pub mod render_pipeline; use crate::{ coords::Mapper, diff --git a/src/pipeline/pool.rs b/src/pipeline/pool.rs new file mode 100644 index 0000000..45e7ff3 --- /dev/null +++ b/src/pipeline/pool.rs @@ -0,0 +1,161 @@ +use crate::errors::PoolError; +use smallvec::SmallVec; +use std::collections::VecDeque; + +type PResult = Result; + +pub struct Pool { + items: VecDeque<(i64, T)>, + current: Option<(i64, usize)>, + len: usize, +} + +impl Pool { + pub fn new(len: usize) -> Self { + Pool { + items: VecDeque::new(), + current: None, + len, + } + } + + pub fn init(&mut self, timestamp: i64, item: T) -> PResult<()> { + let len = self.items.len(); + + if len == 0 { + self.items.push_back((timestamp, item)); + self.current.replace((timestamp, 0)); + return Ok(()); + } else { + return Err(PoolError::PoolInitialized("Pool is already initialized")); + } + } + + pub fn add(&mut self, item: T, timestamp: i64) -> PResult<()> { + let len = self.items.len(); + if len == self.len { + return Err(PoolError::PoolFull); + } + if len == 0 { + return Err(PoolError::PoolInitialized("Pool is not initialized")); + } + if len == 1 { + if self.items[0].0 < timestamp { + self.items.push_back((timestamp, item)); + } else { + self.items.push_front((timestamp, item)); + self.current.replace((timestamp, 1)); + } + return Ok(()); + } + + let back = self + .items + .back() + .map(|(last_timestamp, _)| *last_timestamp < timestamp) + .unwrap(); + let front = self + .items + .front() + .map(|(first_timestamp, _)| *first_timestamp > timestamp) + .unwrap(); + + if !(back || front) { + return Err(PoolError::TimestampError); + } + + if back { + self.items.push_back((timestamp, item)); + } else { + self.items.push_front((timestamp, item)); + self.current.as_mut().map(|(_, index)| *index += 1); + } + + Ok(()) + } + + pub fn prev(&self) -> Option<&T> { + self.current + .map(|(_, index)| &self.items[index]) + .map(|(_, item)| item) + } + + pub fn next(&mut self) -> Option<&T> { + let len = self.items.len(); + let current = self.current.as_mut().map(|(_, index)| index); + + if let Some(index) = current { + if *index + 1 < len { + *index += 1; + self.current + .as_ref() + .map(|(_, index)| &self.items[*index]) + .map(|(_, item)| item) + } else { + None + } + } else { + None + } + } + + pub fn get(&self, index: isize) -> Option<&T> { + self.current + .as_ref() + .map(|(_, current_index)| *current_index as isize + index) + .and_then(|index| { + if index >= 0 && index < self.items.len() as isize { + Some(&self.items[index as usize]) + } else { + None + } + }) + .map(|(_, item)| item) + } + + pub fn get_mut(&mut self, index: isize) -> Option<&mut T> { + self.current + .as_ref() + .map(|(_, current_index)| *current_index as isize + index) + .and_then(|index| { + if index >= 0 && index < self.items.len() as isize { + Some(&mut self.items[index as usize]) + } else { + None + } + }) + .map(|(_, item)| item) + } + + pub fn iter(&self) -> impl Iterator { + self.items.iter().map(|(_, item)| item) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.items.iter_mut().map(|(_, item)| item) + } + + pub fn set_current(&mut self, timestamp: i64, item: T) -> PResult<()> { + if self.items.len() == 0 { + return Err(PoolError::PoolInitialized("Pool is not initialized")); + } + + let position = self.items.iter().position(|(time, _)| *time == timestamp); + + if let Some(p) = position { + let start = (p - self.len / 2).max(0); + let end = (p + self.len / 2).min(self.items.len() - 1); + self.items = self.items.drain(start..end).collect(); + self.current.replace((timestamp, self.len / 2)); + return Ok(()); + } else { + self.items.clear(); + self.init(timestamp, item); + return Ok(()); + } + } + + pub fn len(&self) -> usize { + self.items.len() + } +} diff --git a/src/pipeline/render_pipeline.rs b/src/pipeline/render_pipeline.rs new file mode 100644 index 0000000..fb10901 --- /dev/null +++ b/src/pipeline/render_pipeline.rs @@ -0,0 +1,38 @@ +use chrono::prelude::*; +use femtovg::{renderer::OpenGl, Canvas, ImageId}; +use std::sync::{Arc, Mutex}; + +use crate::widgets::{Target, TargetType}; + +use super::offscreen_renderer::CanvasWrapper; + +#[derive(Clone)] +pub struct RenderResult { + canvas: Arc>, + img: Target, + time: DateTime, +} + +impl Drop for RenderResult { + fn drop(&mut self) { + let mut canvas = self.canvas.lock().unwrap(); + if let TargetType::ImageId(img) = self.img.target { + canvas.delete_image(img); + } + let _ = self; + } +} + +impl RenderResult { + pub fn new(canvas: Arc>, img: Target, time: DateTime) -> Self { + Self { canvas, img, time } + } + + pub fn timestamp(&self) -> i64 { + self.time.timestamp() + } + + pub fn target(&self) -> Target { + self.img.clone() + } +} diff --git a/src/pipeline/utils.rs b/src/pipeline/utils.rs index 210025b..ed35010 100644 --- a/src/pipeline/utils.rs +++ b/src/pipeline/utils.rs @@ -1,171 +1,116 @@ -// examples/common/mod.rs -// -// OpenGL convenience wrappers used in the examples. - -use gl; -use gl::types::{GLchar, GLenum, GLint, GLuint}; -use std::fs::File; -use std::io::Read; -use std::os::raw::c_void; -use std::ptr; -use surfman::GLApi; - -pub struct Program { - pub object: GLuint, - #[allow(dead_code)] - vertex_shader: Shader, - #[allow(dead_code)] - fragment_shader: Shader, -} - -impl Program { - pub fn new(vertex_shader: Shader, fragment_shader: Shader) -> Program { - unsafe { - let program = gl::CreateProgram(); - ck(); - gl::AttachShader(program, vertex_shader.object); - ck(); - gl::AttachShader(program, fragment_shader.object); - ck(); - gl::LinkProgram(program); - ck(); - Program { - object: program, - vertex_shader, - fragment_shader, - } - } - } -} - -pub struct Shader { - object: GLuint, -} - -impl Shader { - pub fn new( - name: &str, - kind: ShaderKind, - gl_api: GLApi, - gl_texture_target: GLenum, - resource_loader: &dyn ResourceLoader, - ) -> Shader { - let mut source = vec![]; - match gl_api { - GLApi::GL => source.extend_from_slice(b"#version 330\n"), - GLApi::GLES => source.extend_from_slice(b"#version 300 es\n"), - } - match gl_texture_target { - gl::TEXTURE_2D => {} - gl::TEXTURE_RECTANGLE => source.extend_from_slice(b"#define SAMPLER_RECT\n"), - _ => {} - } - resource_loader.slurp(&mut source, &format!("{}.{}.glsl", name, kind.extension())); - - unsafe { - let shader = gl::CreateShader(kind.to_gl()); - ck(); - gl::ShaderSource( - shader, - 1, - &(source.as_ptr() as *const GLchar), - &(source.len() as GLint), - ); - ck(); - gl::CompileShader(shader); - ck(); - - let mut compile_status = 0; - gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut compile_status); - ck(); - if compile_status != gl::TRUE as GLint { - let mut info_log_length = 0; - gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut info_log_length); - let mut info_log = vec![0; info_log_length as usize + 1]; - gl::GetShaderInfoLog( - shader, - info_log_length, - ptr::null_mut(), - info_log.as_mut_ptr() as *mut _, - ); - eprintln!( - "Failed to compile shader:\n{}", - String::from_utf8_lossy(&info_log) - ); - panic!("Shader compilation failed!"); - } - debug_assert_eq!(compile_status, gl::TRUE as GLint); - - Shader { object: shader } - } - } -} - -pub struct Buffer { - pub object: GLuint, -} - -impl Buffer { - pub fn from_data(data: &[u8]) -> Buffer { - unsafe { - let mut buffer = 0; - gl::GenBuffers(1, &mut buffer); - ck(); - gl::BindBuffer(gl::ARRAY_BUFFER, buffer); - ck(); - gl::BufferData( - gl::ARRAY_BUFFER, - data.len() as isize, - data.as_ptr() as *const c_void, - gl::STATIC_DRAW, - ); - ck(); - Buffer { object: buffer } - } - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum ShaderKind { - Vertex, - Fragment, -} - -impl ShaderKind { - fn extension(self) -> &'static str { - match self { - ShaderKind::Vertex => "vs", - ShaderKind::Fragment => "fs", - } - } - - fn to_gl(self) -> GLenum { - match self { - ShaderKind::Vertex => gl::VERTEX_SHADER, - ShaderKind::Fragment => gl::FRAGMENT_SHADER, - } - } -} - -pub trait ResourceLoader { - fn slurp(&self, dest: &mut Vec, filename: &str); -} - -#[allow(dead_code)] -pub struct FilesystemResourceLoader; - -impl ResourceLoader for FilesystemResourceLoader { - fn slurp(&self, dest: &mut Vec, filename: &str) { - let path = format!("resources/examples/{}", filename); - File::open(&path) - .expect("Failed to open file!") - .read_to_end(dest) - .unwrap(); - } -} +use super::pool::Pool; +use super::render_pipeline::RenderResult; +use crate::errors::RenderError; +use chrono::{prelude::*, Duration}; +use regex::Regex; +use smallvec::SmallVec; +use std::{ + collections::{HashMap, VecDeque}, + future::Future, + pin::Pin, +}; +use tokio::sync::{oneshot, Mutex}; pub fn ck() { unsafe { debug_assert_eq!(gl::GetError(), gl::NO_ERROR); } -} \ No newline at end of file +} + +const DATATIME_FORMAT: regex::Regex = Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap(); + +pub struct Pipeline { + pool: VecDeque>>>>, + switcher: Pool>, + results: Mutex>, +} + +impl Pipeline { + pub fn new(len: usize) -> Self { + Self { + pool: VecDeque::new(), + switcher: Pool::new(len), + results: Mutex::new(SmallVec::new()), + } + } + + pub async fn run(&mut self) { + while let Some(task) = self.pool.pop_front() { + let res = task.await; + if let Some(res) = res { + self.results.lock().await.push(res); + } + } + } + + pub fn add_task(&mut self, timestamp: i64, task: Pin>>) { + let (mut tx, rx) = oneshot::channel::(); + + let future = async move { + tokio::select! { + res = task => Some(res), + _ = tx.closed() => { + println!("task canceled"); + None + }, + } + }; + self.pool.push_back(Box::pin(future)); + self.switcher.add(rx, timestamp); + } + + pub fn cancel_task(&mut self, timestamp: i64) {} +} + +pub struct Dispatcher { + datetime: DateTime, + path_format: HashMap, + fore_len: usize, + back_len: usize, + step: Duration, +} + +impl Dispatcher { + pub fn new(fore_len: usize, back_len: usize, step: Duration) -> Self { + Self { + datetime: Utc::now(), + path_format: HashMap::new(), + fore_len, + back_len, + step, + } + } + + pub fn set_path_format(&mut self, formats: HashMap) { + self.path_format = formats; + } + + pub fn get_path( + &self, + name: &str, + current_time: DateTime, + check_existed: bool, + ) -> Option { + self.path_format.get(name).map(|s| { + let mut path = s.clone(); + let need_formated = DATATIME_FORMAT.captures_iter(&path); + + let mut fore = self.fore_len; + let mut back = self.back_len; + for f in 1..fore + 1 { + let t = current_time - self.step * f as i32; + for need_format in need_formated { + let t = t.format(&need_format[0]).to_string(); + path = path.replace(&need_format[0], &t); + } + + if check_existed { + if std::path::Path::new(&path).exists() { + fore = f; + break; + } + } + } + }) + } +} diff --git a/src/utils.rs b/src/utils.rs index 0c811e2..c34cbbb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,7 +11,7 @@ use surfman::{ GLVersion, NativeConnection, NativeContext, SurfaceAccess, SurfaceType, }; -use crate::render::predefined::color_mapper::BoundaryNorm; +use crate::widgets::render::predefined::color_mapper::BoundaryNorm; pub fn meshgrid(x: ArrayView1, y: ArrayView1) -> (Array2, Array2) where diff --git a/src/dynamic_col/custom_layout/imp.rs b/src/widgets/dynamic_col/custom_layout/imp.rs similarity index 100% rename from src/dynamic_col/custom_layout/imp.rs rename to src/widgets/dynamic_col/custom_layout/imp.rs diff --git a/src/dynamic_col/custom_layout/mod.rs b/src/widgets/dynamic_col/custom_layout/mod.rs similarity index 100% rename from src/dynamic_col/custom_layout/mod.rs rename to src/widgets/dynamic_col/custom_layout/mod.rs diff --git a/src/dynamic_col/imp.rs b/src/widgets/dynamic_col/imp.rs similarity index 100% rename from src/dynamic_col/imp.rs rename to src/widgets/dynamic_col/imp.rs diff --git a/src/dynamic_col/mod.rs b/src/widgets/dynamic_col/mod.rs similarity index 100% rename from src/dynamic_col/mod.rs rename to src/widgets/dynamic_col/mod.rs diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs new file mode 100644 index 0000000..f768cdc --- /dev/null +++ b/src/widgets/mod.rs @@ -0,0 +1,7 @@ +pub mod dynamic_col; +pub mod render; +pub mod timeline; + +pub use dynamic_col::*; +pub use render::*; +pub use timeline::*; diff --git a/src/render/cms.rs b/src/widgets/render/cms.rs similarity index 100% rename from src/render/cms.rs rename to src/widgets/render/cms.rs diff --git a/src/render/exterior/imp.rs b/src/widgets/render/exterior/imp.rs similarity index 100% rename from src/render/exterior/imp.rs rename to src/widgets/render/exterior/imp.rs diff --git a/src/render/exterior/mod.rs b/src/widgets/render/exterior/mod.rs similarity index 98% rename from src/render/exterior/mod.rs rename to src/widgets/render/exterior/mod.rs index 4bf2391..60216a4 100644 --- a/src/render/exterior/mod.rs +++ b/src/widgets/render/exterior/mod.rs @@ -1,5 +1,6 @@ mod imp; -use crate::{coords::Range, render::Render}; +use super::super::Render; +use crate::coords::Range; use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path}; use glib::subclass::types::ObjectSubclassIsExt; diff --git a/src/widgets/render/imp.rs b/src/widgets/render/imp.rs new file mode 100644 index 0000000..b25978e --- /dev/null +++ b/src/widgets/render/imp.rs @@ -0,0 +1,317 @@ +use super::exterior::ExteriorWidget; +use super::interior::InteriorWidget; +use super::{Layer, WindowCoord}; +use crate::coords::proj::Mercator; +use crate::coords::Mapper; +use femtovg::{Canvas, Color, FontId, Renderer}; +use gtk::glib; +use gtk::subclass::prelude::*; +use gtk::traits::{GLAreaExt, WidgetExt}; +use std::cell::RefCell; +use std::num::NonZeroU32; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct RenderConfig { + pub padding: [f32; 4], +} + +#[derive(Debug, Clone)] +pub enum RenderMotion { + Translate, + Scale, + None, +} + +impl Default for RenderMotion { + fn default() -> Self { + Self::None + } +} + +#[derive(Default)] +pub struct RenderStatus { + pub(super) window_size: Option<(i32, i32)>, + pub(super) scale_rate: Option, + pub(super) pointer_location: WindowCoord, + pub(super) motion: RenderMotion, + pub(super) scale: f32, + pub(super) translate: Option<(f32, f32)>, + pub(super) view_range: Option<((f64, f64), (f64, f64))>, + translation: Option<(f64, f64)>, + init_translation: (f64, f64), +} + +pub struct Render { + pub(super) exterior: RefCell, + pub(super) interior: RefCell, + pub(super) canvas: RefCell>>, + pub config: RefCell, + pub status: RefCell, + pub mapper: RefCell, + pub(super) interior_layers: RefCell>, +} + +impl Default for Render { + fn default() -> Self { + Self { + exterior: RefCell::new(ExteriorWidget::default()), + interior: RefCell::new(InteriorWidget::default()), + interior_layers: RefCell::new(Vec::new()), + config: RefCell::new(RenderConfig::default()), + status: RefCell::new(RenderStatus::default()), + mapper: RefCell::new(Mercator::default().into()), + canvas: RefCell::new(None), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for Render { + const NAME: &'static str = "Render"; + type Type = super::Render; + type ParentType = gtk::GLArea; +} + +impl ObjectImpl for Render { + fn constructed(&self) { + self.parent_constructed(); + let area = self.obj(); + area.set_has_stencil_buffer(true); + } +} + +impl WidgetImpl for Render {} + +impl GLAreaImpl for Render { + fn resize(&self, width: i32, height: i32) { + self.status.borrow_mut().window_size = Some((width, height)); + self.ensure_canvas(); + let mut canvas = self.canvas.borrow_mut(); + let canvas = canvas.as_mut().unwrap(); + canvas.set_size( + width as u32, + height as u32, + self.obj().scale_factor() as f32, + ); + + let translation = self.status.borrow().translation; + let scale_rate = self.status.borrow().scale_rate; + let mapper = self.mapper.borrow(); + + if let None = translation { + let mut status = self.status.borrow_mut(); + status.translation = Some((mapper.get_bounds().0, mapper.get_bounds().2)); + status.init_translation = (mapper.get_bounds().0, mapper.get_bounds().2); + } + + if let None = scale_rate { + let scale = (mapper.get_bounds().3 - mapper.get_bounds().2) / canvas.height() as f64; + self.status.borrow_mut().scale_rate = Some(scale); + } + } + + fn render(&self, context: >k::gdk::GLContext) -> bool { + self.ensure_canvas(); + let configs = self.config.borrow(); + let (w, h) = { + let mut canvas = self.canvas.borrow_mut(); + let canvas = canvas.as_mut().unwrap(); + + let dpi = self.obj().scale_factor(); + let w = canvas.width(); + let h = canvas.height(); + + canvas.clear_rect( + 0, + 0, + (w as i32 * dpi) as u32, + (h as i32 * dpi) as u32, + Color::rgba(0, 0, 0, 255), + ); + (w, h) + }; + + let render_range = self.status.borrow().view_range.clone(); + if let Some(((lat1, lat2), (lon1, lon2))) = render_range { + let mut status = self.status.borrow_mut(); + + let mapper = self.mapper.borrow(); + + let (tx, ty) = mapper.map((lon1, lat1)).unwrap(); + status.translation.replace((tx, ty)); + + let (lon1, lat1) = mapper.map((lon1, lat1)).unwrap(); + let (lon2, lat2) = mapper.map((lon2, lat2)).unwrap(); + + let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); + status.scale_rate.replace(scale); + + status.view_range = None; + } else { + let mut status = self.status.borrow_mut(); + match status.motion { + RenderMotion::Translate => { + if let Some((x, y)) = status.translate { + let (ix, iy) = status.init_translation; + status.translation = Some(( + ix + x as f64 * status.scale_rate.unwrap(), + iy + y as f64 * status.scale_rate.unwrap(), + )); + } else { + status.init_translation = status.translation.unwrap(); + } + } + RenderMotion::Scale => { + let scale_rate = status.scale_rate.unwrap(); + let scale_flag = status.scale as f64; + let step = scale_rate * 0.1; + + let (tx, ty) = status.translation.unwrap(); + let (px, py) = status.pointer_location; + + let scaled = scale_rate + scale_flag * step; + status.scale_rate = Some(scaled); + let sx = scale_flag * step * px as f64; + let sy = scale_flag * step * py as f64; + status.translation = Some((tx - sx, ty - sy)); + status.init_translation = status.translation.unwrap(); + } + RenderMotion::None => {} + } + } + + let c = &(*self.interior_layers.borrow()); + self.interior + .borrow() + .draw(c, &self.obj(), self.status.borrow(), configs); + + { + let mut canvas = self.canvas.borrow_mut(); + let canvas = canvas.as_mut().unwrap(); + self.exterior.borrow().draw(canvas, &self.obj()); + canvas.flush(); + } + + true + } +} + +impl Render { + fn ensure_canvas(&self) { + use femtovg::{renderer, Canvas}; + use glow::HasContext; + + if self.canvas.borrow().is_some() { + return; + } + + let widget = self.obj(); + widget.attach_buffers(); + + static LOAD_FN: fn(&str) -> *const std::ffi::c_void = + |s| epoxy::get_proc_addr(s) as *const _; + // SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg + // knows which framebuffer to bind. This is safe as long as we call attach_buffers + // beforehand. Also unbind it here just in case, since this can be called outside render. + let (mut renderer, fbo) = unsafe { + let renderer = + renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer"); + let ctx = glow::Context::from_loader_function(LOAD_FN); + let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) + .expect("No GTK provided framebuffer binding"); + ctx.bind_framebuffer(glow::FRAMEBUFFER, None); + (renderer, glow::NativeFramebuffer(id)) + }; + renderer.set_screen_target(Some(fbo)); + let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); + canvas + .add_font_dir(std::path::Path::new("./src/assets")) + .unwrap(); + + self.canvas.replace(Some(canvas)); + } + + pub(super) fn window_size(&self) -> Option<(i32, i32)> { + self.status.borrow().window_size + } + + pub(super) fn view_range(&self) -> Option<((f64, f64), (f64, f64))> { + let padding = self.padding(); + let (w, h) = self.window_size().unwrap(); + let (w, h) = ( + w as f32 - padding[1] - padding[3], + h as f32 - padding[0] - padding[2], + ); + let (w, h) = (w as f64, h as f64); + + let mapper = self.mapper.borrow(); + + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + let (x1, y1) = (tx + padding[3] as f64, ty + padding[2] as f64); + let (x2, y2) = ( + tx + w * scale + padding[3] as f64, + ty + h * scale + padding[2] as f64, + ); + let (x1, y1) = mapper.inverse_map((x1, y1)).unwrap(); + let (x2, y2) = mapper.inverse_map((x2, y2)).unwrap(); + Some(((x1, x2), (y1, y2))) + }) + }) + } + + pub(super) fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> { + let (x, y) = loc; + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + let (x, y) = (x as f64, y as f64); + Some((tx + x * scale, (ty + y * scale))) + }) + }) + } + + fn padding(&self) -> [f32; 4] { + self.config.borrow().padding + } + + pub(super) fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { + let (x, y) = loc; + let (_, h) = self.window_size().unwrap(); + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + Some(( + (x - tx as f64) as f32 / scale as f32, + h as f32 - (y - ty as f64) as f32 / scale as f32, + )) + }) + }) + } + + pub(super) fn set_translation(&self, translation: (f64, f64)) { + let mut status = self.status.borrow_mut(); + status.translation = Some(translation); + } + + fn pointer_loc(&self) -> (f32, f32) { + let (x, y) = self.status.borrow().pointer_location.clone(); + let (_, h) = self.window_size().unwrap(); + (x, h as f32 - y) + } + + pub(super) fn set_view(&self, range: (f64, f64, f64, f64)) { + let (lat1, lat2, lon1, lon2) = range; + self.status + .borrow_mut() + .view_range + .replace(((lat1, lat2), (lon1, lon2))); + // if let Some((w, h)) = self.window_size() { + // println!("w:{}, h:{}", w, h); + // let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); + // self.status.borrow_mut().scale_rate.replace(scale); + // self.set_translation((lat1, lon1)); + // } + } +} diff --git a/src/render/interior/imp.rs b/src/widgets/render/interior/imp.rs similarity index 93% rename from src/render/interior/imp.rs rename to src/widgets/render/interior/imp.rs index 1580c38..0ab72da 100644 --- a/src/render/interior/imp.rs +++ b/src/widgets/render/interior/imp.rs @@ -1,4 +1,4 @@ -use crate::render::WindowCoord; +use super::super::WindowCoord; use gtk::glib; use gtk::subclass::prelude::*; use std::cell::RefCell; diff --git a/src/render/interior/layers.rs b/src/widgets/render/interior/layers.rs similarity index 98% rename from src/render/interior/layers.rs rename to src/widgets/render/interior/layers.rs index 2713091..73cb7b1 100644 --- a/src/render/interior/layers.rs +++ b/src/widgets/render/interior/layers.rs @@ -1,6 +1,6 @@ +use super::super::{cms::CMS, Render}; +use crate::coords::Range; use crate::pipeline::offscreen_renderer::CanvasWrapper; -use crate::render::cms::CMS; -use crate::{coords::Range, render::Render}; use femtovg::{renderer::OpenGl, Canvas, ImageId}; use std::{ cell::{Ref, RefCell}, diff --git a/src/render/interior/mod.rs b/src/widgets/render/interior/mod.rs similarity index 90% rename from src/render/interior/mod.rs rename to src/widgets/render/interior/mod.rs index 6c19896..54a3491 100644 --- a/src/render/interior/mod.rs +++ b/src/widgets/render/interior/mod.rs @@ -1,11 +1,11 @@ mod imp; mod layers; -use crate::render::Render; +use super::super::Render; use femtovg::{renderer::OpenGl, Canvas}; pub use layers::{Layer, LayerImpl, LayerImplSync, Target, TargetType}; use std::cell::Ref; -use crate::render::imp::{RenderConfig, RenderStatus}; +use super::imp::{RenderConfig, RenderStatus}; glib::wrapper! { pub struct InteriorWidget(ObjectSubclass); diff --git a/src/render/mod.rs b/src/widgets/render/mod.rs similarity index 99% rename from src/render/mod.rs rename to src/widgets/render/mod.rs index 26ca423..b902436 100644 --- a/src/render/mod.rs +++ b/src/widgets/render/mod.rs @@ -16,7 +16,7 @@ use gtk::{EventControllerScrollFlags, Inhibit}; pub use interior::*; use std::cell::{Ref, RefMut}; -pub(super) type WindowCoord = (f32, f32); +pub type WindowCoord = (f32, f32); glib::wrapper! { pub struct Render(ObjectSubclass) diff --git a/src/render/predefined/color_mapper.rs b/src/widgets/render/predefined/color_mapper.rs similarity index 100% rename from src/render/predefined/color_mapper.rs rename to src/widgets/render/predefined/color_mapper.rs diff --git a/src/render/predefined/gis.rs b/src/widgets/render/predefined/gis.rs similarity index 99% rename from src/render/predefined/gis.rs rename to src/widgets/render/predefined/gis.rs index e63fde0..207c620 100644 --- a/src/render/predefined/gis.rs +++ b/src/widgets/render/predefined/gis.rs @@ -2,8 +2,8 @@ use crate::coords::Range; use std::borrow::BorrowMut; use std::path::Path; +use super::super::{Layer, LayerImpl, Render}; use crate::coords::Mapper; -use crate::render::{Layer, LayerImpl, Render}; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Paint}; use geo_types::LineString; diff --git a/src/render/predefined/grid_field_renderer.rs b/src/widgets/render/predefined/grid_field_renderer.rs similarity index 97% rename from src/render/predefined/grid_field_renderer.rs rename to src/widgets/render/predefined/grid_field_renderer.rs index c7ca998..4c07259 100644 --- a/src/render/predefined/grid_field_renderer.rs +++ b/src/widgets/render/predefined/grid_field_renderer.rs @@ -10,11 +10,8 @@ use num_traits::{Num, NumOps}; use std::{fmt::Debug, io::Cursor, marker::PhantomData}; use super::super::renders::DataRenderer; -use crate::{ - data::Radar2d, - render::{cms::CMS, LayerImpl, Render, Target, TargetType}, - utils::meshgrid, -}; +use super::super::{cms::CMS, LayerImpl, Render, Target, TargetType}; +use crate::{data::Radar2d, utils::meshgrid}; #[derive(Debug)] pub struct GridFieldRenderer @@ -206,7 +203,7 @@ where fn draw( &self, canvas: &mut femtovg::Canvas, - cms: crate::render::cms::CMS, + cms: CMS, ) -> Option { let result = self .renderer diff --git a/src/render/predefined/layers.rs b/src/widgets/render/predefined/layers.rs similarity index 94% rename from src/render/predefined/layers.rs rename to src/widgets/render/predefined/layers.rs index 0bd5e4a..cc1f75e 100644 --- a/src/render/predefined/layers.rs +++ b/src/widgets/render/predefined/layers.rs @@ -6,14 +6,9 @@ use std::path::Path; use std::sync::Arc; use std::sync::Mutex; +use super::super::{cms::CMS, Layer, LayerImpl, LayerImplSync, Render, Target, TargetType}; +use crate::data::{AsyncDataLoader, DataLoader, Radar2d}; use crate::pipeline::offscreen_renderer::CanvasWrapper; -use crate::render::cms::CMS; -use crate::render::{LayerImpl, Target}; -use crate::render::{LayerImplSync, TargetType}; -use crate::{ - data::{AsyncDataLoader, DataLoader, Radar2d}, - render::{Layer, Render}, -}; use super::{ color_mapper::ColorMapper, diff --git a/src/render/predefined/mod.rs b/src/widgets/render/predefined/mod.rs similarity index 100% rename from src/render/predefined/mod.rs rename to src/widgets/render/predefined/mod.rs diff --git a/src/render/renders.rs b/src/widgets/render/renders.rs similarity index 100% rename from src/render/renders.rs rename to src/widgets/render/renders.rs diff --git a/src/timeline/imp.rs b/src/widgets/timeline/imp.rs similarity index 100% rename from src/timeline/imp.rs rename to src/widgets/timeline/imp.rs diff --git a/src/timeline/mod.rs b/src/widgets/timeline/mod.rs similarity index 100% rename from src/timeline/mod.rs rename to src/widgets/timeline/mod.rs diff --git a/src/window/imp.rs b/src/window/imp.rs deleted file mode 100644 index 7a978a3..0000000 --- a/src/window/imp.rs +++ /dev/null @@ -1,50 +0,0 @@ -use glib::subclass::InitializingObject; -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk::{glib, Button, CompositeTemplate}; - -#[derive(CompositeTemplate, Default)] -#[template(resource = "/org/cinrad_g/window.ui")] -pub struct Window { - #[template_child] - pub button: TemplateChild