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