This commit is contained in:
Tsuki 2024-03-05 20:39:32 +08:00
parent 3e7d1d08dc
commit c102f99606
54 changed files with 2372 additions and 1291 deletions

BIN
.DS_Store vendored

Binary file not shown.

22
Cargo.lock generated
View File

@ -395,7 +395,6 @@ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
"system-deps", "system-deps",
"x11",
] ]
[[package]] [[package]]
@ -535,6 +534,7 @@ dependencies = [
"svg", "svg",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-condvar",
"toml 0.8.8", "toml 0.8.8",
"topojson", "topojson",
"tracing", "tracing",
@ -1400,6 +1400,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro2 1.0.76", "proc-macro2 1.0.76",
"quote 1.0.35", "quote 1.0.35",
"regex",
"syn 2.0.48", "syn 2.0.48",
] ]
@ -3784,6 +3785,15 @@ dependencies = [
"tokio-macros", "tokio-macros",
] ]
[[package]]
name = "tokio-condvar"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7233b09174540ef9bf9fc8326bcad6ccebc631e7c9a1e2e48d956a133056f9d"
dependencies = [
"tokio",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.2.0" version = "2.2.0"
@ -4545,16 +4555,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "x11"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
dependencies = [
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "x11-dl" name = "x11-dl"
version = "2.21.0" version = "2.21.0"

View File

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
cairo-rs = { version = "0.17.0", features = ["xlib"] } cairo-rs = { version = "0.17.0" }
glib = "0.17.9" glib = "0.17.9"
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] } gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
@ -64,6 +64,7 @@ relm4-icons = { version = "0.6.0", features = [
"fast-forward-filled", "fast-forward-filled",
"home-filled", "home-filled",
"settings-filled", "settings-filled",
"save-filled",
] } ] }
surfman = "0.8.1" surfman = "0.8.1"
euclid = "0.22.9" euclid = "0.22.9"
@ -85,6 +86,7 @@ sorted-vec = "0.8.3"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"
indexmap = "2.2.2" indexmap = "2.2.2"
tokio-condvar = "0.1.0"
[build-dependencies] [build-dependencies]

View File

