Skip to content

Commit

Permalink
Replace websocket communication with IPC/evaluate_script
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelBis committed Jul 18, 2024
1 parent 7ab0c67 commit 6655a74
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 343 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ wry = { version = "0.41", features = ["transparent", "devtools"] }
bevy = { version = "0.13", default-features = false, features = ["bevy_winit"] }
winit = "0.29"
thiserror = "1.0"
tungstenite = "0.21"
serde = { version = "1.0", default-features = false }
serde_json = { version = "1.0", optional = true }
bincode = { version = "1.3", optional = true }
serde_json = { version = "1.0" }

[[example]]
name = "simple"
path = "examples/simple.rs"
required-features = ["bincode", "serde_json", "bevy/bevy_core_pipeline", "bevy/bevy_render", "bevy/bevy_sprite"]

[features]
bincode = ["dep:bincode"]
serde_json = ["dep:serde_json"]
simple-example = ["bevy/bevy_core_pipeline", "bevy/bevy_render", "bevy/bevy_sprite"]

[patch.crates-io]
# At the moment http disallows empty authority and invalidates uris like: "file:///path/to/file"
http = { git = "https://github.com/PawelBis/http", branch = "feature/empty-authority" }
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# About

'bevy_wry' is a [bevy](https://github.com/bevyengine/bevy/) plugin that provides integration with [wry](https://github.com/tauri-apps/wry) - cross platform webview rendering library written in rust.
'bevy_wry' is a [bevy](https://github.com/bevyengine/bevy/) plugin that provides integration with [wry](https://github.com/tauri-apps/wry) - a cross platform webview rendering library written in rust.

'bevy_wry' enables [bevy::Event](https://docs.rs/bevy/latest/bevy/ecs/event/trait.Event.html) based communication with WebView through [websocket](https://github.com/snapview/tungstenite-rs/).
BevyWry allows for [bevy::Event](https://docs.rs/bevy/latest/bevy/ecs/event/trait.Event.html) based communication with WebView:
- Out events are required to implement [OutWryEvent]. This allows for [wry::WebView::evaluate_script](https://docs.rs/wry/latest/wry/struct.WebView.html#method.evaluate_script) communication with WebView
- Incoming events are received via IPC channel registered with [wry::WebViewBuilder::with_ipc_handler](https://docs.rs/wry/latest/wry/struct.WebViewBuilder.html#method.with_ipc_handler)

It is still in very early stages, however I think it is good enough for some experimentation.

Each client is simply reading/writing to websocket in a thread through [`MessageBus`](https://github.com/PawelBis/bevy_wry/blob/main/src/communication.rs#L62). The 'websocket.read()' call is non blocking - current version is relying on [`TcpStream::set_non_clocking(true)`](https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_nonblocking), however this will be improved in the future, as current implementation is quite expensive.

You can read events incoming from websocket with [`EventReader<InEvent<T>>`](https://docs.rs/bevy/latest/bevy/ecs/event/struct.EventReader.html) and write events with [`EventWriter<OutEvent<T>>`](https://docs.rs/bevy/latest/bevy/ecs/event/struct.EventWriter.html).
This plugin is still in very early stages, but it should be good enough for somne experimental work.

# Example

Check the [simple](https://github.com/PawelBis/bevy_wry/blob/main/examples/simple.rs) example for a quick reference.
`cargo run --example simple --features="bincode bevy/bevy_core_pipeline bevy/bevy_render bevy/bevy_sprite"`

# Features
- `bincode` - default bincode `SerializeMessage` and `DeserializeMessage` for types that implement/derive `serde::Serialize` and `serde::Deserialize`.
`cargo run --example simple --features="simple-example"`
62 changes: 33 additions & 29 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use bevy::{app::AppExit, prelude::*};
use bevy_wry::{
communication::types::{InEvent, OutEvent},
BevyWryPlugin,
};
use bevy_wry::{communication::types::OutWryEvent, BevyWryPlugin};
use std::env;

#[derive(Event, Clone, serde::Serialize, serde::Deserialize)]
Expand All @@ -12,6 +9,24 @@ enum Command {
Exit,
}

#[derive(Event, Clone, serde::Serialize, serde::Deserialize)]
struct InWrapper(pub Command);

#[derive(Event, Clone, serde::Serialize, serde::Deserialize)]
struct OutWrapper(pub Command);

impl OutWryEvent for OutWrapper {
fn to_script(&self) -> String {
match self.0 {
// ShowButton is our only OutCommand
// Please note that 'showButton' is a method implemented in
// our UI code: examples/web/ui.html
Command::ShowButton => "showButton()".to_string(),
_ => unreachable!(),
}
}
}

fn main() {
// bevy_wry needs absolute path to files for now
let manifest_path = env::var("CARGO_MANIFEST_DIR").unwrap();
Expand All @@ -20,7 +35,7 @@ fn main() {
App::new()
.insert_resource(ClearColor(Color::PURPLE))
.add_plugins(DefaultPlugins)
.add_plugins(BevyWryPlugin::<Command, Command>::new(ui_path))
.add_plugins(BevyWryPlugin::<InWrapper, OutWrapper>::new(ui_path))
.add_systems(Startup, setup)
.add_systems(Update, handle_events)
.run();
Expand All @@ -40,37 +55,26 @@ fn setup(mut commands: Commands) {
}

fn handle_events(
mut event_reader: EventReader<InEvent<Command>>,
mut event_writer: EventWriter<OutEvent<Command>>,
mut event_reader: EventReader<InWrapper>,
mut event_writer: EventWriter<OutWrapper>,
mut exit_writer: EventWriter<AppExit>,
mut sprite: Query<(&mut Transform, &Sprite)>,
) {
for event in event_reader.read() {
if let InEvent::Text(string) = event {
let command: Command = match serde_json::from_str(&string) {
Ok(c) => c,
Err(_) => {
info!(string);
return;
}
};
match event.0 {
Command::Rotate { angle } => {
let (mut transform, _) = sprite.single_mut();
transform.rotate_z(f32::to_radians(angle));

let (mut transform, _) = sprite.single_mut();
match command {
Command::Rotate { angle } => {
transform.rotate_z(f32::to_radians(angle));

let (_, z) = transform.rotation.to_axis_angle();
if z == f32::to_radians(180.0) {
let show_btn_command = serde_json::to_string(&Command::ShowButton).unwrap();
event_writer.send(OutEvent::Text(show_btn_command));
}
}
Command::Exit => {
exit_writer.send(AppExit);
let (_, z) = transform.rotation.to_axis_angle();
if z == f32::to_radians(180.0) {
event_writer.send(OutWrapper(Command::ShowButton));
}
_ => (),
}
Command::Exit => {
exit_writer.send(AppExit);
}
_ => (),
}
}
}
19 changes: 6 additions & 13 deletions examples/web/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
}
</style>
</head>
<body style="background-color:transparent;">
<body style="background-color:transparent;text-align:center">
<h1>Rotate the square at least 180 degrees to show exit button</h1>
<button class="left" onclick=rotate(-30)>
Rotate left
</button>
Expand All @@ -66,36 +67,28 @@
Rotate right
</button>

<!-- In proper application this should be in main.js or main.ts or just file.js -->
<script type="text/javascript">
let websocket = new WebSocket("ws://localhost:8876");
websocket.onopen = () => {
websocket.onmessage = read_message
}

function rotate(d) {
let rotation_command = {
Rotate: {
angle: -d,
}
}
websocket.send(JSON.stringify(rotation_command));
let msg = JSON.stringify(rotation_command);
window.ipc.postMessage(msg);
}

function exit() {
let msg = "Exit";
websocket.send(JSON.stringify(msg))
window.ipc.postMessage(JSON.stringify(msg));
}

function read_message(m) {
let event = JSON.parse(m.data)
if (event === "ShowButton") {
window.showButton = () => {
const buttons = document.getElementsByClassName("disabled");
for (let i = 0; i < buttons.length; i++) {
let b = buttons.item(i);
b.classList.remove("disabled");
}
}
}
</script>
</body>
Expand Down
24 changes: 0 additions & 24 deletions src/communication/bincode.rs

This file was deleted.

3 changes: 1 addition & 2 deletions src/communication/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#[derive(Debug)]
pub enum Error {
#[cfg(feature = "bincode")]
Bincode(bincode::Error),
Deserialize,
BadMessageType,
CloseRequested,
EvaluateScript,
}
27 changes: 24 additions & 3 deletions src/communication/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
pub mod types;

#[cfg(feature = "bincode")]
pub mod bincode;

pub mod error;
use bevy::prelude::{Event, EventReader, EventWriter, NonSend, ResMut};
pub use error::Error;
use wry::WebView;

use self::types::{MessageBus, OutWryEvent};

pub fn consume_in_events<T: Event>(message_bus: ResMut<MessageBus<T>>, mut events: EventWriter<T>) {
let messages = { message_bus.lock().split_off(0) };
for msg in messages {
events.send(msg);
}
}

pub fn send_out_events<T: Event + OutWryEvent>(
webview: NonSend<WebView>,
mut events: EventReader<T>,
) -> Result<(), Error> {
for event in events.read() {
webview
.evaluate_script(&event.to_script())
.map_err(|_| Error::EvaluateScript)?;
}

Ok(())
}
60 changes: 5 additions & 55 deletions src/communication/types.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,14 @@
use std::sync::{Arc, Mutex, MutexGuard};

use super::Error;
use bevy::prelude::{Deref, Event, Resource};
use tungstenite::Message;
use serde::{Deserialize, Serialize};

#[derive(Event)]
pub enum OutEvent<T: Event + SerializeMessage> {
Text(String),
Event(T),
pub trait OutWryEvent: Event + Serialize + Send {
fn to_script(&self) -> String;
}

impl<T: Event + SerializeMessage> OutEvent<T> {
pub fn to_message(&self) -> Result<Message, Error> {
let msg = match self {
OutEvent::Text(text) => Message::Text(text.clone()),
OutEvent::Event(event) => {
Message::Binary(event.to_binary().map_err(|_| Error::Deserialize)?)
}
};

Ok(msg)
}
}

#[derive(Event)]
pub enum InEvent<T: Event + DeserializeMessage<Event = T>> {
Text(String),
Event(T),
}

impl<T> TryFrom<Message> for InEvent<T>
where
T: Event + DeserializeMessage<Event = T>,
{
type Error = Error;

fn try_from(value: Message) -> Result<Self, Self::Error> {
match value {
Message::Text(text) => Ok(Self::Text(text)),
Message::Binary(buffer) => Ok(Self::Event(
T::from_binary(buffer).map_err(|_| Error::Deserialize)?,
)),
Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => Err(Error::BadMessageType),
Message::Close(_) => Err(Error::CloseRequested),
}
}
}
pub trait InWryEvent<'de>: Event + Deserialize<'de> + Send {}
impl<'de, T> InWryEvent<'de> for T where T: Event + Deserialize<'de> + Send {}

#[derive(Deref, Resource)]
pub struct MessageBus<T: Send>(pub Arc<Mutex<Vec<T>>>);
Expand All @@ -67,16 +30,3 @@ impl<T: Send> Clone for MessageBus<T> {
Self(self.0.clone())
}
}

pub trait DeserializeMessage {
type Event;
type Error: std::fmt::Debug;

fn from_binary(_: Vec<u8>) -> Result<Self::Event, Self::Error>;
}

pub trait SerializeMessage {
type Error: std::fmt::Debug;

fn to_binary(&self) -> Result<Vec<u8>, Self::Error>;
}
4 changes: 0 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use thiserror::Error;

use crate::websocket;

#[derive(Error, Debug)]
pub enum Error {
#[error("missing `{0}` resource")]
Expand All @@ -10,6 +8,4 @@ pub enum Error {
FailedToGetMainWindow,
#[error("wry error: {0}")]
Wry(#[from] wry::Error),
#[error("websocket error: {0}")]
Websocket(#[from] websocket::Error),
}
Loading

0 comments on commit 6655a74

Please sign in to comment.