Skip to content

Commit

Permalink
refactor: enhance event system rust apis (#7996)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
  • Loading branch information
amrbashir and lucasfernog committed Oct 23, 2023
1 parent 198abe3 commit 93c8a77
Show file tree
Hide file tree
Showing 9 changed files with 521 additions and 347 deletions.
12 changes: 12 additions & 0 deletions .changes/tauri-event-system-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'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`.
- Removed `Window::emit`, and moved the implementation to `Manager::emit`.
- Removed `Window::emit_and_trigger` and `Window::trigger`, use `Window::emit` instead.
- Changed `Window::emit_to` to only trigger the target window listeners so it won't be catched by `Manager::listen_global`
2 changes: 1 addition & 1 deletion core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ impl<R: Runtime> GlobalWindowEvent<R> {
&self.event
}

/// The window that the menu belongs to.
/// The window that the event belongs to.
pub fn window(&self) -> &Window<R> {
&self.window
}
Expand Down
113 changes: 75 additions & 38 deletions core/tauri/src/event/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use super::{Event, EventId};
use crate::{Runtime, Window};

use super::{EmitArgs, Event, EventId};

use std::{
boxed::Box,
Expand All @@ -15,33 +17,33 @@ use std::{
};

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

/// 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<EventId, Handler>>>,
pending: Mutex<Vec<Pending>>,
struct InnerListeners<R: Runtime> {
handlers: Mutex<HashMap<String, HashMap<EventId, Handler<R>>>>,
pending: Mutex<Vec<Pending<R>>>,
function_name: &'static str,
listeners_object_name: &'static str,
next_event_id: Arc<AtomicU32>,
}

/// 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 @@ -55,15 +57,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> {
pub(crate) fn next_event_id(&self) -> EventId {
self.inner.next_event_id.fetch_add(1, Ordering::Relaxed)
}
Expand All @@ -79,7 +81,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 @@ -89,7 +91,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 @@ -102,13 +104,17 @@ impl Listeners {
for action in pending {
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::Listen(id, event, handler) => self.listen_with_id(id, event, handler),
Pending::Emit(args) => {
self.emit(&args)?;
}
}
}

Ok(())
}

fn listen_(&self, id: EventId, event: String, handler: Handler) {
fn listen_with_id(&self, id: EventId, 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 @@ -117,11 +123,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,
) -> EventId {
let id = self.next_event_id();
Expand All @@ -130,16 +136,16 @@ impl Listeners {
callback: Box::new(handler),
};

self.listen_(id, event, handler);
self.listen_with_id(id, event, handler);

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,
) {
let self_ = self.clone();
Expand All @@ -164,19 +170,42 @@ 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<F>(&self, emit_args: &EmitArgs, filter: Option<F>) -> crate::Result<()>
where
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(emit_args.clone())),
Ok(lock) => {
if let Some(handlers) = lock.get(event) {
for (&id, handler) in handlers {
if handler.window.is_none() || window == handler.window {
if let Some(handlers) = lock.get(&emit_args.event_name) {
let handlers = if let Some(filter) = filter {
handlers
.iter()
.filter(|h| {
h.1
.window
.as_ref()
.map(|w| {
// clippy sees this as redundant closure but
// fixing it will result in a compiler error
#[allow(clippy::redundant_closure)]
filter(w)
})
.unwrap_or(false)
})
.collect::<Vec<_>>()
} else {
handlers.iter().collect::<Vec<_>>()
};

if !handlers.is_empty() {
for (&id, handler) in handlers {
maybe_pending = true;
(handler.callback)(self::Event {
id,
data: payload.clone(),
data: emit_args.payload.clone(),
})
}
}
Expand All @@ -185,14 +214,22 @@ impl Listeners {
}

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

Ok(())
}

/// Emits the given event with its payload.
pub(crate) fn emit(&self, emit_args: &EmitArgs) -> crate::Result<()> {
self.emit_filter(emit_args, None::<&dyn Fn(&Window<R>) -> bool>)
}
}

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

// dummy event handler function
Expand All @@ -206,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 @@ -222,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 @@ -248,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();
// call listen with e and the event_fn dummy func
let listeners: Listeners<MockRuntime> = Default::default();
// call listen with key 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));
// call on event with key and d.
listeners.emit(&EmitArgs { event_name: key.clone(), event: serde_json::to_string(&key).unwrap(), source_window_label: "null".into(), payload: serde_json::to_string(&d).unwrap() })?;

// lock the mutex
let l = listeners.inner.handlers.lock().unwrap();
Expand Down
Loading

0 comments on commit 93c8a77

Please sign in to comment.