From 23a1949676e8502efa47536b90342f87532c2355 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 22 Jul 2026 14:17:07 +0200 Subject: [PATCH] Com protocol --- Cargo.lock | 10 ++ Cargo.toml | 4 +- pulse-macros/Cargo.toml | 12 ++ pulse-macros/src/lib.rs | 61 +++++++++ src/daemon/ptc.rs | 1 + src/pc.rs | 105 ++++++++++++++++ src/ptc.rs | 247 +++++++++++++++++++------------------ src/terminal/command.rs | 2 +- src/terminal/formatting.rs | 25 ++-- src/terminal/main.rs | 2 +- src/terminal/ptc.rs | 1 + 11 files changed, 329 insertions(+), 141 deletions(-) create mode 100644 pulse-macros/Cargo.toml create mode 100644 pulse-macros/src/lib.rs create mode 100644 src/pc.rs diff --git a/Cargo.lock b/Cargo.lock index 44c8c16..1f87829 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,12 +304,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulse-macros" +version = "0.1.0-alpha.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pulse-trader" version = "0.1.0-alpha.0" dependencies = [ "chrono", "crossterm", + "pulse-macros", "pulse-ui", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index c786396..572e0f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,15 @@ edition = "2024" [dependencies] chrono = "0.4.45" pulse-ui = { workspace = true } +pulse-macros = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } crossterm = { workspace = true } [workspace] -members = ["pulse-ui"] +members = ["pulse-macros", "pulse-ui"] [workspace.dependencies] +pulse-macros = { path = "pulse-macros", version = "0.1.0-alpha.0" } pulse-ui = { path = "pulse-ui", version = "0.1.0-alpha.0" } tokio = "1.52.3" crossterm = "0.29.0" diff --git a/pulse-macros/Cargo.toml b/pulse-macros/Cargo.toml new file mode 100644 index 0000000..882d11d --- /dev/null +++ b/pulse-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pulse-macros" +version = "0.1.0-alpha.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/pulse-macros/src/lib.rs b/pulse-macros/src/lib.rs new file mode 100644 index 0000000..cd62b37 --- /dev/null +++ b/pulse-macros/src/lib.rs @@ -0,0 +1,61 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Fields, ItemStruct, parse_macro_input}; + +#[proc_macro_attribute] +pub fn p_com(_: TokenStream, item: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(item as ItemStruct); + + let name = &input.ident; + + match &mut input.fields { + Fields::Named(fields) => { + for field in fields.named.iter_mut() { + field.vis = syn::Visibility::Public(syn::token::Pub::default()); + } + } + _ => { + return syn::Error::new_spanned(input, "p_com only supports structs with named fields") + .to_compile_error() + .into(); + } + } + + let fields = match &input.fields { + Fields::Named(fields) => &fields.named, + _ => { + return syn::Error::new_spanned(input, "p_com only supports structs with named fields") + .to_compile_error() + .into(); + } + }; + + let field_names = fields.iter().map(|f| f.ident.as_ref().unwrap()); + let field_names2 = fields.iter().map(|f| f.ident.as_ref().unwrap()); + let field_types = fields.iter().map(|f| &f.ty); + + TokenStream::from(quote! { + #[derive(Debug, Clone)] + #input + + impl PulseCom for #name { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + + #( + vec.extend(self.#field_names.to_com()); + )* + + vec + } + + fn from_com(com: &mut Vec) -> Self { + Self { + #( + #field_names2: <#field_types>::from_com(com), + )* + } + } + } + }) +} diff --git a/src/daemon/ptc.rs b/src/daemon/ptc.rs index 436f33c..ab16651 100644 --- a/src/daemon/ptc.rs +++ b/src/daemon/ptc.rs @@ -1 +1,2 @@ +include!("../pc.rs"); include!("../ptc.rs"); diff --git a/src/pc.rs b/src/pc.rs new file mode 100644 index 0000000..8c5cb31 --- /dev/null +++ b/src/pc.rs @@ -0,0 +1,105 @@ +pub trait PulseCom { + fn to_com(&self) -> Vec; + fn from_com(_com: &mut Vec) -> Self; +} + +impl PulseCom for Vec { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + + vec.extend_from_slice(&(self.len() as u32).to_le_bytes()); + + for item in self { + vec.extend(item.to_com()); + } + + vec + } + + fn from_com(com: &mut Vec) -> Self { + let len_bytes: [u8; 4] = com.drain(..4).collect::>().try_into().unwrap(); + + let len = u32::from_le_bytes(len_bytes) as usize; + + let mut result = Vec::with_capacity(len); + + for _ in 0..len { + result.push(T::from_com(com)); + } + + result + } +} + +impl PulseCom for String { + fn to_com(&self) -> Vec { + let bytes = self.as_bytes(); + let mut out = Vec::with_capacity(4 + bytes.len()); + + out.extend_from_slice(&(bytes.len() as u32).to_le_bytes()); + out.extend_from_slice(bytes); + + out + } + + fn from_com(com: &mut Vec) -> Self { + let len = u32::from_le_bytes(com[..4].try_into().unwrap()) as usize; + com.drain(..4); + + let bytes: Vec = com.drain(..len).collect(); + String::from_utf8(bytes).unwrap() + } +} + +macro_rules! int_com { + ($t:ty) => { + impl PulseCom for $t { + fn to_com(&self) -> Vec { + self.to_le_bytes().to_vec() + } + + fn from_com(com: &mut Vec) -> Self { + const N: usize = std::mem::size_of::<$t>(); + let bytes: [u8; N] = com.drain(..N).collect::>().try_into().unwrap(); + <$t>::from_le_bytes(bytes) + } + } + }; +} + +int_com!(i8); +int_com!(i16); +int_com!(i32); +int_com!(i64); +int_com!(isize); + +int_com!(u8); +int_com!(u16); +int_com!(u32); +int_com!(u64); +int_com!(usize); + +int_com!(f64); +int_com!(f32); + +#[macro_export] +macro_rules! p_com { + (struct $name:ident { $($n:ident: $v:ty),* $(,)? }) => { + #[derive(Debug, Clone)] + pub struct $name { $(pub $n: $v),* } + + impl PulseCom for $name { + fn to_com(&self) -> Vec { + let mut vec = Vec::new(); + $(vec.extend(self.$n.to_com());)* + vec + } + + fn from_com(com: &mut Vec) -> Self { + Self { + $($n: <$v>::from_com(com),)* + } + } + } + }; +} diff --git a/src/ptc.rs b/src/ptc.rs index 2cde269..b25b6ee 100644 --- a/src/ptc.rs +++ b/src/ptc.rs @@ -1,24 +1,117 @@ -#[derive(Debug, Clone)] -pub struct WatchListItem { - pub symbol: String, - pub price: f64, - pub trend: f64, +use pulse_macros::p_com; + +#[p_com] +struct WatchListItem { + symbol: String, + price: f64, + trend: f64, } -#[derive(Debug, Clone)] -pub struct ActivePosition { - pub symbol: String, - pub profit: f64, - pub amount: f64, +#[p_com] +struct ActivePosition { + symbol: String, + profit: f64, + amount: f64, } -#[derive(Debug, Clone, Copy)] -pub enum MarketTrend { +#[p_com] +enum MarketTrend { Bullish, Bearish, Neutral, } +#[p_com] +enum Volatility { + Low, + Medium, + High, +} + +#[p_com] +struct MarketOverview { + trend: MarketTrend, + volatility: Volatility, + pressure: f64, + + alerts: Vec, +} + +#[p_com] +enum Feed { + Connected, + Disconnected, + Connecting, + Failed, +} + +#[p_com] +struct Status { + feed: Feed, + exchange: String, + dex: String, + latency: u16, +} + +#[p_com] +enum SignalKind { + Buy, + Sell, +} + +#[p_com] +enum SignalParameter { + Lim, + Stl, + Tap, + Chk, +} + +#[p_com] +struct Signal { + kind: SignalKind, + symbol: String, + param: SignalParameter, + price: f64, +} + +#[p_com] +enum LogKind { + Info, + Warn, + Err, + Debug, +} + +#[p_com] +struct EventLog { + kind: LogKind, + name: String, + message: String, +} + +#[p_com] +enum AlertLevel { + High, + Medium, + Low, +} + +#[p_com] +struct Alert { + level: AlertLevel, + message: String, +} + +#[p_com] +enum InspectTarget { + None, + Symbol(WatchListItem), + Position(ActivePosition), + Signal(Signal), + Alert(Alert), +} + impl std::fmt::Display for MarketTrend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -29,11 +122,14 @@ impl std::fmt::Display for MarketTrend { } } -#[derive(Debug, Clone, Copy)] -pub enum Volatility { - Low, - Medium, - High, +impl std::fmt::Display for AlertLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::High => write!(f, "H"), + Self::Medium => write!(f, "M"), + Self::Low => write!(f, "L"), + } + } } impl std::fmt::Display for Volatility { @@ -46,80 +142,6 @@ impl std::fmt::Display for Volatility { } } -#[derive(Debug, Clone)] -pub struct MarketOverview { - pub trend: MarketTrend, - pub volatility: Volatility, - pub pressure: f64, - - pub alerts: Vec, -} - -#[derive(Debug, Clone, Copy)] -pub enum Feed { - Connected, - Disconnected, - Connecting, - Failed, -} - -pub struct Status { - pub feed: Feed, - pub exchange: String, - pub dex: String, - pub latency: u16, -} - -#[derive(Debug, Clone, Copy)] -pub enum SignalKind { - Buy, - Sell, -} - -impl std::fmt::Display for SignalKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Buy => write!(f, "BUY"), - Self::Sell => write!(f, "SELL"), - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum SignalParameter { - Lim, - Stl, - Tap, - Chk, -} - -impl std::fmt::Display for SignalParameter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Lim => write!(f, "LIM"), - Self::Stl => write!(f, "STL"), - Self::Tap => write!(f, "TAP"), - Self::Chk => write!(f, "CHK"), - } - } -} - -#[derive(Debug, Clone)] -pub struct Signal { - pub kind: SignalKind, - pub symbol: String, - pub param: SignalParameter, - pub price: f64, -} - -#[derive(Debug, Clone, Copy)] -pub enum LogKind { - Info, - Warn, - Err, - Debug, -} - impl std::fmt::Display for LogKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -131,41 +153,22 @@ impl std::fmt::Display for LogKind { } } -#[derive(Debug, Clone)] -pub struct EventLog { - pub kind: LogKind, - pub name: &'static str, - pub message: String, -} - -#[derive(Debug, Clone, Copy)] -pub enum AlertLevel { - High, - Medium, - Low, -} - -impl std::fmt::Display for AlertLevel { +impl std::fmt::Display for SignalKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::High => write!(f, "H"), - Self::Medium => write!(f, "M"), - Self::Low => write!(f, "L"), + Self::Buy => write!(f, "BUY"), + Self::Sell => write!(f, "SELL"), } } } -#[derive(Debug, Clone)] -pub struct Alert { - pub level: AlertLevel, - pub message: String, -} - -#[derive(Debug, Clone)] -pub enum InspectTarget { - None, - Symbol(Box<(WatchListItem, MarketOverview)>), - Position(ActivePosition), - Signal(Signal), - Alert(Alert), +impl std::fmt::Display for SignalParameter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Lim => write!(f, "LIM"), + Self::Stl => write!(f, "STL"), + Self::Tap => write!(f, "TAP"), + Self::Chk => write!(f, "CHK"), + } + } } diff --git a/src/terminal/command.rs b/src/terminal/command.rs index d0df560..8a8a4f6 100644 --- a/src/terminal/command.rs +++ b/src/terminal/command.rs @@ -20,7 +20,7 @@ impl PulseTradeApp { _ => { self.logs.lock().await.push(EventLog { kind: crate::ptc::LogKind::Err, - name: "cmd", + name: "cmd".to_string(), message: format!("Command '{}' not found", command), }); } diff --git a/src/terminal/formatting.rs b/src/terminal/formatting.rs index c316ed9..9b62766 100644 --- a/src/terminal/formatting.rs +++ b/src/terminal/formatting.rs @@ -12,22 +12,15 @@ impl Formatted for InspectTarget { match self { Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], - Self::Symbol(boxed) => { - let (watch, overview) = &**boxed; - vec![ - Property("Symbol", format!("\x1b[35m{}\x1b[0m", watch.symbol)), - Property("Price", format!("\x1b[96m{}\x1b[0m", format_f64(watch.price))), - Property("Trend", format!("{}", format_f64(watch.trend))), - Property("Market", format!("{}", overview.trend)), - Property("Volatility", format!("{}", overview.volatility)), - Property( - "Pressure", - format!("{:+.2}%", overview.pressure * 100.0), - ), - Property("Alerts", format!("{}", overview.alerts.len())), - ] - .get_formatted() - } + Self::Symbol(watch) => vec![ + Property("Symbol", format!("\x1b[35m{}\x1b[0m", watch.symbol)), + Property( + "Price", + format!("\x1b[96m{}\x1b[0m", format_f64(watch.price)), + ), + Property("Trend", format!("{}", format_f64(watch.trend))), + ] + .get_formatted(), Self::Position(pos) => vec![ Property("Symbol", format!("\x1b[35m{}\x1b[0m", pos.symbol)), diff --git a/src/terminal/main.rs b/src/terminal/main.rs index 6fa640c..1b90a56 100644 --- a/src/terminal/main.rs +++ b/src/terminal/main.rs @@ -112,7 +112,7 @@ impl App for PulseTradeApp { logs.push(EventLog { kind: ptc::LogKind::Warn, - name: "pulse.init", + name: "pulse.init".to_string(), message: "We're still not done yet ;)".to_string(), }); diff --git a/src/terminal/ptc.rs b/src/terminal/ptc.rs index 436f33c..ab16651 100644 --- a/src/terminal/ptc.rs +++ b/src/terminal/ptc.rs @@ -1 +1,2 @@ +include!("../pc.rs"); include!("../ptc.rs");