Browse Source

Integrating rust with python interface

master
Lev 3 years ago
parent
commit
8164fa9874
  1. 1
      Cargo.lock
  2. 21
      degeon/src/chat.rs
  3. 12
      degeon/src/state.rs
  4. 1
      degeon_core/Cargo.toml
  5. 51
      degeon_core/src/degeon_worker.rs
  6. 4
      degeon_py/button.py
  7. 2
      degeon_py/config.py
  8. 8
      degeon_py/degeon.py
  9. BIN
      degeon_py/degeon_core.so
  10. 4
      src/bin/worker.rs

1
Cargo.lock generated

@ -586,6 +586,7 @@ dependencies = [
"iced",
"ironforce",
"pyo3",
"rand",
"serde",
"serde_json",
]

21
degeon/src/chat.rs

@ -1,9 +1,9 @@
use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput};
use iced_native::button;
use crate::gui_events::GuiEvent;
use crate::state;
use crate::styles::style;
pub use degeon_core::Chat;
use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput};
use iced_native::button;
pub trait RenderableChat {
/// Render header of the chat
@ -29,10 +29,10 @@ pub trait RenderableChat {
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent>;
}
impl RenderableChat for Chat {
/// Render header of the chat
fn header<'a>(name: String) -> Element<'a, GuiEvent> {
@ -96,19 +96,32 @@ impl RenderableChat for Chat {
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent> {
let pkey_clone = self.pkey.clone();
let msgs = self.messages.iter().filter_map(move |msg| state::view_message(msg, pkey_clone.clone())).collect();
let msgs = self
.messages
.iter()
.filter_map(move |msg| state::view_message(msg, pkey_clone.clone()))
.collect();
Column::new()
.align_items(Align::End)
.height(Length::Fill)
.width(Length::FillPortion(4))
.push(Self::header(self.profile.name.clone()))
.push(
iced::Container::new(
iced::Scrollable::new(scroll_state)
.push(
Column::with_children(msgs)
.padding(20)
.spacing(10)
.align_items(Align::End),
)
.align_items(Align::End)
.width(Length::Fill),
)
.align_y(Align::End)
.height(Length::FillPortion(9)),
)
.spacing(10)

12
degeon/src/state.rs

@ -61,6 +61,8 @@ pub struct DegeonApp {
name_input_state: iced::text_input::State,
/// Button on the profile screen
profile_done_button_state: iced::button::State,
/// Scroll state
scroll: iced::scrollable::State,
}
impl DegeonApp {
@ -94,6 +96,7 @@ impl DegeonApp {
selected_chat: usize,
send_button_state: &'a mut button::State,
text_input_state: &'a mut iced::text_input::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent> {
if selected_chat >= chats.len() {
Text::new("No chat")
@ -102,7 +105,7 @@ impl DegeonApp {
.width(Length::FillPortion(4))
.into()
} else {
chats[selected_chat].view(text_input_state, send_button_state)
chats[selected_chat].view(text_input_state, send_button_state, scroll_state)
}
}
}
@ -115,6 +118,7 @@ impl DegeonApp {
send_button_state,
text_input_state,
preview_button_states,
scroll: scroll_state,
..
} = self;
Row::new()
@ -129,6 +133,7 @@ impl DegeonApp {
*selected_chat,
send_button_state,
text_input_state,
scroll_state,
))
.height(Length::Fill)
.into()
@ -182,8 +187,12 @@ impl Application for DegeonApp {
fn new(_: ()) -> (Self, iced::Command<GuiEvent>) {
let mut data = Degeon::restore_from_file("".to_string()).unwrap();
if data.chats.is_empty() {
data.chats = vec![Chat::example(1, &data.keys), Chat::example(2, &data.keys)];
}
data.send_multicast(ProtocolMsg::Ping).unwrap();
let mut scroll: iced::scrollable::State = Default::default();
scroll.scroll_to(1., iced::Rectangle::with_size(iced::Size::ZERO), iced::Rectangle::with_size(iced::Size::UNIT));
let st = DegeonApp {
screen: if data.profile.name.is_empty() {
AppScreen::ProfileEditor
@ -197,6 +206,7 @@ impl Application for DegeonApp {
preview_button_states: vec![Default::default(), Default::default()],
name_input_state: Default::default(),
profile_done_button_state: Default::default(),
scroll,
};
(st, iced::Command::none())
}

1
degeon_core/Cargo.toml

@ -13,6 +13,7 @@ serde_json = "1.0.72"
futures = "0.3.18"
chrono = "0.4.19"
iced = { version = "0.3.0", features = ["glow"] }
rand = "0.8.4"
[dependencies.pyo3]
version = "0.14.0"

51
degeon_core/src/degeon_worker.rs

@ -42,7 +42,7 @@ fn get_initialized_ironforce() -> (Arc<Mutex<IronForce>>, Keys) {
let ironforce = IronForce::from_file("".to_string()).unwrap();
let keys = ironforce.keys.clone();
println!("ID: {}", keys.get_public().get_short_id());
let (_thread, ironforce) = ironforce.launch_main_loop(1000);
let (_thread, ironforce) = ironforce.launch_main_loop(270);
(ironforce, keys)
}
@ -213,27 +213,44 @@ impl Degeon {
#[pymethods]
impl Degeon {
/// Create a new text message and send it
pub fn send_text_message(&self, text: String, chat_i: usize) -> PyResult<()> {
self.send_message(
ProtocolMsg::NewMessage(DegMessage::new_text(text, &self.keys.get_public())),
&self.chats[chat_i].pkey,
/// Create a new text message and send it (in thread)
///
/// `text` is the content of the message,
/// `chat_i` is the index of the chat in the list
pub fn send_text_message(&mut self, text: String, chat_i: usize) {
let msg = DegMessage::new_text(text, &self.keys.get_public());
self.chats[chat_i].messages.push(msg.clone());
let cloned_self = self.clone();
std::thread::spawn(move || {
cloned_self
.send_message(
ProtocolMsg::NewMessage(msg),
&cloned_self.chats[chat_i].pkey,
)
.map_err(|e| {
pyo3::exceptions::PyTypeError::new_err(format!("There was an error in Rust: {:?}", e))
})
.map_err(|e| println!("There was an error in Rust: {:?}", e))
});
}
/// Handle one message
pub fn handling_loop_iteration(&mut self) {
let event = self.read_message_and_create_event();
if let Some(event) = event {
self.process_event(&event, true).unwrap_or_else(|e| println!("Error: {:?}", e));
self.process_event(&event, true)
.unwrap_or_else(|e| println!("Error: {:?}", e));
}
}
/// Get length of the IF's message queue
///
/// If IF worker is locked, returns 0
pub fn message_queue_len(&self) -> usize {
self.ironforce.lock().unwrap().messages.len()
self.ironforce.try_lock().map(|r| r.messages.len()).unwrap_or(0)
}
/// Check if the message was written by the current user (since `PublicKey` isn't a python type).
/// Returns True if the author of the message is the current user
pub fn check_message_ownership(&self, message: &DegMessage) -> bool {
message.sender == self.keys.get_public()
}
}
@ -293,6 +310,7 @@ impl Degeon {
}
impl Degeon {
/// Get one message from the IF message queue and process it, resulting in an event
pub fn read_message_and_create_event(&self) -> Option<GuiEvent> {
let msg_raw = self.ironforce.lock().unwrap().read_message();
let msg = msg_raw
@ -317,7 +335,7 @@ impl Degeon {
}
match msg {
Some(Some(event)) => Some(event),
_ => None
_ => None,
}
}
}
@ -326,10 +344,19 @@ impl Stream for Degeon {
type Item = GuiEvent;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.ironforce.try_lock().is_err() {
return Poll::Ready(Some(GuiEvent::None));
}
let timestamp_0 = std::time::Instant::now();
let msg = self.read_message_and_create_event();
if timestamp_0.elapsed() < std::time::Duration::from_millis(5) {
std::thread::sleep(std::time::Duration::from_millis(5));
if rand::random::<bool>() {
return Poll::Pending;
}
}
if timestamp_0.elapsed() > std::time::Duration::from_millis(800) {
println!("Poll_next took {:?}", timestamp_0.elapsed());
}
Poll::Ready(Some(msg.unwrap_or(GuiEvent::None)))
}

4
degeon_py/button.py

@ -87,7 +87,7 @@ class Button:
# if this is a mouse event
if event.type in [pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN]:
# if the mouse event is inside the button
if self.top_left[0] <= event.pos[0] < self.bottom and self.top_left[1] <= event.pos[1] < self.right:
if self.top_left[0] <= event.pos[0] < self.right and self.top_left[1] <= event.pos[1] < self.bottom:
if event.type == pygame.MOUSEBUTTONUP:
self.is_pressed = False
return True
@ -96,4 +96,6 @@ class Button:
self.is_pressed = True
elif event.type == pygame.MOUSEMOTION:
self.is_hovered = True
else:
self.is_hovered = False
return False

2
degeon_py/config.py

@ -3,7 +3,7 @@ import pygame
pygame.init()
# Fontss
# Fonts
font = pygame.font.Font('CRC35.otf', 32)
font_large = pygame.font.Font('CRC35.otf', 50)

8
degeon_py/degeon.py

@ -55,7 +55,8 @@ class Degeon:
"""
Do all the necessary Rust work
"""
pass # todo
while self.core.message_queue_len():
self.core.handling_loop_iteration()
def tick(self):
"""
@ -78,7 +79,10 @@ class Degeon:
else:
self.active_chat = None
if self.active_chat is not None:
self.active_chat.process_event(event)
# If the result is a string, it's a message
result: typing.Optional[str] = self.active_chat.process_event(event)
if result:
self.core.send_text_message(result, self.chat_selector.active_chat)
def main_loop(self, screen: pygame.Surface):
"""

BIN
degeon_py/degeon_core.so

Binary file not shown.

4
src/bin/worker.rs

@ -8,7 +8,7 @@ fn main() -> IFResult<()> {
"Our public key: {}",
base64::encode(if_keys.get_public().to_vec().as_slice())
);
let (_thread, if_mutex) = ironforce.launch_main_loop(100);
let (_thread, if_mutex) = ironforce.launch_main_loop(50);
let stdin = std::io::stdin();
let if_mutex_clone = if_mutex.clone();
let if_keys_clone = if_keys.clone();
@ -19,7 +19,7 @@ fn main() -> IFResult<()> {
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap()
);
}
std::thread::sleep(std::time::Duration::from_millis(300))
std::thread::sleep(std::time::Duration::from_millis(200))
});
loop {
let mut buf = String::new();

Loading…
Cancel
Save