From 74767cf961238aa6e183c0c97ce9ccc8faaf3bd4 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 16 Nov 2024 16:38:06 +0100 Subject: [PATCH] added C for threading --- .gitignore | 5 +- README.md | 4 ++ build.rs | 4 ++ build.sh | 4 ++ src/app.c | 54 +++++++++++++++++++++ src/app.rs | 107 +++++++++++++++-------------------------- src/lib.rs | 80 +++++++++++++++---------------- src/macros.rs | 116 ++++++++++++++++++++++----------------------- src/main.rs | 5 +- src/test.rs | 16 ------- src/ui/elements.rs | 39 ++------------- src/ui/mod.rs | 7 +-- src/utils.rs | 6 +-- 13 files changed, 219 insertions(+), 228 deletions(-) create mode 100644 build.rs create mode 100755 build.sh create mode 100644 src/app.c delete mode 100644 src/test.rs diff --git a/.gitignore b/.gitignore index d01bd1a..9a3499e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +*.a +*.o \ No newline at end of file diff --git a/README.md b/README.md index 350c877..54ba261 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,10 @@ OSUI provides several macros to simplify the definition and usage of elements: - **rsx!**: Create a nested structure of UI components easily. - **command!**: Simplify command creation for updates. +## Credits + +Credit to priz.js (discord) for the idea of using C + ## Contributing Contributions are welcome! If you have suggestions or improvements, feel free to submit a pull request or open an issue. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..32d1e0f --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-search=native=lib"); + println!("cargo:rustc-link-lib=static=app"); +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..3bc7006 --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +rm -rf ./lib/ +mkdir lib +gcc -c -o lib/app.o src/app.c +ar rcs lib/libapp.a lib/app.o \ No newline at end of file diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..313dd75 --- /dev/null +++ b/src/app.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +typedef struct { + uint32_t* state; + void* element; +} EventArgs; + +extern void render(void* element, uint32_t state); +extern void event(void* element, uint32_t* state); + +void sleep_ms(long milliseconds) { + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +void *event_checker(void *args) +{ + EventArgs* event_args = (EventArgs*)args; + while (1) { + if (event_args->state == 0) { return NULL; } + event(event_args->element, event_args->state); + } + return NULL; +} + +boolean_t c_run(void* element) +{ + pthread_t thread; + uint32_t state = 2; + EventArgs event_args = { .element = element, .state = &state }; + + if (pthread_create(&thread, NULL, event_checker, &event_args) != 0) { + perror("Failed to create thread"); + } + + while (1) + { + if (state == 0) { + free(element); + return 0; + } else if (state == 1) { + free(element); + return 1; + } + render(element, state); + sleep_ms(200); + } +} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 8dd7e82..f4d887a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,79 +1,50 @@ -use std::sync::{mpsc, Arc}; -use std::thread; +use crossterm::terminal::enable_raw_mode; -use crate::{ - clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, Element, State, Value, -}; +use crate::{clear, create_frame, flush, get_term_size, render_to_frame, Element, State, StateChanger, Value}; +use std::ffi::c_void; -/// Renders an element with a given state to the terminal. -/// -/// This macro handles the process of creating a frame, rendering the element to it, -/// clearing the terminal, and then printing the rendered frame. -/// -/// # Parameters -/// -/// * `$element` - The element to be rendered. -/// * `$state` - The current state of the element. -/// -/// # Returns -/// -/// This macro does not return a value. It performs side effects by printing to the terminal. -macro_rules! render { - ($element:expr, $state:expr) => { - let (width, height) = get_term_size(); - let mut frame: Vec = create_frame(Value::new(width), Value::new(height)); - render_to_frame($state, width, &mut frame, $element); - clear(); - print!("{}", frame.join("")); - flush(); - }; +pub struct ElementWrapper<'a> { + element: &'a mut Box, } -pub fn run(element: Arc) -> bool { - hide_cursor(); - crossterm::terminal::enable_raw_mode().unwrap(); - clear(); +impl<'a> ElementWrapper<'a> { + pub fn new(element: &'a mut Box) -> *mut c_void { + Box::into_raw(Box::new(ElementWrapper { element })) as *mut c_void + } - let (tx, rx) = mpsc::channel(); + pub unsafe fn from_raw(ptr: *mut c_void) -> &'a mut Box { + assert!(!ptr.is_null(), "Pointer to ElementWrapper is null"); + let wrapper = ptr as *mut ElementWrapper; + (*wrapper).element + } +} - let (exit_tx, exit_) = mpsc::channel::<()>(); +extern "C" { + fn c_run(element: *mut c_void) -> bool; +} - thread::spawn({ - let element_thread = Arc::clone(&element); - move || loop { - if exit_.try_recv().is_ok() { - break; - } - let event = crossterm::event::read().unwrap(); - let e = Arc::clone(&element_thread); - let command_handler = crate::CommandHandler(tx.clone()); +#[no_mangle] +pub extern "C" fn render(ptr: *mut c_void, state: u32) { + let element = unsafe { ElementWrapper::from_raw(ptr) }; + let (width, height) = get_term_size(); + let mut frame: Vec = create_frame(Value::new(width), Value::new(height)); + render_to_frame(State::from_u32(state), width, &mut frame, element); + clear(); + print!("{}", frame.join("")); + flush(); +} - thread::spawn(move || { - e.event(&command_handler, event); - }); - } - }); +#[no_mangle] +pub extern "C" fn event(ptr: *mut c_void, state: *mut u32) { + let event = crossterm::event::read().unwrap(); + let element = unsafe { ElementWrapper::from_raw(ptr) }; + element.event(event, &StateChanger(state)); +} - let mut current_state = State::Hover; - loop { - if let Ok(command) = rx.try_recv() { - match command { - crate::Command::SetState(state) => { - current_state = state; - } - crate::Command::Exit => { - exit_tx.send(()).unwrap(); - break; - } - crate::Command::Rebuild => { - exit_tx.send(()).unwrap(); - return true; - } - } - } else { - render!(&element, current_state.clone()); - } - thread::sleep(std::time::Duration::from_millis(20)); +pub fn run(data: &mut Box) -> bool { + enable_raw_mode().unwrap(); + unsafe { + let element_ptr = ElementWrapper::new(data); + return c_run(element_ptr); } - false } diff --git a/src/lib.rs b/src/lib.rs index 716e884..b8c7c60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,13 +22,10 @@ pub mod macros; pub mod ui; pub mod utils; -use std::sync::{mpsc, Arc, Mutex}; pub use utils::*; pub mod app; -mod test; - /// Enum representing the wether it's the default or custom. /// /// - `Default(usize)` - Uses a default value. @@ -81,67 +78,70 @@ impl Value { pub trait Element: Send + Sync { fn get_data(&self) -> (Value, usize, String); - fn update_data(&self, width: usize, height: usize); + fn update_data(&mut self, width: usize, height: usize); - fn get_element_by_id(&self, id: &str) -> Option>; + fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box>; fn render(&self, state: State) -> String { _ = state; String::new() } - fn event(&self, ch: &CommandHandler, event: crossterm::event::Event) { - _ = (ch, event); + fn event(&mut self, event: crossterm::event::Event, state: &StateChanger) { + (_, _) = (event, state) } } -pub struct Handler(pub Box); +pub struct Handler(pub Box); impl Handler { pub fn new(handler_fn: F) -> Handler where - F: FnMut(&T, &CommandHandler, crossterm::event::Event) + 'static + Send, + F: FnMut(&T, &StateChanger, crossterm::event::Event) + 'static + Send, { Handler(Box::new(handler_fn)) } } -pub struct CommandHandler(mpsc::Sender); - -impl CommandHandler { - pub fn set_state(&self, state: State) { - self.0.send(Command::SetState(state)).unwrap(); - } - - pub fn exit(&self) { - self.0.send(Command::Exit).unwrap(); - } - - pub fn get_element_by_id(&self) { - self.0.send(Command::Exit).unwrap(); - } - - pub fn rebuild(&self) -> bool { - self.0.send(Command::Rebuild).is_ok() - } -} - -#[derive(Debug, Clone)] -pub enum Command { - SetState(State), - Exit, - Rebuild, -} - #[derive(Debug, Clone)] pub enum State { + Exit, + Rebuild, Normal, Hover, Custom(usize), - CustomString(String), } -pub fn run_test() { - let i = Arc::new(Mutex::new(0)); - while app::run(test::app(Arc::clone(&i))) {} +impl State { + pub fn from_u32(s: u32) -> State { + match s { + 0 => State::Exit, + 1 => State::Rebuild, + 2 => State::Normal, + 3 => State::Hover, + _ => State::Custom((s - 4) as usize), + } + } + pub fn to_u32(&self) -> u32 { + match self { + State::Exit => 0, + State::Rebuild => 1, + State::Normal => 2, + State::Hover => 3, + State::Custom(s) => (4 + s) as u32, + } + } +} + +pub struct StateChanger(*mut u32); + +impl StateChanger { + pub fn set_state(&self, s: State) { + unsafe { + (*self.0) = s.to_u32(); + } + } + pub fn get_state(&self) -> State { + State::from_u32(unsafe { *self.0 }) + } } diff --git a/src/macros.rs b/src/macros.rs index ce7e16e..d2d58d5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -85,35 +85,34 @@ macro_rules! element { ) => { $(#[$meta])* pub struct $name<'a> { - pub x: std::sync::Mutex<$crate::Value>, - pub y: std::sync::Mutex, - pub width: std::sync::Mutex<$crate::Value>, - pub height: std::sync::Mutex<$crate::Value>, - pub children: std::sync::Mutex>>, - pub child: std::sync::Mutex, - pub text: std::sync::Mutex, - pub id: std::sync::Mutex<&'a str>, + pub x: $crate::Value, + pub y: usize, + pub width: $crate::Value, + pub height: $crate::Value, + pub children: Vec>, + pub child: usize, + pub text: String, + pub id: &'a str, $( $inner )* } impl $crate::Element for $name<'_> { fn get_data(&self) -> ($crate::Value, usize, String) { - (self.x.lock().unwrap().clone(), self.y.lock().unwrap().clone(), self.id.lock().unwrap().to_string()) + (self.x, self.y, self.id.to_string()) } - fn update_data(&self, width: usize, height: usize) { - self.width.lock().unwrap().try_set_value(width); - self.height.lock().unwrap().try_set_value(height); - let children = self.children.lock().unwrap(); - for child in children.iter() { + fn update_data(&mut self, width: usize, height: usize) { + self.width.try_set_value(width); + self.height.try_set_value(height); + for child in &mut self.children { child.update_data(width, height); } } - fn get_element_by_id(&self, id: &str) -> Option> { - for elem in &mut self.children.lock().unwrap().iter() { + fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box> { + for elem in &mut self.children { if elem.get_data().2 == id { - return Some(std::sync::Arc::clone(elem)); + return Some(elem); } } None @@ -125,7 +124,7 @@ macro_rules! element { impl<'a> $name<'_> { /// Creates a new instance of the element with default values. pub fn new() -> $name<'a> { - $crate::def_!($name { + $name { children: Vec::new(), x: $crate::Value::Default(0), y: 0, @@ -135,16 +134,15 @@ macro_rules! element { text: String::new(), id: "", $( $defaults )* - }) + } } - pub fn get_child(&self) -> Option> { - if let Ok(elements) = self.children.lock() { - if let Some(e) = elements.get(*self.child.lock().unwrap()) { - return Some(std::sync::Arc::clone(e)); - } - } - None + /// Retrieves a mutable reference to the current child element, if any. + /// + /// # Returns + /// An `Option` containing a mutable reference to the child `Element` or `None`. + pub fn get_child(&mut self) -> Option<&mut Box> { + self.children.get_mut(self.child) } } }; @@ -188,63 +186,63 @@ macro_rules! command { #[macro_export] macro_rules! parse_rsx_param { // Properties - ($elem:expr, $a:ident $($k:ident).+: $v:expr) => { - $elem.$a.lock().unwrap().$($k).+ = $v; + ($elem:expr, $($k:ident).+: $v:expr) => { + $elem.$($k).+ = $v; }; - ($elem:expr, $a:ident $(.$($k:ident).+)?: $v:expr, $($rest:tt)*) => { - *$elem.$a.lock().unwrap()$($($k).+)? = $v; - $crate::parse_rsx_param!($elem, $($rest)*); + ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => { + $elem.$($k).+ = $v; + osui::parse_rsx_param!($elem, $($rest)*); }; // for completion purposes - // ($elem:expr, $p:path) => { - // $p; - // }; - // ($elem:expr, $a:ident . $($k:ident).+, $($rest:tt)*) => { - // $elem.$a.lock().unwrap().$($k).+; - // $crate::parse_rsx_param!($elem, $($rest)*); - // }; - // ($elem:expr, $a:ident . $($k:ident).+., $($rest:tt)*) => { - // $elem.$a.lock().unwrap().$($k). - // +.; - // $crate::parse_rsx_param!($elem, $($rest)*); - // }; + ($elem:expr, $p:path) => { + $p; + }; + ($elem:expr, $($k:ident).+, $($rest:tt)*) => { + $elem.$($k).+; + osui::parse_rsx_param!($elem, $($rest)*); + }; + ($elem:expr, $($k:ident).+., $($rest:tt)*) => { + $elem.$($k). + +.; + osui::parse_rsx_param!($elem, $($rest)*); + }; - // ($elem:expr, $a:ident . $($k:ident).+.: $v:expr) => { - // $elem.$a.lock().unwrap().$($k).+. = $v; - // }; - // ($elem:expr, $a:ident . $($k:ident).*: $v:expr, $($rest:tt)*) => { - // $elem.$a.lock().unwrap().$($k).+. = $v; - // $crate::parse_rsx_param!($elem, $($rest)*); - // }; + ($elem:expr, $($k:ident).+.: $v:expr) => { + $elem.$($k).+. = $v; + }; + ($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => { + $elem.$($k).+. = $v; + osui::parse_rsx_param!($elem, $($rest)*); + }; // For loop ($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => { for $($for)* { - $elem.children.lock().unwrap().push({$($inner)*}) + $elem.children.push({$($inner)*}) } - $crate::parse_rsx_param!($elem, $($rest)*); + osui::parse_rsx_param!($elem, $($rest)*); }; // Expression ($elem:expr, {$ielem:expr} $($rest:tt)*) => { - $elem.children.lock().unwrap().push($ielem); - $crate::parse_rsx_param!($elem, $($rest)*); + $elem.children.push($ielem); + osui::parse_rsx_param!($elem, $($rest)*); }; // Element ($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => { - $elem.children.lock().unwrap().push($crate::rsx_elem!($pelem { $($inner)* })); - $crate::parse_rsx_param!($elem, $($rest)*) + $elem.children.push(osui::rsx_elem!($pelem { $($inner)* })); + osui::parse_rsx_param!($elem, $($rest)*) }; // Text ($elem:expr, $text:expr) => { - *$elem.text.lock().unwrap() = format!($text); + $elem.text = format!($text); }; ($elem:expr, $text:expr, $($inner:tt)*) => { - *$elem.text.lock().unwrap() = format!($text, $($inner)*); + $elem.text = format!($text, $($inner)*); }; // Empty @@ -281,7 +279,7 @@ macro_rules! rsx_elem { #[allow(unused_mut)] let mut elem = $elem(); $crate::parse_rsx_param!(elem, $($inner)*); - elem as std::sync::Arc + elem as Box }}; } diff --git a/src/main.rs b/src/main.rs index c458a20..7a0b653 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -use osui::run_test; +use osui::{app::run, rsx_elem, ui::text}; + fn main() { - run_test(); + run(&mut rsx_elem! { text { "Hello, World!" } }); } diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index d619cc1..0000000 --- a/src/test.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::{rsx_elem, ui::*, Element, Handler}; - -pub(crate) fn app(i: Arc>) -> std::sync::Arc { - let i1 = Arc::clone(&i); - rsx_elem! { text { on_click: Handler::new(move |t: &Text, ch, _| { - *i1.lock().unwrap() += 1; - ch.rebuild(); - // { - // let mut i = i1.lock().unwrap(); - // *i += 1; - // *t.text.lock().unwrap() = i.to_string(); - // } - }), "{}", i.lock().unwrap() } } -} diff --git a/src/ui/elements.rs b/src/ui/elements.rs index 500dcb9..dcca735 100644 --- a/src/ui/elements.rs +++ b/src/ui/elements.rs @@ -1,38 +1,9 @@ -use std::sync::Mutex; - -use crate::{element, Handler, State}; +use crate::{element, State}; element! { - Text { - pub on_click: Mutex>> - } - defaults { - on_click: Handler::new(|_, _, _| {}) - } - fn render(&self, _: State) -> String { - self.text.lock().unwrap().clone() - } - - fn event(&self, ch: &crate::CommandHandler, event: crossterm::event::Event) { - (*self.on_click.lock().unwrap().0)(self, ch, event); - // *self.text.lock().unwrap() = "Done!".to_string(); - } -} - -element! { - Div {} + Text {} defaults {} - fn render(&self, s: State) -> String { - if let Some(child) = self.get_child() { - child.render(s) - } else { - format!("None") - } + fn render(&self, _state: State) -> String { + self.text.clone() } - - fn event(&self, ch: &crate::CommandHandler, event: crossterm::event::Event) { - if let Some(e) = self.get_element_by_id("test") { - e.event(ch, event); - } - } -} +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c218482..1c8fd4e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,10 +7,7 @@ pub use styles::*; pub mod elements; pub use elements::*; -pub fn text<'a>() -> std::sync::Arc> { - std::sync::Arc::new(Text::new()) -} -pub fn div<'a>() -> std::sync::Arc> { - std::sync::Arc::new(Div::new()) +pub fn text<'a>() -> Box> { + Box::new(Text::new()) } \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 2b2c85a..6c8e231 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -78,7 +78,7 @@ pub fn render_to_frame( state: crate::State, width: usize, frame: &mut Vec, - element: &std::sync::Arc, + element: &mut Box, ) { let data = element.get_data(); for (i, line) in element.render(state).split('\n').enumerate() { @@ -130,7 +130,7 @@ pub enum Direction { } pub fn closest_component( - components: &[std::sync::Arc], + components: &[&mut dyn Element], current_index: usize, direction: Direction, ) -> usize { @@ -138,7 +138,7 @@ pub fn closest_component( components .iter() - .enumerate() // Keep track of indices + .enumerate() .filter(|(i, comp_)| { let comp = comp_.get_data(); match direction {