mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-09 22:31:30 +11:00
tag registry (#350)
- add Tags extractor - add tags.json to extracted - send `SynchronizeTagsS2c` packet on join - fix encode ## Description Adds a `TagsRegistry` resource that contains all the information needed to build and send `SynchronizeTagsS2c` on join. closes #349
This commit is contained in:
parent
c5557e744d
commit
975014e76b
|
@ -67,7 +67,7 @@ use valence_instance::packet::{
|
||||||
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c,
|
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c,
|
||||||
};
|
};
|
||||||
use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet};
|
use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet};
|
||||||
use valence_registry::{RegistryCodec, RegistryCodecSet};
|
use valence_registry::{RegistryCodec, RegistryCodecSet, TagsRegistry};
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
|
@ -678,6 +678,7 @@ struct ClientJoinQuery {
|
||||||
|
|
||||||
fn initial_join(
|
fn initial_join(
|
||||||
codec: Res<RegistryCodec>,
|
codec: Res<RegistryCodec>,
|
||||||
|
tags: Res<TagsRegistry>,
|
||||||
mut clients: Query<ClientJoinQuery, Added<Client>>,
|
mut clients: Query<ClientJoinQuery, Added<Client>>,
|
||||||
instances: Query<&Instance>,
|
instances: Query<&Instance>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
@ -724,6 +725,8 @@ fn initial_join(
|
||||||
last_death_location,
|
last_death_location,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
q.client.enc.append_bytes(tags.sync_tags_packet());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// TODO: enable all the features?
|
// TODO: enable all the features?
|
||||||
q.client.write_packet(&FeatureFlags {
|
q.client.write_packet(&FeatureFlags {
|
||||||
|
|
|
@ -142,7 +142,7 @@ impl Default for CoreSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains global server state accessible as a [`Resource`].
|
/// Contains global server state accessible as a [`Resource`].
|
||||||
#[derive(Resource)]
|
#[derive(Resource, Default)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
/// Incremented on every tick.
|
/// Incremented on every tick.
|
||||||
current_tick: i64,
|
current_tick: i64,
|
||||||
|
|
|
@ -1372,26 +1372,3 @@ pub mod map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to valence_registry?
|
|
||||||
pub mod synchronize_tags {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
|
||||||
#[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)]
|
|
||||||
pub struct SynchronizeTagsS2c<'a> {
|
|
||||||
pub tags: Vec<TagGroup<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
|
||||||
pub struct TagGroup<'a> {
|
|
||||||
pub kind: Ident<Cow<'a, str>>,
|
|
||||||
pub tags: Vec<Tag<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
|
||||||
pub struct Tag<'a> {
|
|
||||||
pub name: Ident<Cow<'a, str>>,
|
|
||||||
pub entries: Vec<VarInt>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,12 +2,13 @@ use std::io::{Read, Write};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use byteorder::ReadBytesExt;
|
use byteorder::ReadBytesExt;
|
||||||
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::protocol::{Decode, Encode};
|
use crate::protocol::{Decode, Encode};
|
||||||
|
|
||||||
/// An `i32` encoded with variable length.
|
/// An `i32` encoded with variable length.
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deserialize)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct VarInt(pub i32);
|
pub struct VarInt(pub i32);
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,6 @@ tracing.workspace = true
|
||||||
valence_core.workspace = true
|
valence_core.workspace = true
|
||||||
valence_nbt.workspace = true
|
valence_nbt.workspace = true
|
||||||
bevy_ecs.workspace = true
|
bevy_ecs.workspace = true
|
||||||
bevy_app.workspace = true
|
bevy_app.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
|
@ -25,6 +25,10 @@ use tracing::error;
|
||||||
use valence_core::ident::Ident;
|
use valence_core::ident::Ident;
|
||||||
use valence_nbt::{compound, Compound, List, Value};
|
use valence_nbt::{compound, Compound, List, Value};
|
||||||
|
|
||||||
|
mod tags;
|
||||||
|
|
||||||
|
pub use tags::*;
|
||||||
|
|
||||||
pub struct RegistryPlugin;
|
pub struct RegistryPlugin;
|
||||||
|
|
||||||
/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that
|
/// The [`SystemSet`] where the [`RegistryCodec`] cache is rebuilt. Systems that
|
||||||
|
@ -35,8 +39,11 @@ pub struct RegistryCodecSet;
|
||||||
impl Plugin for RegistryPlugin {
|
impl Plugin for RegistryPlugin {
|
||||||
fn build(&self, app: &mut bevy_app::App) {
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
app.init_resource::<RegistryCodec>()
|
app.init_resource::<RegistryCodec>()
|
||||||
|
.init_resource::<TagsRegistry>()
|
||||||
.configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate))
|
.configure_set(RegistryCodecSet.in_base_set(CoreSet::PostUpdate))
|
||||||
.add_system(cache_registry_codec.in_set(RegistryCodecSet));
|
.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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
84
crates/valence_registry/src/tags.rs
Normal file
84
crates/valence_registry/src/tags.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use valence_core::ident::Ident;
|
||||||
|
use valence_core::protocol::encode::{PacketWriter, WritePacket};
|
||||||
|
use valence_core::protocol::var_int::VarInt;
|
||||||
|
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
|
||||||
|
use valence_core::Server;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Encode, Decode, Packet)]
|
||||||
|
#[packet(id = packet_id::SYNCHRONIZE_TAGS_S2C)]
|
||||||
|
pub struct SynchronizeTagsS2c<'a> {
|
||||||
|
pub registries: Cow<'a, [Registry]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Resource, Default)]
|
||||||
|
pub struct TagsRegistry {
|
||||||
|
pub registries: Vec<Registry>,
|
||||||
|
cached_packet: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Encode, Decode)]
|
||||||
|
pub struct Registry {
|
||||||
|
pub registry: Ident<String>,
|
||||||
|
pub tags: Vec<TagEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Encode, Decode)]
|
||||||
|
pub struct TagEntry {
|
||||||
|
pub name: Ident<String>,
|
||||||
|
pub entries: Vec<VarInt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TagsRegistry {
|
||||||
|
pub(crate) fn build_synchronize_tags(&'a self) -> SynchronizeTagsS2c<'a> {
|
||||||
|
SynchronizeTagsS2c {
|
||||||
|
registries: Cow::Borrowed(&self.registries),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_tags_packet(&self) -> &Vec<u8> {
|
||||||
|
&self.cached_packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_tags_registry(mut tags: ResMut<TagsRegistry>) {
|
||||||
|
let registries =
|
||||||
|
serde_json::from_str::<Vec<Registry>>(include_str!("../../../extracted/tags.json"))
|
||||||
|
.expect("tags.json is invalid");
|
||||||
|
tags.registries = registries;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cache_tags_packet(server: Res<Server>, tags: ResMut<TagsRegistry>) {
|
||||||
|
if tags.is_changed() {
|
||||||
|
let tags = tags.into_inner();
|
||||||
|
let packet = tags.build_synchronize_tags();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
let mut scratch = vec![];
|
||||||
|
let mut writer =
|
||||||
|
PacketWriter::new(&mut bytes, server.compression_threshold(), &mut scratch);
|
||||||
|
writer.write_packet(&packet);
|
||||||
|
tags.cached_packet = bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::RegistryPlugin;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke_test() {
|
||||||
|
let mut app = bevy_app::App::new();
|
||||||
|
app.add_plugin(RegistryPlugin);
|
||||||
|
app.insert_resource(Server::default());
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let tags_registry = app.world.get_resource::<TagsRegistry>().unwrap();
|
||||||
|
let packet = tags_registry.build_synchronize_tags();
|
||||||
|
assert!(!packet.registries.is_empty());
|
||||||
|
assert!(!tags_registry.cached_packet.is_empty());
|
||||||
|
}
|
||||||
|
}
|
7235
extracted/tags.json
Normal file
7235
extracted/tags.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -74,9 +74,9 @@ public class Main implements ModInitializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
||||||
LOGGER.info("Server starting, Extracting registry codec...");
|
LOGGER.info("Server starting, Running startup extractors...");
|
||||||
|
// TODO: make `Codec` implement `Extractor`
|
||||||
var codecExtractor = new Codec(server);
|
var codecExtractor = new Codec(server);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var out = outputDirectory.resolve(codecExtractor.fileName());
|
var out = outputDirectory.resolve(codecExtractor.fileName());
|
||||||
var compound = codecExtractor.extract();
|
var compound = codecExtractor.extract();
|
||||||
|
@ -92,6 +92,22 @@ public class Main implements ModInitializer {
|
||||||
LOGGER.error("Extractor for \"" + codecExtractor.fileName() + "\" failed.", e);
|
LOGGER.error("Extractor for \"" + codecExtractor.fileName() + "\" failed.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var startupExtractors = new Extractor[]{
|
||||||
|
new Tags(server),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var ext : startupExtractors) {
|
||||||
|
try {
|
||||||
|
var out = outputDirectory.resolve(ext.fileName());
|
||||||
|
var fileWriter = new FileWriter(out.toFile(), StandardCharsets.UTF_8);
|
||||||
|
gson.toJson(ext.extract(), fileWriter);
|
||||||
|
fileWriter.close();
|
||||||
|
LOGGER.info("Wrote " + out.toAbsolutePath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Extractor for \"" + ext.fileName() + "\" failed.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.info("Done.");
|
LOGGER.info("Done.");
|
||||||
server.shutdown();
|
server.shutdown();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package rs.valence.extractor.extractors;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import net.minecraft.entity.SpawnGroup;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.collection.Weighted;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.registry.RegistryKey;
|
||||||
|
import net.minecraft.registry.RegistryKeys;
|
||||||
|
import net.minecraft.registry.RegistryWrapper;
|
||||||
|
import net.minecraft.registry.BuiltinRegistries;
|
||||||
|
import net.minecraft.registry.DynamicRegistryManager;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import rs.valence.extractor.Main;
|
||||||
|
import net.minecraft.registry.tag.TagKey;
|
||||||
|
import net.minecraft.registry.CombinedDynamicRegistries;
|
||||||
|
import net.minecraft.registry.ServerDynamicRegistryType;
|
||||||
|
import net.minecraft.registry.entry.RegistryEntry;
|
||||||
|
import net.minecraft.registry.entry.RegistryEntryList;
|
||||||
|
import net.minecraft.registry.SerializableRegistries;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class Tags implements Main.Extractor {
|
||||||
|
private CombinedDynamicRegistries<ServerDynamicRegistryType> dynamicRegistryManager;
|
||||||
|
|
||||||
|
public Tags(MinecraftServer server) {
|
||||||
|
this.dynamicRegistryManager = server.getCombinedDynamicRegistries();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fileName() {
|
||||||
|
return "tags.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement extract() {
|
||||||
|
var tagsJson = new JsonArray();
|
||||||
|
|
||||||
|
Map<RegistryKey<? extends Registry<?>>, Map<Identifier, JsonArray>> registryTags =
|
||||||
|
SerializableRegistries.streamRegistryManagerEntries(this.dynamicRegistryManager)
|
||||||
|
.map(registry -> Pair.of(registry.key(), serializeTags(registry.value())))
|
||||||
|
.filter(pair -> !(pair.getSecond()).isEmpty())
|
||||||
|
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
|
||||||
|
|
||||||
|
for (var registry : registryTags.entrySet()) {
|
||||||
|
var registryIdent = registry.getKey().getValue().toString();
|
||||||
|
var tagGroupJson = new JsonObject();
|
||||||
|
var tagGroupTagsJson = new JsonArray();
|
||||||
|
|
||||||
|
for (var tag : registry.getValue().entrySet()) {
|
||||||
|
var tagJson = new JsonObject();
|
||||||
|
var ident = tag.getKey().toString();
|
||||||
|
var raw_ids = tag.getValue();
|
||||||
|
|
||||||
|
tagJson.addProperty("name", ident);
|
||||||
|
tagJson.add("entries", raw_ids);
|
||||||
|
tagGroupTagsJson.add(tagJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
tagGroupJson.addProperty("registry", registryIdent.toString());
|
||||||
|
tagGroupJson.add("tags", tagGroupTagsJson);
|
||||||
|
tagsJson.add(tagGroupJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagsJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Map<Identifier, JsonArray> serializeTags(Registry<T> registry) {
|
||||||
|
HashMap<Identifier, JsonArray> map = new HashMap<Identifier, JsonArray>();
|
||||||
|
registry.streamTagsAndEntries().forEach(pair -> {
|
||||||
|
RegistryEntryList<T> registryEntryList = (RegistryEntryList<T>)pair.getSecond();
|
||||||
|
JsonArray intList = new JsonArray(registryEntryList.size());
|
||||||
|
for (RegistryEntry<T> registryEntry : registryEntryList) {
|
||||||
|
if (registryEntry.getType() != RegistryEntry.Type.REFERENCE) {
|
||||||
|
throw new IllegalStateException("Can't serialize unregistered value " + registryEntry);
|
||||||
|
}
|
||||||
|
intList.add(registry.getRawId(registryEntry.value()));
|
||||||
|
}
|
||||||
|
map.put(((TagKey)pair.getFirst()).id(), intList);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue