Skip to content

Commit

Permalink
refactor: enhance event system rust apis
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir committed Oct 10, 2023
1 parent 713f84d commit b1dcf8e
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 261 deletions.
11 changes: 11 additions & 0 deletions .changes/tauri-event-system-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'tauri': 'major:breaking'
---

The event system APIS on Rust is recieving a few changes for consistency and quality of life improvements:

- Renamed `Manager::emit_all` to just `Manager::emit` and will now both trigger the events on JS side as well as Rust.
- Removed `Manager::trigger_global`, use `Manager::emit`
- Added `Manager::emit_filter`.
- Changed `Window::emit` to trigger the events on the Rust side as well.
- Removed `Window::emit_and_trigger` and `Window::trigger`, use `Window::emit` instead.
26 changes: 6 additions & 20 deletions core/tauri/src/event/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,10 @@ pub fn emit<R: Runtime>(
window_label: Option<WindowLabel>,
payload: Option<JsonValue>,
) -> Result<()> {
// dispatch the event to Rust listeners
window.trigger(
&event.0,
payload.as_ref().and_then(|p| {
serde_json::to_string(&p)
.map_err(|e| {
#[cfg(debug_assertions)]
eprintln!("{e}");
e
})
.ok()
}),
);

// emit event to JS
if let Some(target) = window_label {
window.emit_to(&target.0, &event.0, payload)
} else {
window.emit_all(&event.0, payload)
}
window.emit_filter(&event.0, payload, |l| {
window_label
.as_ref()
.map(|label| label.0 == l.label())
.unwrap_or(true)
})
}
117 changes: 81 additions & 36 deletions core/tauri/src/event/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use crate::{Runtime, Window};

use super::{Event, EventHandler};

