azalea_world/
container.rs

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