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}