Skip to content

Commit

Permalink
cancelable events
Browse files Browse the repository at this point in the history
  • Loading branch information
GStudiosX2 committed Jan 12, 2025
1 parent d11c03e commit ce37083
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 22 deletions.
19 changes: 10 additions & 9 deletions src/bin/src/packet_handlers/login_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use ferrumc_core::transform::position::Position;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_ecs::components::storage::ComponentRefMut;
use ferrumc_ecs::entities::Entity;
use ferrumc_events::errors::EventsError;
use ferrumc_events::infrastructure::Event;
use ferrumc_macros::event_handler;
use ferrumc_net::connection::{ConnectionState, PlayerStartLoginEvent, StreamWriter};
Expand Down Expand Up @@ -47,19 +46,21 @@ async fn handle_login_start(
let event = PlayerStartLoginEvent {
entity: login_start_event.conn_id,
profile: PlayerIdentity::new(username.to_string(), uuid),
cancelled: false,
};

match PlayerStartLoginEvent::trigger(event, state.clone()).await {
Err(NetError::Kick(msg)) => Err(NetError::Kick(msg)),
Err(NetError::EventsError(EventsError::Cancelled)) => Ok(login_start_event),
Ok(event) => {
// Add the player identity component to the ECS for the entity.
ferrumc_net::connection::send_login_success(
state,
login_start_event.conn_id,
event.profile,
)
.await?;
if !event.is_cancelled() {
// Add the player identity component to the ECS for the entity.
ferrumc_net::connection::send_login_success(
state,
login_start_event.conn_id,
event.profile,
)
.await?;
}
Ok(login_start_event)
}
e => e.map(|_| login_start_event),
Expand Down
14 changes: 11 additions & 3 deletions src/bin/src/packet_handlers/velocity.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ferrumc_config::statics::get_global_config;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_events::{errors::EventsError, infrastructure::Event};
use ferrumc_events::infrastructure::Event;
use ferrumc_macros::event_handler;
use ferrumc_net::packets::incoming::server_bound_plugin_message::*;
use ferrumc_net::packets::outgoing::client_bound_plugin_message::*;
use ferrumc_net::packets::outgoing::disconnect::DISCONNECT_STRING;
use ferrumc_net::utils::ecs_helpers::EntityExt;
use ferrumc_net::{
connection::{PlayerStartLoginEvent, StreamWriter},
Expand All @@ -25,7 +26,7 @@ struct VelocityMessageId(u32);

#[event_handler]
async fn handle_login_start(
event: PlayerStartLoginEvent,
mut event: PlayerStartLoginEvent,
state: GlobalState,
) -> NetResult<PlayerStartLoginEvent> {
if get_global_config().velocity.enabled {
Expand All @@ -47,7 +48,9 @@ async fn handle_login_start(
.add_component(entity, VelocityMessageId(id))?;

// this stops the packet handler from doing login success
Err(NetError::EventsError(EventsError::Cancelled))
event.cancel(true);

Ok(event)
} else {
Ok(event)
}
Expand Down Expand Up @@ -113,10 +116,15 @@ async fn handle_velocity_response(
let e = PlayerStartLoginEvent {
entity: event.entity,
profile: PlayerIdentity::decode(&mut buf, &NetDecodeOpts::None)?,
cancelled: false,
};

match PlayerStartLoginEvent::trigger(e, state.clone()).await {
Ok(e) => {
if e.is_cancelled() {
return Err(NetError::kick(DISCONNECT_STRING.to_string()));
}

state
.universe
.remove_component::<VelocityMessageId>(event.entity)?;
Expand Down
28 changes: 28 additions & 0 deletions src/lib/derive_macros/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,32 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
}
};

let mut cancellable = false;

for attr in crate::helpers::get_derive_attributes(&input, "event") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("cancellable") {
cancellable = true;
return Ok(());
}

Err(meta.error("unrecognized attributes"))
})
.unwrap();
}

let cancellable_impl = cancellable.then(|| {
quote! {
fn is_cancelled(&self) -> bool {
self.cancelled
}

fn cancel(&mut self, value: bool) {
self.cancelled = value;
}
}
});

let output = quote! {
impl ::ferrumc_events::infrastructure::Event for #name {
type Data = Self;
Expand All @@ -137,6 +163,8 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
fn name() -> &'static str {
stringify!(#name)
}

#cancellable_impl
}
};

Expand Down
2 changes: 0 additions & 2 deletions src/lib/events/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ pub enum EventsError {
},
#[error("A listener failed")]
ListenerFailed,
#[error("cancelled")]
Cancelled,
#[error("{0}")]
Other(String),
}
38 changes: 30 additions & 8 deletions src/lib/events/src/infrastructure.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{any::Any, future::Future, pin::Pin, sync::LazyLock};

use dashmap::DashMap;
use futures::{stream, StreamExt};
use futures::{stream, StreamExt, TryStreamExt};

/// A Lazily initialized HashMap wrapped in a ShardedLock optimized for reads.
type LazyRwListenerMap<K, V> = LazyLock<DashMap<K, V>>;
Expand Down Expand Up @@ -52,10 +52,15 @@ impl<E: Event> Priority for EventListener<E> {
}
}

