feat(wasm-api): add ServerTickStartEvent (#2099)

* feat(plugin-api): add ServerTickStartEvent

Add a non-cancellable event fired at the start of every server tick,
carrying the 0-indexed tick number. 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) <noreply@anthropic.com>

* fix ci/ fmt

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
とぴ。(おーけ
2026-05-17 16:48:06 +09:00
committed by GitHub
parent 3401520ca2
commit 898f208ab6
8 changed files with 88 additions and 4 deletions

View File

@@ -1,7 +1,9 @@
pub mod server_broadcast;
pub mod server_command;
pub mod server_tick_start;
pub mod spawn_change;
pub use server_broadcast::*;
pub use server_command::*;
pub use server_tick_start::*;
pub use spawn_change::*;

View File

@@ -0,0 +1,25 @@
use crate::wit::pumpkin::plugin::event::{Event, EventType, ServerTickStartEventData};
use super::super::FromIntoEvent;
/// An event that fires at the start of every server tick (~20 Hz under the
/// default tick rate).
///
/// The associated [`ServerTickStartEventData`] carries the 0-indexed `tick`
/// number of the tick about to run. This event is non-cancellable.
pub struct ServerTickStartEvent;
impl FromIntoEvent for ServerTickStartEvent {
const EVENT_TYPE: EventType = EventType::ServerTickStartEvent;
type Data = ServerTickStartEventData;
fn data_from_event(event: Event) -> Self::Data {
match event {
Event::ServerTickStartEvent(data) => data,
_ => panic!("unexpected event"),
}
}
fn data_into_event(data: Self::Data) -> Event {
Event::ServerTickStartEvent(data)
}
}

View File

@@ -294,6 +294,10 @@ interface event {
cancelled: bool
}
record server-tick-start-event-data {
tick: s32
}
record packet-received-event-data {
player: player,
packet: serverbound-packet,
@@ -341,6 +345,7 @@ interface event {
server-command-event,
spawn-change-event,
server-broadcast-event,
server-tick-start-event,
packet-received-event,
packet-sent-event,
}
@@ -380,6 +385,7 @@ interface event {
server-command-event(server-command-event-data),
spawn-change-event(spawn-change-event-data),
server-broadcast-event(server-broadcast-event-data),
server-tick-start-event(server-tick-start-event-data),
packet-received-event(packet-received-event-data),
packet-sent-event(packet-sent-event-data),
}

View File

@@ -1,2 +1,3 @@
pub mod server_broadcast;
pub mod server_command;
pub mod server_tick_start;

View File

@@ -0,0 +1,16 @@
use pumpkin_macros::Event;
/// An event that fires at the start of every server tick.
#[derive(Event, Clone)]
pub struct ServerTickStartEvent {
/// 0-indexed number of the tick about to run.
pub tick: i32,
}
impl ServerTickStartEvent {
/// Creates a new `ServerTickStartEvent`.
#[must_use]
pub const fn new(tick: i32) -> Self {
Self { tick }
}
}

View File

@@ -229,6 +229,7 @@ async fn register_server_event(
) {
use crate::plugin::server::{
server_broadcast::ServerBroadcastEvent, server_command::ServerCommandEvent,
server_tick_start::ServerTickStartEvent,
};
match event_type {
@@ -239,6 +240,10 @@ async fn register_server_event(
register_typed_event::<ServerBroadcastEvent>(resource, handler, priority, blocking)
.await;
}
EventType::ServerTickStartEvent => {
register_typed_event::<ServerTickStartEvent>(resource, handler, priority, blocking)
.await;
}
_ => unreachable!("non-server event should not be routed to register_server_event"),
}
}
@@ -302,7 +307,9 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
let handler = Arc::new(WasmPluginEventHandler { handler_id, plugin });
match event_type {
event_type @ (EventType::ServerCommandEvent | EventType::ServerBroadcastEvent) => {
event_type @ (EventType::ServerCommandEvent
| EventType::ServerBroadcastEvent
| EventType::ServerTickStartEvent) => {
register_server_event(resource, &handler, priority, blocking, event_type).await;
}
event_type @ EventType::SpawnChangeEvent => {

View File

@@ -3,10 +3,15 @@ use crate::plugin::{
state::PluginHostState,
wit::v0_1::{
events::{ToFromWasmEvent, consume_text_component},
pumpkin::plugin::event::{Event, ServerBroadcastEventData, ServerCommandEventData},
pumpkin::plugin::event::{
Event, ServerBroadcastEventData, ServerCommandEventData, ServerTickStartEventData,
},
},
},
server::{server_broadcast::ServerBroadcastEvent, server_command::ServerCommandEvent},
server::{
server_broadcast::ServerBroadcastEvent, server_command::ServerCommandEvent,
server_tick_start::ServerTickStartEvent,
},
};
impl ToFromWasmEvent for ServerCommandEvent {
@@ -55,3 +60,16 @@ impl ToFromWasmEvent for ServerBroadcastEvent {
}
}
}
impl ToFromWasmEvent for ServerTickStartEvent {
fn to_wasm_event(&self, _state: &mut PluginHostState) -> Event {
Event::ServerTickStartEvent(ServerTickStartEventData { tick: self.tick })
}
fn from_wasm_event(event: Event, _state: &mut PluginHostState) -> Self {
match event {
Event::ServerTickStartEvent(data) => Self { tick: data.tick },
_ => panic!("unexpected event type"),
}
}
}

View File

@@ -1,5 +1,8 @@
use crate::{STOP_INTERRUPT, server::Server};
use crate::{
STOP_INTERRUPT, plugin::server::server_tick_start::ServerTickStartEvent, server::Server,
};
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tokio::time::{Instant, sleep_until};
use tracing::debug;
@@ -17,6 +20,12 @@ impl Ticker {
manager.tick();
let tick_number = server.tick_count.load(Ordering::Relaxed);
let _ = server
.plugin_manager
.fire(ServerTickStartEvent::new(tick_number))
.await;
if manager.is_sprinting() {
manager.start_sprint_tick_work();
server.tick().await;