use serde::Serialize;
use std::{
boxed::Box,
cell::Cell,
Expand All @@ -13,32 +16,32 @@ use std::{
use uuid::Uuid;

/// What to do with the pending handler when resolving it?
enum Pending {
enum Pending<R: Runtime> {
Unlisten(EventHandler),
Listen(EventHandler, String, Handler),
Trigger(String, Option<String>, Option<String>),
Listen(EventHandler, String, Handler<R>),
Emit(String, Option<String>, Option<String>),
}

/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
struct Handler {
window: Option<String>,
struct Handler<R: Runtime> {
window: Option<Window<R>>,
callback: Box<dyn Fn(Event) + Send>,
}

/// Holds event handlers and pending event handlers, along with the salts associating them.
struct InnerListeners {
handlers: Mutex<HashMap<String, HashMap<EventHandler, Handler>>>,
pending: Mutex<Vec<Pending>>,
struct InnerListeners<R: Runtime> {
handlers: Mutex<HashMap<String, HashMap<EventHandler, Handler<R>>>>,
pending: Mutex<Vec<Pending<R>>>,
function_name: Uuid,
listeners_object_name: Uuid,
}

/// A self-contained event manager.
pub struct Listeners {
inner: Arc<InnerListeners>,
pub struct Listeners<R: Runtime> {
inner: Arc<InnerListeners<R>>,
}

impl Default for Listeners {
impl<R: Runtime> Default for Listeners<R> {
fn default() -> Self {
Self {
inner: Arc::new(InnerListeners {
Expand All @@ -51,15 +54,15 @@ impl Default for Listeners {
}
}

impl Clone for Listeners {
impl<R: Runtime> Clone for Listeners<R> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}

impl Listeners {
impl<R: Runtime> Listeners<R> {
/// Randomly generated function name to represent the JavaScript event function.
pub(crate) fn function_name(&self) -> String {
self.inner.function_name.to_string()
Expand All @@ -71,7 +74,7 @@ impl Listeners {
}

/// Insert a pending event action to the queue.
fn insert_pending(&self, action: Pending) {
fn insert_pending(&self, action: Pending<R>) {
self
.inner
.pending
Expand All @@ -81,7 +84,7 @@ impl Listeners {
}

/// Finish all pending event actions.
fn flush_pending(&self) {
fn flush_pending(&self) -> crate::Result<()> {
let pending = {
let mut lock = self
.inner
Expand All @@ -95,12 +98,16 @@ impl Listeners {
match action {
Pending::Unlisten(id) => self.unlisten(id),
Pending::Listen(id, event, handler) => self.listen_(id, event, handler),
Pending::Trigger(ref event, window, payload) => self.trigger(event, window, payload),
Pending::Emit(ref event, window, payload) => {
self.emit(event, window, payload)?;
}
}
}

Ok(())
}

fn listen_(&self, id: EventHandler, event: String, handler: Handler) {
fn listen_(&self, id: EventHandler, event: String, handler: Handler<R>) {
match self.inner.handlers.try_lock() {
Err(_) => self.insert_pending(Pending::Listen(id, event, handler)),
Ok(mut lock) => {
Expand All @@ -109,11 +116,11 @@ impl Listeners {
}
}

/// Adds an event listener for JS events.
/// Adds an event listener.
pub(crate) fn listen<F: Fn(Event) + Send + 'static>(
&self,
event: String,
window: Option<String>,
window: Option<Window<R>>,
handler: F,
) -> EventHandler {
let id = EventHandler(Uuid::new_v4());
Expand All @@ -127,11 +134,11 @@ impl Listeners {
id
}

/// Listen to a JS event and immediately unlisten.
/// Listen to an event and immediately unlisten.
pub(crate) fn once<F: FnOnce(Event) + Send + 'static>(
&self,
event: String,
window: Option<String>,
window: Option<Window<R>>,
handler: F,
) -> EventHandler {
let self_ = self.clone();
Expand All @@ -156,35 +163,73 @@ impl Listeners {
}
}

/// Triggers the given global event with its payload.
pub(crate) fn trigger(&self, event: &str, window: Option<String>, payload: Option<String>) {
/// Emits the given event with its payload based on a filter.
pub(crate) fn emit_filter<S, F>(
&self,
event: &str,
window: Option<String>,
payload: Option<S>,
filter: F,
) -> crate::Result<()>
where
S: Serialize + Clone,
F: Fn(&Window<R>) -> bool,
{
let mut maybe_pending = false;
match self.inner.handlers.try_lock() {
Err(_) => self.insert_pending(Pending::Trigger(event.to_owned(), window, payload)),
Err(_) => self.insert_pending(Pending::Emit(
event.to_owned(),
window,
payload.map(|p| serde_json::to_string(&p)).transpose()?,
)),
Ok(lock) => {
if let Some(handlers) = lock.get(event) {
for (&id, handler) in handlers {
if handler.window.is_none() || window == handler.window {
maybe_pending = true;
(handler.callback)(self::Event {
id,
data: payload.clone(),
})
let handlers = handlers
.iter()
.filter(|h| h.1.window.as_ref().map(|w| filter(w)).unwrap_or(true))

Check failure on line 189 in core/tauri/src/event/listener.rs

View workflow job for this annotation

GitHub Actions / empty

redundant closure

error: redundant closure --> core/tauri/src/event/listener.rs:189:49 | 189 | .filter(|h| h.1.window.as_ref().map(|w| filter(w)).unwrap_or(true)) | ^^^^^^^^^^^^^ help: replace the closure with the function itself: `filter` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings`
.collect::<Vec<_>>();

if !handlers.is_empty() {
let data = payload.map(|p| serde_json::to_string(&p)).transpose()?;

for (&id, handler) in handlers {
if handler.window.is_none()
|| window.as_deref() == handler.window.as_ref().map(|w| w.label())
{
maybe_pending = true;
(handler.callback)(self::Event {
id,
data: data.clone(),
})
}
}
}
}
}
}

if maybe_pending {
self.flush_pending();
self.flush_pending()?;
}

Ok(())
}

/// Emits the given event with its payload.
pub(crate) fn emit(
&self,
event: &str,
window: Option<String>,
payload: Option<String>,
) -> crate::Result<()> {
self.emit_filter(event, window, payload, |_| true)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::test::MockRuntime;
use proptest::prelude::*;

// dummy event handler function
Expand All @@ -198,7 +243,7 @@ mod test {
// check to see if listen() is properly passing keys into the LISTENERS map
#[test]
fn listeners_check_key(e in "[a-z]+") {
let listeners: Listeners = Default::default();
let listeners: Listeners<MockRuntime> = Default::default();
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
Expand All @@ -214,7 +259,7 @@ mod test {
// check to see if listen inputs a handler function properly into the LISTENERS map.
#[test]
fn listeners_check_fn(e in "[a-z]+") {
let listeners: Listeners = Default::default();
let listeners: Listeners<MockRuntime> = Default::default();
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
Expand All @@ -240,11 +285,11 @@ mod test {
// check to see if on_event properly grabs the stored function from listen.
#[test]
fn check_on_event(key in "[a-z]+", d in "[a-z]+") {
let listeners: Listeners = Default::default();
let listeners: Listeners<MockRuntime> = Default::default();
// call listen with e and the event_fn dummy func
listeners.listen(key.clone(), None, event_fn);
// call on event with e and d.
listeners.trigger(&key, None, Some(d));
listeners.emit(&key, None, Some(d))?;

// lock the mutex
let l = listeners.inner.handlers.lock().unwrap();
Expand Down
50 changes: 33 additions & 17 deletions core/tauri/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use serde::Serialize;
use std::{fmt, hash::Hash};
use uuid::Uuid;

Expand Down Expand Up @@ -38,7 +39,7 @@ impl fmt::Display for EventHandler {
}
}

/// An event that was triggered.
/// An event that was emitted.
#[derive(Debug, Clone)]
pub struct Event {
id: EventHandler,
Expand Down Expand Up @@ -68,22 +69,6 @@ pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
.build()
}

pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: usize) -> String {
format!(
"
(function () {{
const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
if (listeners) {{
const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
if (index > -1) {{
window['{listeners_object_name}']['{event_name}'].splice(index, 1)
}}
}}
}})()
",
)
}

pub fn listen_js(
listeners_object_name: String,
event: String,
Expand Down Expand Up @@ -118,3 +103,34 @@ pub fn listen_js(
},
)
}

pub fn emit_js<S: Serialize>(
event_emit_function_name: &str,
event: &str,
source_window_label: Option<&str>,
payload: S,
) -> crate::Result<String> {
Ok(format!(
"(function () {{ const fn = window['{}']; fn && fn({{event: {}, windowLabel: {}, payload: {}}}) }})()",
event_emit_function_name,
serde_json::to_string(event)?,
serde_json::to_string(&source_window_label)?,
serde_json::to_value(payload)?,
))
}

pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: usize) -> String {
format!(
"
(function () {{
const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
if (listeners) {{
const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
if (index > -1) {{
window['{listeners_object_name}']['{event_name}'].splice(index, 1)
}}
}}
}})()
",
)
}
Loading

0 comments on commit b1dcf8e

Please sign in to comment.