Skip to main content

basalt_api/context/
response.rs

1//! Response enum and queue for deferred operations.
2
3use std::cell::RefCell;
4
5use crate::broadcast::BroadcastMessage;
6use crate::components::{BlockPosition, ChunkPosition, Position, Rotation};
7use crate::context::UnlockReason;
8use crate::recipes::RecipeId;
9use basalt_types::Slot;
10use basalt_types::nbt::NbtCompound;
11
12/// A deferred operation queued by a sync event handler.
13#[derive(Debug, Clone)]
14pub enum Response {
15    /// Broadcast a message to all connected players.
16    Broadcast(BroadcastMessage),
17    /// Send a block action acknowledgement.
18    SendBlockAck {
19        /// Sequence number.
20        sequence: i32,
21    },
22    /// Send a system chat message.
23    SendSystemChat {
24        /// The formatted text component as NBT.
25        content: NbtCompound,
26        /// Whether to display as action bar.
27        action_bar: bool,
28    },
29    /// Teleport the current player.
30    SendPosition {
31        /// Teleport ID.
32        teleport_id: i32,
33        /// Target position.
34        position: Position,
35        /// Target facing direction.
36        rotation: Rotation,
37    },
38    /// Stream chunks around a chunk position.
39    StreamChunks(ChunkPosition),
40    /// Send a game state change.
41    SendGameStateChange {
42        /// Reason code.
43        reason: u8,
44        /// Associated value.
45        value: f32,
46    },
47    /// Schedule a chunk for asynchronous persistence.
48    PersistChunk(ChunkPosition),
49    /// Spawn a dropped item entity in the world.
50    SpawnDroppedItem {
51        /// Block position where the item spawns.
52        position: BlockPosition,
53        /// Item ID to drop.
54        item_id: i32,
55        /// Item count.
56        count: i32,
57    },
58    /// Open a chest container at the given position.
59    OpenChest(BlockPosition),
60    /// Open a crafting table window for the current player.
61    ///
62    /// Sends an OpenScreen packet with the 3x3 crafting grid type
63    /// and attaches a CraftingGrid component to the player entity.
64    OpenCraftingTable {
65        /// Block position of the crafting table.
66        position: BlockPosition,
67    },
68    /// Open a custom container window for the current player.
69    ///
70    /// Accepts a [`Container`](crate::container::Container) template
71    /// value built via [`Container::builder()`](crate::container::Container::builder).
72    OpenContainer(crate::container::Container),
73    /// Broadcast a `BlockAction` packet to all connected players.
74    ///
75    /// Used by container/door/note-block plugins for state-change
76    /// animations (chest lid open/close, door open/close, etc.). The
77    /// meaning of `action_id` and `action_param` depends on the
78    /// `block_id` registry value.
79    BroadcastBlockAction {
80        /// World position of the block.
81        position: BlockPosition,
82        /// Action identifier (block-specific).
83        action_id: u8,
84        /// Action parameter (block-specific; for chests this is the
85        /// number of viewers, 0 = closed).
86        action_param: u8,
87        /// Block registry ID (e.g. 185 for chest in 1.21.4).
88        block_id: i32,
89    },
90    /// Send a `SetContainerSlot` to every player viewing the same
91    /// block-backed container, **excluding** the current player.
92    ///
93    /// Used by `ContainerPlugin` to keep co-viewers' open chests in
94    /// sync when a slot is mutated by the source player. The server
95    /// resolves which players are co-viewers by scanning the
96    /// `OpenContainer` components.
97    NotifyContainerViewers {
98        /// World position of the block-backed container.
99        position: BlockPosition,
100        /// Protocol slot index that changed.
101        slot_index: i16,
102        /// New slot contents to broadcast.
103        item: Slot,
104    },
105    /// Remove a block entity at the given position and dispatch
106    /// `BlockEntityDestroyedEvent` with its last state.
107    ///
108    /// Used by `ContainerPlugin` on chest break to drive the destroy
109    /// → drop-items chain through the event pipeline. No-op if no
110    /// block entity exists at the position.
111    DestroyBlockEntity {
112        /// World position of the block entity to remove.
113        position: BlockPosition,
114    },
115    /// Unlock a recipe for the current player.
116    ///
117    /// The server inserts the recipe id into the player's
118    /// `KnownRecipes` component, sends a `Recipe Book Add` S2C packet,
119    /// and dispatches `RecipeUnlockedEvent` at Post. No-op if the
120    /// recipe is already unlocked.
121    UnlockRecipe {
122        /// Stable identifier of the recipe to unlock.
123        recipe_id: RecipeId,
124        /// Why the unlock happened — surfaced on `RecipeUnlockedEvent`.
125        reason: UnlockReason,
126    },
127    /// Lock a recipe for the current player.
128    ///
129    /// The server removes the recipe from the player's `KnownRecipes`,
130    /// sends a `Recipe Book Remove` S2C packet, and dispatches
131    /// `RecipeLockedEvent` at Post. No-op if the recipe is not
132    /// currently unlocked.
133    LockRecipe {
134        /// Stable identifier of the recipe to lock.
135        recipe_id: RecipeId,
136    },
137}
138
139/// Thread-local queue for deferred async responses.
140///
141/// Used by `ServerContext` implementations (basalt-server) to collect
142/// deferred operations during handler dispatch. Visibility is `pub`
143/// so that the production `ServerContext` in basalt-server can
144/// construct and read from it.
145pub struct ResponseQueue {
146    inner: RefCell<Vec<Response>>,
147}
148
149impl Default for ResponseQueue {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155impl ResponseQueue {
156    /// Creates an empty response queue.
157    pub fn new() -> Self {
158        Self {
159            inner: RefCell::new(Vec::new()),
160        }
161    }
162
163    /// Pushes a deferred response onto the queue.
164    pub fn push(&self, response: Response) {
165        self.inner.borrow_mut().push(response);
166    }
167
168    /// Drains all queued responses, returning them as a `Vec`.
169    pub fn drain(&self) -> Vec<Response> {
170        self.inner.borrow_mut().drain(..).collect()
171    }
172}