1use std::ops::Deref;
4use std::sync::{Arc, OnceLock, Weak};
5
6use glam::DVec3;
7use simdnbt::borrow::{
8 BaseNbtCompound as BorrowedNbtCompound, NbtCompound as BorrowedNbtCompoundView,
9};
10use steel_registry::RegistryExt;
11use steel_registry::entity_type::EntityTypeRef;
12use steel_registry::{REGISTRY, RegistryEntry};
13use uuid::Uuid;
14
15use super::entities::RawEntity;
16use super::generated_entities::register_entity_factories;
17use super::{
18 EntityBaseLoad, EntityBaseSaveData, EntityFireFreezeState, SharedEntity, next_entity_id,
19};
20use crate::world::World;
21
22pub type EntityFactory = fn(EntityTypeRef, i32, DVec3, Weak<World>) -> SharedEntity;
28
29pub type EntityLoadFactory = fn(EntityTypeRef, EntityBaseLoad) -> SharedEntity;
33
34pub struct EntityLoadRequest {
36 pub entity_type: EntityTypeRef,
38 pub position: DVec3,
40 pub uuid: Uuid,
42 pub velocity: DVec3,
44 pub rotation: (f32, f32),
46 pub fall_distance: f64,
48 pub fire_freeze: EntityFireFreezeState,
50 pub on_ground: bool,
52 pub save_data: EntityBaseSaveData,
54 pub world: Weak<World>,
56}
57
58impl EntityLoadRequest {
59 fn into_base_load(self) -> (EntityTypeRef, EntityBaseLoad) {
60 (
61 self.entity_type,
62 EntityBaseLoad {
63 id: next_entity_id(),
64 position: self.position,
65 uuid: self.uuid,
66 velocity: self.velocity,
67 rotation: self.rotation,
68 fall_distance: self.fall_distance,
69 fire_freeze: self.fire_freeze,
70 on_ground: self.on_ground,
71 save_data: self.save_data,
72 world: self.world,
73 },
74 )
75 }
76}
77
78struct EntityEntry {
80 factory: Option<EntityFactory>,
82 load_factory: Option<EntityLoadFactory>,
84}
85
86pub struct EntityRegistry {
91 entries: Vec<EntityEntry>,
92}
93
94impl EntityRegistry {
95 fn finish_registered_load(entity: &SharedEntity, nbt: &BorrowedNbtCompound<'_>) {
98 let yaw = entity.rotation().0;
99 if let Some(living) = entity.as_living_entity() {
100 living.set_y_head_rot(yaw);
101 living.set_y_body_rot(yaw);
102 }
103
104 let nbt: BorrowedNbtCompoundView<'_, '_> = nbt.into();
105 entity.load_additional(nbt);
106 entity.sync_base_entity_data();
107 }
108
109 #[must_use]
111 pub fn new() -> Self {
112 let count = REGISTRY.entity_types.len();
113 let entries = (0..count)
114 .map(|_| EntityEntry {
115 factory: None,
116 load_factory: None,
117 })
118 .collect();
119
120 Self { entries }
121 }
122
123 pub fn register(&mut self, entity_type: EntityTypeRef, factory: EntityFactory) {
129 let id = entity_type.id();
130 assert!(
131 self.entries[id].factory.is_none(),
132 "entity factory for {} is already registered",
133 entity_type.key
134 );
135 self.entries[id].factory = Some(factory);
136 }
137
138 pub fn register_load(&mut self, entity_type: EntityTypeRef, factory: EntityLoadFactory) {
146 let id = entity_type.id();
147 assert!(
148 self.entries[id].load_factory.is_none(),
149 "entity load factory for {} is already registered",
150 entity_type.key
151 );
152 self.entries[id].load_factory = Some(factory);
153 }
154
155 #[must_use]
159 pub fn create(
160 &self,
161 entity_type: EntityTypeRef,
162 entity_id: i32,
163 pos: DVec3,
164 world: Weak<World>,
165 ) -> Option<SharedEntity> {
166 let id = entity_type.id();
167 self.entries
168 .get(id)?
169 .factory
170 .map(|f| f(entity_type, entity_id, pos, world))
171 }
172
173 #[must_use]
175 pub fn create_and_load_or_raw(
176 &self,
177 request: EntityLoadRequest,
178 nbt: &BorrowedNbtCompound<'_>,
179 ) -> SharedEntity {
180 let (entity_type, load) = request.into_base_load();
181 let id = entity_type.id();
182 if let Some(load_factory) = self.entries.get(id).and_then(|entry| entry.load_factory) {
183 let entity = load_factory(entity_type, load);
184 Self::finish_registered_load(&entity, nbt);
185 return entity;
186 }
187
188 let entity: SharedEntity = Arc::new(RawEntity::from_saved(load, entity_type));
189 let nbt: BorrowedNbtCompoundView<'_, '_> = nbt.into();
190 entity.load_additional(nbt);
191 entity
192 }
193
194 #[must_use]
196 pub fn has_factory(&self, entity_type: EntityTypeRef) -> bool {
197 let id = entity_type.id();
198 self.entries.get(id).is_some_and(|e| e.factory.is_some())
199 }
200}
201
202impl Default for EntityRegistry {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208pub struct EntityRegistryLock(OnceLock<EntityRegistry>);
210
211impl Deref for EntityRegistryLock {
212 type Target = EntityRegistry;
213
214 fn deref(&self) -> &Self::Target {
215 self.0.get().expect("Entity registry not initialized")
216 }
217}
218
219impl EntityRegistryLock {
220 pub fn set(&self, registry: EntityRegistry) -> Result<(), EntityRegistry> {
222 self.0.set(registry)
223 }
224
225 fn get_or_init(&self, init: impl FnOnce() -> EntityRegistry) -> &EntityRegistry {
226 self.0.get_or_init(init)
227 }
228
229 #[must_use]
231 pub fn get(&self) -> Option<&EntityRegistry> {
232 self.0.get()
233 }
234}
235
236pub static ENTITIES: EntityRegistryLock = EntityRegistryLock(OnceLock::new());
240
241pub fn init_entities() {
243 ENTITIES.get_or_init(|| {
244 let mut registry = EntityRegistry::new();
245 register_entity_factories(&mut registry);
246 registry
247 });
248}
249
250#[cfg(test)]
251mod tests {
252 use std::io::Cursor;
253
254 use simdnbt::borrow::read_compound as read_borrowed_compound;
255 use simdnbt::owned::NbtCompound;
256 use steel_registry::init_vanilla_registry;
257 use steel_registry::vanilla_entities;
258
259 use super::*;
260
261 #[test]
262 fn registered_living_load_restores_current_head_and_body_yaw() {
263 init_vanilla_registry();
264 let mut registry = EntityRegistry::new();
265 register_entity_factories(&mut registry);
266 let mut bytes = Vec::new();
267 NbtCompound::new().write(&mut bytes);
268 let borrowed = read_borrowed_compound(&mut Cursor::new(&bytes))
269 .unwrap_or_else(|error| panic!("test nbt should reborrow: {error}"));
270
271 let entity = registry.create_and_load_or_raw(
272 EntityLoadRequest {
273 entity_type: &vanilla_entities::PIG,
274 position: DVec3::ZERO,
275 uuid: Uuid::from_u128(1),
276 velocity: DVec3::ZERO,
277 rotation: (135.0, -20.0),
278 fall_distance: 0.0,
279 fire_freeze: EntityFireFreezeState::new(),
280 on_ground: false,
281 save_data: EntityBaseSaveData::new(),
282 world: Weak::new(),
283 },
284 &borrowed,
285 );
286
287 assert_eq!(entity.rotation(), (135.0, -20.0));
288 assert_eq!(entity.base().old_rotation(), (135.0, -20.0));
289 let Some(living) = entity.as_living_entity() else {
290 panic!("registered pig should expose living behavior");
291 };
292 let living_rotation = living.living_rotation_state();
293 assert_eq!(living_rotation.y_head_rot().to_bits(), 135.0_f32.to_bits());
294 assert_eq!(living_rotation.y_body_rot().to_bits(), 135.0_f32.to_bits());
295 assert_eq!(living_rotation.y_head_rot_o().to_bits(), 0.0_f32.to_bits());
296 assert_eq!(living_rotation.y_body_rot_o().to_bits(), 0.0_f32.to_bits());
297 }
298
299 #[test]
300 fn create_and_load_or_raw_preserves_unregistered_entity_data() {
301 init_vanilla_registry();
302 let registry = EntityRegistry::new();
303 let mut nbt = NbtCompound::new();
304 nbt.insert("SteelRawMarker", "raw");
305 let mut bytes = Vec::new();
306 nbt.write(&mut bytes);
307 let borrowed =
308 read_borrowed_compound(&mut Cursor::new(&bytes)).expect("test nbt should reborrow");
309
310 let entity = registry.create_and_load_or_raw(
311 EntityLoadRequest {
312 entity_type: &vanilla_entities::VILLAGER,
313 position: DVec3::new(1.0, 2.0, 3.0),
314 uuid: Uuid::from_u128(1),
315 velocity: DVec3::new(0.1, 0.0, 0.2),
316 rotation: (45.0, 10.0),
317 fall_distance: 2.25,
318 fire_freeze: EntityFireFreezeState::new(),
319 on_ground: true,
320 save_data: EntityBaseSaveData {
321 no_gravity: true,
322 invulnerable: true,
323 ..EntityBaseSaveData::new()
324 },
325 world: Weak::new(),
326 },
327 &borrowed,
328 );
329
330 assert_eq!(&entity.entity_type().key, &vanilla_entities::VILLAGER.key);
331 assert_eq!(entity.position(), DVec3::new(1.0, 2.0, 3.0));
332 assert_eq!(entity.velocity(), DVec3::new(0.1, 0.0, 0.2));
333 assert_eq!(entity.rotation(), (45.0, 10.0));
334 assert!((entity.fall_distance() - 2.25).abs() <= f64::EPSILON);
335 assert!(entity.on_ground());
336 assert!(entity.is_no_gravity());
337 assert!(entity.is_invulnerable());
338
339 let mut saved = NbtCompound::new();
340 entity.save_additional(&mut saved);
341 assert_eq!(
342 saved.string("SteelRawMarker").map(ToString::to_string),
343 Some("raw".to_owned())
344 );
345 }
346
347 #[test]
348 fn create_forwards_entity_type_to_factory() {
349 init_vanilla_registry();
350 let mut registry = EntityRegistry::new();
351 registry.register(
352 &vanilla_entities::OAK_BOAT,
353 |entity_type, id, pos, world| Arc::new(RawEntity::new(id, pos, world, entity_type)),
354 );
355
356 let Some(entity) =
357 registry.create(&vanilla_entities::OAK_BOAT, 5, DVec3::ZERO, Weak::new())
358 else {
359 panic!("registered entity factory should create an entity");
360 };
361
362 assert_eq!(entity.entity_type(), &vanilla_entities::OAK_BOAT);
363 }
364}