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 {
48 fn allows_local_position_update(&self) -> bool {
50 false
51 }
52
53 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError>;
55
56 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError>;
58
59 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {}
61
62 fn on_remove(&self, reason: RemovalReason);
64}
65
66pub struct NullEntityCallback;
68
69impl EntityLevelCallback for NullEntityCallback {
70 fn allows_local_position_update(&self) -> bool {
71 true
72 }
73
74 fn validate_move(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
75 Ok(())
76 }
77
78 fn on_move_committed(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
79 Ok(())
80 }
81
82 fn on_remove(&self, _reason: RemovalReason) {}
83}
84
85pub struct InactiveEntityCallback {
87 entity_id: i32,
88}
89
90impl InactiveEntityCallback {
91 #[must_use]
93 pub const fn new(entity_id: i32) -> Self {
94 Self { entity_id }
95 }
96}
97
98impl EntityLevelCallback for InactiveEntityCallback {
99 fn validate_move(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
100 Err(EntityMoveError::Inactive {
101 entity_id: self.entity_id,
102 })
103 }
104
105 fn on_move_committed(&self, _old_pos: DVec3, _new_pos: DVec3) -> Result<(), EntityMoveError> {
106 Err(EntityMoveError::Inactive {
107 entity_id: self.entity_id,
108 })
109 }
110
111 fn on_remove(&self, _reason: RemovalReason) {}
112}
113
114pub struct PlayerEntityCallback {
119 entity_id: i32,
120 world: Weak<World>,
121}
122
123impl PlayerEntityCallback {
124 #[must_use]
126 pub const fn new(entity_id: i32, world: Weak<World>) -> Self {
127 Self { entity_id, world }
128 }
129}
130
131impl EntityLevelCallback for PlayerEntityCallback {
132 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
133 let Some(world) = self.world.upgrade() else {
134 return Err(EntityMoveError::NotLive {
135 entity_id: self.entity_id,
136 });
137 };
138
139 world
140 .entity_manager()
141 .validate_move(self.entity_id, new_pos)
142 .inspect_err(|error| {
143 log::warn!("Rejected player entity move from {old_pos:?} to {new_pos:?}: {error}");
144 })
145 }
146
147 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
148 let Some(world) = self.world.upgrade() else {
149 return Err(EntityMoveError::NotLive {
150 entity_id: self.entity_id,
151 });
152 };
153
154 let update = world
155 .entity_manager()
156 .commit_move(self.entity_id, new_pos)
157 .inspect_err(|error| {
158 log::warn!(
159 "Failed to commit player entity move from {old_pos:?} to {new_pos:?}: {error}"
160 );
161 })?;
162
163 if update.section_changed() {
164 world.entity_tracker().on_entity_section_change(
165 self.entity_id,
166 update.old_chunk,
167 update.new_chunk,
168 |chunk| world.get_packet_tracking_players(chunk),
169 |player_id| world.players.get_by_entity_id(player_id),
170 );
171
172 if let Some(player) = world.players.get_by_entity_id(self.entity_id)
173 && let Some(view) = *player.last_tracking_view.lock()
174 {
175 let sent_chunks = player.chunk_sender.lock().sent_chunks_snapshot();
176 world
177 .entity_tracker()
178 .update_player(&player, &view, |chunk| sent_chunks.contains(&chunk));
179 }
180 }
181
182 Ok(())
183 }
184
185 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {
186 if let Some(world) = self.world.upgrade() {
187 world
188 .entity_manager()
189 .commit_bounding_box_change(self.entity_id);
190 }
191 }
192
193 fn on_remove(&self, _reason: RemovalReason) {
194 }
196}
197
198pub struct EntityChunkCallback {
202 entity_id: i32,
203 world: Weak<World>,
204}
205
206impl EntityChunkCallback {
207 #[must_use]
209 pub const fn new(entity_id: i32, world: Weak<World>) -> Self {
210 Self { entity_id, world }
211 }
212}
213
214impl EntityLevelCallback for EntityChunkCallback {
215 fn validate_move(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
216 let Some(world) = self.world.upgrade() else {
217 return Err(EntityMoveError::NotLive {
218 entity_id: self.entity_id,
219 });
220 };
221
222 world
223 .entity_manager()
224 .validate_move(self.entity_id, new_pos)
225 .inspect_err(|error| {
226 log::warn!("Rejected entity move from {old_pos:?} to {new_pos:?}: {error}");
227 })
228 }
229
230 fn on_move_committed(&self, old_pos: DVec3, new_pos: DVec3) -> Result<(), EntityMoveError> {
231 let Some(world) = self.world.upgrade() else {
232 return Err(EntityMoveError::NotLive {
233 entity_id: self.entity_id,
234 });
235 };
236
237 let update = world
238 .entity_manager()
239 .commit_move(self.entity_id, new_pos)
240 .inspect_err(|error| {
241 log::warn!("Failed to commit entity move from {old_pos:?} to {new_pos:?}: {error}");
242 })?;
243
244 world.mark_chunk_dirty(update.new_chunk);
245 if update.chunk_changed() {
246 world.mark_chunk_dirty(update.old_chunk);
247 }
248
249 if update.section_changed() {
250 if update.became_inaccessible() {
251 world.remove_entity_from_tracker(self.entity_id);
252 } else if update.became_accessible() {
253 if let Some(entity) = world.entity_manager().get_by_id(self.entity_id) {
254 world.add_entity_to_tracker(&entity);
255 }
256 } else if update.new_accessible {
257 world.entity_tracker().on_entity_section_change(
258 self.entity_id,
259 update.old_chunk,
260 update.new_chunk,
261 |chunk| world.get_packet_tracking_players(chunk),
262 |player_id| world.players.get_by_entity_id(player_id),
263 );
264 }
265 }
266
267 Ok(())
268 }
269
270 fn on_bounding_box_changed(&self, _bounding_box: WorldAabb) {
271 if let Some(world) = self.world.upgrade() {
272 world
273 .entity_manager()
274 .commit_bounding_box_change(self.entity_id);
275 }
276 }
277
278 fn on_remove(&self, reason: RemovalReason) {
279 let Some(world) = self.world.upgrade() else {
280 return;
281 };
282
283 let entity = world
284 .entity_manager()
285 .remove_live_entity(self.entity_id, reason);
286 if let Some(entity) = entity {
287 world.mark_chunk_dirty(ChunkPos::from_entity_pos(entity.position()));
288 }
289
290 world.remove_entity_from_tracker(self.entity_id);
291 }
292}