@ -14,8 +14,8 @@ use abi_stable::{
}; };
use parser::{Record, ValueResult}; use parser::{Record, ValueResult};
use radarg_plugin_interface::{ use radarg_plugin_interface::{
CoordType, Error, Plugin, PluginId, PluginMod, PluginMod_Ref, PluginResult, PluginResultType, CoordType, Error, MetaData, Plugin, PluginId, PluginMod, PluginMod_Ref, PluginResult,
PluginType, Plugin_TO, PluginResultType, PluginType, Plugin_TO,
}; };
#[export_root_module] #[export_root_module]
@ -133,9 +133,19 @@ impl Plugin for ETWSLoader {
} }
}) })
.collect::<RVec<_>>(); .collect::<RVec<_>>();
let meta = MetaData {
datetime: RSome(record.filetime.timestamp()),
site_info: RNone,
lon_range: RSome([0.0, 0.0]),
lat_range: RSome([1.0, 1.0]),
data_format: RSome("Eastone Washon Radar".into()),
other_info: RNone,
};
ROk(PluginResult { ROk(PluginResult {
datetime: RString::from(record.filetime.format("%Y%m%d%H%M").to_string()), datetime: RString::from(record.filetime.format("%Y%m%d%H%M").to_string()),
blocks: result_blocks, blocks: result_blocks,
meta,
}) })
} else { } else {
RErr(Error::UnsupportedFormat) RErr(Error::UnsupportedFormat)

View File

@ -1,7 +1,6 @@
{"$message_type":"diagnostic","message":"unused imports: `Write`, `self`","code":{"code":"unused_imports","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":327,"line_start":13,"line_end":13,"column_start":15,"column_end":19,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":335,"byte_end":340,"line_start":13,"line_end":13,"column_start":27,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":27,"highlight_end":32}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_imports)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"remove the unused imports","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":329,"line_start":13,"line_end":13,"column_start":15,"column_end":21,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":21}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null},{"file_name":"src/parser.rs","byte_start":333,"byte_end":340,"line_start":13,"line_end":13,"column_start":25,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":25,"highlight_end":32}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused imports: `Write`, `self`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:13:15\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m13\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::io::{self, Read, Write};\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"unused imports: `Write`, `self`","code":{"code":"unused_imports","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":327,"line_start":13,"line_end":13,"column_start":15,"column_end":19,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":335,"byte_end":340,"line_start":13,"line_end":13,"column_start":27,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":27,"highlight_end":32}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_imports)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"remove the unused imports","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":323,"byte_end":329,"line_start":13,"line_end":13,"column_start":15,"column_end":21,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":15,"highlight_end":21}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null},{"file_name":"src/parser.rs","byte_start":333,"byte_end":340,"line_start":13,"line_end":13,"column_start":25,"column_end":32,"is_primary":true,"text":[{"text":"use std::io::{self, Read, Write};","highlight_start":25,"highlight_end":32}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused imports: `Write`, `self`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:13:15\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m13\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::io::{self, Read, Write};\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"unused import: `RNone`","code":{"code":"unused_imports","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":180,"byte_end":185,"line_start":9,"line_end":9,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" RNone, ROk,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"remove the unused import","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":180,"byte_end":187,"line_start":9,"line_end":9,"column_start":9,"column_end":16,"is_primary":true,"text":[{"text":" RNone, ROk,","highlight_start":9,"highlight_end":16}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `RNone`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:9:9\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m9\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m RNone, ROk,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"unused variable: `hlen2`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_variables)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":"_hlen2","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:198:21\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m198\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (input, hlen2) = Self::_parse_u32(input, order)?;\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"unused variable: `hlen2`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(unused_variables)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":6651,"byte_end":6656,"line_start":198,"line_end":198,"column_start":21,"column_end":26,"is_primary":true,"text":[{"text":" let (input, hlen2) = Self::_parse_u32(input, order)?;","highlight_start":21,"highlight_end":26}],"label":null,"suggested_replacement":"_hlen2","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:198:21\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m198\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (input, hlen2) = Self::_parse_u32(input, order)?;\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_hlen2`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"unused variable: `order`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":"_order","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `order`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:279:9\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m279\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m order: Order,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_order`\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"unused variable: `order`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/parser.rs","byte_start":9035,"byte_end":9040,"line_start":279,"line_end":279,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" order: Order,","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":"_order","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `order`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:279:9\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m279\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m order: Order,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_order`\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"unused variable: `dimension_len`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1019,"byte_end":1032,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":1019,"byte_end":1032,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":"_dimension_len","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dimension_len`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:41:26\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m41\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (dimension_len, data) = match b.data {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_dimension_len`\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"unused variable: `dimension_len`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"src/lib.rs","byte_start":1029,"byte_end":1042,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"src/lib.rs","byte_start":1029,"byte_end":1042,"line_start":41,"line_end":41,"column_start":26,"column_end":39,"is_primary":true,"text":[{"text":" let (dimension_len, data) = match b.data {","highlight_start":26,"highlight_end":39}],"label":null,"suggested_replacement":"_dimension_len","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dimension_len`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/lib.rs:41:26\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m41\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m let (dimension_len, data) = match b.data {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_dimension_len`\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"variants `I64` and `U64` are never constructed","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":563,"byte_end":573,"line_start":28,"line_end":28,"column_start":6,"column_end":16,"is_primary":false,"text":[{"text":"enum ValueTypes {","highlight_start":6,"highlight_end":16}],"label":"variants in this enum","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":580,"byte_end":583,"line_start":29,"line_end":29,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" I64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":616,"byte_end":619,"line_start":33,"line_end":33,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" U64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(dead_code)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: variants `I64` and `U64` are never constructed\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:29:5\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m28\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0menum ValueTypes {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mvariants in this enum\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m I64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m33\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m U64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"variants `I64` and `U64` are never constructed","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"src/parser.rs","byte_start":563,"byte_end":573,"line_start":28,"line_end":28,"column_start":6,"column_end":16,"is_primary":false,"text":[{"text":"enum ValueTypes {","highlight_start":6,"highlight_end":16}],"label":"variants in this enum","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":580,"byte_end":583,"line_start":29,"line_end":29,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" I64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"src/parser.rs","byte_start":616,"byte_end":619,"line_start":33,"line_end":33,"column_start":5,"column_end":8,"is_primary":true,"text":[{"text":" U64,","highlight_start":5,"highlight_end":8}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`#[warn(dead_code)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: variants `I64` and `U64` are never constructed\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/parser.rs:29:5\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m28\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0menum ValueTypes {\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mvariants in this enum\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m I64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m33\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m U64,\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\n\n"}
{"$message_type":"diagnostic","message":"6 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: 6 warnings emitted\u001b[0m\n\n"} {"$message_type":"diagnostic","message":"5 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: 5 warnings emitted\u001b[0m\n\n"}

92
geo-macros/Cargo.lock generated Normal file
View File

@ -0,0 +1,92 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "geo-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -12,3 +12,4 @@ proc-macro = true
syn = { version = "2.0.16", features = ["full"] } syn = { version = "2.0.16", features = ["full"] }
quote = "1.0.27" quote = "1.0.27"
proc-macro2 = "1.0.58" proc-macro2 = "1.0.58"
regex = "1.10.3"

View File

@ -1,10 +1,18 @@
extern crate proc_macro; extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident;
use proc_macro2::Literal;
use proc_macro2::TokenStream as TokenStream2;
use quote::format_ident; use quote::format_ident;
use quote::quote; use quote::quote;
use regex::Regex;
use syn::parse_macro_input; use syn::parse_macro_input;
use syn::parse_str;
use syn::Expr;
use syn::Fields::Named; use syn::Fields::Named;
use syn::ItemStruct; use syn::ItemStruct;
use syn::Lit;
use syn::LitStr;
#[proc_macro_derive(Prj)] #[proc_macro_derive(Prj)]
pub fn prj_macro(input: TokenStream) -> TokenStream { pub fn prj_macro(input: TokenStream) -> TokenStream {
@ -42,3 +50,155 @@ pub fn prj_macro(input: TokenStream) -> TokenStream {
.into(), .into(),
} }
} }
#[proc_macro_derive(StructToMap)]
pub fn struct_to_map(input: TokenStream) -> TokenStream {
let struct_item = parse_macro_input!(input as ItemStruct);
let stru_name = struct_item.ident;
match struct_item.fields {
Named(fields) => {
let idents = fields
.named
.iter()
.filter(|x| {
if let syn::Visibility::Public(_) = x.vis {
true
} else {
false
}
})
.map(|x| x.ident.as_ref().unwrap());
quote!(
impl #stru_name{
pub fn to_map(&self) -> std::collections::HashMap<String,String>{
let mut map = std::collections::HashMap::new();
#(
if let Some(x) = &self.#idents{
map.insert(stringify!(#idents).to_string(), format!("{:?}", x));
}
)*
map
}
}
)
.into()
}
_ => quote!(
struct A {}
)
.into(),
}
}
#[proc_macro_derive(ToHashMap, attributes(pformat))]
pub fn to_hash_map(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let ident = input.ident;
let mut map_entries = Vec::new();
if let syn::Data::Struct(data) = input.data {
for field in data.fields.iter() {
let field_ident = field.ident.as_ref().unwrap();
for attr in &field.attrs {
if attr.path().is_ident("pformat") {
if let Ok(args) = attr.parse_args::<Expr>() {
match args {
Expr::Lit(m) => {
if let Lit::Str(s) = m.lit {
litstr_to_format(&mut map_entries, s, field_ident, None);
}
}
Expr::Tuple(m) => {
let keyname = m.elems[0].clone();
let value = m.elems[1].clone();
if let Expr::Lit(m) = keyname {
if let Lit::Str(key_name) = m.lit {
if let Expr::Lit(m) = value {
if let Lit::Str(s) = m.lit {
let key_name = key_name.value();
litstr_to_format(
&mut map_entries,
s,
field_ident,
Some(key_name),
);
}
}
}
}
}
_ => {}
}
}
}
}
}
}
quote! {
impl #ident {
pub fn to_map(&self) -> std::collections::HashMap<String, String> {
let mut map = std::collections::HashMap::new();
#(#map_entries)*
map
}
}
}
.into()
}
// 定义属性宏,使其接受一个参数
#[proc_macro_attribute]
pub fn pformat(args: TokenStream, input: TokenStream) -> TokenStream {
input
}
fn litstr_to_format(
map_entries: &mut Vec<TokenStream2>,
s: LitStr,
field_ident: &Ident,
key_name: Option<String>,
) {
let field_name = field_ident.to_string();
let re = Regex::new(r"\{.*?\}").unwrap();
let mut tags = Vec::new();
let mut v = s.value().clone();
for cap in re.captures_iter(s.value().as_str()) {
if let Some(pipe_pos) = cap[0].find('|') {
let (expression, format_specifier) = cap[0].split_at(pipe_pos);
v = v.replace(
&cap[0].to_string(),
&format!("{{{}", &format_specifier[1..]),
);
tags.push(Some(expression[1..].to_string()));
} else {
tags.push(None);
}
}
let tags = tags
.into_iter()
.map(|v| {
if let Some(v) = v {
let v = v.replace("this", &format!("{}", field_name));
let expr: Expr = parse_str(&v).unwrap();
quote!(#expr)
} else {
quote!(#field_ident)
}
})
.collect::<Vec<_>>();
let k = key_name
.map(|k| format_ident!("{}", k))
.unwrap_or(field_ident.clone());
map_entries.push({
let lit = Literal::string(&v);
let lit_key = Literal::string(&k.to_string());
quote! {
if let Some(#field_ident) = &self.#field_ident{
map.insert(#lit_key.to_string(), format!(#lit, #(#tags,)*));
}
}
.into()
});
}

View File

@ -55,6 +55,17 @@ pub enum DataShape {
Matrix, Matrix,
} }
#[repr(C)]
#[derive(Debug, StableAbi, Clone)]
pub struct MetaData {
pub datetime: ROption<i64>,
pub site_info: ROption<RString>,
pub lat_range: ROption<[f64; 2]>,
pub lon_range: ROption<[f64; 2]>,
pub data_format: ROption<RString>,
pub other_info: ROption<RString>,
}
#[repr(C)] #[repr(C)]
#[derive(StableAbi, Clone, Debug)] #[derive(StableAbi, Clone, Debug)]
#[sabi(impl_InterfaceType(Sync, Send, Debug))] #[sabi(impl_InterfaceType(Sync, Send, Debug))]
@ -85,6 +96,7 @@ pub enum PluginResultType {
#[sabi(impl_InterfaceType(Sync, Send, Debug))] #[sabi(impl_InterfaceType(Sync, Send, Debug))]
pub struct PluginResult { pub struct PluginResult {
pub datetime: RString, pub datetime: RString,
pub meta: MetaData,
pub blocks: RVec<Block>, pub blocks: RVec<Block>,
} }

View File

@ -8,112 +8,4 @@ use crate::{
}; };
use abi_stable::std_types::{RBoxError, RResult, RStr, RString}; use abi_stable::std_types::{RBoxError, RResult, RStr, RString};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Sends a json encoded command to a plugin,and returns the response by encoding it to json.
///
/// # Errors
///
/// These are all error that this function returns
/// (this does not include error returned as part of the command):
///
/// - Error::Serialize:
/// If the command/return value could not be serialized to JSON.
///
/// - Error::Deserialize
/// If the command/return value could not be deserialized from JSON
/// (this comes from the plugin).
///
/// - Error::UnsupportedCommand
/// If the command is not supported by the plugin.
///
pub fn process_command<'de, P, C, R, F>(
this: &mut P,
command: RStr<'de>,
f: F,
) -> RResult<RString, Error>
where
P: Plugin,
F: FnOnce(&mut P, C) -> Result<R, Error>,
C: Deserialize<'de>,
R: Serialize,
{
(|| -> Result<RString, Error> {
let command = command.as_str();
let which_variant = serde_json::from_str::<WhichVariant>(&command)
.map_err(|e| Error::Deserialize(RBoxError::new(e), WhichCommandRet::Command))?;
let command = serde_json::from_str::<CommandUnion<C>>(command).map_err(|e| {
Error::unsupported_command(Unsupported {
plugin_name: this.plugin_id().named.clone().into_owned(),
command_name: which_variant.variant,
error: RBoxError::new(e),
supported_commands: this.list_commands(),
})
})?;
let ret: ReturnValUnion<R> = match command {
CU::Basic(BasicCommand::GetCommands) => {
let commands = this.list_commands();
RVU::Basic(BasicRetVal::GetCommands(commands))
}
CU::ForPlugin(cmd) => RVU::ForPlugin(f(this, cmd)?),
};
match serde_json::to_string(&ret) {
Ok(v) => Ok(v.into()),
Err(e) => Err(Error::Serialize(RBoxError::new(e), WhichCommandRet::Return)),
}
})()
.into()
}
/// Sends a typed command to a plugin.
///
/// # Errors
///
/// These are all error that this function returns
/// (this does not include error returned as part of the command):
///
/// - Error::Serialize:
/// If the command/return value could not be serialized to JSON.
///
/// - Error::Deserialize
/// If the command/return value could not be deserialized from JSON
/// (this comes from the plugin).
///
/// - Error::UnsupportedReturnValue:
/// If the return value could not be deserialized from JSON
/// (after checking that it has the `{"name":"...",description: ... }` format),
/// containing the name of the command this is a return value for .
///
/// - Error::UnsupportedCommand
/// If the command is not supported by the plugin.
///
pub fn send_command<C>(
this: &mut PluginType,
command: &C,
app: ApplicationMut<'_>,
) -> Result<C::Returns, Error>
where
C: CommandTrait,
{
let cmd = serde_json::to_string(&command)
.map_err(|e| Error::Serialize(RBoxError::new(e), WhichCommandRet::Command))?;
let ret = this.json_command(RStr::from(&*cmd), app).into_result()?;
let which_variant = serde_json::from_str::<WhichVariant>(&*ret)
.map_err(|e| Error::Deserialize(RBoxError::new(e), WhichCommandRet::Return))?;
serde_json::from_str::<C::Returns>(&ret).map_err(|e| {
Error::unsupported_return_value(Unsupported {
plugin_name: this.plugin_id().named.clone().into_owned(),
command_name: which_variant.variant,
error: RBoxError::new(e),
supported_commands: this.list_commands(),
})
})
}

View File

@ -1,31 +1,3 @@
use crate::{
coords::{proj::Mercator, Mapper},
data::{self, CoordType, Radar2d},
errors::RenderError,
pipeline::{
self,
offscreen_renderer::OffscreenRenderer,
render_pipeline::RenderResult,
utils::{data_to_layer, Dispatcher, Pipeline},
},
plugin_system::init_plugin,
widgets::{
render::{predefined::color_mapper::BoundaryNorm, Layer},
CMS,
},
PLUGIN_MANAGER,
};
use adw::prelude::*;
use gtk::prelude::*;
use std::{
borrow::{Borrow, BorrowMut},
cell::RefCell,
collections::HashMap,
rc::Rc,
sync::{Arc, Mutex},
};
use tracing::{debug, error, info, warn};
use super::{ use super::{
control_panel::{ControlPanelInputMsg, ControlPanelModel}, control_panel::{ControlPanelInputMsg, ControlPanelModel},
messages::{MonitorInputMsg, MonitorOutputMsg}, messages::{MonitorInputMsg, MonitorOutputMsg},
@ -33,15 +5,27 @@ use super::{
setting::SettingModel, setting::SettingModel,
ControlPanelOutputMsg, TimelineMsg, ControlPanelOutputMsg, TimelineMsg,
}; };
use abi_stable::std_types::RStr; use crate::pipeline::element::Element;
use chrono::{prelude::*, DateTime, Duration, Utc}; use crate::{
use futures::future::{try_join_all, BoxFuture}; coords::{
use gtk::{ cms::CMS,
prelude::{ApplicationExt, BoxExt, GtkWindowExt, WidgetExt}, proj::{Mercator, ProjectionS},
traits::OrientableExt, Mapper,
},
data::MetaInfo,
errors::RenderError,
pipeline::{utils::data_to_element, Dispatcher, Pipeline, RenderResult},
plugin_system::init_plugin,
widgets::render::Layer,
CONFIG, PLUGIN_MANAGER,
}; };
use ndarray::{Array1, Array2, Array3}; use abi_stable::std_types::RStr;
use radarg_plugin_interface::{Block, DataShape, PluginId, VecResult}; use adw::prelude::*;
use chrono::{prelude::*, Duration};
use futures::future::BoxFuture;
use glib::clone;
use gtk::prelude::*;
use once_cell::sync::Lazy;
use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup}; use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup};
use relm4::*; use relm4::*;
use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
@ -49,40 +33,54 @@ use relm4_components::open_dialog::{
OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings, OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use sorted_vec::SortedSet; use std::{
any::Any,
borrow::{Borrow, BorrowMut},
cell::RefCell,
collections::{BTreeMap, HashMap},
path::PathBuf,
rc::Rc,
sync::{Arc, Mutex},
};
use radarg_plugin_interface::PluginResult;
use tokio::{sync::oneshot, task}; use tokio::{sync::oneshot, task};
use tracing::{debug, error, info, warn};
relm4::new_action_group!(FileActionGroup, "file"); relm4::new_action_group!(FileActionGroup, "file");
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open"); relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
pub static FILE_PATH_ROOT: Lazy<Mutex<PathBuf>> = Lazy::new(|| Mutex::new(PathBuf::new()));
pub type ElementKey = String;
#[derive(Debug)] #[derive(Debug)]
pub enum AppMsg { pub enum AppMsg {
CloseRequest, CloseRequest,
Close, Close,
OpenDialog, OpenDialog,
SwitchTo((String, DateTime<Utc>, Option<Layer>)), NewElement(Element),
RenderLayer((Layer, DateTime<Utc>)), DeleteElement(ElementKey),
OpenDialogMulti, NewLayer(Layer),
RenderSuccess,
} }
pub type Buffer = Rc<RefCell<HashMap<String, HashMap<DateTime<Utc>, Option<RenderResult>>>>>; pub type Buffer = Rc<RefCell<HashMap<String, BTreeMap<DateTime<Utc>, Option<RenderResult>>>>>;
type RcDispatcher = Rc<RefCell<Dispatcher>>; type RcDispatcher = Rc<Dispatcher>;
#[tracker::track] #[tracker::track]
pub struct AppModel { pub struct AppModel {
#[do_not_track] #[do_not_track]
dispatcher: RcDispatcher, dispatcher: RcDispatcher,
#[do_not_track] #[do_not_track]
buffer: Buffer, cms: Arc<Mutex<CMS>>,
waiting_for: Option<DateTime<Utc>>, waiting_for: Option<DateTime<Utc>>,
#[do_not_track] #[do_not_track]
open_dialog: Controller<OpenDialog>, open_dialog: Controller<OpenDialog>,
#[do_not_track] #[do_not_track]
control: Controller<ControlPanelModel>, control: Controller<ControlPanelModel>,
#[do_not_track] #[do_not_track]
target_pipeline: HashMap<String, Pipeline>,
#[do_not_track]
render: Controller<MonitorModel>, render: Controller<MonitorModel>,
#[do_not_track] #[do_not_track]
layers: Rc<RefCell<Vec<Layer>>>,
#[do_not_track]
elements: Vec<Arc<Mutex<Element>>>,
#[do_not_track]
setting: Controller<SettingModel>, setting: Controller<SettingModel>,
} }
@ -106,9 +104,8 @@ impl Component for AppModel {
set_default_width: 1200, set_default_width: 1200,
set_default_height: 900, set_default_height: 900,
set_focus_on_click:true, set_focus_on_click:true,
connect_close_request[sender,app] => move |_| { connect_close_request[sender] => move |_| {
sender.input(AppMsg::CloseRequest); sender.input(AppMsg::CloseRequest);
app.quit();
gtk::Inhibit(true) gtk::Inhibit(true)
}, },
gtk::Box{ gtk::Box{
@ -190,58 +187,58 @@ impl Component for AppModel {
let control = ControlPanelModel::builder().launch(0).forward( let control = ControlPanelModel::builder().launch(0).forward(
sender.input_sender(), sender.input_sender(),
|msg| match msg { |msg| match msg {
ControlPanelOutputMsg::OpenFile((key, time)) => AppMsg::SwitchTo((key, time, None)), ControlPanelOutputMsg::OpenFile((key, time)) => {
// AppMsg::SwitchTo((key, time, None, None))
AppMsg::Close
}
}, },
); );
let render = let render =
MonitorModel::builder() MonitorModel::builder()
.launch(()) .launch(())
.forward(sender.input_sender(), |a| match a { .forward(sender.input_sender(), |a| match a {
MonitorOutputMsg::LayerRenderFinished => AppMsg::RenderSuccess, MonitorOutputMsg::LayerRenderFinished => AppMsg::Close,
_ => AppMsg::Close, _ => AppMsg::Close,
}); });
let setting = SettingModel::builder() let setting = SettingModel::builder()
.launch(()) .launch(())
.forward(sender.input_sender(), |a| AppMsg::Close); .forward(sender.input_sender(), |a| AppMsg::Close);
let mut dispatcher = Rc::new(Dispatcher::new(5, 5, chrono::Duration::minutes(1)));
let cms = Arc::new(Mutex::new(CMS::new(
Mercator::default().into(),
(3000.0, 3000.0),
)));
let dialog_dispatcher = dispatcher.clone();
let dialog_cms = cms.clone();
let dialog = OpenDialog::builder() let dialog = OpenDialog::builder()
.transient_for_native(&root) .transient_for_native(&root)
.launch(OpenDialogSettings::default()) .launch(OpenDialogSettings::default())
.forward(sender.input_sender(), |response| match response { .forward(sender.input_sender(), move |response| match response {
OpenDialogResponse::Accept(path) => { OpenDialogResponse::Accept(path) => {
if let Some((a, b)) = Self::open_file(path) { let data = Self::open_file_only(path);
AppMsg::SwitchTo((b.name.clone(), a, Some(b))) let mut layer = Layer::new(true, "New Layer".to_string(), None);
} else { AppMsg::NewLayer(layer)
AppMsg::Close
}
} }
_ => AppMsg::Close, _ => AppMsg::Close,
}); });
let app = relm4::main_application();
relm4_icons::initialize_icons(); relm4_icons::initialize_icons();
let buffer: Buffer = Rc::new(RefCell::new(HashMap::new())); let buffer: Buffer = Rc::new(RefCell::new(HashMap::new()));
let mut dispatcher = Dispatcher::new(5, 5, chrono::Duration::minutes(1), buffer.clone());
let model = AppModel { let model = AppModel {
buffer: buffer, cms,
dispatcher: Rc::new(RefCell::new(dispatcher)), dispatcher,
waiting_for: None, waiting_for: None,
elements: Vec::with_capacity(20),
open_dialog: dialog, open_dialog: dialog,
target_pipeline: HashMap::new(),
control, control,
render, render,
layers: Rc::new(RefCell::new(Vec::with_capacity(20))),
setting, setting,
tracker: 0, tracker: 0,
}; };
let widgets = view_output!(); let widgets = view_output!();
let mut group = RelmActionGroup::<FileActionGroup>::new(); let mut group = RelmActionGroup::<FileActionGroup>::new();
relm4::main_application().set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
app.set_accelerators_for_action::<OpenAction>(&["<primary>O"]);
let action: RelmAction<OpenAction> = { let action: RelmAction<OpenAction> = {
RelmAction::new_stateless(move |_| { RelmAction::new_stateless(move |_| {
sender.input(AppMsg::OpenDialog); sender.input(AppMsg::OpenDialog);
@ -262,117 +259,24 @@ impl Component for AppModel {
) { ) {
self.reset(); self.reset();
match msg { match msg {
AppMsg::SwitchTo((key, datetime, layer)) => { AppMsg::NewElement(element) => {
println!("Switch to {}", datetime); let (key, id) = (element.key(), element.id());
self.create_pipeline(key.clone()); let element = Arc::new(Mutex::new(element));
self.create_buffer(key.clone()); self.elements.push(element.clone());
// let layer = Layer::new(true, key, Some(element));
{ // _sender.input(AppMsg::NewLayer(layer));
let mut current_buffer = (*self.buffer).borrow_mut();
let current_buffer = current_buffer.get_mut(key.as_str()).unwrap();
if let Some(layer) = layer {
self.control.emit(ControlPanelInputMsg::Disable);
_sender.input(AppMsg::RenderLayer((layer, datetime)));
} else {
if let Some(v) = current_buffer.get_mut(&datetime) {
// Task already in pipeline
if v.is_none() {
// Still on the way, need to show the progress
self.control.emit(ControlPanelInputMsg::Disable);
self.waiting_for = Some(datetime);
info!("Task still on the way");
let toast =
adw::Toast::builder().title("Task still on the way").build();
widgets.monitor_toast.add_toast(toast);
} else {
// Task finished, Need to show the result
let v = v.as_ref().unwrap().clone();
info!("Task finished");
_sender.input(AppMsg::RenderLayer((v.layer, datetime)));
} }
} else { AppMsg::NewLayer(layer) => {
// Task not in pipeline, Need to open this file (*self.layers).borrow_mut().push(layer);
let layer = (*self.dispatcher)
.borrow()
.get_single_path(&key, datetime, true)
.map(|p| Self::open_file(p))
.flatten()
.map(|x| x.1);
if layer.is_none() {
// TODO: Show Error
} else {
self.control.emit(ControlPanelInputMsg::Disable);
_sender.input(AppMsg::RenderLayer((layer.unwrap(), datetime)));
} }
AppMsg::CloseRequest => {
relm4::main_application().quit();
} }
}
}
let tasks = self.create_tasks(key.clone(), datetime);
let pipeline = self.target_pipeline.get_mut(key.as_str()).unwrap();
let worker = Pipeline::run(pipeline);
let render_sender = self.render.sender();
let control_sender = self.control.sender();
let new_sender = _sender.clone();
if let Some(tasks) = tasks {
if tasks.len() == 0 {
widgets.monitor_toast.add_toast(
adw::Toast::builder()
.title(format!("no data found: {}", datetime))
.build(),
);
return;
}
(*self.buffer)
.borrow_mut()
.get_mut(key.clone().as_str())
.unwrap()
.extend(tasks.clone());
let (thumb_recivers, listening_func) = self.create_listening_with_thumb(
key.clone(),
tasks.len(),
new_sender.clone(),
);
control_sender.emit(ControlPanelInputMsg::SetThumb(
thumb_recivers
.into_iter()
.map(|p| (None, Some(p), datetime))
.collect(),
));
let listening = self
.current_pipeline(key.clone())
.listening_one_by_one(listening_func);
// Spawn the worker and listen to the result
new_sender.oneshot_command(async move {
worker.await;
listening.await;
AppCommand::Test
});
}
}
AppMsg::CloseRequest => {}
AppMsg::Close => {} AppMsg::Close => {}
AppMsg::OpenDialog => { AppMsg::OpenDialog => {
self.open_dialog.emit(OpenDialogMsg::Open); self.open_dialog.emit(OpenDialogMsg::Open);
} }
AppMsg::RenderLayer((layer, dt)) => { _ => {}
self.render.emit(MonitorInputMsg::AddLayer(layer));
self.control.emit(ControlPanelInputMsg::Selection(Some(dt)));
self.control
.emit(ControlPanelInputMsg::TimeLine(TimelineMsg::SetStart(
dt - Duration::minutes(30),
)));
}
AppMsg::RenderSuccess => {
self.control.emit(ControlPanelInputMsg::Enable);
}
AppMsg::OpenDialogMulti => {}
} }
} }
@ -384,18 +288,6 @@ impl Component for AppModel {
) { ) {
match message { match message {
AppCommand::PrepareFinished(mut v) => {} AppCommand::PrepareFinished(mut v) => {}
AppCommand::TestBuffer((key, result)) => {
let datetime = result.time();
let mut buffer = (*self.buffer).borrow_mut();
let mut buffer = buffer.get_mut(key.as_str()).unwrap();
if let Some(waiting_for) = self.waiting_for {
if waiting_for == datetime {
self.waiting_for = None;
sender.input(AppMsg::RenderLayer((result.layer.clone(), datetime)));
}
}
buffer.get_mut(&datetime).unwrap().replace(result);
}
_ => { _ => {
println!("test"); println!("test");
} }
@ -404,99 +296,27 @@ impl Component for AppModel {
} }
impl AppModel { impl AppModel {
fn create_buffer(&mut self, key: impl Borrow<str>) { fn open_file(
if !(*self.buffer).borrow().contains_key(key.borrow()) { path: impl AsRef<std::path::Path>,
(*self.buffer) dispatcher: Rc<Dispatcher>,
.borrow_mut() cms: Arc<Mutex<CMS>>,
.insert(key.borrow().to_string(), HashMap::new()); ) -> Option<(Option<Box<dyn Any + Send + Sync>>, Element)> {
}
}
fn create_pipeline(&mut self, key: impl Borrow<str>) -> &mut Pipeline {
if !self.target_pipeline.contains_key(key.borrow()) {
let mut pipeline = Pipeline::new(10, key.borrow().to_string());
pipeline.init().set_dispatcher(self.dispatcher.clone());
self.target_pipeline
.insert(key.borrow().to_string(), pipeline);
}
self.target_pipeline.get_mut(key.borrow()).unwrap()
}
fn create_tasks(
&mut self,
key: impl AsRef<str>,
time: DateTime<Utc>,
) -> Option<Vec<(DateTime<Utc>, Option<RenderResult>)>> {
let pipeline = self.target_pipeline.get_mut(key.as_ref()).unwrap();
let time_list = pipeline.set_current(time, true, 3);
time_list.map(|time_list| time_list.into_iter().map(|t| (t, None)).collect())
}
fn current_pipeline(&mut self, key: impl AsRef<str>) -> &mut Pipeline {
self.target_pipeline.get_mut(key.as_ref()).unwrap()
}
fn create_listening_with_thumb(
&self,
key: impl Borrow<str>,
num: usize,
_sender: ComponentSender<Self>,
) -> (
Vec<oneshot::Receiver<gtk::gdk::Texture>>,
Vec<
impl FnOnce(
oneshot::Receiver<Result<RenderResult, RenderError>>,
) -> BoxFuture<'static, ()>
+ Send
+ 'static
+ Sync,
>,
) {
let mut thumb_senders: Vec<oneshot::Sender<gtk::gdk::Texture>> = Vec::new();
let mut thumb_recivers = Vec::new();
let mut current_buffer = (*self.buffer).borrow_mut();
let mut current_buffer = current_buffer.get_mut(key.borrow()).unwrap();
for _ in 0..num {
let (tx, rx) = oneshot::channel();
thumb_recivers.push(rx);
thumb_senders.push(tx);
}
let mut p = Vec::new();
for sender in thumb_senders {
let new_sender = _sender.clone();
let f = move |tx: oneshot::Receiver<Result<RenderResult, RenderError>>| {
Box::pin(async move {
if let Ok(r) = tx.await {
let r = r.unwrap();
let tex = (&r)
.layer
.render_target()
.lock()
.unwrap()
.as_ref()
.unwrap()
.thumbnail
.clone();
sender.send(tex.unwrap()).unwrap();
new_sender
.command_sender()
.emit(AppCommand::TestBuffer((r.layer.name.clone(), r)));
}
}) as BoxFuture<'static, ()>
};
p.push(f);
}
(thumb_recivers, p)
}
fn open_file(path: impl AsRef<std::path::Path>) -> Option<(DateTime<Utc>, Layer)> {
let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap(); let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
let mut result = plugin let mut result = plugin
.load(RStr::from_str(path.as_ref().to_str().unwrap())) .load(RStr::from_str(path.as_ref().to_str().unwrap()))
.unwrap(); .unwrap();
let mut block = result.blocks.pop().unwrap(); let block = result.blocks.first().unwrap();
data_to_layer(block) data_to_element(block, dispatcher, cms)
.map(|v| (Some(Box::new(result) as Box<dyn Any + Send + Sync>), v))
}
fn open_file_only(
path: impl AsRef<std::path::Path>,
) -> PluginResult {
let plugin = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
let mut result = plugin
.load(RStr::from_str(path.as_ref().to_str().unwrap()))
.unwrap();
return result;
} }
} }

View File

@ -24,6 +24,7 @@ pub struct ControlPanelModel {
enabled: bool, enabled: bool,
timeline_start: DateTime<Utc>, timeline_start: DateTime<Utc>,
selection: Option<DateTime<Utc>>, selection: Option<DateTime<Utc>>,
key: Option<String>,
#[tracker::no_eq] #[tracker::no_eq]
list_img_wrapper: TypedListView<ImgItem, gtk::SingleSelection>, list_img_wrapper: TypedListView<ImgItem, gtk::SingleSelection>,
} }
@ -73,8 +74,8 @@ impl SimpleComponent for ControlPanelModel {
set_spacing:10, set_spacing:10,
gtk::Button{ gtk::Button{
set_icon_name: "rewind-filled", set_icon_name: "rewind-filled",
#[track = "model.changed(ControlPanelModel::enabled())"] #[track = "model.changed(ControlPanelModel::enabled()) || model.changed(ControlPanelModel::key())"]
set_sensitive: model.enabled, set_sensitive: model.enabled && model.key.is_some(),
connect_clicked[sender] => move |_| { connect_clicked[sender] => move |_| {
sender.input(ControlPanelInputMsg::SelectionRewind); sender.input(ControlPanelInputMsg::SelectionRewind);
}, },
@ -84,8 +85,8 @@ impl SimpleComponent for ControlPanelModel {
}, },
gtk::Button{ gtk::Button{
set_icon_name: "fast-forward-filled", set_icon_name: "fast-forward-filled",
#[track = "model.changed(ControlPanelModel::enabled())"] #[track = "model.changed(ControlPanelModel::enabled()) || model.changed(ControlPanelModel::key())"]
set_sensitive: model.enabled, set_sensitive: model.enabled && model.key.is_some(),
connect_clicked[sender] => move |_| { connect_clicked[sender] => move |_| {
sender.input(ControlPanelInputMsg::SelectionFastForward); sender.input(ControlPanelInputMsg::SelectionFastForward);
}, },
@ -216,6 +217,7 @@ impl SimpleComponent for ControlPanelModel {
let timeline_start = Utc::now(); let timeline_start = Utc::now();
let model = ControlPanelModel { let model = ControlPanelModel {
timeline_enabled: true, timeline_enabled: true,
key: None,
enabled: true, enabled: true,
selection: None, selection: None,
timeline_start, timeline_start,
@ -250,7 +252,7 @@ impl SimpleComponent for ControlPanelModel {
if let Some(current) = current { if let Some(current) = current {
self.set_selection(Some(current - Duration::minutes(1))); self.set_selection(Some(current - Duration::minutes(1)));
_sender.output(ControlPanelOutputMsg::OpenFile(( _sender.output(ControlPanelOutputMsg::OpenFile((
format!("DBZ"), self.key.clone().unwrap(),
current - Duration::minutes(1), current - Duration::minutes(1),
))); )));
} }
@ -260,7 +262,7 @@ impl SimpleComponent for ControlPanelModel {
if let Some(current) = current { if let Some(current) = current {
self.set_selection(Some(current + Duration::minutes(1))); self.set_selection(Some(current + Duration::minutes(1)));
_sender.output(ControlPanelOutputMsg::OpenFile(( _sender.output(ControlPanelOutputMsg::OpenFile((
format!("DBZ"), self.key.clone().unwrap(),
current + Duration::minutes(1), current + Duration::minutes(1),
))); )));
} }
@ -282,6 +284,10 @@ impl SimpleComponent for ControlPanelModel {
ControlPanelInputMsg::Enable => { ControlPanelInputMsg::Enable => {
self.set_enabled(true); self.set_enabled(true);
} }
ControlPanelInputMsg::SetKey(key) => {
self.set_key(Some(key));
}
} }
} }
} }

View File

@ -24,6 +24,7 @@ pub enum ControlPanelInputMsg {
SetThumbByDate((DateTime<Utc>, Option<gtk::gdk::Texture>)), SetThumbByDate((DateTime<Utc>, Option<gtk::gdk::Texture>)),
SelectionRewind, SelectionRewind,
SelectionFastForward, SelectionFastForward,
SetKey(String),
Disable, Disable,
Enable, Enable,
} }

View File

@ -1,12 +1,20 @@
use std::fmt::Debug; use std::{collections::HashMap, fmt::Debug};
use crate::widgets::{render::Layer, widget::Widget}; use crate::{
components::app::ElementKey,
pipeline::element::ElementID,
widgets::{render::Layer, widget::Widget},
};
pub enum MonitorInputMsg { pub enum MonitorInputMsg {
NewElement((ElementKey, ElementID)),
AddWidget(Box<dyn Widget>), AddWidget(Box<dyn Widget>),
RemoveWidget, RemoveWidget,
AddLayer(Layer), AddLayer(Layer),
RemoveLayer(String), RemoveLayer(String),
AddMetaItem(HashMap<String, String>),
ClearMetaItems,
UpdateMetaItem(HashMap<String, String>),
UpdateLayer((String, Box<dyn Fn(&mut Layer) + 'static>)), UpdateLayer((String, Box<dyn Fn(&mut Layer) + 'static>)),
None, None,
} }
@ -14,12 +22,16 @@ pub enum MonitorInputMsg {
impl Debug for MonitorInputMsg { impl Debug for MonitorInputMsg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
MonitorInputMsg::NewElement(_) => write!(f, "MonitorInputMsg::NewElement"),
MonitorInputMsg::AddLayer(_) => write!(f, "MonitorInputMsg::AddLayer"), MonitorInputMsg::AddLayer(_) => write!(f, "MonitorInputMsg::AddLayer"),
MonitorInputMsg::RemoveLayer(_) => write!(f, "MonitorInputMsg::RemoveLayer"), MonitorInputMsg::RemoveLayer(_) => write!(f, "MonitorInputMsg::RemoveLayer"),
MonitorInputMsg::UpdateLayer(_) => write!(f, "MonitorInputMsg::UpdateLayer"), MonitorInputMsg::UpdateLayer(_) => write!(f, "MonitorInputMsg::UpdateLayer"),
MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"), MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"),
MonitorInputMsg::AddWidget(_) => write!(f, "MonitorInputMsg::AddWidget"), MonitorInputMsg::AddWidget(_) => write!(f, "MonitorInputMsg::AddWidget"),
MonitorInputMsg::RemoveWidget => write!(f, "MonitorInputMsg::RemoveWidget"), MonitorInputMsg::RemoveWidget => write!(f, "MonitorInputMsg::RemoveWidget"),
MonitorInputMsg::AddMetaItem(_) => write!(f, "MonitorInputMsg::RemoveWidget"),
MonitorInputMsg::ClearMetaItems => write!(f, "MonitorInputMsg::ClearMetaItems"),
MonitorInputMsg::UpdateMetaItem(_) => write!(f, "MonitorInputMsg::UpdateMetaItem"),
} }
} }
} }

View File

@ -1,8 +1,9 @@
use super::messages::{MonitorInputMsg, MonitorOutputMsg}; use super::messages::{MonitorInputMsg, MonitorOutputMsg};
use crate::coords::cms::CMS;
use crate::pipeline::offscreen_renderer::OffscreenRenderer; use crate::pipeline::offscreen_renderer::OffscreenRenderer;
use crate::widgets::predefined::color_mapper::BoundaryNorm; use crate::widgets::predefined::color_mapper::BoundaryNorm;
use crate::widgets::predefined::widgets::ColorBar; use crate::widgets::predefined::widgets::ColorBar;
use crate::widgets::render::{RenderConfig, Target, CMS}; use crate::widgets::render::RenderConfig;
use crate::widgets::widget::{Widget, WidgetType}; use crate::widgets::widget::{Widget, WidgetType};
use crate::widgets::WidgetFrame; use crate::widgets::WidgetFrame;
use crate::{ use crate::{
@ -10,19 +11,20 @@ use crate::{
widgets::dynamic_col::DynamicCol, widgets::dynamic_col::DynamicCol,
widgets::render::{Layer, Render}, widgets::render::{Layer, Render},
}; };
use geo::k_nearest_concave_hull;
use glib::{clone, PropertyGet}; use glib::{clone, PropertyGet};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use super::sidebar::{sidebar::SideBarModel, Msg, SideBarOutputMsg}; use super::sidebar::{sidebar::SideBarModel, SideBarInputMsg, SideBarOutputMsg};
use adw::prelude::*; use adw::prelude::*;
use relm4::{component::Component, *}; use relm4::{component::Component, *};
#[derive(Debug)] #[derive(Debug)]
pub enum MonitorCommand { pub enum MonitorCommand {
NewLayer(Layer), // NewLayer(Layer),
None, None,
} }
#[tracker::track] #[tracker::track]
@ -35,7 +37,7 @@ pub struct MonitorModel {
#[no_eq] #[no_eq]
widgets: Vec<WidgetFrame>, widgets: Vec<WidgetFrame>,
#[no_eq] #[no_eq]
layers: Rc<RefCell<HashMap<String, Layer>>>, layers: Rc<RefCell<Vec<Layer>>>,
#[no_eq] #[no_eq]
sidebar: Controller<SideBarModel>, sidebar: Controller<SideBarModel>,
} }
@ -76,8 +78,8 @@ impl Component for MonitorModel {
gtk::Overlay{ gtk::Overlay{
#[wrap(Some)] #[wrap(Some)]
set_child = &Render{ set_child = &Render{
#[track = "model.changed(MonitorModel::new_layer())"] // #[track = "model.changed(MonitorModel::new_layer())"]
set_interior_layers: model.layers.borrow().values().cloned().collect(), // set_interior_layers: model.layers.clone(),
#[track = "model.changed(MonitorModel::render_cfg())"] #[track = "model.changed(MonitorModel::render_cfg())"]
set_cfg: model.render_cfg, set_cfg: model.render_cfg,
#[track = "model.changed(MonitorModel::render_range())"] #[track = "model.changed(MonitorModel::render_range())"]
@ -101,8 +103,7 @@ impl Component for MonitorModel {
#[wrap(Some)] #[wrap(Some)]
set_end_child=model.sidebar.widget(), set_end_child=model.sidebar.widget(),
} }
}, }
} }
} }
@ -110,66 +111,78 @@ impl Component for MonitorModel {
self.reset(); self.reset();
match message { match message {
MonitorInputMsg::AddLayer(layer) => { MonitorInputMsg::AddLayer(layer) => {
let need_prepare = { layer.get_prepare().lock().unwrap().is_some() }; self.layers.borrow_mut().push(layer);
{
let mut layers = self.layers.borrow_mut();
if !layers.contains_key(layer.name.as_str()) {
let mut widgets = layer.widgets.lock().unwrap();
if widgets.is_some() {
let ws = widgets.take().unwrap();
ws.into_iter().for_each(|w| {
sender.input(MonitorInputMsg::AddWidget(w));
});
}
}
}
if need_prepare {
sender.oneshot_command(async move {
let mut back = OffscreenRenderer::new(3000, 3000).unwrap();
let canvas = back.create_canvas();
let f = {
let p = layer.get_prepare();
let mut _p = p.lock().unwrap();
_p.take().unwrap()
};
let imp = layer.get_imp().unwrap();
let map: Mapper = Mercator::default().into();
let cms = CMS::new(map, (3000.0, 3000.0));
let canvas = Arc::new(Mutex::new(canvas));
let target = f(imp, canvas, cms);
layer.set_render_target(target);
MonitorCommand::NewLayer(layer)
});
} else {
{
let mut layers = self.layers.borrow_mut();
if layers.contains_key(layer.name.as_str()) {
let p = layers.get_mut(layer.name.as_str()).unwrap();
*p = layer;
} else {
layers.insert(layer.name.clone(), layer);
}
}
let raw_id = self.get_new_layer(); let raw_id = self.get_new_layer();
self.set_new_layer(*raw_id + 1); self.set_new_layer(*raw_id + 1);
self.sidebar.sender().send(Msg::RefreshList); self.sidebar.sender().send(SideBarInputMsg::RefreshList);
// let need_prepare = { layer.get_prepare().lock().unwrap().is_some() };
// {
// let mut layers = self.layers.borrow_mut();
// if !layers.contains_key(layer.name.as_str()) {
// let mut widgets = layer.widgets.lock().unwrap();
// if widgets.is_some() {
// let ws = widgets.take().unwrap();
// ws.into_iter().for_each(|w| {
// sender.input(MonitorInputMsg::AddWidget(w));
// });
// }
// }
// }
// if need_prepare {
// sender.oneshot_command(async move {
// let mut back = OffscreenRenderer::new(3000, 3000).unwrap();
// let canvas = back.create_canvas();
// let f = {
// let p = layer.get_prepare();
// let mut _p = p.lock().unwrap();
// _p.take().unwrap()
// };
// let imp = layer.get_imp().unwrap();
// let map: Mapper = Mercator::default().into();
// let cms = CMS::new(map, (3000.0, 3000.0));
// let canvas = Arc::new(Mutex::new(canvas));
// let target = f(imp, canvas, cms);
// layer.set_render_target(target);
// MonitorCommand::NewLayer(layer)
// });
// } else {
// {
// let mut layers = self.layers.borrow_mut();
// if layers.contains_key(layer.name.as_str()) {
// let p = layers.get_mut(layer.name.as_str()).unwrap();
// *p = layer;
// } else {
// layers.insert(layer.name.clone(), layer);
// }
// }
// let raw_id = self.get_new_layer();
// self.set_new_layer(*raw_id + 1);
// self.sidebar.sender().send(SideBarInputMsg::RefreshList);
// }
} }
MonitorInputMsg::AddMetaItem(map) => {
self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
} }
MonitorInputMsg::RemoveLayer(k) => { MonitorInputMsg::ClearMetaItems => self.sidebar.emit(SideBarInputMsg::ClearMetaItems),
self.layers.borrow_mut().remove(&k); MonitorInputMsg::UpdateMetaItem(map) => {
sender self.sidebar.emit(SideBarInputMsg::ClearMetaItems);
.output_sender() self.sidebar.emit(SideBarInputMsg::AddMetaItems(map))
.send(MonitorOutputMsg::LayerRemoved(0))
.unwrap();
}
MonitorInputMsg::UpdateLayer((k, f)) => {
f(&mut (*self.layers.borrow_mut().get_mut(&k).unwrap()));
sender
.output_sender()
.send(MonitorOutputMsg::LayerUpdated(0))
.unwrap();
} }
// MonitorInputMsg::RemoveLayer(k) => {
// self.layers.borrow_mut().remove(&k);
// sender
// .output_sender()
// .send(MonitorOutputMsg::LayerRemoved(0))
// .unwrap();
// }
// MonitorInputMsg::UpdateLayer((k, f)) => {
// f(&mut (*self.layers.borrow_mut().get_mut(&k).unwrap()));
// sender
// .output_sender()
// .send(MonitorOutputMsg::LayerUpdated(0))
// .unwrap();
// }
MonitorInputMsg::AddWidget(widget) => match widget.widget_type() { MonitorInputMsg::AddWidget(widget) => match widget.widget_type() {
WidgetType::Cairo => { WidgetType::Cairo => {
let frame = WidgetFrame::new(); let frame = WidgetFrame::new();
@ -181,6 +194,7 @@ impl Component for MonitorModel {
}, },
MonitorInputMsg::RemoveWidget => {} MonitorInputMsg::RemoveWidget => {}
MonitorInputMsg::None => {} MonitorInputMsg::None => {}
_ => {}
} }
} }
@ -189,7 +203,7 @@ impl Component for MonitorModel {
root: &Self::Root, root: &Self::Root,
sender: ComponentSender<Self>, sender: ComponentSender<Self>,
) -> ComponentParts<Self> { ) -> ComponentParts<Self> {
let layers = Rc::new(RefCell::new(HashMap::new())); let layers = Rc::new(RefCell::new(Vec::new()));
let sidebar: Controller<SideBarModel> = SideBarModel::builder() let sidebar: Controller<SideBarModel> = SideBarModel::builder()
.launch(layers.clone()) .launch(layers.clone())
.forward(sender.input_sender(), |msg| match msg { .forward(sender.input_sender(), |msg| match msg {
@ -225,13 +239,16 @@ impl Component for MonitorModel {
) { ) {
self.reset(); self.reset();
match msg { match msg {
MonitorCommand::NewLayer(layer) => { // MonitorCommand::NewLayer(layer) => {
self.layers.borrow_mut().insert(layer.name.clone(), layer); // // self.layers.borrow_mut().insert(layer.name.clone(), layer);
self.set_render_range((29.13, 30.16, 119.53, 121.13)); // // self.set_render_range((29.13, 30.16, 119.53, 121.13));
self.sidebar.sender().send(Msg::RefreshList).unwrap(); // // self.sidebar
let raw_id = self.get_new_layer(); // // .sender()
self.set_new_layer(*raw_id + 1); // // .send(SideBarInputMsg::RefreshList)
} // // .unwrap();
// // let raw_id = self.get_new_layer();
// // self.set_new_layer(*raw_id + 1);
// }
_ => {} _ => {}
} }
} }

View File

@ -6,7 +6,7 @@ use relm4::{
FactorySender, FactorySender,
}; };
use super::Msg; use super::SideBarInputMsg;
#[derive(Debug)] #[derive(Debug)]
pub enum TestMsg { pub enum TestMsg {
@ -33,7 +33,7 @@ impl BottomBarModel {
#[relm4::factory(pub)] #[relm4::factory(pub)]
impl FactoryComponent for BottomBarModel { impl FactoryComponent for BottomBarModel {
type ParentWidget = gtk::Box; type ParentWidget = gtk::Box;
type ParentInput = Msg; type ParentInput = SideBarInputMsg;
type Input = (); type Input = ();
type Output = TestMsg; type Output = TestMsg;
type Init = BottomBarModel; type Init = BottomBarModel;
@ -53,14 +53,11 @@ impl FactoryComponent for BottomBarModel {
init init
} }
fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) { fn update(&mut self, message: Self::Input, sender: FactorySender<Self>) {}
}
fn forward_to_parent(_output: Self::Output) -> Option<Self::ParentInput> { fn forward_to_parent(_output: Self::Output) -> Option<Self::ParentInput> {
Some(match _output { Some(match _output {
_ => Msg::None _ => SideBarInputMsg::None,
}) })
} }
} }

View File

@ -0,0 +1,58 @@
use gtk::prelude::*;
use relm4::{
binding::{Binding, U8Binding},
factory::FactoryView,
gtk,
prelude::{DynamicIndex, FactoryComponent},
typed_list_view::{LabelColumn, RelmColumn, TypedColumnView},
view, FactorySender, RelmObjectExt,
};
#[derive(Debug, PartialEq, Eq)]
pub(super) struct MyListItem {
tag: String,
info: String,
}
impl MyListItem {
pub fn new(tag: String, info: String) -> Self {
Self { tag, info }
}
}
pub(super) struct TagColumn;
impl LabelColumn for TagColumn {
type Item = MyListItem;
type Value = String;
const COLUMN_NAME: &'static str = "tag";
const ENABLE_SORT: bool = true;
fn get_cell_value(item: &Self::Item) -> Self::Value {
// item.value
item.tag.clone()
}
fn format_cell_value(value: &Self::Value) -> String {
format!("{}", value)
}
}
pub(super) struct InfoColumn;
impl RelmColumn for InfoColumn {
type Root = gtk::Label;
type Widgets = ();
type Item = MyListItem;
const COLUMN_NAME: &'static str = "info";
fn setup(_item: &gtk::ListItem) -> (Self::Root, Self::Widgets) {
let a = gtk::Label::new(None);
(a, ())
}
fn bind(item: &mut Self::Item, _: &mut Self::Widgets, label: &mut Self::Root) {
label.set_text(item.info.as_str());
}
}

View File

@ -1,3 +1,4 @@
pub mod sidebar; pub mod sidebar;
pub use sidebar::*; pub use sidebar::*;
pub mod bottom_bar; pub mod bottom_bar;
pub mod meta_data_list;

View File

@ -1,5 +1,3 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use abi_stable::type_level::trait_marker::Hash; use abi_stable::type_level::trait_marker::Hash;
use glib::clone; use glib::clone;
use gtk::prelude::WidgetExt; use gtk::prelude::WidgetExt;
@ -8,9 +6,10 @@ use relm4::{
binding::{Binding, U8Binding}, binding::{Binding, U8Binding},
factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque}, factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque},
prelude::*, prelude::*,
typed_list_view::{RelmListItem, TypedListView}, typed_list_view::{RelmListItem, TypedColumnView, TypedListView},
RelmObjectExt, RelmObjectExt,
}; };
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::{ use crate::{
chart::Chart, chart::Chart,
@ -18,16 +17,25 @@ use crate::{
widgets::render::{predefined::color_mapper::BoundaryNorm, Layer}, widgets::render::{predefined::color_mapper::BoundaryNorm, Layer},
}; };
use super::bottom_bar::BottomBarModel; use super::{
bottom_bar::BottomBarModel,
meta_data_list::{InfoColumn, MyListItem, TagColumn},
};
relm4::new_action_group!(FileActionGroup, "file");
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
pub struct SideBarModel { pub struct SideBarModel {
layers: Rc<RefCell<HashMap<String, Layer>>>, layers: Rc<RefCell<Vec<Layer>>>,
counter: u8, counter: u8,
list_view_wrapper: TypedListView<LayerItem, gtk::SingleSelection>, list_view_wrapper: TypedListView<LayerItem, gtk::SingleSelection>,
bottom_bar_vec: FactoryVecDeque<BottomBarModel>, bottom_bar_vec: FactoryVecDeque<BottomBarModel>,
meta_list_view: TypedColumnView<MyListItem, gtk::NoSelection>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Msg { pub enum SideBarInputMsg {
AddMetaItems(HashMap<String, String>),
ClearMetaItems,
RefreshList, RefreshList,
None, None,
} }
@ -39,11 +47,12 @@ pub enum SideBarOutputMsg {
#[relm4::component(pub)] #[relm4::component(pub)]
impl SimpleComponent for SideBarModel { impl SimpleComponent for SideBarModel {
type Init = Rc<RefCell<HashMap<String, Layer>>>; type Init = Rc<RefCell<Vec<Layer>>>;
type Output = SideBarOutputMsg; type Output = SideBarOutputMsg;
type Input = Msg; type Input = SideBarInputMsg;
view! { view! {
#[root]
gtk::Box { gtk::Box {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_spacing: 5, set_spacing: 5,
@ -51,37 +60,37 @@ impl SimpleComponent for SideBarModel {
gtk::Paned{ gtk::Paned{
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_position: 300, set_position: 200,
#[wrap(Some)] #[wrap(Some)]
set_start_child = &gtk::Box{ set_start_child = &gtk::Box{
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_spacing: 5, set_spacing: 5,
gtk::Frame{ gtk::Frame{
add_css_class: "rb", add_css_class: "rb",
#[local] #[name="meta_panel"]
top_panel -> gtk::Notebook{} gtk::Notebook::builder().vexpand(true).hexpand(true).build() -> gtk::Notebook{}
},
gtk::Button {
set_label: "Add Layers",
connect_clicked[sender] => move |_| {
},
},
gtk::Button {
set_label: "Add Layer",
connect_clicked[sender] => move |_| {
println!("hello");
sender.output(
SideBarOutputMsg::NewLayer(
Layer::grid_render_layer_with_path(
std::path::Path::new("./test2.npz"),
"DBZ".to_string(),
Npz,
BoundaryNorm::default(),
)
)
).unwrap()
},
}, },
// gtk::Button {
// set_label: "Add Layers",
// connect_clicked[sender] => move |_| {
// },
// },
// gtk::Button {
// set_label: "Add Layer",
// connect_clicked[sender] => move |_| {
// println!("hello");
// sender.output(
// SideBarOutputMsg::NewLayer(
// Layer::grid_render_layer_with_path(
// std::path::Path::new("./test2.npz"),
// "DBZ".to_string(),
// Npz,
// BoundaryNorm::default(),
// )
// )
// ).unwrap()
// },
// },
}, },
#[wrap(Some)] #[wrap(Some)]
@ -89,10 +98,10 @@ impl SimpleComponent for SideBarModel {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_vexpand: true, set_vexpand: true,
set_hexpand: true, set_hexpand: true,
#[local] #[name="bottom_panel"]
bottom_panel -> gtk::Notebook{ gtk::Notebook::builder().vexpand(true).build() -> gtk::Notebook{
set_margin_top: 10, set_margin_top: 10,
set_margin_bottom: 5, set_margin_bottom: 5
}, },
#[local_ref] #[local_ref]
counter_box -> gtk::Box{ counter_box -> gtk::Box{
@ -101,7 +110,32 @@ impl SimpleComponent for SideBarModel {
} }
}, },
},
layer_page = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build() -> gtk::ScrolledWindow{
#[wrap(Some)]
#[local_ref]
set_child=my_view -> gtk::ListView{},
set_margin_horizontal:5,
set_margin_vertical:3
},
#[local_ref]
meta_view -> gtk::ColumnView{
set_hexpand:true,
set_vexpand:true,
set_show_column_separators: true,
set_show_row_separators: true,
set_enable_rubberband:true,
set_reorderable:false,
},
bottom_panel.append_page(&layer_page, Some(&gtk::Label::new(Some("Layers")))),
meta_panel.append_page(meta_view, Some(&gtk::Label::new(Some("Meta")))),
meta_panel.append_page(&Chart::new(), Some(&gtk::Label::new(Some("Chart")))),
#[local_ref]
info_c -> gtk::ColumnViewColumn{
set_expand: true
} }
} }
@ -123,47 +157,46 @@ impl SimpleComponent for SideBarModel {
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-up-filled".to_string())); bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-up-filled".to_string()));
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-down-filled".to_string())); bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-down-filled".to_string()));
} }
let mut meta_list_view = TypedColumnView::new();
meta_list_view.append_column::<TagColumn>();
meta_list_view.append_column::<InfoColumn>();
let model = SideBarModel { let model = SideBarModel {
meta_list_view,
layers: init, layers: init,
counter: 0, counter: 0,
list_view_wrapper, list_view_wrapper,
bottom_bar_vec, bottom_bar_vec,
}; };
let my_view = &model.list_view_wrapper.view; let my_view = &model.list_view_wrapper.view;
let top_panel = gtk::Notebook::builder().vexpand(true).hexpand(true).build();
top_panel.append_page(&Chart::new(), Some(&gtk::Label::new(Some("Chart"))));
let bottom_panel = gtk::Notebook::builder().vexpand(true).build();
let layer_page = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build();
let counter_box = model.bottom_bar_vec.widget(); let counter_box = model.bottom_bar_vec.widget();
let meta_view = &model.meta_list_view.view;
layer_page.set_child(Some(my_view)); let columns = model.meta_list_view.get_columns();
layer_page.set_margin_horizontal(5); let info_c = columns.get("info").unwrap();
layer_page.set_margin_vertical(3);
bottom_panel.append_page(&layer_page, Some(&gtk::Label::new(Some("Layers"))));
let widgets = view_output!(); let widgets = view_output!();
ComponentParts { model, widgets } ComponentParts { model, widgets }
} }
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) { fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message { match message {
Msg::RefreshList => { SideBarInputMsg::RefreshList => {
let mut list = self let mut list = self
.layers .layers
.borrow() .borrow()
.iter() .iter()
.map(|(k, v)| LayerItem::new(k.clone(), v.visiable)) .map(|v| LayerItem::new(v.name.clone(), v.visiable, v.get_thumbnail()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.list_view_wrapper.clear(); self.list_view_wrapper.clear();
self.list_view_wrapper.extend_from_iter(list); self.list_view_wrapper.extend_from_iter(list);
} }
SideBarInputMsg::AddMetaItems(hs) => {
for (k, v) in hs {
self.meta_list_view.append(MyListItem::new(k, v));
}
}
SideBarInputMsg::ClearMetaItems => {
self.meta_list_view.clear();
}
_ => {} _ => {}
} }
} }
@ -173,20 +206,24 @@ impl SimpleComponent for SideBarModel {
struct LayerItem { struct LayerItem {
layer_name: String, layer_name: String,
visiable: bool, visiable: bool,
img: Option<gtk::gdk::Texture>,
} }
impl LayerItem { impl LayerItem {
fn new(name: String, visiable: bool) -> Self { fn new(name: String, visiable: bool, img: Option<gtk::gdk::Texture>) -> Self {
Self { Self {
layer_name: name, layer_name: name,
visiable, visiable,
img,
} }
} }
} }
struct Widgets { struct Widgets {
label: gtk::Label, label: gtk::Label,
screen_shot: gtk::Image,
button: gtk::CheckButton, button: gtk::CheckButton,
menu: gtk::PopoverMenu,
} }
impl RelmListItem for LayerItem { impl RelmListItem for LayerItem {
@ -194,8 +231,32 @@ impl RelmListItem for LayerItem {
type Widgets = Widgets; type Widgets = Widgets;
fn setup(_item: &gtk::ListItem) -> (gtk::Box, Widgets) { fn setup(_item: &gtk::ListItem) -> (gtk::Box, Widgets) {
relm4::menu! {
main_menu: {
"File" {
"Open" => OpenAction,
"Open Folder" => OpenAction,
},
"Edit" {
"New Layer" => OpenAction,
"Undo" => OpenAction,
"Redo" => OpenAction,
},
"Plugins" {
"Plugin1" => OpenAction,
"Plugin2" => OpenAction,
},
}
}
relm4::view! { relm4::view! {
my_box = gtk::Box { my_box = gtk::Box {
gtk::Frame{
set_margin_end: 10,
#[name = "screen_shot"]
gtk::Image{
set_size_request: (65, 40),
}
},
#[name = "label"] #[name = "label"]
gtk::Label{ gtk::Label{
set_halign: gtk::Align::Start, set_halign: gtk::Align::Start,
@ -207,17 +268,40 @@ impl RelmListItem for LayerItem {
gtk::CheckButton{ gtk::CheckButton{
set_halign: gtk::Align::End, set_halign: gtk::Align::End,
}, },
#[name = "menu"]
gtk::PopoverMenu::from_model(Some(&main_menu)){}
} }
} }
let widgets = Widgets { label, button }; let widgets = Widgets {
screen_shot,
label,
button,
menu,
};
(my_box, widgets) (my_box, widgets)
} }
fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) { fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
let Widgets { label, button } = widgets; let Widgets {
label,
button,
screen_shot,
menu,
} = widgets;
let gesture_click = gtk::GestureClick::new();
gesture_click.set_button(gtk::gdk::BUTTON_SECONDARY);
screen_shot.set_paintable(self.img.as_ref());
let menu = menu.clone();
gesture_click.connect_released(clone!(@weak menu => move |gesture_click, _, x, y| {
menu.set_pointing_to(Some(&gtk::gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
menu.popup();
}));
_root.add_controller(gesture_click);
label.set_label(&format!("Layer: {} ", &self.layer_name)); label.set_label(&format!("Layer: {} ", &self.layer_name));
button.set_active(self.visiable); button.set_active(self.visiable);
} }

View File

@ -1,4 +1,5 @@
#[derive(Debug)] #[derive(Debug)]
pub enum SettingMsg { pub enum SettingMsg {
PathFormats((String, (String, String))), PathFormats((String, (String, String))),
SaveConfig,
} }

View File

@ -55,12 +55,34 @@ impl SimpleComponent for SettingModel {
set_margin_end:10, set_margin_end:10,
}} }}
}, },
path_page = adw::PreferencesPage{ path_page = gtk::Box{
set_orientation:gtk::Orientation::Vertical,
gtk::Box{
set_hexpand:true,
gtk::Label::new(Some("Paths")){
add_css_class: "h1",
set_halign: gtk::Align::Start,
},
gtk::Button{
set_halign: gtk::Align::End,
connect_clicked[sender] => move |_| {
sender.input(SettingMsg::SaveConfig);
},
#[wrap(Some)]
set_child=&adw::ButtonContent{
set_label: "Save",
set_icon_name: "save-filled",
}
}
},
adw::PreferencesPage{
set_title:"Paths", set_title:"Paths",
#[local_ref] #[local_ref]
add=my_view -> adw::PreferencesGroup{ add=my_view -> adw::PreferencesGroup{
set_title:"Add", set_title:"Add",
} }
}
}, },
stack.add_titled(&path_page, None, "Paths"), stack.add_titled(&path_page, None, "Paths"),
@ -103,6 +125,10 @@ impl SimpleComponent for SettingModel {
let mut list_guard = self.path_list.guard(); let mut list_guard = self.path_list.guard();
// list_guard.push_back(PathItem::new(k, v)); // list_guard.push_back(PathItem::new(k, v));
} }
SettingMsg::SaveConfig => {
let mut config = CONFIG.lock().unwrap();
config.save();
}
} }
} }
} }

View File

@ -66,4 +66,21 @@ impl Config {
Err(ConfigError::DefaultConfigError) Err(ConfigError::DefaultConfigError)
} }
pub fn save(&self) -> Result<(), ConfigError> {
if let Some(dir_path) = env::var("RADARG_CONFIG")
.ok()
.map(|x| PathBuf::from(x))
.or(dirs::config_dir())
{
let path = dir_path.join("radarg.toml");
let mut file = std::fs::File::create(path)?;
let ser_config = toml::to_string_pretty(&self).unwrap();
file.write_all(ser_config.as_bytes());
Ok(())
} else {
Err(ConfigError::DefaultConfigError)
}
}
} }

59
src/coords/cms.rs Normal file
View File

@ -0,0 +1,59 @@
use std::ops::Range;
use geo_types::LineString;
use crate::coords::Mapper;
#[derive(Debug)]
pub struct CMS {
mapper: Mapper,
window_size: (f32, f32),
bounds: (f64, f64, f64, f64),
}
unsafe impl Send for CMS {}
unsafe impl Sync for CMS {}
impl CMS {
pub fn new(mapper: Mapper, window_size: (f32, f32)) -> Self {
let bounds = mapper.get_bounds();
Self {
mapper,
window_size,
bounds,
}
}
pub fn set_lat_range(&mut self, lat_range: Range<f64>) {
self.mapper.set_lat_range(lat_range);
self.bounds = self.mapper.get_bounds()
}
pub fn set_lon_range(&mut self, lon_range: Range<f64>) {
self.mapper.set_lon_range(lon_range);
self.bounds = self.mapper.get_bounds();
}
pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> {
self.mapper.map(loc).ok().map(|(x, y)| {
// println!("x: {}, y: {}", x, y);
let (w, h) = self.window_size;
let (w, h) = (w as f64, h as f64);
let (x, y) = (x - self.bounds.0, y - self.bounds.2);
let (x, y) = (
x / (self.bounds.1 - self.bounds.0),
1.0 - y / (self.bounds.3 - self.bounds.2),
);
let (x, y) = (x * w, y * h);
(x as f32, y as f32)
})
}
pub fn ring_map(&self, line: &LineString) -> Option<LineString<f32>> {
Some(
line.points()
.into_iter()
.map(|p| self.map((p.x(), p.y())).unwrap())
.collect::<Vec<_>>()
.into(),
)
}
}

View File

@ -14,6 +14,16 @@ pub struct Mapper {
unsafe impl Send for Mapper {} unsafe impl Send for Mapper {}
unsafe impl Sync for Mapper {} unsafe impl Sync for Mapper {}
impl std::fmt::Debug for Mapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mapper")
.field("proj", &self.proj)
.field("range", &self.range)
.field("bounds", &self.bounds)
.finish()
}
}
impl Clone for Mapper { impl Clone for Mapper {
fn clone(&self) -> Self { fn clone(&self) -> Self {
let c = self.proj.proj_info(); let c = self.proj.proj_info();

View File

@ -3,6 +3,7 @@ use num_traits::Num;
pub mod mapper; pub mod mapper;
pub mod proj; pub mod proj;
pub mod wgs84; pub mod wgs84;
pub mod cms;
pub use mapper::Mapper; pub use mapper::Mapper;
// pub use wgs84::LatLonCoord; // pub use wgs84::LatLonCoord;

35
src/data/meta.rs Normal file
View File

@ -0,0 +1,35 @@
use chrono::prelude::*;
use geo_macros::{pformat, StructToMap, ToHashMap};
use radarg_plugin_interface::MetaData;
#[derive(ToHashMap, Clone, Debug, Default)]
pub struct MetaInfo {
#[pformat(("日期时间", "{this.format(\"%Y-%m-%d %H:%M:%S\")|}"))]
pub datetime: Option<DateTime<Utc>>,
#[pformat(("站点信息","{}"))]
pub site_info: Option<String>,
#[pformat(("纬度范围","start: {this.0|:.2} end: {this.1|:.2}"))]
pub lat_range: Option<(f64, f64)>,
#[pformat(("经度范围","start: {this.0|:.2} end: {this.1|:.2}"))]
pub lon_range: Option<(f64, f64)>,
#[pformat(("数据格式说明","{}"))]
pub data_format: Option<String>,
#[pformat(("{}"))]
pub other_info: Option<String>,
}
impl From<MetaData> for MetaInfo {
fn from(meta: MetaData) -> Self {
Self {
datetime: meta
.datetime
.into_option()
.map(|p| Utc.timestamp_opt(p, 0).unwrap()),
site_info: meta.site_info.into_option().map(|v| v.into_string()),
lat_range: meta.lat_range.into_option().map(|v| (v[0], v[1])),
lon_range: meta.lon_range.into_option().map(|v| (v[0], v[1])),
data_format: meta.data_format.into_option().map(|v| v.into_string()),
other_info: meta.other_info.into_option().map(|v| v.into_string()),
}
}
}

View File

@ -1,5 +1,7 @@
pub mod meta;
use crate::errors::DataError; use crate::errors::DataError;
use async_trait::async_trait; use async_trait::async_trait;
pub use meta::MetaInfo;
use ndarray::{ use ndarray::{
s, Array1, Array2, Array3, ArrayBase, DataMut, Dimension, Ix1, Ix2, OwnedRepr, RawDataClone, s, Array1, Array2, Array3, ArrayBase, DataMut, Dimension, Ix1, Ix2, OwnedRepr, RawDataClone,
}; };

View File

@ -16,6 +16,8 @@ pub enum PipelineError {
#[from] #[from]
source: ProjError, source: ProjError,
}, },
#[error("data error")]
DataError(String)
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View File

@ -1,4 +1,3 @@
#![feature(step_trait)]
#![allow(unused)] #![allow(unused)]
#![allow(dead_code)] #![allow(dead_code)]
#[macro_use] #[macro_use]
@ -28,13 +27,10 @@ use tracing_subscriber;
mod widgets; mod widgets;
const APP_ID: &str = "org.tsuki.radar_g"; const APP_ID: &str = "org.tsuki.radar_g";
static RUNTIME: SafeLazy<Runtime> = static RUNTIME: SafeLazy<Runtime> =
SafeLazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed.")); SafeLazy::new(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."));
static CONFIG: SafeLazy<Mutex<Config>> = SafeLazy::new(|| Mutex::new(Config::from_env().unwrap())); static CONFIG: SafeLazy<Mutex<Config>> = SafeLazy::new(|| Mutex::new(Config::from_env().unwrap()));
static PLUGIN_MANAGER: SafeLazy<PluginManager> = SafeLazy::new(|| PluginManager::new().unwrap()); static PLUGIN_MANAGER: SafeLazy<PluginManager> = SafeLazy::new(|| PluginManager::new().unwrap());
// static SETTING: UnsafeLazy<Settings> = UnsafeLazy::new(|| Settings::new(APP_ID));
fn main() { fn main() {
// Load GL pointers from epoxy (GL context management library used by GTK). // Load GL pointers from epoxy (GL context management library used by GTK).

240
src/pipeline/dispatcher.rs Normal file
View File

@ -0,0 +1,240 @@
use super::element::TargetType;
use super::offscreen_renderer::{CanvasWrapper, OffscreenRenderer};
use crate::{
components::app::{Buffer, FILE_PATH_ROOT},
CONFIG,
};
use chrono::{prelude::*, Duration};
use once_cell::sync::Lazy;
use regex::Regex;
use std::{cell::Ref, collections::HashMap, path::PathBuf};
use tracing::*;
static REREMAP: Lazy<HashMap<&str, &str>> = Lazy::new(|| {
let mut map = HashMap::new();
map.insert("%Y", r"\d{4}");
map.insert("%m", r"\d{2}");
map.insert("%d", r"\d{2}");
map.insert("%H", r"\d{2}");
map.insert("%M", r"\d{2}");
map.insert("%S", r"\d{2}");
map.insert("{prefix}", r"(.+?)");
map
});
#[derive(Debug)]
pub struct Dispatcher {
datetime: DateTime<Utc>,
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(),
fore_len,
back_len,
step,
}
}
pub fn set_current_time(&mut self, datetime: DateTime<Utc>) {
self.datetime = datetime;
}
pub fn set_step(&mut self, step: Duration) {
self.step = step;
}
pub fn set_fore_len(&mut self, fore_len: usize) {
self.fore_len = fore_len;
}
pub fn set_back_len(&mut self, back_len: usize) {
self.back_len = back_len;
}
pub fn get_single_path(
&self,
name: &str,
current_time: DateTime<Utc>,
check_existed: bool,
) -> Option<String> {
let datetime_format: regex::Regex =
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
let config = CONFIG.lock().unwrap();
let path_format = config
.plugins
.get("etws_loader")
.unwrap()
.path_formats
.as_ref();
if path_format.is_none() {
return None;
}
let c = path_format.unwrap().get(name).map(|s| {
let path = s.clone();
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
let mut result_path = path.clone();
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = current_time.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
result_path
});
if let Some(c) = c {
if check_existed {
if std::path::Path::new(&c).exists() {
Some(c)
} else {
None
}
} else {
Some(c)
}
} else {
None
}
}
pub fn get_path(
&self,
name: &str,
current_time: DateTime<Utc>,
check_existed: bool,
mut max_retry_time: usize,
) -> Option<Vec<(String, DateTime<Utc>)>> {
let datetime_format: regex::Regex =
Regex::new(r"%[YHMSmd](?:[-/:_]?%[YHMSmd])*").unwrap();
let config = CONFIG.lock().unwrap();
let path_format = config
.plugins
.get("etws_loader")
.unwrap()
.path_formats
.as_ref();
if path_format.is_none() {
return None;
}
path_format.unwrap().get(name).map(|s| {
let path = s.clone();
let file_path = { FILE_PATH_ROOT.lock().unwrap().clone() };
let path = if path.starts_with("./") {
let file_root = file_root(&file_path, path.replace("./", ""));
file_root.map(|root| {
let splited = path.split_at(2);
root.join(PathBuf::from(splited.1))
})
} else {
Some(PathBuf::from(path))
};
let mut result_paths = Vec::new();
if let Some(path_uninit) = path {
let mut path_str = path_uninit.to_string_lossy().to_string();
let prefixs = get_prefix(&file_path, &path_str);
for (idx, prefix) in prefixs.iter().enumerate() {
if let Some(s) = prefix.as_ref() {
path_str = path_str.replacen("{prefix}", &s, 1);
}
}
let need_formated = datetime_format.captures_iter(&path_str).collect::<Vec<_>>();
let mut fore = self.fore_len;
let mut back = 1;
while fore > 0 {
let mut result_path = path_str.clone();
let t = current_time - self.step * fore as i32;
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = t.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
if check_existed {
if max_retry_time == 0 {
break;
}
if !std::path::Path::new(&result_path).exists() {
max_retry_time = max_retry_time - 1;
continue;
} else {
result_paths.push((result_path.clone(), t));
}
} else {
result_paths.push((result_path.clone(), t));
}
fore = fore - 1;
}
while back < self.back_len + 1 {
let mut result_path = path_str.clone();
let t = current_time + self.step * back as i32;
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = t.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
if check_existed {
if max_retry_time == 0 {
break;
}
if !std::path::Path::new(&result_path).exists() {
max_retry_time = max_retry_time - 1;
continue;
} else {
result_paths.push((result_path.clone(), t));
}
} else {
result_paths.push((result_path.clone(), t));
}
back = back + 1;
}
}
result_paths
})
}
}
fn file_root(path: &PathBuf, pat: String) -> Option<PathBuf> {
let new_pat = rereplace(&pat);
let path_str = path.to_string_lossy();
let re = Regex::new(&new_pat).unwrap();
let splited = re.split(&path_str);
let splited = splited.collect::<Vec<_>>();
if splited.len() == 2 {
let root = splited.first().unwrap();
Some(PathBuf::from(root))
} else {
None
}
}
fn rereplace(pat: &str) -> String {
let mut result = pat.to_string();
for (k, v) in REREMAP.iter() {
result = result.replace(k, v);
}
result
}
fn get_prefix(path: &PathBuf, pat: &str) -> Vec<Option<String>> {
let replaced = rereplace(pat);
let path_str = path.to_string_lossy();
let re = Regex::new(&replaced).unwrap();
let prefixs = re.captures(&path_str).unwrap();
let mut prefixs = prefixs.iter().collect::<Vec<_>>();
prefixs.remove(0);
prefixs
.into_iter()
.map(|x| x.map(|v| v.as_str().to_string()))
.collect()
}

320
src/pipeline/element.rs Normal file
View File

@ -0,0 +1,320 @@
use super::{offscreen_renderer::CanvasWrapper, Dispatcher, Pipeline};
use crate::components::app::AppCommand;
use crate::components::ControlPanelInputMsg;
use crate::coords::cms::CMS;
use crate::data::MetaInfo;
use crate::errors::{PipelineError, RenderError};
use crate::RUNTIME;
use crate::{coords::Range, widgets::widget::Widget};
use chrono::{DateTime, Utc};
use femtovg::{renderer::OpenGl, Canvas, ImageId};
use futures::StreamExt;
use glib::PropertyGet;
use std::any::Any;
use std::borrow::Borrow;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Formatter;
use std::rc::Rc;
use std::sync::atomic::AtomicUsize;
use std::{
cell::{Ref, RefCell},
fmt::Debug,
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use tokio::sync::{
oneshot::{channel, Receiver, Sender},
Notify,
};
use tracing::Instrument;
pub type ElementID = usize;
static ELEMENT_ID: AtomicUsize = AtomicUsize::new(0);
pub type Data = Box<dyn Any + Send + Sync>;
pub type Buffer =
Arc<Mutex<BTreeMap<DateTime<Utc>, (Option<Data>, Option<RenderResult>)>>>;
type DrawFunc = Box<dyn Fn(&mut CanvasWrapper, &CMS)>;
type IResult<T> = Result<T, PipelineError>;
#[derive(Debug)]
pub enum Element {
TimeSeries(TimeSeriesElement),
Instant(InstantElement),
}
impl Element {
pub fn create_time_series<T: ElementImpl>(
imp: T,
dispatcher: Rc<Dispatcher>,
key: String,
cms: Arc<Mutex<CMS>>,
) -> Self {
Element::TimeSeries(TimeSeriesElement::new(imp, dispatcher, cms, key))
}
pub fn create_instant(
_type: InstantElementDrawerType,
dispatcher: Arc<Dispatcher>,
key: String,
) -> Self {
Element::Instant(InstantElement::new(_type, dispatcher, key))
}
pub fn id(&self) -> ElementID {
match self {
Element::TimeSeries(e) => e.id,
Element::Instant(e) => e.id,
}
}
pub fn key(&self) -> String {
match self {
Element::TimeSeries(e) => e.key.clone(),
Element::Instant(e) => e.key.clone(),
}
}
}
#[derive(Debug)]
pub struct TimeSeriesElement {
pub id: ElementID,
pub key: String,
cms: Arc<Mutex<CMS>>,
imp: Arc<dyn ElementImpl>,
registers: Arc<Mutex<HashMap<DateTime<Utc>, Vec<Arc<Notify>>>>>,
pipeline: Pipeline,
buffer: Buffer,
dispatcher: Rc<Dispatcher>,
}
pub enum InstantElementDrawerType {
Draw(DrawFunc),
Prepared(Target),
}
impl Debug for InstantElementDrawerType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InstantElementDrawerType").finish()
}
}
#[derive(Debug)]
pub struct InstantElement {
pub id: ElementID,
pub key: String,
draw_type: InstantElementDrawerType,
dispatcher: Arc<Dispatcher>,
}
pub trait ElementImpl: Debug + Send + Sync + 'static {
fn render(&self, data: Box<dyn Any>, canvas: &mut CanvasWrapper, cms: &mut CMS) -> Target;
}
impl InstantElement {
fn new(_type: InstantElementDrawerType, dispatcher: Arc<Dispatcher>, key: String) -> Self {
let id = ELEMENT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self {
id,
key,
draw_type: _type,
dispatcher,
}
}
pub fn render(&self, canvas: &mut CanvasWrapper, cms: &CMS) {
match self.draw_type {
InstantElementDrawerType::Draw(ref func) => {
func(canvas, cms);
}
InstantElementDrawerType::Prepared(ref target) => {}
}
}
}
impl TimeSeriesElement {
fn new<T: ElementImpl>(
imp: T,
dispatcher: Rc<Dispatcher>,
cms: Arc<Mutex<CMS>>,
key: String,
) -> Self {
let id = ELEMENT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let pipeline = Pipeline::new(20, key.clone());
let buffer = Arc::new(Mutex::new(BTreeMap::new()));
Self {
id,
key,
imp: Arc::new(imp),
cms,
registers: Arc::new(Mutex::new(HashMap::new())),
buffer,
dispatcher,
pipeline,
}
}
pub fn register(
&mut self,
datetime: DateTime<Utc>,
) -> IResult<Receiver<Result<RenderResult, RenderError>>> {
use tokio::sync::Notify;
use tokio::task;
let notifer = Arc::new(Notify::new());
let new_notifer = notifer.clone();
let (sender, recv) = channel::<Result<RenderResult, RenderError>>();
self.change_time(datetime)?;
let buffer = self.buffer.lock().unwrap();
if buffer.contains_key(&datetime) {
let (_, target) = buffer.get(&datetime).unwrap();
// Already in buffer
if let Some(target) = target {
sender.send(Ok(target.clone())).unwrap();
} else {
// self.register_noti(datetime, notifer);
let buffer = self.buffer.clone();
task::spawn_local(async move {
new_notifer.notified().await;
let result = buffer.lock().unwrap().get(&datetime).unwrap().1.clone();
sender.send(Ok(result.unwrap())).unwrap();
});
}
return Ok(recv);
} else {
return Err(PipelineError::DataError("No data found".to_string()));
}
}
pub fn set_cms(&mut self, cms: Arc<Mutex<CMS>>) {
self.cms = cms;
}
fn register_noti(&mut self, datetime: DateTime<Utc>, noti: Arc<Notify>) {
self.registers
.lock()
.unwrap()
.entry(datetime)
.or_insert_with(Vec::new)
.push(noti);
}
pub fn change_time(&mut self, date_time: DateTime<Utc>) -> IResult<()> {
let imp = self.imp.clone();
let tasks = self.pipeline.set_current(
date_time,
true,
3,
Arc::new(move |canvas, cms| imp.render(Box::new(()), canvas, cms)),
self.cms.clone(),
);
let tasks = tasks.map(|tms| tms.into_iter().map(|time| (time, (None, None))));
if let Some(tasks) = tasks {
if tasks.len() == 0 {
return Err(PipelineError::DataError("No data found".to_string()));
}
let mut buffer = self.buffer.lock().unwrap();
buffer.extend(tasks);
}
let buffer = self.buffer.clone();
let registers = self.registers.clone();
let listening_func = self.pipeline.listening(move |recv, idx| {
let buffer = buffer.clone();
let registers = registers.clone();
Box::pin(async move {
let registers = registers;
let (dt, result) = recv.await.unwrap();
if let Ok(result) = result {
let mut buffer = buffer.lock().unwrap();
(*(&mut buffer.get_mut(&dt).unwrap())).1 = Some(result);
}
{
registers
.lock()
.unwrap()
.get_mut(&dt)
.unwrap()
.into_iter()
.for_each(|n| {
n.notify_waiters();
});
}
})
});
let runner = Pipeline::run(&mut self.pipeline);
RUNTIME.spawn(listening_func);
RUNTIME.spawn(runner);
Ok(())
}
pub fn insert_data(&mut self, time: DateTime<Utc>, data: Box<dyn Any + Send + Sync>) {
let mut buffer = self.buffer.lock().unwrap();
buffer.insert(time, (Some(data), None));
}
}
#[derive(Debug, Clone)]
pub struct RenderResult {
target: Target,
meta_info: MetaInfo,
}
impl RenderResult {
pub fn new(target: Target, meta_info: MetaInfo) -> Self {
Self { target, meta_info }
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Target {
pub target: TargetType,
pub thumbnail: Option<gtk::gdk::Texture>,
pub width: f32,
pub height: f32,
pub bounds: (Range, Range),
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub enum TargetType {
ImageId(ImageId),
Mem(Vec<u8>),
}
impl Target {
pub fn new(
target: TargetType,
width: f32,
height: f32,
bounds: (Range, Range),
thumbnail: Option<gtk::gdk::Texture>,
) -> Self {
Self {
target,
width,
height,
bounds,
thumbnail,
}
}
pub fn size(&self, cms: &CMS) -> (f32, f32) {
let (x, y) = self.bounds;
let p1 = (x.0, y.0);
let p2 = (x.1, y.1);
let (x1, y1) = cms.map(p1).unwrap();
let (x2, y2) = cms.map(p2).unwrap();
((x2 - x1).abs(), (y2 - y1).abs())
}
pub fn origin(&self, cms: &CMS) -> (f32, f32) {
let (x, y) = self.bounds;
let p1 = (x.0, y.1);
cms.map(p1).unwrap()
}
pub fn set_target(&mut self, target: TargetType) {
self.target = target;
}
}

View File

@ -0,0 +1,57 @@
use super::predefined::GridFieldRenderer;
use super::renders::DataRenderer;
use crate::data::Radar2d;
use crate::pipeline::element::{ElementImpl, Target};
use crate::pipeline::offscreen_renderer::CanvasWrapper;
use crate::widgets::predefined::color_mapper::ColorMapper;
use crate::widgets::{LayerImpl, CMS};
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
use std::any::Any;
use std::fmt::Debug;
#[derive(Debug)]
pub struct GridElementImpl<CMAP, T>
where
T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
CMAP: ColorMapper<T>,
{
renderer: GridFieldRenderer<CMAP, T>,
}
impl<CMAP, T> GridElementImpl<CMAP, T>
where
T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
CMAP: ColorMapper<T>,
{
pub fn new(color: CMAP) -> Self {
Self {
renderer: GridFieldRenderer::new(color),
}
}
}
impl<CMAP, T> ElementImpl for GridElementImpl<CMAP, T>
where
T: Num
+ NumOps
+ PartialOrd
+ Copy
+ Clone
+ Debug
+ Send
+ Sync
+ FromPrimitive
+ AsPrimitive<f64>,
CMAP: ColorMapper<T> + Debug + 'static,
{
fn render(
&self,
data: Box<dyn Any>,
canvas: &mut CanvasWrapper,
cms: &mut crate::coords::cms::CMS,
) -> crate::pipeline::element::Target {
let data = data.downcast::<Radar2d<T>>().unwrap();
let result = self.renderer.render(canvas, cms, &data, (3000.0, 3000.0));
result
}
}

View File

@ -1,134 +1,13 @@
use anyhow::{Ok, Result}; pub mod dispatcher;
use femtovg::{self}; pub mod element;
use geo_types::{line_string, LineString}; mod element_impl;
use image::RgbImage; mod new_pipeline;
use ndarray::parallel::prelude::*;
use ndarray::{Array2, ArrayView2};
use num_traits::{Num, AsPrimitive, FromPrimitive};
pub mod offscreen_renderer; pub mod offscreen_renderer;
mod predefined;
mod renders;
pub mod utils; pub mod utils;
pub mod pool;
pub mod render_pipeline;
use crate::{ pub use dispatcher::Dispatcher;
coords::Mapper, pub use element::RenderResult;
data::{Radar2d, RadarData2d}, pub use new_pipeline::Pipeline;
}; pub use offscreen_renderer::OffscreenRenderer;
pub struct Color(femtovg::Color);
impl Color {
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(femtovg::Color::rgba(r, g, b, a))
}
}
impl From<femtovg::Color> for Color {
fn from(value: femtovg::Color) -> Self {
Self(value)
}
}
pub trait Pipeline<T> {
type Output;
fn run(&self, input: T) -> Result<Self::Output>;
}
pub struct ProjPipe<'a> {
pub mapper: &'a Mapper,
}
impl<'a> ProjPipe<'a> {
pub fn new(mapper: &'a Mapper) -> Self {
Self { mapper }
}
}
impl<'a, 'b: 'a, T, Raw> Pipeline<&'b RadarData2d<T, Raw>> for ProjPipe<'a>
where
T: Num + Clone + PartialEq + PartialOrd,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
type Output = Array2<LineString>;
fn run(&self, input: &'b RadarData2d<T, Raw>) -> Result<Self::Output> {
let dim1 = input.dim1.view();
let dim2 = input.dim2.view();
let shape = input.data.shape();
let mut polygons = Vec::with_capacity(dim1.len() * dim2.len());
let d1_dpi = dim1[1] - dim1[0];
let d2_dpi = dim2[1] - dim2[0];
for d1 in dim1 {
for d2 in dim2 {
let line: LineString = vec![
(*d1, *d2),
(*d1 + d1_dpi, *d2),
(*d1 + d1_dpi, *d2 + d2_dpi),
(*d1, *d2 + d2_dpi),
(*d1, *d2),
]
.into();
let projed_polygon = self.mapper.ring_map(&line)?;
polygons.push(projed_polygon);
}
}
Ok(Array2::from_shape_vec([shape[0], shape[1]], polygons)?)
}
}
pub struct ShadePipe<T: Num> {
colors: Vec<Color>,
levels: Vec<T>,
}
struct ShaderPrepare(Array2<LineString>);
impl<T: Num + PartialOrd> ShadePipe<T> {
pub fn new(levels: Vec<T>, colors: Vec<Color>) -> Self {
Self { colors, levels }
}
pub fn get_color(&self, v: T) -> &Color {
let len = self.levels.len();
let mut left = 0;
let mut right = len - 1;
while left < right - 1 {
let middle = (right + left) / 2;
if v > self.levels[middle] {
left = middle;
} else {
right = middle;
}
}
&self.colors[left]
}
}
impl<'a, T, Raw> Pipeline<&'a RadarData2d<T, Raw>> for ShadePipe<T>
where
T: Num + PartialEq + PartialOrd + Clone + FromPrimitive,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
type Output = Array2<Option<femtovg::Color>>;
fn run(&self, input: &'a RadarData2d<T, Raw>) -> Result<Array2<Option<femtovg::Color>>> {
let data = input.data.view();
let result = data.mapv(|v| {
if T::from_i8(-125).unwrap() == v {
None
} else {
let color = self.get_color(v);
Some(color.0)
}
});
Ok(result)
}
}

View File

@ -0,0 +1,208 @@
use super::{
dispatcher::Dispatcher,
element::RenderResult,
offscreen_renderer::{CanvasWrapper, OffscreenRenderer},
utils::data_to_element,
};
use crate::coords::cms::CMS;
use crate::pipeline::element::Target;
use crate::{
coords::{proj::Mercator, Mapper},
data::MetaInfo,
errors::RenderError,
widgets::Layer,
PLUGIN_MANAGER,
};
use chrono::prelude::*;
use femtovg::{renderer::OpenGl, Canvas, ImageId};
use futures::{future::BoxFuture, Future};
use smallvec::SmallVec;
use std::fmt::{Debug, Formatter};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex},
};
use tokio::{
sync::{mpsc, oneshot},
task,
};
// #[derive(Clone, Debug)]
// pub struct RenderResult {
// target: Target,
// meta_info: MetaInfo,
// }
// impl RenderResult {
// pub fn new(target: Target, meta_info: MetaInfo) -> Self {
// Self { target, meta_info }
// }
// }
type RenderR = Result<RenderResult, RenderError>;
pub struct Pipeline {
pool: Vec<BoxFuture<'static, ()>>,
results: SmallVec<[RenderResult; 20]>,
dispatcher: Option<Rc<Dispatcher>>,
handlers: Option<Vec<oneshot::Receiver<(DateTime<Utc>, RenderR)>>>,
handler: Option<mpsc::Receiver<RenderR>>,
sender: Option<mpsc::Sender<RenderR>>,
key: String,
}
impl Debug for Pipeline {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pipeline").finish()
}
}
impl Pipeline {
pub fn new(len: usize, key: String) -> Self {
Self {
pool: Vec::new(),
results: SmallVec::new(),
dispatcher: None,
handlers: None,
handler: None,
sender: None,
key,
}
}
pub fn set_dispatcher(&mut self, dispatcher: Rc<Dispatcher>) {
self.dispatcher = Some(dispatcher);
}
pub fn init(&mut self) -> &mut Self {
self
}
pub fn set_current(
&mut self,
current_time: DateTime<Utc>,
check_existed: bool,
max_retry_time: usize,
task: Arc<dyn Fn(&mut CanvasWrapper, &mut CMS) -> Target + Send + Sync>,
cms: Arc<Mutex<CMS>>,
) -> Option<Vec<DateTime<Utc>>> {
let paths = {
self.dispatcher.as_ref().unwrap().get_path(
&self.key,
current_time,
check_existed,
max_retry_time,
)
};
if let Some(paths) = paths {
let mut recvs = Vec::new();
let mut result = Vec::new();
for (path, datetime) in paths.into_iter() {
let (sender, mut receiver) = oneshot::channel();
self.add_task(
datetime,
self.worker(datetime, task.clone(), cms.clone(), path),
sender,
);
recvs.push(receiver);
result.push(datetime);
}
self.handlers.replace(recvs);
Some(result)
} else {
None
}
}
pub fn work_num(&self) -> usize {
self.pool.len()
}
fn worker(
&self,
datetime: DateTime<Utc>,
task: Arc<dyn Fn(&mut CanvasWrapper, &mut CMS) -> Target + Send + Sync>,
cms: Arc<Mutex<CMS>>,
path: impl AsRef<str> + Send + 'static,
) -> BoxFuture<'static, RenderR> {
Box::pin(async move {
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
let first_block = loaded_data.blocks.pop().unwrap();
let handle = task::spawn_blocking(move || {
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
let mut canvas_wrapper = offscreen_renderer.create_canvas();
let mut cms = cms.lock().unwrap();
let target = task(&mut canvas_wrapper, &mut cms);
target
});
let target = handle.await.unwrap();
Ok(RenderResult::new(target, loaded_data.meta.into()))
})
}
pub fn add_task<TASK>(
&mut self,
time: DateTime<Utc>,
task: TASK,
tx: oneshot::Sender<(DateTime<Utc>, RenderR)>,
) where
TASK: Future<Output = RenderR> + 'static + Send,
{
let future = async move {
let data = task.await;
tx.send((time, data)).unwrap();
};
self.pool.push(Box::pin(future));
}
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
let pool = value.get_pool();
Box::pin(async move {
for f in pool.into_iter() {
task::spawn(f);
}
})
}
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
where
F: Fn(oneshot::Receiver<(DateTime<Utc>, RenderR)>, usize) -> BoxFuture<'static, ()>
+ Send
+ 'static
+ Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
for f in l {
task::spawn(f);
}
})
}
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
where
F: FnOnce(oneshot::Receiver<(DateTime<Utc>, RenderR)>) -> BoxFuture<'static, ()>
+ Send
+ 'static
+ Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
for (h, f) in handler.into_iter().zip(f) {
task::spawn(f(h));
}
})
}
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
use std::mem::replace;
let pool = replace(&mut self.pool, Vec::new());
pool
}
}

View File

@ -1,4 +1,3 @@
use crate::widgets::Target;
use super::utils::*; use super::utils::*;
use euclid::Size2D; use euclid::Size2D;
@ -10,10 +9,12 @@ use std::num::NonZeroU32;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::{Mutex, RwLock}; use std::sync::{Mutex, RwLock};
use std::{cell::RefCell, sync::Arc}; use std::{cell::RefCell, sync::Arc};
use std::fmt::{Debug, Formatter};
use surfman::{ use surfman::{
device, Adapter, Connection, Context, ContextAttributeFlags, Device, Error, GLApi, device, Adapter, Connection, Context, ContextAttributeFlags, Device, Error, GLApi,
NativeConnection, NativeDevice, NativeConnection, NativeDevice,
}; };
use crate::pipeline::element::Target;
pub struct OffscreenRenderer { pub struct OffscreenRenderer {
context: Arc<RwLock<Context>>, context: Arc<RwLock<Context>>,
@ -23,6 +24,12 @@ pub struct OffscreenRenderer {
size: (i32, i32), // canvas: Arc<Mutex<CanvasWrapper>>, size: (i32, i32), // canvas: Arc<Mutex<CanvasWrapper>>,
} }
impl Debug for OffscreenRenderer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OffscreenRenderer").finish()
}
}
impl OffscreenRenderer { impl OffscreenRenderer {
pub fn new(width: i32, height: i32) -> Result<Self, surfman::Error> { pub fn new(width: i32, height: i32) -> Result<Self, surfman::Error> {
let connection = Connection::new()?; let connection = Connection::new()?;
@ -103,7 +110,8 @@ impl OffscreenRenderer {
CanvasWrapper::new(canvas) CanvasWrapper::new(canvas)
} }
pub fn get_img(&self, target: Target) {} pub fn get_img(&self, target: Target) {
}
pub fn get_mem_img(&self) -> Vec<u8> { pub fn get_mem_img(&self) -> Vec<u8> {
let (w, h) = self.size; let (w, h) = self.size;
@ -132,7 +140,6 @@ impl Drop for OffscreenRenderer {
let mut context = self.context.write().unwrap(); let mut context = self.context.write().unwrap();
self.device.destroy_context(&mut context).unwrap(); self.device.destroy_context(&mut context).unwrap();
let _ = self; let _ = self;
println!("OffscreenRenderer dropped");
} }
} }
@ -140,6 +147,13 @@ unsafe impl Send for OffscreenRenderer {}
unsafe impl Sync for OffscreenRenderer {} unsafe impl Sync for OffscreenRenderer {}
pub struct CanvasWrapper(femtovg::Canvas<OpenGl>); pub struct CanvasWrapper(femtovg::Canvas<OpenGl>);
impl Debug for CanvasWrapper {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CanvasWrapper").finish()
}
}
impl CanvasWrapper { impl CanvasWrapper {
fn new(canvas: femtovg::Canvas<OpenGl>) -> Self { fn new(canvas: femtovg::Canvas<OpenGl>) -> Self {
Self(canvas) Self(canvas)

View File

@ -1,55 +0,0 @@
use crate::errors::PoolError;
use chrono::prelude::*;
use smallvec::SmallVec;
use sorted_vec::{SortedSet, SortedVec};
use std::collections::VecDeque;
type PResult<T> = Result<T, PoolError>;
pub struct DataPool<T>
where
T: Send + Sync,
{
datetime_pool: SortedSet<(DateTime<Utc>, usize)>,
items: Vec<T>,
current: Option<usize>,
len: usize,
}
impl<T> DataPool<T>
where
T: Send + Sync + Ord,
{
pub fn new(len: usize) -> Self {
DataPool {
datetime_pool: SortedSet::with_capacity(len),
items: Vec::with_capacity(len),
current: None,
len,
}
}
pub fn add(&mut self, item: T, datetime: DateTime<Utc>) -> PResult<()> {
let len = self.items.len();
if len == self.len {
return Err(PoolError::PoolFull);
}
self.items.push(item);
Ok(())
}
pub fn set_current(&mut self, datetime: DateTime<Utc>) -> PResult<()> {
if self.datetime_pool.is_empty() {
return Err(PoolError::PoolInitialized("Pool is empty"));
}
if let Some(t) = self.datetime_pool.iter().position(|(x, _)| *x == datetime) {
let pre = (t - self.len / 2).max(0);
let post = (t + self.len / 2).min(self.len - 1);
self.datetime_pool.drain(pre..post);
}
Ok(())
}
pub fn next(&self) {}
}

View File

@ -0,0 +1,183 @@
use super::super::renders::DataRenderer;
use crate::coords::cms::CMS;
use crate::pipeline::element::{Target, TargetType};
use crate::widgets::predefined::color_mapper::ColorMapper;
use crate::{data::Radar2d, utils::meshgrid};
use femtovg::ImageInfo;
use femtovg::{
renderer::OpenGl, Canvas, ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget,
};
use geo_types::LineString;
use gl::types::GLvoid;
use image::{imageops::resize, ImageBuffer, Rgba};
use ndarray::ArrayView2;
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
use std::{fmt::Debug, io::Cursor, marker::PhantomData};
#[derive(Debug)]
pub struct GridFieldRenderer<CMAP, T>
where
T: NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64>,
CMAP: ColorMapper<T>,
{
cmap: CMAP,
value_phantom: PhantomData<T>,
}
impl<T: NumOps + PartialOrd + Copy + FromPrimitive + AsPrimitive<f64>, CMAP: ColorMapper<T>>
GridFieldRenderer<CMAP, T>
{
pub fn new(cmap: CMAP) -> Self {
Self {
cmap,
value_phantom: PhantomData,
}
}
fn draw_2d(
&self,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
cms: &CMS,
data: ArrayView2<T>,
dims: (ArrayView2<f64>, ArrayView2<f64>),
window_size: (f32, f32),
fill_value: T,
) {
let shape = data.shape();
let (rows, cols) = (shape[0], shape[1]);
let (dim1, dim2) = dims;
let d1_s = dim1[[0, 0]];
let d1_e = dim1[[0, cols - 1]];
let d2_s = dim2[[0, 0]];
let d2_e = dim2[[rows - 1, 0]];
for r in 0..rows - 1 {
for c in 0..cols - 1 {
let lb_lat = dim2[[r, c]];
let lb_lon = dim1[[r, c]];
let rt_lat = dim2[[r + 1, c + 1]];
let rt_lon = dim1[[r + 1, c + 1]];
let cell: LineString = vec![
(lb_lon, lb_lat),
(rt_lon + 0.001, lb_lat),
(rt_lon + 0.001, rt_lat),
(lb_lon, rt_lat + 0.001),
(lb_lon, lb_lat + 0.001),
]
.into();
let v = &data[[r, c]];
let mapped_color = self.cmap.map_value_to_color(*v, fill_value);
if None == mapped_color {
continue;
}
let mapped_ring = cms.ring_map(&cell).unwrap();
let mut path = Path::new();
let mut points = mapped_ring.points();
let first_point = points.next().unwrap();
path.move_to(first_point.x(), first_point.y());
for point in points {
path.line_to(point.x(), point.y());
}
path.close();
canvas.fill_path(&path, &Paint::color(mapped_color.unwrap()));
}
}
}
}
impl<T, CMAP> DataRenderer for GridFieldRenderer<CMAP, T>
where
T: Num + NumOps + PartialOrd + Copy + Clone + FromPrimitive + AsPrimitive<f64>,
CMAP: ColorMapper<T>,
{
type Data = Radar2d<T>;
fn render(
&self,
canvas: &mut Canvas<OpenGl>,
mut cms: &mut CMS,
data: &Self::Data,
size: (f32, f32),
) -> Target {
let (w, h) = size;
let new_img = canvas
.create_image_empty(w as usize, h as usize, Rgba8, ImageFlags::empty())
.expect("Can't Create Image");
canvas.image_size(new_img).unwrap();
canvas.set_render_target(RenderTarget::Image(new_img));
let _data = data.data.view();
let (_dim1, _dim2) = meshgrid(data.dim1.view(), data.dim2.view());
let lat_start = data.dim2.view().first().unwrap().clone();
let lat_end = data.dim2.view().last().unwrap().clone();
let lon_start = data.dim1.view().first().unwrap().clone();
let lon_end = data.dim1.view().last().unwrap().clone();
cms.set_lat_range(lat_start..lat_end);
cms.set_lon_range(lon_start..lon_end);
self.draw_2d(
canvas,
&cms,
_data,
(_dim1.view(), _dim2.view()),
(w, h),
data.fill_value,
);
canvas.flush();
let mut pixels: Vec<u8> = vec![0; w as usize * h as usize * 4];
unsafe {
gl::ReadPixels(
0,
0,
w as i32,
h as i32,
gl::RGBA,
gl::UNSIGNED_BYTE,
pixels.as_mut_ptr() as *mut GLvoid,
);
debug_assert_eq!(gl::GetError(), gl::NO_ERROR);
}
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(w as u32, h as u32, pixels)
.expect("Failed to create ImageBuffer");
let thumbnail = resize(&img, 500, 500, image::imageops::FilterType::Lanczos3);
let mut thumb_buffer = Cursor::new(Vec::new());
img.write_to(&mut thumb_buffer, image::ImageOutputFormat::Png)
.expect("Failed to write PNG buffer");
let thumb_data = thumb_buffer.into_inner();
let thumbnail_tex =
gtk::gdk::Texture::from_bytes(&gtk::glib::Bytes::from(&thumb_data)).unwrap();
// 将 ImageBuffer 编码为 PNG
let mut png_buffer = Cursor::new(Vec::new());
img.write_to(&mut png_buffer, image::ImageOutputFormat::Bmp)
.expect("Failed to write PNG buffer");
let png_data = png_buffer.into_inner();
let d1_start = (data.dim1.view()).first().unwrap().clone();
let d1_end = (data.dim1.view()).last().unwrap().clone();
let d2_start = data.dim2.view().first().unwrap().clone();
let d2_end = data.dim2.view().last().unwrap().clone();
Target::new(
TargetType::Mem(png_data),
w,
h,
((d1_start, d1_end).into(), (d2_start, d2_end).into()),
Some(thumbnail_tex),
)
}
}

View File

@ -0,0 +1,3 @@
mod grid_field_renderer;
pub use grid_field_renderer::*;

View File

@ -1,30 +1,38 @@
use super::{
dispatcher::Dispatcher,
offscreen_renderer::{CanvasWrapper, OffscreenRenderer},
};
use crate::{
coords::{cms::CMS, proj::Mercator, Mapper},
data::MetaInfo,
errors::RenderError,
widgets::Layer,
PLUGIN_MANAGER,
};
use chrono::prelude::*; use chrono::prelude::*;
use femtovg::{renderer::OpenGl, Canvas, ImageId}; use femtovg::{renderer::OpenGl, Canvas, ImageId};
use std::sync::{Arc, Mutex}; use futures::{future::BoxFuture, Future};
use smallvec::SmallVec;
use crate::widgets::{Layer, Target, TargetType}; use std::{
cell::RefCell,
use super::offscreen_renderer::CanvasWrapper; rc::Rc,
sync::{Arc, Mutex},
};
use tokio::{
sync::{mpsc, oneshot},
task,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RenderResult { pub struct RenderResult {
pub meta: MetaInfo,
pub layer: Layer, pub layer: Layer,
time: DateTime<Utc>, time: DateTime<Utc>,
} }
// 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 { impl RenderResult {
pub fn new(layer: Layer, time: DateTime<Utc>) -> Self { pub fn new(layer: Layer, time: DateTime<Utc>, meta: MetaInfo) -> Self {
Self { layer, time } Self { layer, time, meta }
} }
pub fn timestamp(&self) -> i64 { pub fn timestamp(&self) -> i64 {
@ -35,3 +43,168 @@ impl RenderResult {
self.time self.time
} }
} }
type RenderR = Result<RenderResult, RenderError>;
pub struct Pipeline {
pool: Vec<BoxFuture<'static, ()>>,
results: SmallVec<[RenderResult; 20]>,
dispatcher: Option<Rc<RefCell<Dispatcher>>>,
handlers: Option<Vec<oneshot::Receiver<RenderR>>>,
handler: Option<mpsc::Receiver<RenderR>>,
sender: Option<mpsc::Sender<RenderR>>,
key: String,
}
impl Pipeline {
pub fn new(len: usize, key: String) -> Self {
Self {
pool: Vec::new(),
results: SmallVec::new(),
dispatcher: None,
handlers: None,
handler: None,
sender: None,
key,
}
}
pub fn set_dispatcher(&mut self, dispatcher: Rc<RefCell<Dispatcher>>) {
self.dispatcher = Some(dispatcher);
}
pub fn init(&mut self) -> &mut Self {
self
}
pub fn set_current(
&mut self,
current_time: DateTime<Utc>,
check_existed: bool,
max_retry_time: usize,
) -> Option<Vec<DateTime<Utc>>> {
let paths = {
let dispatcher = self.dispatcher.as_ref().unwrap();
let dispatcher = dispatcher.borrow();
dispatcher.get_path(&self.key, current_time, check_existed, max_retry_time)
};
if let Some(paths) = paths {
let mut recvs = Vec::new();
let mut result = Vec::new();
for (path, datetime) in paths.into_iter() {
let (sender, mut receiver) = oneshot::channel::<RenderR>();
self.add_task(datetime.timestamp(), self.worker(datetime, path), sender);
recvs.push(receiver);
result.push(datetime);
}
self.handlers.replace(recvs);
Some(result)
} else {
None
}
}
pub fn work_num(&self) -> usize {
self.pool.len()
}
fn worker(
&self,
datetime: DateTime<Utc>,
path: impl AsRef<str> + Send + 'static,
) -> BoxFuture<'static, RenderR> {
Box::pin(async move {
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
let first_block = loaded_data.blocks.pop().unwrap();
if let Some((_, layer)) = data_to_layer(first_block) {
let handle = task::spawn_blocking(move || {
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
let canvas_wrapper = offscreen_renderer.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.clone(), cms);
let canvas = canvas.lock().unwrap();
Some(c)
} else {
None
};
layer.set_render_target(target.unwrap());
layer
});
let target = handle.await.unwrap();
Ok(RenderResult::new(target, datetime, loaded_data.meta.into()))
} else {
println!("no layer");
Err(RenderError::None)
}
})
}
pub fn add_task<TASK>(&mut self, timestamp: i64, task: TASK, tx: oneshot::Sender<RenderR>)
where
TASK: Future<Output = RenderR> + 'static + Send,
{
let future = async move {
let data = task.await;
tx.send(data).unwrap();
};
self.pool.push(Box::pin(future));
}
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
let pool = value.get_pool();
Box::pin(async move {
for f in pool.into_iter() {
task::spawn(f);
// f.await;
}
})
}
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
where
F: Fn(oneshot::Receiver<RenderR>, usize) -> BoxFuture<'static, ()> + Send + 'static + Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
for f in l.into_iter() {
f.await;
}
})
}
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
where
F: FnOnce(oneshot::Receiver<RenderR>) -> BoxFuture<'static, ()> + Send + 'static + Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
for (h, f) in handler.into_iter().zip(f) {
task::spawn(f(h));
// f(h).await;
}
})
}
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
use std::mem::replace;
let pool = replace(&mut self.pool, Vec::new());
pool
}
pub fn cancel_task(&mut self, timestamp: i64) {}
}

14
src/pipeline/renders.rs Normal file
View File

@ -0,0 +1,14 @@
use crate::coords::cms::CMS;
use crate::pipeline::element::Target;
use femtovg::{renderer::OpenGl, Canvas};
pub trait DataRenderer {
type Data;
fn render(
&self,
canvas: &mut Canvas<OpenGl>,
cms: &mut CMS,
data: &Self::Data,
size: (f32, f32),
) -> Target;
}

View File

@ -1,30 +1,16 @@
use super::render_pipeline::RenderResult; use crate::{
use super::{offscreen_renderer::OffscreenRenderer, pool::DataPool}; coords::cms::CMS, data::Radar2d, errors::RenderError,
use crate::components::app::Buffer; widgets::predefined::color_mapper::BoundaryNorm,
use crate::coords::proj::Mercator; };
use crate::coords::Mapper; use chrono::{prelude::*, Duration};
use crate::widgets::{Render, Target, TargetType, CMS}; use radarg_plugin_interface::*;
use crate::CONFIG; use std::sync::Arc;
use crate::{data::Radar2d, errors::RenderError, widgets::Layer, PLUGIN_MANAGER}; use std::{rc::Rc, sync::Mutex};
use chrono::{prelude::*, Duration}; use tracing::*;
use euclid::approxord::max;
use futures::future::*; use super::{
use radarg_plugin_interface::*; element::Element, element_impl::GridElementImpl, predefined::GridFieldRenderer, Dispatcher,
use regex::Regex;
use smallvec::SmallVec;
use std::cell::{Ref, RefCell};
use std::rc::Rc;
use std::sync::Mutex;
use std::{
borrow::Borrow,
collections::{HashMap, VecDeque},
future::Future,
ops::Deref,
pin::Pin,
sync::Arc,
}; };
use tokio::sync::{mpsc, oneshot};
use tokio::task;
pub fn ck() { pub fn ck() {
unsafe { unsafe {
@ -32,371 +18,15 @@ pub fn ck() {
} }
} }
type RenderR = Result<RenderResult, RenderError>;
pub struct Pipeline {
pool: Vec<BoxFuture<'static, ()>>,
results: SmallVec<[RenderResult; 20]>,
dispatcher: Option<Rc<RefCell<Dispatcher>>>,
handlers: Option<Vec<oneshot::Receiver<RenderR>>>,
handler: Option<mpsc::Receiver<RenderR>>,
sender: Option<mpsc::Sender<RenderR>>,
key: String,
}
impl Pipeline {
pub fn new(len: usize, key: String) -> Self {
Self {
pool: Vec::new(),
results: SmallVec::new(),
dispatcher: None,
handlers: None,
handler: None,
sender: None,
key,
}
}
pub fn set_dispatcher(&mut self, dispatcher: Rc<RefCell<Dispatcher>>) {
self.dispatcher = Some(dispatcher);
}
pub fn init(&mut self) -> &mut Self {
self
}
pub fn set_current(
&mut self,
current_time: DateTime<Utc>,
check_existed: bool,
max_retry_time: usize,
) -> Option<Vec<DateTime<Utc>>> {
let dispatcher = self.dispatcher.clone().unwrap();
let dispatcher = dispatcher.borrow_mut();
let paths = dispatcher.get_path(&self.key, current_time, check_existed, max_retry_time);
if let Some(paths) = paths {
let mut recvs = Vec::new();
let mut result = Vec::new();
for (path, datetime) in paths.into_iter() {
// let sender = self.sender.clone().unwrap();
let (sender, mut receiver) = oneshot::channel::<RenderR>();
self.add_task(datetime.timestamp(), self.worker(datetime, path), sender);
recvs.push(receiver);
result.push(datetime);
}
self.handlers.replace(recvs);
Some(result)
} else {
None
}
}
pub fn work_num(&self) -> usize {
// self.pool.as_ref().unwrap().len()
self.pool.len()
}
fn worker(
&self,
datetime: DateTime<Utc>,
path: impl AsRef<str> + Send + 'static,
) -> BoxFuture<'static, RenderR> {
Box::pin(async move {
let loader = PLUGIN_MANAGER.get_plugin_by_name("etws_loader").unwrap();
let mut loaded_data = loader.load(path.as_ref().into()).unwrap();
let first_block = loaded_data.blocks.pop().unwrap();
if let Some((_, layer)) = data_to_layer(first_block) {
let handle = task::spawn_blocking(move || {
let mut offscreen_renderer = OffscreenRenderer::new(3000, 3000).unwrap();
let canvas_wrapper = offscreen_renderer.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.clone(), cms);
let canvas = canvas.lock().unwrap();
Some(c)
} else {
None
};
layer.set_render_target(target.unwrap());
layer
});
let target = handle.await.unwrap();
Ok(RenderResult::new(target, datetime))
} else {
println!("no layer");
Err(RenderError::None)
}
})
}
pub fn add_task<TASK>(&mut self, timestamp: i64, task: TASK, tx: oneshot::Sender<RenderR>)
where
TASK: Future<Output = RenderR> + 'static + Send,
{
let future = async move {
let data = task.await;
tx.send(data).unwrap();
};
self.pool.push(Box::pin(future));
}
pub fn run(value: &mut Self) -> BoxFuture<'static, ()> {
let pool = value.get_pool();
Box::pin(async move {
for f in pool.into_iter() {
task::spawn(f);
// f.await;
}
})
}
pub fn listening<F>(&mut self, f: F) -> BoxFuture<'static, ()>
where
F: Fn(oneshot::Receiver<RenderR>, usize) -> BoxFuture<'static, ()> + Send + 'static + Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
let l = handler.into_iter().enumerate().map(|(h, i)| f(i, h));
for f in l.into_iter() {
f.await;
}
})
}
pub fn listening_one_by_one<F>(&mut self, f: Vec<F>) -> BoxFuture<'static, ()>
where
F: FnOnce(oneshot::Receiver<RenderR>) -> BoxFuture<'static, ()> + Send + 'static + Sync,
{
let mut handler = self.handlers.take().unwrap();
Box::pin(async move {
for (h, f) in handler.into_iter().zip(f) {
task::spawn(f(h));
// f(h).await;
}
})
}
pub fn get_pool(&mut self) -> Vec<BoxFuture<'static, ()>> {
// self.pool.clone()
use std::mem::replace;
let pool = replace(&mut self.pool, Vec::new());
pool
}
pub fn cancel_task(&mut self, timestamp: i64) {}
}
pub struct Dispatcher {
datetime: DateTime<Utc>,
fore_len: usize,
back_len: usize,
step: Duration,
registered_buffer: Buffer,
}
impl Dispatcher {
pub fn new(
fore_len: usize,
back_len: usize,
step: Duration,
registered_buffer: Buffer,
) -> Self {
// let config = CONFIG.lock().unwrap();
// let config = CONFIG.lock().unwrap();
// let etws_loader = CONFIG.lock().unwrap().plugins.get("etws_loader").unwrap();
// let format = etws_loader.path_formats.as_ref();
Self {
datetime: Utc::now(),
// path_format: format,
fore_len,
back_len,
step,
registered_buffer,
}
}
// pub fn set_path_format(&mut self, formats: HashMap<String, String>) {
// self.path_format = formats;
// }
pub fn set_current_time(&mut self, datetime: DateTime<Utc>) {
self.datetime = datetime;
}
pub fn set_step(&mut self, step: Duration) {
self.step = step;
}
pub fn set_fore_len(&mut self, fore_len: usize) {
self.fore_len = fore_len;
}
pub fn set_back_len(&mut self, back_len: usize) {
self.back_len = back_len;
}
pub fn get_single_path(
&self,
name: &str,
current_time: DateTime<Utc>,
check_existed: bool,
) -> Option<String> {
let datetime_format: regex::Regex =
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
let config = CONFIG.lock().unwrap();
let path_format = config
.plugins
.get("etws_loader")
.unwrap()
.path_formats
.as_ref();
if path_format.is_none() {
return None;
}
let c = path_format.unwrap().get(name).map(|s| {
let path = s.clone();
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
let mut result_path = path.clone();
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = current_time.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
result_path
});
if let Some(c) = c {
if check_existed {
if std::path::Path::new(&c).exists() {
Some(c)
} else {
None
}
} else {
Some(c)
}
} else {
None
}
}
pub fn get_path(
&self,
name: &str,
current_time: DateTime<Utc>,
check_existed: bool,
mut max_retry_time: usize,
) -> Option<Vec<(String, DateTime<Utc>)>> {
let datetime_format: regex::Regex =
Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
let config = CONFIG.lock().unwrap();
let path_format = config
.plugins
.get("etws_loader")
.unwrap()
.path_formats
.as_ref();
if path_format.is_none() {
return None;
}
path_format.unwrap().get(name).map(|s| {
let path = s.clone();
let need_formated = datetime_format.captures_iter(&path).collect::<Vec<_>>();
let mut fore = self.fore_len;
let mut back = 0;
let mut result_paths = Vec::new();
let buffer: Ref<'_, HashMap<_, _>> = (*self.registered_buffer).borrow();
let buffer = buffer.get(name).unwrap();
while fore > 0 {
let mut result_path = path.clone();
let t = current_time - self.step * fore as i32;
if buffer.get(&t).is_some() {
fore = fore - 1;
continue;
}
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = t.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
if check_existed {
if max_retry_time == 0 {
break;
}
if !std::path::Path::new(&result_path).exists() {
max_retry_time = max_retry_time - 1;
continue;
} else {
result_paths.push((result_path.clone(), t));
}
} else {
result_paths.push((result_path.clone(), t));
}
fore = fore - 1;
}
while back < self.back_len {
let mut result_path = path.clone();
let t = current_time + self.step * back as i32;
if buffer.get(&t).is_some() {
back = back + 1;
continue;
}
for need_format in need_formated.iter() {
let fmt = need_format.get(0).unwrap().as_str();
let t = t.format(fmt).to_string();
result_path = result_path.replace(fmt, &t);
}
if check_existed {
if max_retry_time == 0 {
break;
}
if !std::path::Path::new(&result_path).exists() {
max_retry_time = max_retry_time - 1;
continue;
} else {
result_paths.push((result_path.clone(), t));
}
} else {
result_paths.push((result_path.clone(), t));
}
back = back + 1;
}
result_paths
})
}
}
macro_rules! match_in_macro { macro_rules! match_in_macro {
($block:ident,$(($branch:path,$name:literal, $t:ty, $color:expr)),+) => { ($block:ident,$dispatcher:ident,$cms:ident,$(($branch:path,$name:literal, $t:ty, $color:expr)),+) => {
{ {
let datetime = Utc.timestamp_opt($block.datetime, 0).unwrap(); let datetime = Utc.timestamp_opt($block.datetime, 0).unwrap();
match $block.data_type { match $block.data_type {
$( $(
$branch => { $branch => {
let data: $t = $block.into(); let element = Element::create_time_series(GridElementImpl::new($color), $dispatcher, $name.to_string(), $cms);
let layer = Layer::grid_render_layer(data, format!($name), $color); Some(element)
Some(( datetime ,layer))
}, },
)+ )+
_ => None _ => None
@ -406,12 +36,18 @@ macro_rules! match_in_macro {
}; };
} }
pub fn data_to_layer(block: Block) -> Option<(DateTime<Utc>, Layer)> { pub fn data_to_element(
block: &Block,
dispatcher: Rc<Dispatcher>,
cms: Arc<Mutex<CMS>>,
) -> Option<Element> {
use crate::utils::*; use crate::utils::*;
use radarg_plugin_interface::PluginResultType; use radarg_plugin_interface::PluginResultType;
match block.shape { match block.shape {
DataShape::Matrix => match_in_macro!( DataShape::Matrix => match_in_macro!(
block, block,
dispatcher,
cms,
( (
PluginResultType::DBZ, PluginResultType::DBZ,
"DBZ", "DBZ",
@ -486,5 +122,3 @@ pub fn data_to_layer(block: Block) -> Option<(DateTime<Utc>, Layer)> {
_ => None, _ => None,
} }
} }
// Pin<Box<dyn Future<Output = Result<Vec<RenderResult>, RenderError>> + Send + 'static>>

View File

@ -1,7 +1,5 @@
use std::ops::Range; use std::ops::Range;
use geo_types::LineString; use geo_types::LineString;
use crate::coords::Mapper; use crate::coords::Mapper;
pub struct CMS { pub struct CMS {

View File

@ -0,0 +1,180 @@
use super::super::{Render};
use crate::pipeline::offscreen_renderer::CanvasWrapper;
use crate::{coords::Range, widgets::widget::Widget};
use femtovg::{renderer::OpenGl, Canvas, ImageId};
use std::{
cell::{Ref, RefCell},
fmt::Debug,
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use crate::coords::cms::CMS;
use crate::pipeline::element::Target;
type PrepareFunc = Arc<
Mutex<
Option<
Box<
dyn FnOnce(
LayerImplSync,
// Box<dyn LayerImpl + Send + Sync>,
Arc<Mutex<CanvasWrapper>>,
CMS,
) -> Target
+ Sync
+ Send,
>,
>,
>,
>;
type DrawFunc = Arc<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>;
pub type LayerImplSync = Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>;
#[derive(Clone)]
pub struct Layer {
pub visiable: bool,
pub name: String,
pub widgets: Arc<Mutex<Option<Vec<Box<dyn Widget>>>>>,
target: Arc<Mutex<Option<Target>>>,
prepare: PrepareFunc,
imp: Option<Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>>,
draw: DrawFunc,
}
impl Debug for Layer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Layer")
.field("visiable", &self.visiable)
.field("target", &self.target)
.field("imp", &self.imp)
.finish()
}
}
pub trait LayerImpl: Debug {
fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: &CMS) -> Option<Target>;
}
impl Layer {
pub fn new<
F: 'static + Fn(&Self, Render, (f32, f32)) + Send + Sync,
PREPARE: FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Send + Sync + 'static,
IMP: LayerImpl + Sync + Send + 'static,
>(
visiable: bool,
draw: F,
widgets: Option<Vec<Box<dyn Widget>>>,
layer_name: String,
prepare: Option<PREPARE>,
imp: Option<IMP>,
) -> Self {
Layer {
visiable,
target: Arc::new(Mutex::new(None)),
name: layer_name,
widgets: Arc::new(Mutex::new(widgets)),
prepare: Arc::new(Mutex::new(prepare.map(|p| {
Box::new(move |a, b, c| p(a, b, c))
as Box<
dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target
+ Sync
+ Send,
>
}))),
draw: Arc::new(Box::new(draw)),
imp: imp.map(|i| {
Arc::new(Mutex::new(
Box::new(i) as Box<dyn LayerImpl + Send + Sync + 'static>
))
}),
}
}
pub fn draw(&self, render: &Render, window_size: (f32, f32)) {
if self.visiable {
let drawer = &self.draw;
drawer(self, render.clone(), window_size);
}
}
pub fn get_prepare(&self) -> PrepareFunc {
self.prepare.clone()
}
pub fn set_render_target(&self, target: Target) {
self.target.lock().unwrap().replace(target);
}
pub fn render_target(&self) -> Arc<Mutex<Option<Target>>> {
self.target.clone()
}
pub fn get_imp(&self) -> Option<Arc<Mutex<Box<dyn LayerImpl + Sync + Send + 'static>>>> {
// self.imp.map(|p| p.clone())
self.imp.clone()
}
pub fn get_thumbnail(&self) -> Option<gtk::gdk::Texture> {
self.target
.lock()
.unwrap()
.as_ref()
.and_then(|t| t.thumbnail.clone())
}
}
// #[derive(Clone, Debug, PartialEq, PartialOrd)]
// pub struct Target {
// pub target: TargetType,
// pub thumbnail: Option<gtk::gdk::Texture>,
// pub width: f32,
// pub height: f32,
// pub bounds: (Range, Range),
// }
//
// #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
// pub enum TargetType {
// ImageId(ImageId),
// Mem(Vec<u8>),
// }
//
// impl Target {
// pub fn new(
// target: TargetType,
// width: f32,
// height: f32,
// bounds: (Range, Range),
// thumbnail: Option<gtk::gdk::Texture>,
// ) -> Self {
// Self {
// target,
// width,
// height,
// bounds,
// thumbnail,
// }
// }
//
// pub fn size(&self, render: &Render) -> (f32, f32) {
// let (x, y) = self.bounds;
//
// let p1 = (x.0, y.0);
// let p2 = (x.1, y.1);
//
// let (x1, y1) = render.map(p1).unwrap();
// let (x2, y2) = render.map(p2).unwrap();
//
// ((x2 - x1).abs(), (y2 - y1).abs())
// }
//
// pub fn origin(&self, render: &Render) -> (f32, f32) {
// let (x, y) = self.bounds;
// let p1 = (x.0, y.1);
// render.map(p1).unwrap()
// }
//
// pub fn set_target(&mut self, target: TargetType) {
// self.target = target;
// }
// }

View File

@ -1,6 +1,9 @@
use super::super::{cms::CMS, Render}; use super::super::Render;
use crate::coords::cms::CMS;
use crate::pipeline::element::{self, Element, ElementID, Target};
use crate::pipeline::offscreen_renderer::CanvasWrapper; use crate::pipeline::offscreen_renderer::CanvasWrapper;
use crate::{coords::Range, widgets::widget::Widget}; use crate::{coords::Range, widgets::widget::Widget};
use chrono::{prelude::*, DateTime};
use femtovg::{renderer::OpenGl, Canvas, ImageId}; use femtovg::{renderer::OpenGl, Canvas, ImageId};
use std::{ use std::{
cell::{Ref, RefCell}, cell::{Ref, RefCell},
@ -13,159 +16,66 @@ use std::{
type PrepareFunc = Arc< type PrepareFunc = Arc<
Mutex< Mutex<
Option< Option<
Box< Box<dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Sync + Send>,
dyn FnOnce(
LayerImplSync,
// Box<dyn LayerImpl + Send + Sync>,
Arc<Mutex<CanvasWrapper>>,
CMS,
) -> Target
+ Sync
+ Send,
>,
>, >,
>, >,
>; >;
type DrawFunc = Arc<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>; type DrawFunc = Arc<dyn Fn(&Layer, Render, (f32, f32)) + Send + Sync>;
// type LayerImplSync = Box<dyn LayerImpl + Send + Sync>;
pub type LayerImplSync = Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>; pub type LayerImplSync = Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>;
pub enum AssoElement {
TimeSeries(Arc<Mutex<element::TimeSeriesElement>>),
Instant(element::InstantElement),
}
#[derive(Clone)] #[derive(Clone)]
pub struct Layer { pub struct Layer {
pub visiable: bool, pub visiable: bool,
pub name: String, pub name: String,
pub widgets: Arc<Mutex<Option<Vec<Box<dyn Widget>>>>>, associated_element: AssoElement,
target: Arc<Mutex<Option<Target>>>, time: Option<DateTime<Utc>>,
prepare: PrepareFunc,
imp: Option<Arc<Mutex<Box<dyn LayerImpl + Send + Sync>>>>,
draw: DrawFunc,
} }
impl Debug for Layer { impl Debug for Layer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Layer") f.debug_struct("Layer")
.field("visiable", &self.visiable) .field("visiable", &self.visiable)
.field("target", &self.target)
.field("imp", &self.imp)
.finish() .finish()
} }
} }
pub trait LayerImpl: Debug { pub trait LayerImpl: Debug {
fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: CMS) -> Option<Target>; fn draw(&self, canvas: &mut Canvas<OpenGl>, cms: &CMS) -> Option<Target>;
} }
impl Layer { impl Layer {
pub fn new< pub fn new(visiable: bool, layer_name: String, element: AssoElement) -> Self {
F: 'static + Fn(&Self, Render, (f32, f32)) + Send + Sync,
PREPARE: FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target + Send + Sync + 'static,
IMP: LayerImpl + Sync + Send + 'static,
>(
visiable: bool,
draw: F,
widgets: Option<Vec<Box<dyn Widget>>>,
layer_name: String,
prepare: Option<PREPARE>,
imp: Option<IMP>,
) -> Self {
Layer { Layer {
visiable, visiable,
target: Arc::new(Mutex::new(None)),
name: layer_name, name: layer_name,
widgets: Arc::new(Mutex::new(widgets)), associated_element: element,
prepare: Arc::new(Mutex::new(prepare.map(|p| { time: None,
Box::new(move |a, b, c| p(a, b, c))
as Box<
dyn FnOnce(LayerImplSync, Arc<Mutex<CanvasWrapper>>, CMS) -> Target
+ Sync
+ Send,
>
}))),
draw: Arc::new(Box::new(draw)),
imp: imp.map(|i| {
Arc::new(Mutex::new(
Box::new(i) as Box<dyn LayerImpl + Send + Sync + 'static>
))
}),
} }
} }
pub fn draw(&self, render: &Render, window_size: (f32, f32)) { pub fn draw(&self, render: &Render, window_size: (f32, f32)) {
if self.visiable { if self.visiable {
let drawer = &self.draw; if let Some(element) = &self.associated_element {
drawer(self, render.clone(), window_size); let element = element.lock().unwrap();
match *element {
Element::TimeSeries(ref e) => {
if self.time.is_none() {
return;
}
let time = self.time.unwrap();
}
Element::Instant(ref e) => {}
}
}
} }
} }
pub fn get_prepare(&self) -> PrepareFunc { pub fn get_thumbnail(&self) -> Option<gtk::gdk::Texture> {
self.prepare.clone() None
}
pub fn set_render_target(&self, target: Target) {
self.target.lock().unwrap().replace(target);
}
pub fn render_target(&self) -> Arc<Mutex<Option<Target>>> {
self.target.clone()
}
pub fn get_imp(&self) -> Option<Arc<Mutex<Box<dyn LayerImpl + Sync + Send + 'static>>>> {
// self.imp.map(|p| p.clone())
self.imp.clone()
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Target {
pub target: TargetType,
pub thumbnail: Option<gtk::gdk::Texture>,
pub width: f32,
pub height: f32,
pub bounds: (Range, Range),
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub enum TargetType {
ImageId(ImageId),
Mem(Vec<u8>),
}
impl Target {
pub fn new(
target: TargetType,
width: f32,
height: f32,
bounds: (Range, Range),
thumbnail: Option<gtk::gdk::Texture>,
) -> Self {
Self {
target,
width,
height,
bounds,
thumbnail,
}
}
pub fn size(&self, render: &Render) -> (f32, f32) {
let (x, y) = self.bounds;
let p1 = (x.0, y.0);
let p2 = (x.1, y.1);
let (x1, y1) = render.map(p1).unwrap();
let (x2, y2) = render.map(p2).unwrap();
((x2 - x1).abs(), (y2 - y1).abs())
}
pub fn origin(&self, render: &Render) -> (f32, f32) {
let (x, y) = self.bounds;
let p1 = (x.0, y.1);
render.map(p1).unwrap()
}
pub fn set_target(&mut self, target: TargetType) {
self.target = target;
} }
} }

View File

@ -2,7 +2,7 @@ mod imp;
mod layers; mod layers;
use super::super::Render; use super::super::Render;
use femtovg::{renderer::OpenGl, Canvas}; use femtovg::{renderer::OpenGl, Canvas};
pub use layers::{Layer, LayerImpl, LayerImplSync, Target, TargetType}; pub use layers::{Layer, LayerImpl, LayerImplSync};
use std::cell::Ref; use std::cell::Ref;
use super::imp::{RenderConfig, RenderStatus}; use super::imp::{RenderConfig, RenderStatus};

View File

@ -3,7 +3,7 @@ mod exterior;
mod imp; mod imp;
mod interior; mod interior;
pub mod predefined; pub mod predefined;
mod renders; pub mod renders;
pub mod widget; pub mod widget;
pub use self::cms::CMS; pub use self::cms::CMS;
pub use self::imp::{RenderConfig, RenderMotion, RenderStatus}; pub use self::imp::{RenderConfig, RenderMotion, RenderStatus};

View File

@ -1,4 +1,6 @@
use super::color_mapper::{BoundaryNorm, ColorMapper}; use super::color_mapper::{BoundaryNorm, ColorMapper};
use crate::pipeline::element::{Target, TargetType};
use femtovg::ImageInfo;
use femtovg::{ use femtovg::{
renderer::OpenGl, Canvas, ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget, renderer::OpenGl, Canvas, ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget,
}; };
@ -10,8 +12,9 @@ use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
use std::{fmt::Debug, io::Cursor, marker::PhantomData}; use std::{fmt::Debug, io::Cursor, marker::PhantomData};
use super::super::renders::DataRenderer; use super::super::renders::DataRenderer;
use super::super::{cms::CMS, LayerImpl, Render, Target, TargetType}; use super::super::{LayerImpl, Render};
use crate::{data::Radar2d, utils::meshgrid}; use crate::{data::Radar2d, utils::meshgrid};
use crate::coords::cms::CMS;
#[derive(Debug)] #[derive(Debug)]
pub struct GridFieldRenderer<CMAP, T> pub struct GridFieldRenderer<CMAP, T>
@ -101,7 +104,7 @@ where
fn render( fn render(
&self, &self,
canvas: &mut Canvas<OpenGl>, canvas: &mut Canvas<OpenGl>,
mut cms: CMS, mut cms: &CMS,
data: &Self::Data, data: &Self::Data,
size: (f32, f32), size: (f32, f32),
) -> Target { ) -> Target {
@ -133,11 +136,8 @@ where
(w, h), (w, h),
data.fill_value, data.fill_value,
); );
canvas.flush(); canvas.flush();
let mut pixels: Vec<u8> = vec![0; w as usize * h as usize * 4]; let mut pixels: Vec<u8> = vec![0; w as usize * h as usize * 4];
unsafe { unsafe {
gl::ReadPixels( gl::ReadPixels(
0, 0,
@ -153,19 +153,17 @@ where
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(w as u32, h as u32, pixels) let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(w as u32, h as u32, pixels)
.expect("Failed to create ImageBuffer"); .expect("Failed to create ImageBuffer");
let thumbnail = resize(&img, 500, 500, image::imageops::FilterType::Lanczos3); let thumbnail = resize(&img, 500, 500, image::imageops::FilterType::Lanczos3);
let mut thumb_buffer = Cursor::new(Vec::new()); let mut thumb_buffer = Cursor::new(Vec::new());
img.write_to(&mut thumb_buffer, image::ImageOutputFormat::Png) img.write_to(&mut thumb_buffer, image::ImageOutputFormat::Png)
.expect("Failed to write PNG buffer"); .expect("Failed to write PNG buffer");
let thumb_data = thumb_buffer.into_inner(); let thumb_data = thumb_buffer.into_inner();
let thumbnail_tex = let thumbnail_tex =
gtk::gdk::Texture::from_bytes(&gtk::glib::Bytes::from(&thumb_data)).unwrap(); gtk::gdk::Texture::from_bytes(&gtk::glib::Bytes::from(&thumb_data)).unwrap();
// 将 ImageBuffer 编码为 PNG // 将 ImageBuffer 编码为 PNG
let mut png_buffer = Cursor::new(Vec::new()); let mut png_buffer = Cursor::new(Vec::new());
img.write_to(&mut png_buffer, image::ImageOutputFormat::Png) img.write_to(&mut png_buffer, image::ImageOutputFormat::Bmp)
.expect("Failed to write PNG buffer"); .expect("Failed to write PNG buffer");
let png_data = png_buffer.into_inner(); let png_data = png_buffer.into_inner();
@ -189,7 +187,7 @@ where
#[derive(Debug)] #[derive(Debug)]
pub struct GridLayerImpl<CMAP, T> pub struct GridLayerImpl<CMAP, T>
where where
T: Num +NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug, T: Num + NumOps + PartialOrd + FromPrimitive + AsPrimitive<f64> + Send + Sync + Debug,
CMAP: ColorMapper<T>, CMAP: ColorMapper<T>,
{ {
renderer: GridFieldRenderer<CMAP, T>, renderer: GridFieldRenderer<CMAP, T>,
@ -210,13 +208,22 @@ where
impl<CMAP, T> LayerImpl for GridLayerImpl<CMAP, T> impl<CMAP, T> LayerImpl for GridLayerImpl<CMAP, T>
where where
T: Num + NumOps + PartialOrd + Copy + Clone + Debug + Send + Sync + FromPrimitive + AsPrimitive<f64>, T: Num
+ NumOps
+ PartialOrd
+ Copy
+ Clone
+ Debug
+ Send
+ Sync
+ FromPrimitive
+ AsPrimitive<f64>,
CMAP: ColorMapper<T> + Debug, CMAP: ColorMapper<T> + Debug,
{ {
fn draw( fn draw(
&self, &self,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>, canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
cms: CMS, cms: &CMS,
) -> Option<Target> { ) -> Option<Target> {
let result = self let result = self
.renderer .renderer

View File

@ -1,6 +1,7 @@
use femtovg::ImageFlags; use femtovg::ImageFlags;
use femtovg::PixelFormat::Rgba8; use femtovg::PixelFormat::Rgba8;
use femtovg::{renderer::OpenGl, Canvas, Paint}; use femtovg::{renderer::OpenGl, Canvas, Paint};
use glow::NativeTexture;
use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps}; use num_traits::{AsPrimitive, FromPrimitive, Num, NumOps};
use std::fmt::Debug; use std::fmt::Debug;
use std::path::Path; use std::path::Path;
@ -8,12 +9,14 @@ use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use super::super::{ use super::super::{
cms::CMS, widget::Widget, Layer, LayerImpl, LayerImplSync, Render, Target, TargetType, widget::Widget, Layer, LayerImpl, LayerImplSync, Render
}; };
use crate::coords::cms::CMS;
use super::widgets::ColorBar; use super::widgets::ColorBar;
use crate::data::{AsyncDataLoader, DataLoader, Radar2d}; use crate::data::{AsyncDataLoader, DataLoader, Radar2d};
use crate::pipeline::offscreen_renderer::CanvasWrapper; use crate::pipeline::offscreen_renderer::CanvasWrapper;
use tokio::task; use tokio::task;
use crate::pipeline::element::TargetType;
use super::{ use super::{
color_mapper::ColorMapper, color_mapper::ColorMapper,
@ -83,7 +86,7 @@ impl Layer {
move |renderer: LayerImplSync, c: Arc<Mutex<CanvasWrapper>>, cms: CMS| { move |renderer: LayerImplSync, c: Arc<Mutex<CanvasWrapper>>, cms: CMS| {
let mut canvas = c.lock().unwrap(); let mut canvas = c.lock().unwrap();
let renderer = renderer.lock().unwrap(); let renderer = renderer.lock().unwrap();
let img = renderer.draw(&mut canvas, cms).unwrap(); let img = renderer.draw(&mut canvas, &cms).unwrap();
img img
}, },
), ),

View File

@ -1,5 +1,5 @@
pub mod color_mapper; pub mod color_mapper;
pub mod gis; pub mod gis;
pub mod grid_field_renderer; // pub mod grid_field_renderer;
pub mod layers; // pub mod layers;
pub mod widgets; pub mod widgets;

View File

@ -1,14 +1,14 @@
use super::cms::CMS;
use super::Render; use super::Render;
use super::Target; use crate::coords::cms::CMS;
use femtovg::{renderer::OpenGl, Canvas}; use femtovg::{renderer::OpenGl, Canvas};
use crate::pipeline::element::Target;
pub trait DataRenderer { pub trait DataRenderer {
type Data; type Data;
fn render( fn render(
&self, &self,
canvas: &mut Canvas<OpenGl>, canvas: &mut Canvas<OpenGl>,
cms: CMS, cms: &CMS,
data: &Self::Data, data: &Self::Data,
size: (f32, f32), size: (f32, f32),
) -> Target; ) -> Target;