steel_core/entity/
callback.rs1use std::sync::Weak;
4
5use glam::DVec3;
6use steel_utils::{ChunkPos, WorldAabb};
7
8use super::EntityMoveError;
9use crate::world::World;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RemovalReason {
14 Killed,
16 Discarded,
18 UnloadedToChunk,
20 ChangedWorld,
22 StoredWithPlayer,
24}
25
26impl RemovalReason {
27 #[must_use]
29 pub const fn should_destroy(self) -> bool {
30 matches!(self, Self::Killed | Self::Discarded)
31 }
32
33 #[must_use]
39 pub const fn should_save(self) -> bool {
40 matches!(self, Self::UnloadedToChunk)
41 }
42}
43
44pub trait EntityLevelCallback: Send + Sync {
46 fn allows_local_position_update(&self) -> bool {
48 false
49 }
50
51 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError>;
53
54 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError>;
56
57 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {}
59
60 fn on_remove(&self, reason: RemovalReason);
62}
63
64pub struct NullEntityCallback;
66
67impl EntityLevelCallback for NullEntityCallback {
68 fn allows_local_position_update(&self) -> bool {
69 true
70 }
71
72 fn validate_move(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
73 Ok(())
74 }
75
76 fn on_move_committed(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
77 Ok(())
78 }
79
80 fn on_remove(&self, _reason: RemovalReason) {}
81}
82
83pub struct InactiveEntityCallback {
85 entity_id: i32,
86}
87
88impl InactiveEntityCallback {
89 #[must_use]
91 pub const fn new(entity_id: i32) -> Self {
92 Self { entity_id }
93 }
94}
95
96impl EntityLevelCallback for InactiveEntityCallback {
97 fn validate_move(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
98 Err(EntityMoveError::Inactive {
99 entity_id: self.entity_id,
100 })
101 }
102
103 fn on_move_committed(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
104 Err(EntityMoveError::Inactive {
105 entity_id: self.entity_id,
106 })
107 }
108
109 fn on_remove(&self, _reason: RemovalReason) {}
110}
111
112pub struct PlayerEntityCallback {
117 entity_id: i32,
118 world: Weak<World>,
119}
120
121impl PlayerEntityCallback {
122 #[must_use]
124 pub const fn new(entity_id: i32, world: Weak<World>) -> Self {
125 Self { entity_id, world }
126 }
127}
128
129impl EntityLevelCallback for PlayerEntityCallback {
130 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
131 let Some(world) = self.world.upgrade() else {
132 return Err(EntityMoveError::NotLive {
133 entity_id: self.entity_id,
134 });
135 };
136
137 world
138 .entity_manager()
139 .validate_move(self.entity_id, new_pos)
140 .inspect_err(|error| {
141 log::warn!("Rejected player entity move from {old_pos:?} to {new_pos:?}: {error}");
142 })
143 }
144
145 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
146 let Some(world) = self.world.upgrade() else {
147 return Err(EntityMoveError::NotLive {
148 entity_id: self.entity_id,
149 });
150 };
151
152 let update = world
153 .entity_manager()
154 .commit_move(self.entity_id, new_pos)
155 .inspect_err(|error| {
156 log::warn!(
157 "Failed to commit player entity move from {old_pos:?} to {new_pos:?}: {error}"
158 );
159 })?;
160
161 if update.section_changed() {
162 world.entity_tracker().on_entity_section_change(
163 self.entity_id,
164 update.old_chunk,
165 update.new_chunk,
166 |chunk| world.get_packet_tracking_players(chunk),
167 |player_id| world.players.get_by_entity_id(player_id),
168 );
169
170 if let Some(player) = world.players.get_by_entity_id(self.entity_id)
171 && let Some(view) = *player.last_tracking_view.lock()
172 {
173 let sent_chunks = player.chunk_sender().lock().sent_chunks_snapshot();
174 world
175 .entity_tracker()
176 .update_player(&player, &view, |chunk| sent_chunks.contains(&chunk));
177 }
178 }
179
180 Ok(())
181 }
182
183 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {
184 if let Some(world) = self.world.upgrade() {
185 world
186 .entity_manager()
187 .commit_bounding_box_change(self.entity_id);
188 }
189 }
190
191 fn on_remove(&self, _reason: RemovalReason) {
192 }
194}
195
196pub struct EntityChunkCallback {
198 entity_id: i32,
199 world: Weak<World>,
200}
201
202impl EntityChunkCallback {
203 #[must_use]
205 pub const fn new(entity_id: i32, world: Weak<World>) -> Self {
206 Self { entity_id, world }
207 }
208}
209
210impl EntityLevelCallback for EntityChunkCallback {
211 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
212 let Some(world) = self.world.upgrade() else {
213 return Err(EntityMoveError::NotLive {
214 entity_id: self.entity_id,
215 });
216 };
217
218 world
219 .entity_manager()
220 .validate_move(self.entity_id, new_pos)
221 .inspect_err(|error| {
222 log::warn!("Rejected entity move from {old_pos:?} to {new_pos:?}: {error}");
223 })
224 }
225
226 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
227 let Some(world) = self.world.upgrade() else {
228 return Err(EntityMoveError::NotLive {
229 entity_id: self.entity_id,
230 });
231 };
232
233 let update = world
234 .entity_manager()
235 .commit_move(self.entity_id, new_pos)
236 .inspect_err(|error| {
237 log::warn!("Failed to commit entity move from {old_pos:?} to {new_pos:?}: {error}");
238 })?;
239
240 world.mark_chunk_dirty(update.new_chunk);
241 if update.chunk_changed() {
242 world.mark_chunk_dirty(update.old_chunk);
243 }
244
245 if update.section_changed() {
246 if update.became_inaccessible() {
247 world.remove_entity_from_tracker(self.entity_id);
248 } else if update.became_accessible() {
249 if let Some(entity) = world.entity_manager().get_by_id(self.entity_id) {
250 world.add_entity_to_tracker(&entity);
251 }
252 } else if update.new_accessible {
253 world.entity_tracker().on_entity_section_change(
254 self.entity_id,
255 update.old_chunk,
256 update.new_chunk,
257 |chunk| world.get_packet_tracking_players(chunk),
258 |player_id| world.players.get_by_entity_id(player_id),
259 );
260 }
261 }
262
263 Ok(())
264 }
265
266 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {
267 if let Some(world) = self.world.upgrade() {
268 world
269 .entity_manager()
270 .commit_bounding_box_change(self.entity_id);
271 }
272 }
273
274 fn on_remove(&self, reason: RemovalReason) {
275 let Some(world) = self.world.upgrade() else {
276 return;
277 };
278
279 let entity = world
280 .entity_manager()
281 .remove_live_entity(self.entity_id, reason);
282 if let Some(entity) = entity {
283 world.mark_chunk_dirty(ChunkPos::from_entity_pos(entity.position()));
284 }
285
286 world.remove_entity_from_tracker(self.entity_id);
287 }
288}