enum EventTryError<Data, Error> {
Acc(Data),
Err(Error),
}

#[allow(async_fn_in_trait)]
pub trait Event: Sized + Send + Sync + 'static {
/// Event data structure
type Data: Send + Sync;
type Data: Event;

/// State
type State: Send + Sync + Clone;
Expand All @@ -66,6 +71,14 @@ pub trait Event: Sized + Send + Sync + 'static {
/// Stringified name of the event
fn name() -> &'static str;

fn is_cancelled(&self) -> bool {
false
}

fn cancel(&mut self, _value: bool) {
unimplemented!();
}

/// Trigger an event execution
///
/// This method will pass the data to the listener with the highest priority which
Expand All @@ -89,14 +102,20 @@ pub trait Event: Sized + Send + Sync + 'static {
// Maybe some speedup?
// Filter only listeners we can downcast into the correct type
.filter_map(|dyn_list| async { dyn_list.downcast_ref::<EventListener<Self>>() })
.map(Ok)
// Trigger listeners in a row
.fold(Ok(event), |intercepted, listener| {
.try_fold(Ok(event), |intercepted, listener| {
let state = state.clone();
async move {
if intercepted.is_err() {
intercepted
} else {
(listener.listener)(intercepted.unwrap(), state).await
match intercepted {
Ok(event) => {
if !event.is_cancelled() {
Ok((listener.listener)(event, state).await)
} else {
Err(EventTryError::Acc(event))
}
}
Err(e) => Err(EventTryError::Err(e)),
}
}
})
Expand All @@ -105,7 +124,10 @@ pub trait Event: Sized + Send + Sync + 'static {
#[cfg(debug_assertions)]
tracing::trace!("Event {} took {:?}", Self::name(), start.elapsed());

res
match res {
Ok(Ok(event)) | Err(EventTryError::Acc(event)) => Ok(event),
Ok(Err(e)) | Err(EventTryError::Err(e)) => Err(e),
}
}

/// Register a new event listener for this event
Expand Down
3 changes: 3 additions & 0 deletions src/lib/net/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,15 @@ pub struct PlayerDisconnectEvent {
/// a custom handshaking protocol before the player logs in using login plugin messages/etc.
///
#[derive(Event, Clone)]
#[event(cancellable)]
pub struct PlayerStartLoginEvent {
/// The entity that this event was fired for.
pub entity: Entity,

/// This profile can be changed and after the event is finished this will be the new profile.
pub profile: PlayerIdentity,

pub cancelled: bool,
}

/// Since parking_lot is single-threaded, we use spawn_blocking to remove all components from the entity asynchronously (on another thread).
Expand Down

0 comments on commit ce37083

Please sign in to comment.