added C for threading

This commit is contained in:
2024-11-16 16:38:06 +01:00
parent e1da609eb1
commit 74767cf961
13 changed files with 219 additions and 228 deletions
+3
View File
@@ -19,3 +19,6 @@ Cargo.lock
# and can be added to the global gitignore or merged into this file. For a more nuclear # 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. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
*.a
*.o
+4
View File
@@ -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. - **rsx!**: Create a nested structure of UI components easily.
- **command!**: Simplify command creation for updates. - **command!**: Simplify command creation for updates.
## Credits
Credit to priz.js (discord) for the idea of using C
## Contributing ## Contributing
Contributions are welcome! If you have suggestions or improvements, feel free to submit a pull request or open an issue. Contributions are welcome! If you have suggestions or improvements, feel free to submit a pull request or open an issue.
+4
View File
@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-search=native=lib");
println!("cargo:rustc-link-lib=static=app");
}
Executable
+4
View File
@@ -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
+54
View File
@@ -0,0 +1,54 @@
#include <stdio.h>
#include <memory.h>
#include <libc.h>
#include <pthread.h>
#include <time.h>
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);
}
}
+37 -66
View File
@@ -1,79 +1,50 @@
use std::sync::{mpsc, Arc}; use crossterm::terminal::enable_raw_mode;
use std::thread;
use crate::{ use crate::{clear, create_frame, flush, get_term_size, render_to_frame, Element, State, StateChanger, Value};
clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, Element, State, Value, use std::ffi::c_void;
};
/// Renders an element with a given state to the terminal. pub struct ElementWrapper<'a> {
/// element: &'a mut Box<dyn Element>,
/// This macro handles the process of creating a frame, rendering the element to it, }
/// clearing the terminal, and then printing the rendered frame.
/// impl<'a> ElementWrapper<'a> {
/// # Parameters pub fn new(element: &'a mut Box<dyn Element>) -> *mut c_void {
/// Box::into_raw(Box::new(ElementWrapper { element })) as *mut c_void
/// * `$element` - The element to be rendered. }
/// * `$state` - The current state of the element.
/// pub unsafe fn from_raw(ptr: *mut c_void) -> &'a mut Box<dyn Element> {
/// # Returns assert!(!ptr.is_null(), "Pointer to ElementWrapper is null");
/// let wrapper = ptr as *mut ElementWrapper;
/// This macro does not return a value. It performs side effects by printing to the terminal. (*wrapper).element
macro_rules! render { }
($element:expr, $state:expr) => { }
extern "C" {
fn c_run(element: *mut c_void) -> bool;
}
#[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 (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(Value::new(width), Value::new(height)); let mut frame: Vec<String> = create_frame(Value::new(width), Value::new(height));
render_to_frame($state, width, &mut frame, $element); render_to_frame(State::from_u32(state), width, &mut frame, element);
clear(); clear();
print!("{}", frame.join("")); print!("{}", frame.join(""));
flush(); flush();
};
} }
pub fn run(element: Arc<dyn Element>) -> bool { #[no_mangle]
hide_cursor(); pub extern "C" fn event(ptr: *mut c_void, state: *mut u32) {
crossterm::terminal::enable_raw_mode().unwrap();
clear();
let (tx, rx) = mpsc::channel();
let (exit_tx, exit_) = mpsc::channel::<()>();
thread::spawn({
let element_thread = Arc::clone(&element);
move || loop {
if exit_.try_recv().is_ok() {
break;
}
let event = crossterm::event::read().unwrap(); let event = crossterm::event::read().unwrap();
let e = Arc::clone(&element_thread); let element = unsafe { ElementWrapper::from_raw(ptr) };
let command_handler = crate::CommandHandler(tx.clone()); element.event(event, &StateChanger(state));
}
thread::spawn(move || { pub fn run(data: &mut Box<dyn Element>) -> bool {
e.event(&command_handler, event); enable_raw_mode().unwrap();
}); unsafe {
let element_ptr = ElementWrapper::new(data);
return c_run(element_ptr);
} }
});
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));
}
false
} }
+40 -40
View File
@@ -22,13 +22,10 @@
pub mod macros; pub mod macros;
pub mod ui; pub mod ui;
pub mod utils; pub mod utils;
use std::sync::{mpsc, Arc, Mutex};
pub use utils::*; pub use utils::*;
pub mod app; pub mod app;
mod test;
/// Enum representing the wether it's the default or custom. /// Enum representing the wether it's the default or custom.
/// ///
/// - `Default(usize)` - Uses a default value. /// - `Default(usize)` - Uses a default value.
@@ -81,67 +78,70 @@ impl<T: Copy + PartialEq> Value<T> {
pub trait Element: Send + Sync { pub trait Element: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String); fn get_data(&self) -> (Value<usize>, 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<std::sync::Arc<dyn Element>>; fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>>;
fn render(&self, state: State) -> String { fn render(&self, state: State) -> String {
_ = state; _ = state;
String::new() String::new()
} }
fn event(&self, ch: &CommandHandler, event: crossterm::event::Event) { fn event(&mut self, event: crossterm::event::Event, state: &StateChanger) {
_ = (ch, event); (_, _) = (event, state)
} }
} }
pub struct Handler<T>(pub Box<dyn FnMut(&T, &CommandHandler, crossterm::event::Event) + Send>); pub struct Handler<T>(pub Box<dyn FnMut(&T, &StateChanger, crossterm::event::Event) + Send>);
impl<T> Handler<T> { impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T> pub fn new<F>(handler_fn: F) -> Handler<T>
where where
F: FnMut(&T, &CommandHandler, crossterm::event::Event) + 'static + Send, F: FnMut(&T, &StateChanger, crossterm::event::Event) + 'static + Send,
{ {
Handler(Box::new(handler_fn)) Handler(Box::new(handler_fn))
} }
} }
pub struct CommandHandler(mpsc::Sender<Command>);
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)] #[derive(Debug, Clone)]
pub enum State { pub enum State {
Exit,
Rebuild,
Normal, Normal,
Hover, Hover,
Custom(usize), Custom(usize),
CustomString(String),
} }
pub fn run_test() { impl State {
let i = Arc::new(Mutex::new(0)); pub fn from_u32(s: u32) -> State {
while app::run(test::app(Arc::clone(&i))) {} 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 })
}
} }
+57 -59
View File
@@ -85,35 +85,34 @@ macro_rules! element {
) => { ) => {
$(#[$meta])* $(#[$meta])*
pub struct $name<'a> { pub struct $name<'a> {
pub x: std::sync::Mutex<$crate::Value<usize>>, pub x: $crate::Value<usize>,
pub y: std::sync::Mutex<usize>, pub y: usize,
pub width: std::sync::Mutex<$crate::Value<usize>>, pub width: $crate::Value<usize>,
pub height: std::sync::Mutex<$crate::Value<usize>>, pub height: $crate::Value<usize>,
pub children: std::sync::Mutex<Vec<std::sync::Arc<dyn $crate::Element>>>, pub children: Vec<Box<dyn $crate::Element>>,
pub child: std::sync::Mutex<usize>, pub child: usize,
pub text: std::sync::Mutex<String>, pub text: String,
pub id: std::sync::Mutex<&'a str>, pub id: &'a str,
$( $inner )* $( $inner )*
} }
impl $crate::Element for $name<'_> { impl $crate::Element for $name<'_> {
fn get_data(&self) -> ($crate::Value<usize>, usize, String) { fn get_data(&self) -> ($crate::Value<usize>, 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) { fn update_data(&mut self, width: usize, height: usize) {
self.width.lock().unwrap().try_set_value(width); self.width.try_set_value(width);
self.height.lock().unwrap().try_set_value(height); self.height.try_set_value(height);
let children = self.children.lock().unwrap(); for child in &mut self.children {
for child in children.iter() {
child.update_data(width, height); child.update_data(width, height);
} }
} }
fn get_element_by_id(&self, id: &str) -> Option<std::sync::Arc<(dyn $crate::Element + 'static)>> { fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn $crate::Element>> {
for elem in &mut self.children.lock().unwrap().iter() { for elem in &mut self.children {
if elem.get_data().2 == id { if elem.get_data().2 == id {
return Some(std::sync::Arc::clone(elem)); return Some(elem);
} }
} }
None None
@@ -125,7 +124,7 @@ macro_rules! element {
impl<'a> $name<'_> { impl<'a> $name<'_> {
/// Creates a new instance of the element with default values. /// Creates a new instance of the element with default values.
pub fn new() -> $name<'a> { pub fn new() -> $name<'a> {
$crate::def_!($name { $name {
children: Vec::new(), children: Vec::new(),
x: $crate::Value::Default(0), x: $crate::Value::Default(0),
y: 0, y: 0,
@@ -135,16 +134,15 @@ macro_rules! element {
text: String::new(), text: String::new(),
id: "", id: "",
$( $defaults )* $( $defaults )*
}) }
} }
pub fn get_child(&self) -> Option<std::sync::Arc<dyn $crate::Element>> { /// Retrieves a mutable reference to the current child element, if any.
if let Ok(elements) = self.children.lock() { ///
if let Some(e) = elements.get(*self.child.lock().unwrap()) { /// # Returns
return Some(std::sync::Arc::clone(e)); /// An `Option` containing a mutable reference to the child `Element` or `None`.
} pub fn get_child(&mut self) -> Option<&mut Box<dyn $crate::Element>> {
} self.children.get_mut(self.child)
None
} }
} }
}; };
@@ -188,63 +186,63 @@ macro_rules! command {
#[macro_export] #[macro_export]
macro_rules! parse_rsx_param { macro_rules! parse_rsx_param {
// Properties // Properties
($elem:expr, $a:ident $($k:ident).+: $v:expr) => { ($elem:expr, $($k:ident).+: $v:expr) => {
$elem.$a.lock().unwrap().$($k).+ = $v; $elem.$($k).+ = $v;
}; };
($elem:expr, $a:ident $(.$($k:ident).+)?: $v:expr, $($rest:tt)*) => { ($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => {
*$elem.$a.lock().unwrap()$($($k).+)? = $v; $elem.$($k).+ = $v;
$crate::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
}; };
// for completion purposes // for completion purposes
// ($elem:expr, $p:path) => { ($elem:expr, $p:path) => {
// $p; $p;
// }; };
// ($elem:expr, $a:ident . $($k:ident).+, $($rest:tt)*) => { ($elem:expr, $($k:ident).+, $($rest:tt)*) => {
// $elem.$a.lock().unwrap().$($k).+; $elem.$($k).+;
// $crate::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
// }; };
// ($elem:expr, $a:ident . $($k:ident).+., $($rest:tt)*) => { ($elem:expr, $($k:ident).+., $($rest:tt)*) => {
// $elem.$a.lock().unwrap().$($k). $elem.$($k).
// +.; +.;
// $crate::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
// }; };
// ($elem:expr, $a:ident . $($k:ident).+.: $v:expr) => { ($elem:expr, $($k:ident).+.: $v:expr) => {
// $elem.$a.lock().unwrap().$($k).+. = $v; $elem.$($k).+. = $v;
// }; };
// ($elem:expr, $a:ident . $($k:ident).*: $v:expr, $($rest:tt)*) => { ($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => {
// $elem.$a.lock().unwrap().$($k).+. = $v; $elem.$($k).+. = $v;
// $crate::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
// }; };
// For loop // For loop
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => { ($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
for $($for)* { 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 // Expression
($elem:expr, {$ielem:expr} $($rest:tt)*) => { ($elem:expr, {$ielem:expr} $($rest:tt)*) => {
$elem.children.lock().unwrap().push($ielem); $elem.children.push($ielem);
$crate::parse_rsx_param!($elem, $($rest)*); osui::parse_rsx_param!($elem, $($rest)*);
}; };
// Element // Element
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => { ($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
$elem.children.lock().unwrap().push($crate::rsx_elem!($pelem { $($inner)* })); $elem.children.push(osui::rsx_elem!($pelem { $($inner)* }));
$crate::parse_rsx_param!($elem, $($rest)*) osui::parse_rsx_param!($elem, $($rest)*)
}; };
// Text // Text
($elem:expr, $text:expr) => { ($elem:expr, $text:expr) => {
*$elem.text.lock().unwrap() = format!($text); $elem.text = format!($text);
}; };
($elem:expr, $text:expr, $($inner:tt)*) => { ($elem:expr, $text:expr, $($inner:tt)*) => {
*$elem.text.lock().unwrap() = format!($text, $($inner)*); $elem.text = format!($text, $($inner)*);
}; };
// Empty // Empty
@@ -281,7 +279,7 @@ macro_rules! rsx_elem {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut elem = $elem(); let mut elem = $elem();
$crate::parse_rsx_param!(elem, $($inner)*); $crate::parse_rsx_param!(elem, $($inner)*);
elem as std::sync::Arc<dyn $crate::Element> elem as Box<dyn $crate::Element>
}}; }};
} }
+3 -2
View File
@@ -1,4 +1,5 @@
use osui::run_test; use osui::{app::run, rsx_elem, ui::text};
fn main() { fn main() {
run_test(); run(&mut rsx_elem! { text { "Hello, World!" } });
} }
-16
View File
@@ -1,16 +0,0 @@
use std::sync::{Arc, Mutex};
use crate::{rsx_elem, ui::*, Element, Handler};
pub(crate) fn app(i: Arc<Mutex<i32>>) -> std::sync::Arc<dyn Element> {
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() } }
}
+4 -33
View File
@@ -1,38 +1,9 @@
use std::sync::Mutex; use crate::{element, State};
use crate::{element, Handler, State};
element! { element! {
Text { Text {}
pub on_click: Mutex<Handler<Text<'a>>>
}
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 {}
defaults {} defaults {}
fn render(&self, s: State) -> String { fn render(&self, _state: State) -> String {
if let Some(child) = self.get_child() { self.text.clone()
child.render(s)
} else {
format!("None")
}
}
fn event(&self, ch: &crate::CommandHandler, event: crossterm::event::Event) {
if let Some(e) = self.get_element_by_id("test") {
e.event(ch, event);
}
} }
} }
+2 -5
View File
@@ -7,10 +7,7 @@ pub use styles::*;
pub mod elements; pub mod elements;
pub use elements::*; pub use elements::*;
pub fn text<'a>() -> std::sync::Arc<Text<'a>> {
std::sync::Arc::new(Text::new())
}
pub fn div<'a>() -> std::sync::Arc<Div<'a>> { pub fn text<'a>() -> Box<Text<'a>> {
std::sync::Arc::new(Div::new()) Box::new(Text::new())
} }
+3 -3
View File
@@ -78,7 +78,7 @@ pub fn render_to_frame(
state: crate::State, state: crate::State,
width: usize, width: usize,
frame: &mut Vec<String>, frame: &mut Vec<String>,
element: &std::sync::Arc<dyn Element>, element: &mut Box<dyn Element>,
) { ) {
let data = element.get_data(); let data = element.get_data();
for (i, line) in element.render(state).split('\n').enumerate() { for (i, line) in element.render(state).split('\n').enumerate() {
@@ -130,7 +130,7 @@ pub enum Direction {
} }
pub fn closest_component( pub fn closest_component(
components: &[std::sync::Arc<dyn Element>], components: &[&mut dyn Element],
current_index: usize, current_index: usize,
direction: Direction, direction: Direction,
) -> usize { ) -> usize {
@@ -138,7 +138,7 @@ pub fn closest_component(
components components
.iter() .iter()
.enumerate() // Keep track of indices .enumerate()
.filter(|(i, comp_)| { .filter(|(i, comp_)| {
let comp = comp_.get_data(); let comp = comp_.get_data();
match direction { match direction {