From c4741b68b89315cd03eb3b30023e8e0361df1c9b Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Sun, 11 Jun 2023 09:08:38 -0700 Subject: [PATCH] Registry Redesign (#361) ## Description - Revert the "biomes/dimensions as entities" idea since it caused too many problems without much to show for it. - Make use of `valence_nbt`'s serde support in `valence_biome` and `valence_dimension`. - Reduce boilerplate, reorganize `valence_registry` a bit. - Tweak default biome registry such that `BiomeId::default` always corresponds to "minecraft:plains". - Add `Option` and unit variant support to `valence_nbt`'s serde impl. --- Cargo.toml | 2 +- crates/valence/benches/idle.rs | 4 +- crates/valence/examples/advancement.rs | 4 +- crates/valence/examples/anvil_loading.rs | 4 +- crates/valence/examples/bench_players.rs | 4 +- crates/valence/examples/biomes.rs | 4 +- crates/valence/examples/block_entities.rs | 4 +- crates/valence/examples/building.rs | 4 +- crates/valence/examples/chest.rs | 4 +- crates/valence/examples/combat.rs | 4 +- crates/valence/examples/conway.rs | 16 +- crates/valence/examples/cow_sphere.rs | 4 +- crates/valence/examples/death.rs | 4 +- crates/valence/examples/entity_hitbox.rs | 4 +- crates/valence/examples/parkour.rs | 4 +- crates/valence/examples/particles.rs | 4 +- crates/valence/examples/player_list.rs | 4 +- crates/valence/examples/resource_pack.rs | 4 +- crates/valence/examples/terrain.rs | 4 +- crates/valence/examples/text.rs | 4 +- crates/valence/src/tests.rs | 23 +- crates/valence_biome/Cargo.toml | 7 +- crates/valence_biome/src/lib.rs | 262 +++++---------- crates/valence_client/src/lib.rs | 6 +- crates/valence_core/src/protocol/c2s.rs | 156 --------- crates/valence_core/src/protocol/s2c.rs | 338 -------------------- crates/valence_dimension/Cargo.toml | 5 +- crates/valence_dimension/src/lib.rs | 257 ++++++--------- crates/valence_instance/Cargo.toml | 1 + crates/valence_instance/src/chunk.rs | 3 +- crates/valence_instance/src/lib.rs | 41 +-- crates/valence_nbt/src/serde.rs | 104 +----- crates/valence_nbt/src/serde/de.rs | 26 +- crates/valence_nbt/src/serde/ser.rs | 8 +- crates/valence_nbt/src/serde/tests.rs | 109 +++++++ crates/valence_registry/Cargo.toml | 11 +- crates/valence_registry/src/codec.rs | 137 ++++++++ crates/valence_registry/src/lib.rs | 270 ++++++++-------- crates/valence_registry/src/tags.rs | 15 +- tools/playground/src/playground.template.rs | 4 +- 40 files changed, 690 insertions(+), 1183 deletions(-) delete mode 100644 crates/valence_core/src/protocol/c2s.rs delete mode 100644 crates/valence_core/src/protocol/s2c.rs create mode 100644 crates/valence_nbt/src/serde/tests.rs create mode 100644 crates/valence_registry/src/codec.rs diff --git a/Cargo.toml b/Cargo.toml index f0f3f63..424d03b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ valence_dimension.path = "crates/valence_dimension" valence_entity.path = "crates/valence_entity" valence_instance.path = "crates/valence_instance" valence_inventory.path = "crates/valence_inventory" -valence_nbt = { path = "crates/valence_nbt" } +valence_nbt = { path = "crates/valence_nbt", features = ["uuid"] } valence_network.path = "crates/valence_network" valence_player_list.path = "crates/valence_player_list" valence_registry.path = "crates/valence_registry" diff --git a/crates/valence/benches/idle.rs b/crates/valence/benches/idle.rs index fd4ac3a..dd9a2da 100644 --- a/crates/valence/benches/idle.rs +++ b/crates/valence/benches/idle.rs @@ -21,8 +21,8 @@ pub fn idle_update(c: &mut Criterion) { fn setup( mut commands: Commands, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, server: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/advancement.rs b/crates/valence/examples/advancement.rs index e5f6f7a..0801ac7 100644 --- a/crates/valence/examples/advancement.rs +++ b/crates/valence/examples/advancement.rs @@ -43,8 +43,8 @@ fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/anvil_loading.rs b/crates/valence/examples/anvil_loading.rs index c1fd61b..bc97f6f 100644 --- a/crates/valence/examples/anvil_loading.rs +++ b/crates/valence/examples/anvil_loading.rs @@ -80,8 +80,8 @@ pub fn main() { fn setup( mut commands: Commands, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, server: Res, ) { let instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/bench_players.rs b/crates/valence/examples/bench_players.rs index f675bf4..4680574 100644 --- a/crates/valence/examples/bench_players.rs +++ b/crates/valence/examples/bench_players.rs @@ -57,8 +57,8 @@ fn print_tick_time( fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/biomes.rs b/crates/valence/examples/biomes.rs index 1990c0a..d5735e0 100644 --- a/crates/valence/examples/biomes.rs +++ b/crates/valence/examples/biomes.rs @@ -16,8 +16,8 @@ pub fn main() { fn setup( mut commands: Commands, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, biome_reg: Res, server: Res, ) { diff --git a/crates/valence/examples/block_entities.rs b/crates/valence/examples/block_entities.rs index d8d3008..36b26d7 100644 --- a/crates/valence/examples/block_entities.rs +++ b/crates/valence/examples/block_entities.rs @@ -22,8 +22,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/building.rs b/crates/valence/examples/building.rs index c9af37e..f4bb404 100644 --- a/crates/valence/examples/building.rs +++ b/crates/valence/examples/building.rs @@ -26,8 +26,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/chest.rs b/crates/valence/examples/chest.rs index 511aa7a..9a4bd48 100644 --- a/crates/valence/examples/chest.rs +++ b/crates/valence/examples/chest.rs @@ -21,8 +21,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/combat.rs b/crates/valence/examples/combat.rs index b554b61..66198f5 100644 --- a/crates/valence/examples/combat.rs +++ b/crates/valence/examples/combat.rs @@ -32,8 +32,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/conway.rs b/crates/valence/examples/conway.rs index e7e31b4..511a329 100644 --- a/crates/valence/examples/conway.rs +++ b/crates/valence/examples/conway.rs @@ -24,7 +24,6 @@ pub fn main() { App::new() .add_plugins(DefaultPlugins) - .add_startup_system(setup_biomes.before(setup)) .add_startup_system(setup) .add_system(init_clients) .add_systems(( @@ -37,19 +36,16 @@ pub fn main() { .run(); } -// TODO: this is a hack. -fn setup_biomes(mut biomes: Query<&mut Biome>) { - for mut biome in &mut biomes { - biome.grass_color = Some(0x00ff00); - } -} - fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: ResMut, + mut biomes: ResMut, ) { + for (_, _, biome) in biomes.iter_mut() { + biome.effects.grass_color = Some(0x00ff00); + } + let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); for z in -10..10 { diff --git a/crates/valence/examples/cow_sphere.rs b/crates/valence/examples/cow_sphere.rs index 68263c5..8fd696b 100644 --- a/crates/valence/examples/cow_sphere.rs +++ b/crates/valence/examples/cow_sphere.rs @@ -34,8 +34,8 @@ fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/death.rs b/crates/valence/examples/death.rs index 41573a5..3fa5e7e 100644 --- a/crates/valence/examples/death.rs +++ b/crates/valence/examples/death.rs @@ -19,8 +19,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { for block in [BlockState::GRASS_BLOCK, BlockState::DEEPSLATE] { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/entity_hitbox.rs b/crates/valence/examples/entity_hitbox.rs index d867971..8b17eea 100644 --- a/crates/valence/examples/entity_hitbox.rs +++ b/crates/valence/examples/entity_hitbox.rs @@ -25,8 +25,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/parkour.rs b/crates/valence/examples/parkour.rs index 4b72116..e0c5f38 100644 --- a/crates/valence/examples/parkour.rs +++ b/crates/valence/examples/parkour.rs @@ -57,8 +57,8 @@ fn init_clients( Added, >, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, mut commands: Commands, ) { for (entity, mut client, mut loc, mut is_flat, mut game_mode) in clients.iter_mut() { diff --git a/crates/valence/examples/particles.rs b/crates/valence/examples/particles.rs index 5871e22..432a19b 100644 --- a/crates/valence/examples/particles.rs +++ b/crates/valence/examples/particles.rs @@ -24,8 +24,8 @@ struct ParticleVec(Vec); fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/player_list.rs b/crates/valence/examples/player_list.rs index 761ec01..77ba133 100644 --- a/crates/valence/examples/player_list.rs +++ b/crates/valence/examples/player_list.rs @@ -27,8 +27,8 @@ fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/resource_pack.rs b/crates/valence/examples/resource_pack.rs index 97058a0..ccbac65 100644 --- a/crates/valence/examples/resource_pack.rs +++ b/crates/valence/examples/resource_pack.rs @@ -21,8 +21,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/examples/terrain.rs b/crates/valence/examples/terrain.rs index 2551b40..a4a978c 100644 --- a/crates/valence/examples/terrain.rs +++ b/crates/valence/examples/terrain.rs @@ -60,8 +60,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let seconds_per_day = 86_400; let seed = (SystemTime::now() diff --git a/crates/valence/examples/text.rs b/crates/valence/examples/text.rs index 7e8847e..a369cd7 100644 --- a/crates/valence/examples/text.rs +++ b/crates/valence/examples/text.rs @@ -18,8 +18,8 @@ pub fn main() { fn setup( mut commands: Commands, server: Res, - dimensions: Query<&DimensionType>, - biomes: Query<&Biome>, + dimensions: Res, + biomes: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); diff --git a/crates/valence/src/tests.rs b/crates/valence/src/tests.rs index 3a8452a..86e5cbd 100644 --- a/crates/valence/src/tests.rs +++ b/crates/valence/src/tests.rs @@ -4,15 +4,16 @@ use std::time::Instant; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings}; use bytes::{Buf, BufMut, BytesMut}; use uuid::Uuid; +use valence_biome::BiomeRegistry; use valence_client::ClientBundleArgs; use valence_core::protocol::decode::{PacketDecoder, PacketFrame}; use valence_core::protocol::encode::PacketEncoder; use valence_core::protocol::var_int::VarInt; use valence_core::protocol::{Encode, Packet}; use valence_core::{ident, CoreSettings, Server}; +use valence_dimension::DimensionTypeRegistry; use valence_entity::Location; use valence_network::{ConnectionMode, NetworkSettings}; @@ -37,9 +38,17 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) { app.add_plugins(DefaultPlugins); - let server = app.world.resource::(); - let instance = Instance::new_unit_testing(ident!("overworld"), server); + app.update(); // Initialize plugins. + + let instance = Instance::new( + ident!("overworld"), + app.world.resource::(), + app.world.resource::(), + app.world.resource::(), + ); + let instance_ent = app.world.spawn(instance).id(); + let (client, client_helper) = create_mock_client(); let client_ent = app.world.spawn(client).id(); @@ -47,14 +56,6 @@ fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) { // Set initial location. app.world.get_mut::(client_ent).unwrap().0 = instance_ent; - // Print warnings if there are ambiguities in the schedule. - app.edit_schedule(CoreSchedule::Main, |schedule| { - schedule.set_build_settings(ScheduleBuildSettings { - ambiguity_detection: LogLevel::Warn, - ..Default::default() - }); - }); - (client_ent, client_helper) } diff --git a/crates/valence_biome/Cargo.toml b/crates/valence_biome/Cargo.toml index c985f28..5ecd8fc 100644 --- a/crates/valence_biome/Cargo.toml +++ b/crates/valence_biome/Cargo.toml @@ -4,10 +4,11 @@ version.workspace = true edition.workspace = true [dependencies] +anyhow.workspace = true bevy_app.workspace = true bevy_ecs.workspace = true -anyhow.workspace = true +serde.workspace = true tracing.workspace = true -valence_nbt.workspace = true -valence_registry.workspace = true valence_core.workspace = true +valence_nbt = { workspace = true, features = ["serde"] } +valence_registry.workspace = true diff --git a/crates/valence_biome/src/lib.rs b/crates/valence_biome/src/lib.rs index 6ce3eea..83bbd6f 100644 --- a/crates/valence_biome/src/lib.rs +++ b/crates/valence_biome/src/lib.rs @@ -17,111 +17,43 @@ clippy::dbg_macro )] -use std::ops::Index; +use std::ops::{Deref, DerefMut}; -use anyhow::{bail, Context}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use serde::{Deserialize, Serialize}; use tracing::error; use valence_core::ident; use valence_core::ident::Ident; -use valence_nbt::{compound, Value}; -use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; +use valence_nbt::serde::CompoundSerializer; +use valence_registry::codec::{RegistryCodec, RegistryValue}; +use valence_registry::{Registry, RegistryIdx, RegistrySet}; pub struct BiomePlugin; -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] - -struct BiomeSet; - impl Plugin for BiomePlugin { - fn build(&self, app: &mut bevy_app::App) { - app.insert_resource(BiomeRegistry { - id_to_biome: vec![], - }) - .configure_set( - BiomeSet - .in_base_set(CoreSet::PostUpdate) - .before(RegistryCodecSet), - ) - .add_systems( - (update_biome_registry, remove_biomes_from_registry) - .chain() - .in_set(BiomeSet), - ) - .add_startup_system(load_default_biomes.in_base_set(StartupSet::PreStartup)); + fn build(&self, app: &mut App) { + app.init_resource::() + .add_startup_system(load_default_biomes.in_base_set(CoreSet::PreUpdate)) + .add_system( + update_biome_registry + .in_base_set(CoreSet::PostUpdate) + .before(RegistrySet), + ); } } -fn load_default_biomes( - mut reg: ResMut, - codec: Res, - mut commands: Commands, -) { - let mut helper = move || { +fn load_default_biomes(mut reg: ResMut, codec: Res) { + let mut helper = move || -> anyhow::Result<()> { for value in codec.registry(BiomeRegistry::KEY) { - let downfall = *value - .element - .get("downfall") - .and_then(|v| v.as_float()) - .context("invalid downfall")?; + let biome = Biome::deserialize(value.element.clone())?; - let Some(Value::Compound(effects)) = value.element.get("effects") else { - bail!("missing biome effects") - }; - - let fog_color = *effects - .get("fog_color") - .and_then(|v| v.as_int()) - .context("invalid fog color")?; - - let sky_color = *effects - .get("sky_color") - .and_then(|v| v.as_int()) - .context("invalid sky color")?; - - let water_color = *effects - .get("water_color") - .and_then(|v| v.as_int()) - .context("invalid water color")?; - - let water_fog_color = *effects - .get("water_fog_color") - .and_then(|v| v.as_int()) - .context("invalid water fog color")?; - - let grass_color = effects.get("grass_color").and_then(|v| v.as_int()).copied(); - - let has_precipitation = *value - .element - .get("has_precipitation") - .and_then(|v| v.as_byte()) - .context("invalid has_precipitation")? - != 0; - - let temperature = *value - .element - .get("temperature") - .and_then(|v| v.as_float()) - .context("invalid temperature")?; - - let entity = commands - .spawn(Biome { - name: value.name.clone(), - downfall, - fog_color, - sky_color, - water_color, - water_fog_color, - grass_color, - has_precipitation, - temperature, - }) - .id(); - - reg.id_to_biome.push(entity); + reg.insert(value.name.clone(), biome); } + // Move "plains" to the front so that `BiomeId::default()` is the ID of plains. + reg.swap_to_front(ident!("plains")); + Ok(()) }; @@ -130,126 +62,102 @@ fn load_default_biomes( } } -/// Add new biomes to or update existing biomes in the registry. -fn update_biome_registry( - mut reg: ResMut, - mut codec: ResMut, - biomes: Query<(Entity, &Biome), Changed>, -) { - for (entity, biome) in &biomes { - let biome_registry = codec.registry_mut(BiomeRegistry::KEY); +fn update_biome_registry(reg: Res, mut codec: ResMut) { + if reg.is_changed() { + let biomes = codec.registry_mut(BiomeRegistry::KEY); - let mut effects = compound! { - "fog_color" => biome.fog_color, - "sky_color" => biome.sky_color, - "water_color" => biome.water_color, - "water_fog_color" => biome.water_fog_color, - }; + biomes.clear(); - if let Some(grass_color) = biome.grass_color { - effects.insert("grass_color", grass_color); - } - - let biome_compound = compound! { - "downfall" => biome.downfall, - "effects" => effects, - "has_precipitation" => biome.has_precipitation, - "temperature" => biome.temperature, - }; - - if let Some(value) = biome_registry.iter_mut().find(|v| v.name == biome.name) { - value.name = biome.name.clone(); - value.element.merge(biome_compound); - } else { - biome_registry.push(RegistryValue { - name: biome.name.clone(), - element: biome_compound, - }); - reg.id_to_biome.push(entity); - } - - assert_eq!( - biome_registry.len(), - reg.id_to_biome.len(), - "biome registry and biome lookup table differ in length" - ); + biomes.extend(reg.iter().map(|(_, name, biome)| { + RegistryValue { + name: name.into(), + element: biome + .serialize(CompoundSerializer) + .expect("failed to serialize biome"), + } + })); } } -/// Remove deleted biomes from the registry. -fn remove_biomes_from_registry( - mut biomes: RemovedComponents, - mut reg: ResMut, - mut codec: ResMut, -) { - for biome in biomes.iter() { - if let Some(idx) = reg.id_to_biome.iter().position(|entity| *entity == biome) { - reg.id_to_biome.remove(idx); - codec.registry_mut(BiomeRegistry::KEY).remove(idx); - } - } -} - -#[derive(Resource)] +#[derive(Resource, Default, Debug)] pub struct BiomeRegistry { - id_to_biome: Vec, + reg: Registry, } impl BiomeRegistry { - pub const KEY: Ident<&str> = ident!("minecraft:worldgen/biome"); + pub const KEY: Ident<&str> = ident!("worldgen/biome"); +} - pub fn get_by_id(&self, id: BiomeId) -> Option { - self.id_to_biome.get(id.0 as usize).cloned() - } +impl Deref for BiomeRegistry { + type Target = Registry; - pub fn iter(&self) -> impl Iterator + '_ { - self.id_to_biome - .iter() - .enumerate() - .map(|(id, biome)| (BiomeId(id as _), *biome)) + fn deref(&self) -> &Self::Target { + &self.reg } } -impl Index for BiomeRegistry { - type Output = Entity; - - fn index(&self, index: BiomeId) -> &Self::Output { - self.id_to_biome - .get(index.0 as usize) - .unwrap_or_else(|| panic!("invalid {index:?}")) +impl DerefMut for BiomeRegistry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reg } } -/// An index into the biome registry. -#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct BiomeId(pub u16); +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct BiomeId(u32); -#[derive(Component, Clone, Debug)] +impl RegistryIdx for BiomeId { + const MAX: usize = u32::MAX as _; + + #[inline] + fn to_index(self) -> usize { + self.0 as _ + } + + #[inline] + fn from_index(idx: usize) -> Self { + Self(idx as _) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct Biome { - pub name: Ident, pub downfall: f32, - pub fog_color: i32, - pub sky_color: i32, - pub water_color: i32, - pub water_fog_color: i32, - pub grass_color: Option, + pub effects: BiomeEffects, pub has_precipitation: bool, pub temperature: f32, // TODO: more stuff. } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct BiomeEffects { + pub fog_color: u32, + pub sky_color: u32, + pub water_color: u32, + pub water_fog_color: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub grass_color: Option, + // TODO: more stuff. +} + impl Default for Biome { fn default() -> Self { Self { - name: ident!("plains").into(), downfall: 0.4, - fog_color: 12638463, - sky_color: 7907327, - water_color: 4159204, - water_fog_color: 329011, - grass_color: None, + effects: BiomeEffects::default(), has_precipitation: true, temperature: 0.8, } } } + +impl Default for BiomeEffects { + fn default() -> Self { + Self { + fog_color: 12638463, + sky_color: 7907327, + water_color: 4159204, + water_fog_color: 329011, + grass_color: None, + } + } +} diff --git a/crates/valence_client/src/lib.rs b/crates/valence_client/src/lib.rs index aec65a0..caebc3f 100644 --- a/crates/valence_client/src/lib.rs +++ b/crates/valence_client/src/lib.rs @@ -67,7 +67,9 @@ use valence_instance::packet::{ ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c, }; use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet}; -use valence_registry::{RegistryCodec, RegistryCodecSet, TagsRegistry}; +use valence_registry::codec::RegistryCodec; +use valence_registry::tags::TagsRegistry; +use valence_registry::RegistrySet; pub mod action; pub mod chat; @@ -112,7 +114,7 @@ impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.add_systems( ( - initial_join.after(RegistryCodecSet), + initial_join.after(RegistrySet), update_chunk_load_dist, read_data_in_old_view .after(WriteUpdatePacketsToInstancesSet) diff --git a/crates/valence_core/src/protocol/c2s.rs b/crates/valence_core/src/protocol/c2s.rs deleted file mode 100644 index 06b4dff..0000000 --- a/crates/valence_core/src/protocol/c2s.rs +++ /dev/null @@ -1,156 +0,0 @@ -/* -pub use advancement_tab::AdvancementTabC2s; -pub use boat_paddle::BoatPaddleStateC2s; -pub use book_update::BookUpdateC2s; -pub use button_click::ButtonClickC2s; -pub use chat_message::ChatMessageC2s; -pub use click_slot::ClickSlotC2s; -pub use client_command::ClientCommandC2s; -pub use client_settings::ClientSettingsC2s; -pub use client_status::ClientStatusC2s; -pub use close_handled_screen::CloseHandledScreenC2s; -pub use command_execution::CommandExecutionC2s; -pub use craft_request::CraftRequestC2s; -pub use creative_inventory_action::CreativeInventoryActionC2s; -pub use custom_payload::CustomPayloadC2s; -pub use hand_swing::HandSwingC2s; -pub use jigsaw_generating::JigsawGeneratingC2s; -pub use keep_alive::KeepAliveC2s; -pub use message_acknowledgment::MessageAcknowledgmentC2s; -pub use pick_from_inventory::PickFromInventoryC2s; -pub use play_pong::PlayPongC2s; -pub use player_action::PlayerActionC2s; -pub use player_input::PlayerInputC2s; -pub use player_interact_block::PlayerInteractBlockC2s; -pub use player_interact_entity::PlayerInteractEntityC2s; -pub use player_interact_item::PlayerInteractItemC2s; -pub use player_move::{Full, LookAndOnGround, OnGroundOnly, PositionAndOnGround}; -pub use player_session::PlayerSessionC2s; -pub use query_block_nbt::QueryBlockNbtC2s; -pub use query_entity_nbt::QueryEntityNbtC2s; -pub use recipe_book_data::RecipeBookDataC2s; -pub use recipe_category_options::RecipeCategoryOptionsC2s; -pub use rename_item::RenameItemC2s; -pub use request_command_completions::RequestCommandCompletionsC2s; -pub use resource_pack_status::ResourcePackStatusC2s; -pub use select_merchant_trade::SelectMerchantTradeC2s; -pub use spectator_teleport::SpectatorTeleportC2s; -pub use teleport_confirm::TeleportConfirmC2s; -pub use update_beacon::UpdateBeaconC2s; -pub use update_command_block::UpdateCommandBlockC2s; -pub use update_command_block_minecart::UpdateCommandBlockMinecartC2s; -pub use update_difficulty::UpdateDifficultyC2s; -pub use update_difficulty_lock::UpdateDifficultyLockC2s; -pub use update_jigsaw::UpdateJigsawC2s; -pub use update_player_abilities::UpdatePlayerAbilitiesC2s; -pub use update_selected_slot::UpdateSelectedSlotC2s; -pub use update_sign::UpdateSignC2s; -pub use update_structure_block::UpdateStructureBlockC2s; -pub use vehicle_move::VehicleMoveC2s; - -pub mod advancement_tab; -pub mod boat_paddle; -pub mod book_update; -pub mod button_click; -pub mod chat_message; -pub mod click_slot; -pub mod client_command; -pub mod client_settings; -pub mod client_status; -pub mod close_handled_screen; -pub mod command_execution; -pub mod craft_request; -pub mod creative_inventory_action; -pub mod custom_payload; -pub mod hand_swing; -pub mod jigsaw_generating; -pub mod keep_alive; -pub mod message_acknowledgment; -pub mod pick_from_inventory; -pub mod play_pong; -pub mod player_action; -pub mod player_input; -pub mod player_interact_block; -pub mod player_interact_entity; -pub mod player_interact_item; -pub mod player_move; -pub mod player_session; -pub mod query_block_nbt; -pub mod query_entity_nbt; -pub mod recipe_book_data; -pub mod recipe_category_options; -pub mod rename_item; -pub mod request_command_completions; -pub mod resource_pack_status; -pub mod select_merchant_trade; -pub mod spectator_teleport; -pub mod teleport_confirm; -pub mod update_beacon; -pub mod update_command_block; -pub mod update_command_block_minecart; -pub mod update_difficulty; -pub mod update_difficulty_lock; -pub mod update_jigsaw; -pub mod update_player_abilities; -pub mod update_selected_slot; -pub mod update_sign; -pub mod update_structure_block; -pub mod vehicle_move; - -packet_group! { - #[derive(Clone)] - C2sPlayPacket<'a> { - AdvancementTabC2s<'a>, - BoatPaddleStateC2s, - BookUpdateC2s<'a>, - ButtonClickC2s, - ChatMessageC2s<'a>, - ClickSlotC2s, - ClientCommandC2s, - ClientSettingsC2s<'a>, - ClientStatusC2s, - CloseHandledScreenC2s, - CommandExecutionC2s<'a>, - CraftRequestC2s<'a>, - CreativeInventoryActionC2s, - CustomPayloadC2s<'a>, - Full, - HandSwingC2s, - JigsawGeneratingC2s, - KeepAliveC2s, - LookAndOnGround, - MessageAcknowledgmentC2s, - OnGroundOnly, - PickFromInventoryC2s, - PlayerActionC2s, - PlayerInputC2s, - PlayerInteractBlockC2s, - PlayerInteractEntityC2s, - PlayerInteractItemC2s, - PlayerSessionC2s<'a>, - PlayPongC2s, - PositionAndOnGround, - QueryBlockNbtC2s, - QueryEntityNbtC2s, - RecipeBookDataC2s<'a>, - RecipeCategoryOptionsC2s, - RenameItemC2s<'a>, - RequestCommandCompletionsC2s<'a>, - ResourcePackStatusC2s, - SelectMerchantTradeC2s, - SpectatorTeleportC2s, - TeleportConfirmC2s, - UpdateBeaconC2s, - UpdateCommandBlockC2s<'a>, - UpdateCommandBlockMinecartC2s<'a>, - UpdateDifficultyC2s, - UpdateDifficultyLockC2s, - UpdateJigsawC2s<'a>, - UpdatePlayerAbilitiesC2s, - UpdateSelectedSlotC2s, - UpdateSignC2s<'a>, - UpdateStructureBlockC2s<'a>, - VehicleMoveC2s, - } -} -*/ diff --git a/crates/valence_core/src/protocol/s2c.rs b/crates/valence_core/src/protocol/s2c.rs deleted file mode 100644 index 567bc0c..0000000 --- a/crates/valence_core/src/protocol/s2c.rs +++ /dev/null @@ -1,338 +0,0 @@ -/* -pub use advancement_update::AdvancementUpdateS2c; -pub use block_breaking_progress::BlockBreakingProgressS2c; -pub use block_entity_update::BlockEntityUpdateS2c; -pub use block_event::BlockEventS2c; -pub use block_update::BlockUpdateS2c; -pub use boss_bar::BossBarS2c; -pub use bundle_splitter::BundleSplitter; -pub use chat_message::ChatMessageS2c; -pub use chat_suggestions::ChatSuggestionsS2c; -pub use chunk_biome_data::ChunkBiomeDataS2c; -pub use chunk_data::ChunkDataS2c; -pub use chunk_delta_update::ChunkDeltaUpdateS2c; -pub use chunk_load_distance::ChunkLoadDistanceS2c; -pub use chunk_render_distance_center::ChunkRenderDistanceCenterS2c; -pub use clear_title::ClearTitleS2c; -pub use close_screen::CloseScreenS2c; -pub use command_suggestions::CommandSuggestionsS2c; -pub use command_tree::CommandTreeS2c; -pub use cooldown_update::CooldownUpdateS2c; -pub use craft_failed_response::CraftFailedResponseS2c; -pub use custom_payload::CustomPayloadS2c; -pub use damage_tilt::DamageTiltS2c; -pub use death_message::DeathMessageS2c; -pub use difficulty::DifficultyS2c; -pub use disconnect::DisconnectS2c; -pub use end_combat::EndCombatS2c; -pub use enter_combat::EnterCombatS2c; -pub use entities_destroy::EntitiesDestroyS2c; -pub use entity_animation::EntityAnimationS2c; -pub use entity_attach::EntityAttachS2c; -pub use entity_attributes::EntityAttributesS2c; -pub use entity_damage::EntityDamageS2c; -pub use entity_equipment_update::EntityEquipmentUpdateS2c; -pub use entity_move::{MoveRelative, Rotate, RotateAndMoveRelative}; -pub use entity_passengers_set::EntityPassengersSetS2c; -pub use entity_position::EntityPositionS2c; -pub use entity_set_head_yaw::EntitySetHeadYawS2c; -pub use entity_spawn::EntitySpawnS2c; -pub use entity_status::EntityStatusS2c; -pub use entity_status_effect::EntityStatusEffectS2c; -pub use entity_tracker_update::EntityTrackerUpdateS2c; -pub use entity_velocity_update::EntityVelocityUpdateS2c; -pub use experience_bar_update::ExperienceBarUpdateS2c; -pub use experience_orb_spawn::ExperienceOrbSpawnS2c; -pub use explosion::ExplosionS2c; -pub use features::FeaturesS2c; -pub use game_join::GameJoinS2c; -pub use game_message::GameMessageS2c; -pub use game_state_change::GameStateChangeS2c; -pub use health_update::HealthUpdateS2c; -pub use inventory::InventoryS2c; -pub use item_pickup_animation::ItemPickupAnimationS2c; -pub use keep_alive::KeepAliveS2c; -pub use light_update::LightUpdateS2c; -pub use look_at::LookAtS2c; -pub use map_update::MapUpdateS2c; -pub use nbt_query_response::NbtQueryResponseS2c; -pub use open_horse_screen::OpenHorseScreenS2c; -pub use open_screen::OpenScreenS2c; -pub use open_written_book::OpenWrittenBookS2c; -pub use overlay_message::OverlayMessageS2c; -pub use particle::ParticleS2c; -pub use play_ping::PlayPingS2c; -pub use play_sound::PlaySoundS2c; -pub use play_sound_from_entity::PlaySoundFromEntityS2c; -pub use player_abilities::PlayerAbilitiesS2c; -pub use player_action_response::PlayerActionResponseS2c; -pub use player_list::PlayerListS2c; -pub use player_list_header::PlayerListHeaderS2c; -pub use player_position_look::PlayerPositionLookS2c; -pub use player_remove::PlayerRemoveS2c; -pub use player_respawn::PlayerRespawnS2c; -pub use player_spawn::PlayerSpawnS2c; -pub use player_spawn_position::PlayerSpawnPositionS2c; -pub use profileless_chat_message::ProfilelessChatMessageS2c; -pub use remove_entity_status_effect::RemoveEntityStatusEffectS2c; -pub use remove_message::RemoveMessageS2c; -pub use resource_pack_send::ResourcePackSendS2c; -pub use scoreboard_display::ScoreboardDisplayS2c; -pub use scoreboard_objective_update::ScoreboardObjectiveUpdateS2c; -pub use scoreboard_player_update::ScoreboardPlayerUpdateS2c; -pub use screen_handler_property_update::ScreenHandlerPropertyUpdateS2c; -pub use screen_handler_slot_update::ScreenHandlerSlotUpdateS2c; -pub use select_advancement_tab::SelectAdvancementTabS2c; -pub use server_metadata::ServerMetadataS2c; -pub use set_camera_entity::SetCameraEntityS2c; -pub use set_trade_offers::SetTradeOffersS2c; -pub use sign_editor_open::SignEditorOpenS2c; -pub use simulation_distance::SimulationDistanceS2c; -pub use statistics::StatisticsS2c; -pub use stop_sound::StopSoundS2c; -pub use subtitle::SubtitleS2c; -pub use synchronize_recipes::SynchronizeRecipesS2c; -pub use synchronize_tags::SynchronizeTagsS2c; -pub use team::TeamS2c; -pub use title::TitleS2c; -pub use title_fade::TitleFadeS2c; -pub use unload_chunk::UnloadChunkS2c; -pub use unlock_recipes::UnlockRecipesS2c; -pub use update_selected_slot::UpdateSelectedSlotS2c; -pub use vehicle_move::VehicleMoveS2c; -pub use world_border_center_changed::WorldBorderCenterChangedS2c; -pub use world_border_initialize::WorldBorderInitializeS2c; -pub use world_border_interpolate_size::WorldBorderInterpolateSizeS2c; -pub use world_border_size_changed::WorldBorderSizeChangedS2c; -pub use world_border_warning_blocks_changed::WorldBorderWarningBlocksChangedS2c; -pub use world_border_warning_time_changed::WorldBorderWarningTimeChangedS2c; -pub use world_event::WorldEventS2c; -pub use world_time_update::WorldTimeUpdateS2c; - -pub mod advancement_update; -pub mod block_breaking_progress; -pub mod block_entity_update; -pub mod block_event; -pub mod block_update; -pub mod boss_bar; -pub mod bundle_splitter; -pub mod chat_message; -pub mod chat_suggestions; -pub mod chunk_biome_data; -pub mod chunk_data; -pub mod chunk_delta_update; -pub mod chunk_load_distance; -pub mod chunk_render_distance_center; -pub mod clear_title; -pub mod close_screen; -pub mod command_suggestions; -pub mod command_tree; -pub mod cooldown_update; -pub mod craft_failed_response; -pub mod custom_payload; -pub mod damage_tilt; -pub mod death_message; -pub mod difficulty; -pub mod disconnect; -pub mod end_combat; -pub mod enter_combat; -pub mod entities_destroy; -pub mod entity_animation; -pub mod entity_attach; -pub mod entity_attributes; -pub mod entity_damage; -pub mod entity_equipment_update; -pub mod entity_move; -pub mod entity_passengers_set; -pub mod entity_position; -pub mod entity_set_head_yaw; -pub mod entity_spawn; -pub mod entity_status; -pub mod entity_status_effect; -pub mod entity_tracker_update; -pub mod entity_velocity_update; -pub mod experience_bar_update; -pub mod experience_orb_spawn; -pub mod explosion; -pub mod features; -pub mod game_join; -pub mod game_message; -pub mod game_state_change; -pub mod health_update; -pub mod inventory; -pub mod item_pickup_animation; -pub mod keep_alive; -pub mod light_update; -pub mod look_at; -pub mod map_update; -pub mod nbt_query_response; -pub mod open_horse_screen; -pub mod open_screen; -pub mod open_written_book; -pub mod overlay_message; -pub mod particle; -pub mod play_ping; -pub mod play_sound; -pub mod play_sound_from_entity; -pub mod player_abilities; -pub mod player_action_response; -pub mod player_list; -pub mod player_list_header; -pub mod player_position_look; -pub mod player_remove; -pub mod player_respawn; -pub mod player_spawn; -pub mod player_spawn_position; -pub mod profileless_chat_message; -pub mod remove_entity_status_effect; -pub mod remove_message; -pub mod resource_pack_send; -pub mod scoreboard_display; -pub mod scoreboard_objective_update; -pub mod scoreboard_player_update; -pub mod screen_handler_property_update; -pub mod screen_handler_slot_update; -pub mod select_advancement_tab; -pub mod server_metadata; -pub mod set_camera_entity; -pub mod set_trade_offers; -pub mod sign_editor_open; -pub mod simulation_distance; -pub mod statistics; -pub mod stop_sound; -pub mod subtitle; -pub mod synchronize_recipes; -pub mod synchronize_tags; -pub mod team; -pub mod title; -pub mod title_fade; -pub mod unload_chunk; -pub mod unlock_recipes; -pub mod update_selected_slot; -pub mod vehicle_move; -pub mod world_border_center_changed; -pub mod world_border_initialize; -pub mod world_border_interpolate_size; -pub mod world_border_size_changed; -pub mod world_border_warning_blocks_changed; -pub mod world_border_warning_time_changed; -pub mod world_event; -pub mod world_time_update; - -packet_group! { - #[derive(Clone)] - S2cPlayPacket<'a> { - AdvancementUpdateS2c<'a>, - BlockBreakingProgressS2c, - BlockEntityUpdateS2c<'a>, - BlockEventS2c, - BlockUpdateS2c, - BossBarS2c, - BundleSplitter, - ChatMessageS2c<'a>, - ChatSuggestionsS2c<'a>, - ChunkBiomeDataS2c<'a>, - ChunkDataS2c<'a>, - ChunkDeltaUpdateS2c<'a>, - ChunkLoadDistanceS2c, - ChunkRenderDistanceCenterS2c, - ClearTitleS2c, - CloseScreenS2c, - CommandSuggestionsS2c<'a>, - CommandTreeS2c<'a>, - CooldownUpdateS2c, - CraftFailedResponseS2c<'a>, - CustomPayloadS2c<'a>, - DamageTiltS2c, - DeathMessageS2c<'a>, - DifficultyS2c, - DisconnectS2c<'a>, - EndCombatS2c, - EnterCombatS2c, - EntitiesDestroyS2c<'a>, - EntityAnimationS2c, - EntityAttachS2c, - EntityAttributesS2c<'a>, - EntityDamageS2c, - EntityEquipmentUpdateS2c, - EntityPassengersSetS2c, - EntityPositionS2c, - EntitySetHeadYawS2c, - EntitySpawnS2c, - EntityStatusEffectS2c, - EntityStatusS2c, - EntityTrackerUpdateS2c<'a>, - EntityVelocityUpdateS2c, - ExperienceBarUpdateS2c, - ExperienceOrbSpawnS2c, - ExplosionS2c<'a>, - FeaturesS2c<'a>, - GameJoinS2c<'a>, - GameMessageS2c<'a>, - GameStateChangeS2c, - HealthUpdateS2c, - InventoryS2c<'a>, - ItemPickupAnimationS2c, - KeepAliveS2c, - LightUpdateS2c, - LookAtS2c, - MapUpdateS2c<'a>, - MoveRelative, - NbtQueryResponseS2c, - OpenHorseScreenS2c, - OpenScreenS2c<'a>, - OpenWrittenBookS2c, - OverlayMessageS2c<'a>, - ParticleS2c<'a>, - PlayerAbilitiesS2c, - PlayerActionResponseS2c, - PlayerListHeaderS2c<'a>, - PlayerListS2c<'a>, - PlayerPositionLookS2c, - PlayerRemoveS2c<'a>, - PlayerRespawnS2c<'a>, - PlayerSpawnPositionS2c, - PlayerSpawnS2c, - PlayPingS2c, - PlaySoundFromEntityS2c, - PlaySoundS2c<'a>, - ProfilelessChatMessageS2c<'a>, - RemoveEntityStatusEffectS2c, - RemoveMessageS2c<'a>, - ResourcePackSendS2c<'a>, - Rotate, - RotateAndMoveRelative, - ScoreboardDisplayS2c<'a>, - ScoreboardObjectiveUpdateS2c<'a>, - ScoreboardPlayerUpdateS2c<'a>, - ScreenHandlerPropertyUpdateS2c, - ScreenHandlerSlotUpdateS2c<'a>, - SelectAdvancementTabS2c<'a>, - ServerMetadataS2c<'a>, - SetCameraEntityS2c, - SetTradeOffersS2c, - SignEditorOpenS2c, - SimulationDistanceS2c, - StatisticsS2c, - StopSoundS2c<'a>, - SubtitleS2c<'a>, - SynchronizeRecipesS2c<'a>, - SynchronizeTagsS2c<'a>, - TeamS2c<'a>, - TitleFadeS2c, - TitleS2c<'a>, - UnloadChunkS2c, - UnlockRecipesS2c<'a>, - UpdateSelectedSlotS2c, - VehicleMoveS2c, - WorldBorderCenterChangedS2c, - WorldBorderInitializeS2c, - WorldBorderInterpolateSizeS2c, - WorldBorderSizeChangedS2c, - WorldBorderWarningBlocksChangedS2c, - WorldBorderWarningTimeChangedS2c, - WorldEventS2c, - WorldTimeUpdateS2c, - } -} -*/ \ No newline at end of file diff --git a/crates/valence_dimension/Cargo.toml b/crates/valence_dimension/Cargo.toml index b7e884c..3aadc6a 100644 --- a/crates/valence_dimension/Cargo.toml +++ b/crates/valence_dimension/Cargo.toml @@ -7,7 +7,8 @@ edition.workspace = true anyhow.workspace = true bevy_app.workspace = true bevy_ecs.workspace = true +serde.workspace = true tracing.workspace = true -valence_registry.workspace = true -valence_nbt.workspace = true valence_core.workspace = true +valence_nbt = { workspace = true, features = ["serde"] } +valence_registry.workspace = true \ No newline at end of file diff --git a/crates/valence_dimension/src/lib.rs b/crates/valence_dimension/src/lib.rs index 7ff57e8..579ec7a 100644 --- a/crates/valence_dimension/src/lib.rs +++ b/crates/valence_dimension/src/lib.rs @@ -17,155 +17,39 @@ clippy::dbg_macro )] -use std::collections::BTreeMap; -use std::str::FromStr; +use std::ops::{Deref, DerefMut}; -use anyhow::{bail, Context}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use serde::{Deserialize, Serialize}; use tracing::{error, warn}; use valence_core::ident; use valence_core::ident::Ident; -use valence_nbt::{compound, Value}; -use valence_registry::{RegistryCodec, RegistryCodecSet, RegistryValue}; +use valence_nbt::serde::CompoundSerializer; +use valence_registry::codec::{RegistryCodec, RegistryValue}; +use valence_registry::{Registry, RegistryIdx, RegistrySet}; pub struct DimensionPlugin; -#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -struct DimensionSet; - impl Plugin for DimensionPlugin { fn build(&self, app: &mut App) { - app.insert_resource(DimensionTypeRegistry { - name_to_dimension: BTreeMap::new(), - }) - .configure_set( - DimensionSet - .in_base_set(CoreSet::PostUpdate) - .before(RegistryCodecSet), - ) - .add_systems( - ( - update_dimension_type_registry, - remove_dimension_types_from_registry, - ) - .chain() - .in_set(DimensionSet), - ) - .add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup)); + app.init_resource::() + .add_startup_system(load_default_dimension_types.in_base_set(StartupSet::PreStartup)) + .add_system( + update_dimension_type_registry + .in_base_set(CoreSet::PostUpdate) + .before(RegistrySet), + ); } } -fn update_dimension_type_registry( - mut reg: ResMut, - mut codec: ResMut, - dimension_types: Query<(Entity, &DimensionType), Changed>, -) { - for (entity, dim) in &dimension_types { - // In case the name was changed. - reg.name_to_dimension.insert(dim.name.clone(), entity); - - let dimension_type_compound = compound! { - "ambient_light" => dim.ambient_light, - "bed_works" => dim.bed_works, - "coordinate_scale" => dim.coordinate_scale, - "effects" => Ident::from(dim.effects), - "has_ceiling" => dim.has_ceiling, - "has_raids" => dim.has_raids, - "has_skylight" => dim.has_skylight, - "height" => dim.height, - "infiniburn" => &dim.infiniburn, - "logical_height" => dim.logical_height, - "min_y" => dim.min_y, - "monster_spawn_block_light_limit" => dim.monster_spawn_block_light_limit, - "natural" => dim.natural, - "piglin_safe" => dim.piglin_safe, - "respawn_anchor_works" => dim.respawn_anchor_works, - "ultrawarm" => dim.ultrawarm, - }; - - let dimension_type_reg = codec.registry_mut(DimensionTypeRegistry::KEY); - - if let Some(value) = dimension_type_reg.iter_mut().find(|v| v.name == dim.name) { - value.name = dim.name.clone(); - value.element.merge(dimension_type_compound); - } else { - dimension_type_reg.push(RegistryValue { - name: dim.name.clone(), - element: dimension_type_compound, - }); - } - } -} - -fn remove_dimension_types_from_registry( - mut reg: ResMut, - mut codec: ResMut, - mut dimension_types: RemovedComponents, -) { - for entity in dimension_types.iter() { - if let Some((name, _)) = reg.name_to_dimension.iter().find(|(_, &e)| e == entity) { - let name = name.clone(); - reg.name_to_dimension.remove(name.as_str()); - - let dimension_type_reg = codec.registry_mut(DimensionTypeRegistry::KEY); - - if let Some(idx) = dimension_type_reg.iter().position(|v| v.name == name) { - dimension_type_reg.remove(idx); - } - } - } -} - -fn load_default_dimension_types( - mut reg: ResMut, - codec: Res, - mut commands: Commands, -) { +/// Loads the default dimension types from the registry codec. +fn load_default_dimension_types(mut reg: ResMut, codec: Res) { let mut helper = move || -> anyhow::Result<()> { for value in codec.registry(DimensionTypeRegistry::KEY) { - macro_rules! get { - ($name:literal, $f:expr) => {{ - value - .element - .get($name) - .and_then($f) - .context(concat!("invalid ", $name))? - }}; - } + let dimension_type = DimensionType::deserialize(value.element.clone())?; - let entity = commands - .spawn(DimensionType { - name: value.name.clone(), - ambient_light: *get!("ambient_light", Value::as_float), - bed_works: *get!("bed_works", Value::as_byte) != 0, - coordinate_scale: *get!("coordinate_scale", Value::as_double), - effects: DimensionEffects::from_str(get!("effects", Value::as_string))?, - has_ceiling: *get!("has_ceiling", Value::as_byte) != 0, - has_raids: *get!("has_raids", Value::as_byte) != 0, - has_skylight: *get!("has_skylight", Value::as_byte) != 0, - height: *get!("height", Value::as_int), - infiniburn: get!("infiniburn", Value::as_string).clone(), - logical_height: *get!("logical_height", Value::as_int), - min_y: *get!("min_y", Value::as_int), - monster_spawn_block_light_limit: *get!( - "monster_spawn_block_light_limit", - Value::as_int - ), - natural: *get!("natural", Value::as_byte) != 0, - piglin_safe: *get!("piglin_safe", Value::as_byte) != 0, - respawn_anchor_works: *get!("respawn_anchor_works", Value::as_byte) != 0, - ultrawarm: *get!("ultrawarm", Value::as_byte) != 0, - }) - .id(); - - if reg - .name_to_dimension - .insert(value.name.clone(), entity) - .is_some() - { - warn!("duplicate dimension type name of \"{}\"", &value.name); - } + reg.insert(value.name.clone(), dimension_type); } Ok(()) @@ -176,30 +60,75 @@ fn load_default_dimension_types( } } -#[derive(Resource)] +/// Updates the registry codec as the dimension type registry is modified by +/// users. +fn update_dimension_type_registry( + reg: Res, + mut codec: ResMut, +) { + if reg.is_changed() { + let dimension_types = codec.registry_mut(DimensionTypeRegistry::KEY); + + dimension_types.clear(); + + dimension_types.extend(reg.iter().map(|(_, name, dim)| { + RegistryValue { + name: name.into(), + element: dim + .serialize(CompoundSerializer) + .expect("failed to serialize dimension type"), + } + })); + } +} + +#[derive(Resource, Default, Debug)] pub struct DimensionTypeRegistry { - name_to_dimension: BTreeMap, Entity>, + reg: Registry, } impl DimensionTypeRegistry { - pub const KEY: Ident<&str> = ident!("minecraft:dimension_type"); + pub const KEY: Ident<&str> = ident!("dimension_type"); +} - pub fn get_by_name(&self, name: Ident<&str>) -> Option { - self.name_to_dimension.get(name.as_str()).copied() +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct DimensionTypeId(u16); + +impl RegistryIdx for DimensionTypeId { + const MAX: usize = u16::MAX as _; + + fn to_index(self) -> usize { + self.0 as _ } - pub fn dimensions(&self) -> impl Iterator + '_ { - self.name_to_dimension.values().copied() + fn from_index(idx: usize) -> Self { + Self(idx as _) } } -#[derive(Component, Clone, PartialEq, Debug)] +impl Deref for DimensionTypeRegistry { + type Target = Registry; + + fn deref(&self) -> &Self::Target { + &self.reg + } +} + +impl DerefMut for DimensionTypeRegistry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.reg + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(deny_unknown_fields)] pub struct DimensionType { - pub name: Ident, pub ambient_light: f32, pub bed_works: bool, pub coordinate_scale: f64, pub effects: DimensionEffects, + #[serde(skip_serializing_if = "Option::is_none")] + pub fixed_time: Option, pub has_ceiling: bool, pub has_raids: bool, pub has_skylight: bool, @@ -208,7 +137,7 @@ pub struct DimensionType { pub logical_height: i32, pub min_y: i32, pub monster_spawn_block_light_limit: i32, - /// TODO: monster_spawn_light_level + pub monster_spawn_light_level: MonsterSpawnLightLevel, pub natural: bool, pub piglin_safe: bool, pub respawn_anchor_works: bool, @@ -218,11 +147,11 @@ pub struct DimensionType { impl Default for DimensionType { fn default() -> Self { Self { - name: ident!("minecraft:overworld").into(), ambient_light: 1.0, bed_works: true, coordinate_scale: 1.0, effects: DimensionEffects::default(), + fixed_time: None, has_ceiling: false, has_raids: true, has_skylight: true, @@ -231,42 +160,46 @@ impl Default for DimensionType { logical_height: 384, min_y: -64, monster_spawn_block_light_limit: 0, + monster_spawn_light_level: MonsterSpawnLightLevel::Int(7), natural: true, piglin_safe: false, - respawn_anchor_works: true, + respawn_anchor_works: false, ultrawarm: false, } } } /// Determines what skybox/fog effects to use in dimensions. -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)] pub enum DimensionEffects { + #[serde(rename = "minecraft:overworld")] #[default] Overworld, + #[serde(rename = "minecraft:the_nether")] TheNether, + #[serde(rename = "minecraft:the_end")] TheEnd, } -impl From for Ident<&'static str> { - fn from(value: DimensionEffects) -> Self { - match value { - DimensionEffects::Overworld => ident!("overworld"), - DimensionEffects::TheNether => ident!("the_nether"), - DimensionEffects::TheEnd => ident!("the_end"), - } - } +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MonsterSpawnLightLevel { + Int(i32), + Tagged(MonsterSpawnLightLevelTagged), } -impl FromStr for DimensionEffects { - type Err = anyhow::Error; +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] +pub enum MonsterSpawnLightLevelTagged { + #[serde(rename = "minecraft:uniform")] + Uniform { + min_inclusive: i32, + max_inclusive: i32, + }, +} - fn from_str(s: &str) -> Result { - match Ident::new(s)?.as_str() { - "minecraft:overworld" => Ok(DimensionEffects::Overworld), - "minecraft:the_nether" => Ok(DimensionEffects::TheNether), - "minecraft:the_end" => Ok(DimensionEffects::TheEnd), - other => bail!("unknown dimension effect \"{other}\""), - } +impl From for MonsterSpawnLightLevel { + fn from(value: i32) -> Self { + Self::Int(value) } } diff --git a/crates/valence_instance/Cargo.toml b/crates/valence_instance/Cargo.toml index 5e277fa..6f9266b 100644 --- a/crates/valence_instance/Cargo.toml +++ b/crates/valence_instance/Cargo.toml @@ -19,3 +19,4 @@ valence_core.workspace = true valence_dimension.workspace = true valence_entity.workspace = true valence_nbt.workspace = true +valence_registry.workspace = true diff --git a/crates/valence_instance/src/chunk.rs b/crates/valence_instance/src/chunk.rs index 7df31ef..4e364a1 100644 --- a/crates/valence_instance/src/chunk.rs +++ b/crates/valence_instance/src/chunk.rs @@ -13,6 +13,7 @@ use valence_core::protocol::var_int::VarInt; use valence_core::protocol::var_long::VarLong; use valence_core::protocol::Encode; use valence_nbt::{compound, Compound}; +use valence_registry::RegistryIdx; use crate::packet::{ BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataBlockEntity, ChunkDataS2c, ChunkDeltaUpdateS2c, @@ -414,7 +415,7 @@ impl Chunk { sect.biomes .encode_mc_format( &mut *scratch, - |b| b.0.into(), + |b| b.to_index() as _, 0, 3, bit_width(info.biome_registry_len - 1), diff --git a/crates/valence_instance/src/lib.rs b/crates/valence_instance/src/lib.rs index 34b43a5..862e31e 100644 --- a/crates/valence_instance/src/lib.rs +++ b/crates/valence_instance/src/lib.rs @@ -31,7 +31,7 @@ pub use chunk_entry::*; use glam::{DVec3, Vec3}; use num_integer::div_ceil; use rustc_hash::FxHashMap; -use valence_biome::Biome; +use valence_biome::BiomeRegistry; use valence_core::block_pos::BlockPos; use valence_core::chunk_pos::ChunkPos; use valence_core::despawn::Despawned; @@ -44,7 +44,7 @@ use valence_core::protocol::packet::sound::{PlaySoundS2c, Sound, SoundCategory}; use valence_core::protocol::var_int::VarInt; use valence_core::protocol::{Encode, Packet}; use valence_core::Server; -use valence_dimension::DimensionType; +use valence_dimension::DimensionTypeRegistry; use valence_entity::packet::{ EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, RotateAndMoveRelativeS2c, @@ -432,40 +432,33 @@ pub struct InstanceInfo { #[derive(Debug)] pub struct PartitionCell { /// The chunk in this cell. - #[doc(hidden)] pub chunk: Option>, /// If `chunk` went from `Some` to `None` this tick. - #[doc(hidden)] pub chunk_removed: bool, /// Minecraft entities in this cell. - #[doc(hidden)] pub entities: BTreeSet, /// Minecraft entities that have entered the chunk this tick, paired with /// the cell position in this instance they came from. - #[doc(hidden)] pub incoming: Vec<(Entity, Option)>, /// Minecraft entities that have left the chunk this tick, paired with the /// cell position in this world they arrived at. - #[doc(hidden)] pub outgoing: Vec<(Entity, Option)>, /// A cache of packets to send to all clients that are in view of this cell /// at the end of the tick. - #[doc(hidden)] pub packet_buf: Vec, } impl Instance { + #[track_caller] pub fn new( dimension_type_name: impl Into>, - dimensions: &Query<&DimensionType>, - biomes: &Query<&Biome>, + dimensions: &DimensionTypeRegistry, + biomes: &BiomeRegistry, server: &Server, ) -> Self { let dimension_type_name = dimension_type_name.into(); - let Some(dim) = dimensions.iter().find(|d| d.name == dimension_type_name) else { - panic!("missing dimension type with name \"{dimension_type_name}\"") - }; + let dim = &dimensions[dimension_type_name.as_str_ident()]; assert!(dim.height > 0, "invalid dimension height of {}", dim.height); @@ -497,28 +490,6 @@ impl Instance { } } - /// TODO: Temporary hack for unit testing. Do not use! - #[doc(hidden)] - pub fn new_unit_testing( - dimension_type_name: impl Into>, - server: &Server, - ) -> Self { - Self { - partition: FxHashMap::default(), - info: InstanceInfo { - dimension_type_name: dimension_type_name.into(), - section_count: 24, - min_y: -64, - biome_registry_len: 1, - compression_threshold: server.compression_threshold(), - filler_sky_light_mask: vec![].into(), - filler_sky_light_arrays: vec![].into(), - }, - packet_buf: vec![], - scratch: vec![], - } - } - pub fn dimension_type_name(&self) -> Ident<&str> { self.info.dimension_type_name.as_str_ident() } diff --git a/crates/valence_nbt/src/serde.rs b/crates/valence_nbt/src/serde.rs index 7dd780f..7574b2b 100644 --- a/crates/valence_nbt/src/serde.rs +++ b/crates/valence_nbt/src/serde.rs @@ -6,6 +6,8 @@ use thiserror::Error; mod de; mod ser; +#[cfg(test)] +mod tests; /// Errors that can occur while serializing or deserializing. #[derive(Clone, Error, Debug)] @@ -56,105 +58,3 @@ fn i8_vec_to_u8_vec(vec: Vec) -> Vec { Vec::from_raw_parts(vec.as_mut_ptr() as *mut u8, vec.len(), vec.capacity()) } } - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - use serde::{Deserialize, Serialize}; - use serde_json::json; - - use super::*; - use crate::{compound, Compound, List}; - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Struct { - foo: i32, - bar: StructInner, - baz: String, - quux: Vec, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct StructInner { - a: bool, - b: i64, - c: Vec>, - d: Vec, - } - - fn make_struct() -> Struct { - Struct { - foo: i32::MIN, - bar: StructInner { - a: true, - b: 123456789, - c: vec![vec![1, 2, 3], vec![4, 5, 6]], - d: vec![], - }, - baz: "🤨".into(), - quux: vec![std::f32::consts::PI, f32::MAX, f32::MIN], - } - } - - fn make_compound() -> Compound { - compound! { - "foo" => i32::MIN, - "bar" => compound! { - "a" => true, - "b" => 123456789_i64, - "c" => List::IntArray(vec![vec![1, 2, 3], vec![4, 5, 6]]), - "d" => List::End, - }, - "baz" => "🤨", - "quux" => List::Float(vec![ - std::f32::consts::PI, - f32::MAX, - f32::MIN, - ]), - } - } - - fn make_json() -> serde_json::Value { - json!({ - "foo": i32::MIN, - "bar": { - "a": true, - "b": 123456789_i64, - "c": [[1, 2, 3], [4, 5, 6]], - "d": [] - }, - "baz": "🤨", - "quux": [ - std::f32::consts::PI, - f32::MAX, - f32::MIN, - ] - }) - } - - #[test] - fn struct_to_compound() { - let c = make_struct().serialize(CompoundSerializer).unwrap(); - - assert_eq!(c, make_compound()); - } - - #[test] - fn compound_to_struct() { - let s = Struct::deserialize(make_compound()).unwrap(); - - assert_eq!(s, make_struct()); - } - - #[test] - fn compound_to_json() { - let mut j = serde_json::to_value(make_compound()).unwrap(); - - // Bools map to bytes in NBT, but the result should be the same otherwise. - let p = j.pointer_mut("/bar/a").unwrap(); - assert_eq!(*p, serde_json::Value::from(1)); - *p = true.into(); - - assert_eq!(j, make_json()); - } -} diff --git a/crates/valence_nbt/src/serde/de.rs b/crates/valence_nbt/src/serde/de.rs index 199618a..da01782 100644 --- a/crates/valence_nbt/src/serde/de.rs +++ b/crates/valence_nbt/src/serde/de.rs @@ -288,10 +288,32 @@ impl<'de> Deserializer<'de> for Value { } } + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + match self { + Value::String(s) => visitor.visit_enum(s.into_deserializer()), // Unit variant. + other => other.deserialize_any(visitor), + } + } + forward_to_deserialize_any! { i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any + bytes byte_buf unit unit_struct newtype_struct seq tuple + tuple_struct map struct identifier ignored_any } } diff --git a/crates/valence_nbt/src/serde/ser.rs b/crates/valence_nbt/src/serde/ser.rs index 941beff..29993ce 100644 --- a/crates/valence_nbt/src/serde/ser.rs +++ b/crates/valence_nbt/src/serde/ser.rs @@ -324,11 +324,11 @@ impl Serializer for ValueSerializer { unsupported!("none") } - fn serialize_some(self, _value: &T) -> Result + fn serialize_some(self, value: &T) -> Result where T: Serialize, { - unsupported!("some") + value.serialize(self) } fn serialize_unit(self) -> Result { @@ -343,9 +343,9 @@ impl Serializer for ValueSerializer { self, _name: &'static str, _variant_index: u32, - _variant: &'static str, + variant: &'static str, ) -> Result { - unsupported!("unit variant") + Ok(Value::String(variant.into())) } fn serialize_newtype_struct( diff --git a/crates/valence_nbt/src/serde/tests.rs b/crates/valence_nbt/src/serde/tests.rs new file mode 100644 index 0000000..ab4dd2d --- /dev/null +++ b/crates/valence_nbt/src/serde/tests.rs @@ -0,0 +1,109 @@ +use pretty_assertions::assert_eq; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use super::*; +use crate::{compound, Compound, List}; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct Struct { + foo: i32, + bar: StructInner, + baz: String, + quux: Vec, + blah: EnumInner, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct StructInner { + a: bool, + b: i64, + c: Vec>, + d: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +enum EnumInner { + A, + B, + C, +} + +fn make_struct() -> Struct { + Struct { + foo: i32::MIN, + bar: StructInner { + a: true, + b: 123456789, + c: vec![vec![1, 2, 3], vec![4, 5, 6]], + d: vec![], + }, + baz: "🤨".into(), + quux: vec![std::f32::consts::PI, f32::MAX, f32::MIN], + blah: EnumInner::B, + } +} + +fn make_compound() -> Compound { + compound! { + "foo" => i32::MIN, + "bar" => compound! { + "a" => true, + "b" => 123456789_i64, + "c" => List::IntArray(vec![vec![1, 2, 3], vec![4, 5, 6]]), + "d" => List::End, + }, + "baz" => "🤨", + "quux" => List::Float(vec![ + std::f32::consts::PI, + f32::MAX, + f32::MIN, + ]), + "blah" => "B" + } +} + +fn make_json() -> serde_json::Value { + json!({ + "foo": i32::MIN, + "bar": { + "a": true, + "b": 123456789_i64, + "c": [[1, 2, 3], [4, 5, 6]], + "d": [] + }, + "baz": "🤨", + "quux": [ + std::f32::consts::PI, + f32::MAX, + f32::MIN, + ], + "blah": "B" + }) +} + +#[test] +fn struct_to_compound() { + let c = make_struct().serialize(CompoundSerializer).unwrap(); + + assert_eq!(c, make_compound()); +} + +#[test] +fn compound_to_struct() { + let s = Struct::deserialize(make_compound()).unwrap(); + + assert_eq!(s, make_struct()); +} + +#[test] +fn compound_to_json() { + let mut j = serde_json::to_value(make_compound()).unwrap(); + + // Bools map to bytes in NBT, but the result should be the same otherwise. + let p = j.pointer_mut("/bar/a").unwrap(); + assert_eq!(*p, serde_json::Value::from(1)); + *p = true.into(); + + assert_eq!(j, make_json()); +} diff --git a/crates/valence_registry/Cargo.toml b/crates/valence_registry/Cargo.toml index 02cf1af..2dba597 100644 --- a/crates/valence_registry/Cargo.toml +++ b/crates/valence_registry/Cargo.toml @@ -4,10 +4,11 @@ version.workspace = true edition.workspace = true [dependencies] +bevy_app.workspace = true +bevy_ecs.workspace = true +indexmap.workspace = true +serde_json.workspace = true +serde.workspace = true tracing.workspace = true valence_core.workspace = true -valence_nbt.workspace = true -bevy_ecs.workspace = true -bevy_app.workspace = true -serde.workspace = true -serde_json.workspace = true \ No newline at end of file +valence_nbt.workspace = true \ No newline at end of file diff --git a/crates/valence_registry/src/codec.rs b/crates/valence_registry/src/codec.rs new file mode 100644 index 0000000..e3d56bf --- /dev/null +++ b/crates/valence_registry/src/codec.rs @@ -0,0 +1,137 @@ +use std::collections::BTreeMap; + +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use tracing::error; +use valence_core::ident::Ident; +use valence_nbt::{compound, Compound, List, Value}; + +use crate::RegistrySet; + +pub(super) fn build(app: &mut App) { + app.init_resource::() + .add_system(cache_registry_codec.in_set(RegistrySet)); +} + +/// Contains the registry codec sent to all players while joining. This contains +/// information for biomes and dimensions among other things. +/// +/// Generally, end users should not manipulate the registry codec directly. Use +/// one of the other registry resources instead. +#[derive(Resource, Debug)] +pub struct RegistryCodec { + pub registries: BTreeMap, Vec>, + // TODO: store this in binary form? + cached_codec: Compound, +} + +#[derive(Clone, Debug)] +pub struct RegistryValue { + pub name: Ident, + pub element: Compound, +} + +impl RegistryCodec { + pub fn cached_codec(&self) -> &Compound { + &self.cached_codec + } + + pub fn registry(&self, registry_key: Ident<&str>) -> &Vec { + self.registries + .get(registry_key.as_str()) + .unwrap_or_else(|| panic!("missing registry for {registry_key}")) + } + + pub fn registry_mut(&mut self, registry_key: Ident<&str>) -> &mut Vec { + self.registries + .get_mut(registry_key.as_str()) + .unwrap_or_else(|| panic!("missing registry for {registry_key}")) + } +} + +impl Default for RegistryCodec { + fn default() -> Self { + let codec = include_bytes!("../../../extracted/registry_codec_1.19.4.dat"); + let compound = Compound::from_binary(&mut codec.as_slice()) + .expect("failed to decode vanilla registry codec") + .0; + + let mut registries = BTreeMap::new(); + + for (k, v) in compound { + let reg_name: Ident = Ident::new(k).expect("invalid registry name").into(); + let mut reg_values = vec![]; + + let Value::Compound(mut outer) = v else { + error!("registry {reg_name} is not a compound"); + continue + }; + + let values = match outer.remove("value") { + Some(Value::List(List::Compound(values))) => values, + Some(Value::List(List::End)) => continue, + _ => { + error!("missing \"value\" compound in {reg_name}"); + continue; + } + }; + + for mut value in values { + let Some(Value::String(name)) = value.remove("name") else { + error!("missing \"name\" string in value for {reg_name}"); + continue + }; + + let name = match Ident::new(name) { + Ok(n) => n.into(), + Err(e) => { + error!("invalid registry value name \"{}\"", e.0); + continue; + } + }; + + let Some(Value::Compound(element)) = value.remove("element") else { + error!("missing \"element\" compound in value for {reg_name}"); + continue + }; + + reg_values.push(RegistryValue { name, element }); + } + + registries.insert(reg_name, reg_values); + } + + Self { + registries, + // Cache will be created later. + cached_codec: Compound::new(), + } + } +} + +fn cache_registry_codec(codec: ResMut) { + if codec.is_changed() { + let codec = codec.into_inner(); + + codec.cached_codec.clear(); + + for (reg_name, reg) in &codec.registries { + let mut value = vec![]; + + for (id, v) in reg.iter().enumerate() { + value.push(compound! { + "id" => id as i32, + "name" => v.name.as_str(), + "element" => v.element.clone(), + }); + } + + let registry = compound! { + "type" => reg_name.as_str(), + "value" => List::Compound(value), + }; + + codec.cached_codec.insert(reg_name.as_str(), registry); + } + } +} diff --git a/crates/valence_registry/src/lib.rs b/crates/valence_registry/src/lib.rs index 0d9bdaa..b0855cb 100644 --- a/crates/valence_registry/src/lib.rs +++ b/crates/valence_registry/src/lib.rs @@ -17,155 +17,163 @@ clippy::dbg_macro )] -use std::collections::BTreeMap; +pub mod codec; +pub mod tags; + +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::ops::{Index, IndexMut}; use bevy_app::prelude::*; pub use bevy_ecs::prelude::*; -use tracing::error; +use indexmap::map::Entry; +use indexmap::IndexMap; use valence_core::ident::Ident; -use valence_nbt::{compound, Compound, List, Value}; - -mod tags; - -pub use tags::*; pub struct RegistryPlugin; -/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that -/// modify the registry codec should run _before_ this. +/// The [`SystemSet`] where the [`RegistryCodec`](codec::RegistryCodec) and +/// [`TagsRegistry`](tags::TagsRegistry) caches are rebuilt. Systems that modify +/// the registry codec or tags registry should run _before_ this. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct RegistryCodecSet; +pub struct RegistrySet; impl Plugin for RegistryPlugin { fn build(&self, app: &mut bevy_app::App) { - app.init_resource::() - .init_resource::() - .configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate)) - .add_startup_system(init_tags_registry.in_set(RegistryCodecSet)) - .add_system(cache_registry_codec.in_set(RegistryCodecSet)) - .add_system(cache_tags_packet.in_set(RegistryCodecSet)); + app.configure_set(RegistrySet.in_base_set(CoreSet::PostUpdate)); + + codec::build(app); + tags::build(app); } } -fn cache_registry_codec(codec: ResMut) { - if codec.is_changed() { - let codec = codec.into_inner(); - - codec.cached_codec.clear(); - - for (reg_name, reg) in &codec.registries { - let mut value = vec![]; - - for (id, v) in reg.iter().enumerate() { - value.push(compound! { - "id" => id as i32, - "name" => v.name.as_str(), - "element" => v.element.clone(), - }); - } - - let registry = compound! { - "type" => reg_name.as_str(), - "value" => List::Compound(value), - }; - - codec.cached_codec.insert(reg_name.as_str(), registry); - } - } -} - -/// Contains the registry codec sent to all players while joining. This contains -/// information for biomes and dimensions among other things. -/// -/// Generally, end users should not manipulate the registry codec directly. Use -/// one of the other modules instead. -#[derive(Resource, Debug)] -pub struct RegistryCodec { - pub registries: BTreeMap, Vec>, - // TODO: store this in binary form? - cached_codec: Compound, -} - #[derive(Clone, Debug)] -pub struct RegistryValue { - pub name: Ident, - pub element: Compound, +pub struct Registry { + items: IndexMap, V>, + _marker: PhantomData, } -impl RegistryCodec { - pub fn cached_codec(&self) -> &Compound { - &self.cached_codec - } - - pub fn registry(&self, registry_key: Ident<&str>) -> &Vec { - self.registries - .get(registry_key.as_str()) - .unwrap_or_else(|| panic!("missing registry for {registry_key}")) - } - - pub fn registry_mut(&mut self, registry_key: Ident<&str>) -> &mut Vec { - self.registries - .get_mut(registry_key.as_str()) - .unwrap_or_else(|| panic!("missing registry for {registry_key}")) - } -} - -impl Default for RegistryCodec { - fn default() -> Self { - let codec = include_bytes!("../../../extracted/registry_codec_1.19.4.dat"); - let compound = Compound::from_binary(&mut codec.as_slice()) - .expect("failed to decode vanilla registry codec") - .0; - - let mut registries = BTreeMap::new(); - - for (k, v) in compound { - let reg_name: Ident = Ident::new(k).expect("invalid registry name").into(); - let mut reg_values = vec![]; - - let Value::Compound(mut outer) = v else { - error!("registry {reg_name} is not a compound"); - continue - }; - - let values = match outer.remove("value") { - Some(Value::List(List::Compound(values))) => values, - Some(Value::List(List::End)) => continue, - _ => { - error!("missing \"value\" compound in {reg_name}"); - continue; - } - }; - - for mut value in values { - let Some(Value::String(name)) = value.remove("name") else { - error!("missing \"name\" string in value for {reg_name}"); - continue - }; - - let name = match Ident::new(name) { - Ok(n) => n.into(), - Err(e) => { - error!("invalid registry value name \"{}\"", e.0); - continue; - } - }; - - let Some(Value::Compound(element)) = value.remove("element") else { - error!("missing \"element\" compound in value for {reg_name}"); - continue - }; - - reg_values.push(RegistryValue { name, element }); - } - - registries.insert(reg_name, reg_values); - } - +impl Registry { + pub fn new() -> Self { Self { - registries, - // Cache will be created later. - cached_codec: Compound::new(), + items: IndexMap::new(), + _marker: PhantomData, + } + } + + pub fn insert(&mut self, name: impl Into>, item: V) -> Option { + if self.items.len() >= I::MAX { + // Too many items in the registry. + return None; + } + + let len = self.items.len(); + + match self.items.entry(name.into()) { + Entry::Occupied(_) => None, + Entry::Vacant(ve) => { + ve.insert(item); + Some(I::from_index(len)) + } + } + } + + pub fn swap_to_front(&mut self, name: Ident<&str>) { + if let Some(idx) = self.items.get_index_of(name.as_str()) { + self.items.swap_indices(0, idx); + } + } + + pub fn remove(&mut self, name: Ident<&str>) -> Option { + self.items.shift_remove(name.as_str()) + } + + pub fn get(&self, name: Ident<&str>) -> Option<&V> { + self.items.get(name.as_str()) + } + + pub fn get_mut(&mut self, name: Ident<&str>) -> Option<&mut V> { + self.items.get_mut(name.as_str()) + } + + pub fn index_of(&self, name: Ident<&str>) -> Option { + self.items.get_index_of(name.as_str()).map(I::from_index) + } + + pub fn iter( + &self, + ) -> impl DoubleEndedIterator, &V)> + ExactSizeIterator + '_ { + self.items + .iter() + .enumerate() + .map(|(i, (k, v))| (I::from_index(i), k.as_str_ident(), v)) + } + + pub fn iter_mut( + &mut self, + ) -> impl DoubleEndedIterator, &mut V)> + ExactSizeIterator + '_ { + self.items + .iter_mut() + .enumerate() + .map(|(i, (k, v))| (I::from_index(i), k.as_str_ident(), v)) + } +} + +impl Index for Registry { + type Output = V; + + fn index(&self, index: I) -> &Self::Output { + self.items + .get_index(index.to_index()) + .unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index())) + .1 + } +} + +impl IndexMut for Registry { + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.items + .get_index_mut(index.to_index()) + .unwrap_or_else(|| panic!("out of bounds registry index of {}", index.to_index())) + .1 + } +} + +impl<'a, I: RegistryIdx, V> Index> for Registry { + type Output = V; + + fn index(&self, index: Ident<&'a str>) -> &Self::Output { + if let Some(item) = self.items.get(index.as_str()) { + item + } else { + panic!("missing registry item with name '{index}'") } } } + +impl<'a, I: RegistryIdx, V> IndexMut> for Registry { + fn index_mut(&mut self, index: Ident<&'a str>) -> &mut Self::Output { + if let Some(item) = self.items.get_mut(index.as_str()) { + item + } else { + panic!("missing registry item with name '{index}'") + } + } +} + +impl Default for Registry { + fn default() -> Self { + Self { + items: IndexMap::new(), + _marker: PhantomData, + } + } +} + +pub trait RegistryIdx: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Debug { + const MAX: usize; + + fn to_index(self) -> usize; + fn from_index(idx: usize) -> Self; +} diff --git a/crates/valence_registry/src/tags.rs b/crates/valence_registry/src/tags.rs index b80ef0d..631ac29 100644 --- a/crates/valence_registry/src/tags.rs +++ b/crates/valence_registry/src/tags.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use bevy_app::prelude::*; use bevy_ecs::prelude::*; use serde::Deserialize; use valence_core::ident::Ident; @@ -8,6 +9,14 @@ use valence_core::protocol::var_int::VarInt; use valence_core::protocol::{packet_id, Decode, Encode, Packet}; use valence_core::Server; +use crate::RegistrySet; + +pub(super) fn build(app: &mut App) { + app.init_resource::() + .add_startup_system(init_tags_registry) + .add_system(cache_tags_packet.in_set(RegistrySet)); +} + #[derive(Clone, Debug, Encode, Decode, Packet)] #[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)] pub struct SynchronizeTagsS2c<'a> { @@ -32,8 +41,8 @@ pub struct TagEntry { pub entries: Vec, } -impl<'a> TagsRegistry { - pub(crate) fn build_synchronize_tags(&'a self) -> SynchronizeTagsS2c<'a> { +impl TagsRegistry { + fn build_synchronize_tags(&self) -> SynchronizeTagsS2c { SynchronizeTagsS2c { registries: Cow::Borrowed(&self.registries), } @@ -44,7 +53,7 @@ impl<'a> TagsRegistry { } } -pub(crate) fn init_tags_registry(mut tags: ResMut) { +pub fn init_tags_registry(mut tags: ResMut) { let registries = serde_json::from_str::>(include_str!("../../../extracted/tags.json")) .expect("tags.json is invalid"); diff --git a/tools/playground/src/playground.template.rs b/tools/playground/src/playground.template.rs index 58610c4..1282de1 100644 --- a/tools/playground/src/playground.template.rs +++ b/tools/playground/src/playground.template.rs @@ -22,8 +22,8 @@ pub fn build_app(app: &mut App) { fn setup( mut commands: Commands, server: Res, - biomes: Query<&Biome>, - dimensions: Query<&DimensionType>, + biomes: Res, + dimensions: Res, ) { let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);