From f926410e4babc256c7256b6bba32f4fc1b7cd6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A8=E3=81=B4=E3=80=82=EF=BC=88=E3=81=8A=E3=83=BC?= =?UTF-8?q?=E3=81=91?= Date: Fri, 22 May 2026 23:34:09 +0900 Subject: [PATCH] feat(wasm-api): add ServerTickEndEvent (#2100) * feat(plugin-api): add ServerTickEndEvent Add a non-cancellable event fired at the end of every server tick, carrying the 0-indexed tick number and the tick duration in nanoseconds. Wires through all five layers: WIT contract, plugin-api guest binding, runtime host struct, WASM bridge, and the ticker fire site. Co-Authored-By: Claude Opus 4.7 (1M context) * fix ci/ fmt * chore: point pumpkin-plugin-wit submodule to fork with server-tick-end-event Co-Authored-By: Claude Opus 4.7 (1M context) * Revert submodule --------- Co-authored-by: Claude Opus 4.7 (1M context) --- pumpkin-plugin-api/src/events/server/mod.rs | 2 ++ .../src/events/server/server_tick_end.rs | 28 +++++++++++++++++++ pumpkin/src/plugin/api/events/server/mod.rs | 1 + .../api/events/server/server_tick_end.rs | 22 +++++++++++++++ .../loader/wasm/wasm_host/wit/v0_1/context.rs | 6 +++- .../wasm/wasm_host/wit/v0_1/events/server.rs | 24 ++++++++++++++-- pumpkin/src/server/ticker.rs | 13 ++++++++- 7 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 pumpkin-plugin-api/src/events/server/server_tick_end.rs create mode 100644 pumpkin/src/plugin/api/events/server/server_tick_end.rs diff --git a/pumpkin-plugin-api/src/events/server/mod.rs b/pumpkin-plugin-api/src/events/server/mod.rs index 5c9859ada..aecc82dec 100644 --- a/pumpkin-plugin-api/src/events/server/mod.rs +++ b/pumpkin-plugin-api/src/events/server/mod.rs @@ -1,9 +1,11 @@ pub mod server_broadcast; pub mod server_command; +pub mod server_tick_end; pub mod server_tick_start; pub mod spawn_change; pub use server_broadcast::*; pub use server_command::*; +pub use server_tick_end::*; pub use server_tick_start::*; pub use spawn_change::*; diff --git a/pumpkin-plugin-api/src/events/server/server_tick_end.rs b/pumpkin-plugin-api/src/events/server/server_tick_end.rs new file mode 100644 index 000000000..6f30f0a9f --- /dev/null +++ b/pumpkin-plugin-api/src/events/server/server_tick_end.rs @@ -0,0 +1,28 @@ +use crate::wit::pumpkin::plugin::event::{Event, EventType, ServerTickEndEventData}; + +use super::super::FromIntoEvent; + +/// An event that fires at the end of every server tick. +/// +/// The associated [`ServerTickEndEventData`] carries: +/// - `tick`: the 0-indexed number of the tick that just finished. +/// - `duration_nanos`: how long the tick took, measured from the start of +/// the ticker iteration to just after `Server::tick` returned. +/// +/// This event is non-cancellable. +pub struct ServerTickEndEvent; +impl FromIntoEvent for ServerTickEndEvent { + const EVENT_TYPE: EventType = EventType::ServerTickEndEvent; + type Data = ServerTickEndEventData; + + fn data_from_event(event: Event) -> Self::Data { + match event { + Event::ServerTickEndEvent(data) => data, + _ => panic!("unexpected event"), + } + } + + fn data_into_event(data: Self::Data) -> Event { + Event::ServerTickEndEvent(data) + } +} diff --git a/pumpkin/src/plugin/api/events/server/mod.rs b/pumpkin/src/plugin/api/events/server/mod.rs index 3d7522eb8..d68007105 100644 --- a/pumpkin/src/plugin/api/events/server/mod.rs +++ b/pumpkin/src/plugin/api/events/server/mod.rs @@ -1,3 +1,4 @@ pub mod server_broadcast; pub mod server_command; +pub mod server_tick_end; pub mod server_tick_start; diff --git a/pumpkin/src/plugin/api/events/server/server_tick_end.rs b/pumpkin/src/plugin/api/events/server/server_tick_end.rs new file mode 100644 index 000000000..48516260b --- /dev/null +++ b/pumpkin/src/plugin/api/events/server/server_tick_end.rs @@ -0,0 +1,22 @@ +use pumpkin_macros::Event; + +/// An event that fires at the end of every server tick. +#[derive(Event, Clone)] +pub struct ServerTickEndEvent { + /// 0-indexed number of the tick that just finished. + pub tick: i32, + + /// Duration (in nanoseconds) of the tick that just finished. + pub duration_nanos: i64, +} + +impl ServerTickEndEvent { + /// Creates a new `ServerTickEndEvent`. + #[must_use] + pub const fn new(tick: i32, duration_nanos: i64) -> Self { + Self { + tick, + duration_nanos, + } + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs index 2f1c204cf..5a945e92d 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/context.rs @@ -235,7 +235,7 @@ async fn register_server_event( ) { use crate::plugin::server::{ server_broadcast::ServerBroadcastEvent, server_command::ServerCommandEvent, - server_tick_start::ServerTickStartEvent, + server_tick_end::ServerTickEndEvent, server_tick_start::ServerTickStartEvent, }; match event_type { @@ -246,6 +246,9 @@ async fn register_server_event( register_typed_event::(resource, handler, priority, blocking) .await; } + EventType::ServerTickEndEvent => { + register_typed_event::(resource, handler, priority, blocking).await; + } EventType::ServerTickStartEvent => { register_typed_event::(resource, handler, priority, blocking) .await; @@ -317,6 +320,7 @@ impl pumpkin::plugin::context::HostContext for PluginHostState { match event_type { event_type @ (EventType::ServerCommandEvent | EventType::ServerBroadcastEvent + | EventType::ServerTickEndEvent | EventType::ServerTickStartEvent) => { register_server_event(resource, &handler, priority, blocking, event_type).await; } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/server.rs index 866b4eeaf..2b0663b6f 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/server.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/server.rs @@ -4,13 +4,14 @@ use crate::plugin::{ wit::v0_1::{ events::{ToFromWasmEvent, consume_text_component}, pumpkin::plugin::event::{ - Event, ServerBroadcastEventData, ServerCommandEventData, ServerTickStartEventData, + Event, ServerBroadcastEventData, ServerCommandEventData, ServerTickEndEventData, + ServerTickStartEventData, }, }, }, server::{ server_broadcast::ServerBroadcastEvent, server_command::ServerCommandEvent, - server_tick_start::ServerTickStartEvent, + server_tick_end::ServerTickEndEvent, server_tick_start::ServerTickStartEvent, }, }; @@ -61,6 +62,25 @@ impl ToFromWasmEvent for ServerBroadcastEvent { } } +impl ToFromWasmEvent for ServerTickEndEvent { + fn to_wasm_event(&self, _state: &mut PluginHostState) -> Event { + Event::ServerTickEndEvent(ServerTickEndEventData { + tick: self.tick, + duration_nanos: self.duration_nanos, + }) + } + + fn from_wasm_event(event: Event, _state: &mut PluginHostState) -> Self { + match event { + Event::ServerTickEndEvent(data) => Self { + tick: data.tick, + duration_nanos: data.duration_nanos, + }, + _ => panic!("unexpected event type"), + } + } +} + impl ToFromWasmEvent for ServerTickStartEvent { fn to_wasm_event(&self, _state: &mut PluginHostState) -> Event { Event::ServerTickStartEvent(ServerTickStartEventData { tick: self.tick }) diff --git a/pumpkin/src/server/ticker.rs b/pumpkin/src/server/ticker.rs index b56e48616..26fcc606c 100644 --- a/pumpkin/src/server/ticker.rs +++ b/pumpkin/src/server/ticker.rs @@ -1,5 +1,9 @@ use crate::{ - STOP_INTERRUPT, plugin::server::server_tick_start::ServerTickStartEvent, server::Server, + STOP_INTERRUPT, + plugin::server::{ + server_tick_end::ServerTickEndEvent, server_tick_start::ServerTickStartEvent, + }, + server::Server, }; use std::sync::Arc; use std::sync::atomic::Ordering; @@ -38,6 +42,13 @@ impl Ticker { } let tick_duration_nanos = tick_start_time.elapsed().as_nanos() as i64; + + let tick_number = server.tick_count.load(Ordering::Relaxed); + let _ = server + .plugin_manager + .fire(ServerTickEndEvent::new(tick_number, tick_duration_nanos)) + .await; + server.update_tick_times(tick_duration_nanos).await; let tick_interval = if manager.is_sprinting() {