azalea_world/
container.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, Weak},
4};
5
6use azalea_core::registry_holder::RegistryHolder;
7use azalea_registry::identifier::Identifier;
8use bevy_ecs::{component::Component, resource::Resource};
9use derive_more::{Deref, DerefMut};
10use nohash_hasher::IntMap;
11use parking_lot::RwLock;
12use rustc_hash::FxHashMap;
13use tracing::{debug, error};
14
15use crate::{ChunkStorage, Instance};
16
17/// A container of [`Instance`]s (aka worlds).
18///
19/// Instances are stored as a Weak pointer here, so if no clients are using an
20/// instance it will be forgotten.
21#[derive(Default, Resource)]
22pub struct InstanceContainer {
23    // We just refer to the chunks here and don't include entities because there's not that many
24    // cases where we'd want to get every entity in the world (just getting the entities in chunks
25    // should work fine).
26
27    // Entities are garbage collected (by manual reference counting in EntityUuidIndex) so we don't
28    // need to worry about them here.
29
30    // If it looks like we're relying on the server giving us unique world names, that's because we
31    // are. An evil server could give us two worlds with the same name and then we'd have no way of
32    // telling them apart. We hope most servers are nice and don't do that though. It's only an
33    // issue when there's multiple clients with the same WorldContainer in different worlds
34    // anyways.
35    pub instances: FxHashMap<Identifier, Weak<RwLock<Instance>>>,
36}
37
38impl InstanceContainer {
39    pub fn new() -> Self {
40        InstanceContainer::default()
41    }
42
43    /// Get an instance (aka world) from the container.
44    ///
45    /// Returns `None` if none of the clients are in this instance.
46    pub fn get(&self, name: &InstanceName) -> Option<Arc<RwLock<Instance>>> {
47        self.instances.get(name).and_then(|world| world.upgrade())
48    }
49
50    /// Add an empty world to the container (unless it already exists) and
51    /// returns a strong reference to the world.
52    #[must_use = "the world will be immediately forgotten if unused"]
53    pub fn get_or_insert(
54        &mut self,
55        name: Identifier,
56        height: u32,
57        min_y: i32,
58        default_registries: &RegistryHolder,
59    ) -> Arc<RwLock<Instance>> {
60        match self.instances.get(&name).and_then(|world| world.upgrade()) {
61            Some(existing_lock) => {
62                let existing = existing_lock.read();
63                if existing.chunks.height != height {
64                    error!(
65                        "Shared world height mismatch: {} != {height}",
66                        existing.chunks.height
67                    );
68                }
69                if existing.chunks.min_y != min_y {
70                    error!(
71                        "Shared world min_y mismatch: {} != {min_y}",
72                        existing.chunks.min_y
73                    );
74                }
75                existing_lock.clone()
76            }
77            _ => {
78                let world = Arc::new(RwLock::new(Instance {
79                    chunks: ChunkStorage::new(height, min_y),
80                    entities_by_chunk: HashMap::new(),
81                    entity_by_id: IntMap::default(),
82                    registries: default_registries.clone(),
83                }));
84                debug!("Added new instance {name}");
85                self.instances.insert(name, Arc::downgrade(&world));
86                world
87            }
88        }
89    }
90}
91
92/// The name of the [`Instance`] (aka world/dimension) that the entity is in.
93///
94/// If two entities share the same instance name, we assume they're in the
95/// same instance.
96#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut, Hash, Eq)]
97#[doc(alias("worldname", "world name"))]
98pub struct InstanceName(pub Identifier);