mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e38d2f298 | ||
|
|
d7e8c1cc92 | ||
|
|
f5ea7f154d | ||
|
|
b0ef41c9c2 | ||
|
|
9c642a280b | ||
|
|
6095d4880c | ||
|
|
923f425f19 | ||
|
|
a536b00c30 | ||
|
|
23c5335910 | ||
|
|
998f8f4755 | ||
|
|
bd2774b133 | ||
|
|
10e715be15 | ||
|
|
ba59ac2ccf |
No files matched your search
@@ -192,9 +192,17 @@ impl Assets {
|
||||
pub fn reload(&self, root: &Path) -> Result<RenderAssets> {
|
||||
RenderAssets::load(root, self)
|
||||
}
|
||||
/// [`Assets::reload`] for a server, which draws nothing: the models and
|
||||
/// the sprites' layers, without the textures' pixels.
|
||||
pub fn reload_for_server(&self, root: &Path) -> Result<RenderAssets> {
|
||||
RenderAssets::load_with(root, self, false)
|
||||
}
|
||||
}
|
||||
impl RenderAssets {
|
||||
pub fn load(root: &Path, assets: &Assets) -> Result<Self> {
|
||||
Self::load_with(root, assets, true)
|
||||
}
|
||||
fn load_with(root: &Path, assets: &Assets, pixels: bool) -> Result<Self> {
|
||||
let registry = assets.registry;
|
||||
let mut sprites = crate::sprites::Sprites::with_disk_cache(root);
|
||||
for name in MATERIALS {
|
||||
@@ -218,7 +226,11 @@ impl RenderAssets {
|
||||
sprites.insert(root, &format!("minecraft:block/destroy_stage_{stage}"))?;
|
||||
}
|
||||
sprites.save_disk_cache();
|
||||
sprites.finish();
|
||||
if pixels {
|
||||
sprites.finish();
|
||||
} else {
|
||||
sprites.finish_without_pixels();
|
||||
}
|
||||
// `finish` grows the buffer by doubling: keep only the bytes in use.
|
||||
sprites.pixels.shrink_to_fit();
|
||||
Ok(Self {
|
||||
|
||||
@@ -865,13 +865,23 @@ const VIEW_DISTANCE: f64 = 64.0;
|
||||
|
||||
impl BlockEntityRenderer {
|
||||
pub fn load(root: &Path, registry: &BlockRegistry) -> crate::Result<Self> {
|
||||
let visibility = crate::models::load_visibility(root, registry)?;
|
||||
Self::load_with_visibility(root, registry, visibility)
|
||||
}
|
||||
/// [`BlockEntityRenderer::load`] over the face visibility the meshes use
|
||||
/// (`MeshTables::visibility`): a clone shares its shapes and their faces.
|
||||
pub fn load_with_visibility(
|
||||
root: &Path,
|
||||
registry: &BlockRegistry,
|
||||
visibility: FaceVisibility,
|
||||
) -> crate::Result<Self> {
|
||||
let all: Value = serde_json::from_slice(&std::fs::read(root.join("entity-models.json"))?)?;
|
||||
let models = MODELS
|
||||
.iter()
|
||||
.filter_map(|(key, layer)| all.get(*layer).map(|v| (*key, ModelPart::parse(v))))
|
||||
.collect();
|
||||
let mut renderer = Self::new(models, registry);
|
||||
renderer.visibility = Some(Mutex::new(crate::models::load_visibility(root, registry)?));
|
||||
renderer.visibility = Some(Mutex::new(visibility));
|
||||
// Sherd items: provides_pottery_pattern -> decorated_pot_pattern asset id.
|
||||
if let Ok(bytes) = std::fs::read(root.join("items.json")) {
|
||||
let items: Value = serde_json::from_slice(&bytes)?;
|
||||
|
||||
@@ -159,6 +159,14 @@ impl Sprites {
|
||||
/// up to a power of two and limited to [`MAX_ARRAY_BYTES`]. Smaller
|
||||
/// textures are scaled up by whole pixels, so UVs stay per layer.
|
||||
pub fn finish(&mut self) {
|
||||
self.lay_out(true);
|
||||
}
|
||||
/// [`Sprites::finish`] for a server, which draws nothing: the layers'
|
||||
/// size, but neither their pixels nor the animations' frames.
|
||||
pub fn finish_without_pixels(&mut self) {
|
||||
self.lay_out(false);
|
||||
}
|
||||
fn lay_out(&mut self, pixels: bool) {
|
||||
let layers = self.native.len().max(1) as u64;
|
||||
let largest = self.native.iter().map(|n| n.0).max().unwrap_or(SPRITE_SIZE);
|
||||
self.minimum_size = self.native.iter().map(|n| n.0).min().unwrap_or(SPRITE_SIZE);
|
||||
@@ -166,6 +174,12 @@ impl Sprites {
|
||||
while size > SPRITE_SIZE && layers * u64::from(size * size) * 4 > MAX_ARRAY_BYTES {
|
||||
size /= 2;
|
||||
}
|
||||
if !pixels {
|
||||
self.pixels = Vec::new();
|
||||
self.animations = Vec::new();
|
||||
self.size = size;
|
||||
return;
|
||||
}
|
||||
let mut resized = Vec::with_capacity(self.native.len());
|
||||
self.pixels.clear();
|
||||
for (from, frames, initial) in &self.native {
|
||||
|
||||
@@ -16,11 +16,15 @@ use std::{
|
||||
collections::{BinaryHeap, HashSet, VecDeque},
|
||||
sync::{
|
||||
Arc, Condvar, Mutex, MutexGuard, RwLock, TryLockError,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
/// How long a worker waits with nothing to do before it gives back its
|
||||
/// generator's scratch buffers ([`ChunkGenerator::idle`]).
|
||||
pub const IDLE_RELEASE: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
|
||||
/// A chunk under generation, opaque to the streamer.
|
||||
pub type Proto = Box<dyn std::any::Any + Send>;
|
||||
/// Chunk generation in vanilla's stages: a neighbour-independent proto chunk
|
||||
@@ -57,6 +61,9 @@ pub trait ChunkGenerator: Send + Sync + 'static {
|
||||
/// What a loaded chunk needs from the generator before the game uses it,
|
||||
/// on the worker that loaded it.
|
||||
fn prepare_loaded(&self, _worker: &mut Self::Worker, _chunk: &Chunk) {}
|
||||
/// The worker has had nothing to do for [`IDLE_RELEASE`]: give back what
|
||||
/// it keeps between jobs only to go faster.
|
||||
fn idle(&self, _worker: &mut Self::Worker) {}
|
||||
fn proto(&self, worker: &mut Self::Worker, x: i32, z: i32) -> Proto;
|
||||
fn decorates(&self) -> bool {
|
||||
false
|
||||
@@ -220,6 +227,9 @@ struct Shared {
|
||||
/// packs reload; each mesh job takes the current one.
|
||||
context: RwLock<Arc<WorkerContext>>,
|
||||
done: Mailbox,
|
||||
/// Workers that have freed their buffers ([`IDLE_RELEASE`]) and taken no
|
||||
/// job since: the last of them gives the freed memory back to the OS.
|
||||
released: AtomicUsize,
|
||||
}
|
||||
/// Finished jobs, from the workers to the game thread, in the order they
|
||||
/// finished. Not an `mpsc` channel: its receiver waits for a sender it finds
|
||||
@@ -513,7 +523,9 @@ impl Streaming {
|
||||
stop: AtomicBool::new(false),
|
||||
context: RwLock::new(context.clone()),
|
||||
done: Mailbox::default(),
|
||||
released: AtomicUsize::new(0),
|
||||
});
|
||||
let workers = threads.max(1);
|
||||
let light_tables = context.light.clone();
|
||||
let saves: Arc<dyn Saves> = generator.clone();
|
||||
let threads = (0..threads.max(1))
|
||||
@@ -534,14 +546,42 @@ impl Streaming {
|
||||
loop {
|
||||
let job = {
|
||||
let mut queue = shared.queue.lock().unwrap();
|
||||
let mut released = false;
|
||||
loop {
|
||||
if shared.stop.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
if let Some(job) = queue.pop() {
|
||||
if released {
|
||||
shared.released.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
break job.job;
|
||||
}
|
||||
queue = shared.ready.wait(queue).unwrap();
|
||||
if released {
|
||||
queue = shared.ready.wait(queue).unwrap();
|
||||
continue;
|
||||
}
|
||||
let (woken, wait) =
|
||||
shared.ready.wait_timeout(queue, IDLE_RELEASE).unwrap();
|
||||
queue = woken;
|
||||
if wait.timed_out() && queue.is_empty() {
|
||||
// A server nobody is on, a client standing still:
|
||||
// free the generator's buffers (and the mesher's
|
||||
// face cache) until the next job needs them.
|
||||
drop(queue);
|
||||
generator.idle(&mut worker);
|
||||
faces = None;
|
||||
released = true;
|
||||
// The allocator keeps freed pages until memory
|
||||
// runs short: once every worker has freed its own,
|
||||
// hand them back.
|
||||
if shared.released.fetch_add(1, Ordering::Relaxed) + 1
|
||||
== workers
|
||||
{
|
||||
horizon_threads::release_free_memory();
|
||||
}
|
||||
queue = shared.queue.lock().unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
let done = match job {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -66,6 +67,25 @@ fn frame(v: &Value) -> Frame {
|
||||
}
|
||||
}
|
||||
impl Tree {
|
||||
/// [`Tree::load`], shared while anyone holds it: a server's advancement
|
||||
/// packets (`ServerAdvancements`) and its game's criteria read the same
|
||||
/// files, and each kept its own copy (7 MB).
|
||||
pub fn shared(data: &Path) -> Arc<Tree> {
|
||||
static TREES: Mutex<Vec<(PathBuf, Weak<Tree>)>> = Mutex::new(Vec::new());
|
||||
// Held while loading: a second reader waits for the first one's tree.
|
||||
let mut trees = TREES.lock().unwrap_or_else(|e| e.into_inner());
|
||||
trees.retain(|(_, tree)| tree.strong_count() > 0);
|
||||
if let Some(tree) = trees
|
||||
.iter()
|
||||
.find(|(path, _)| path == data)
|
||||
.and_then(|(_, tree)| tree.upgrade())
|
||||
{
|
||||
return tree;
|
||||
}
|
||||
let tree = Arc::new(Tree::load(data));
|
||||
trees.push((data.to_path_buf(), Arc::downgrade(&tree)));
|
||||
tree
|
||||
}
|
||||
/// Loads `<data>/<namespace>/advancement/**/*.json`.
|
||||
pub fn load(data: &Path) -> Tree {
|
||||
let mut tree = Tree::default();
|
||||
|
||||
@@ -1336,6 +1336,17 @@ impl UiResources {
|
||||
panorama: Arc::new(horizon_client::gfx::panorama::Faces::load(root)),
|
||||
})
|
||||
}
|
||||
/// What a server's game holds, since it draws nothing: the font, which books
|
||||
/// and chat measure with, and a one-pixel panorama.
|
||||
pub fn server(root: &std::path::Path) -> Result<Self> {
|
||||
Ok(Self {
|
||||
atlas: HudAtlas::fonts(root)?,
|
||||
panorama: Arc::new(horizon_client::gfx::panorama::Faces {
|
||||
size: 1,
|
||||
pixels: [0u8; 4].repeat(6),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
/// What a resource pack reload replaces (`Minecraft.reloadResourcePacks`):
|
||||
/// block models and sprites with their meshing tables, item models,
|
||||
@@ -15010,6 +15021,7 @@ fn load_resources(
|
||||
settings: &Settings,
|
||||
started: Instant,
|
||||
menu: bool,
|
||||
headless: bool,
|
||||
progress: &Progress,
|
||||
publish: &mut dyn FnMut(UiResources, &std::path::Path),
|
||||
) -> Result<Loaded> {
|
||||
@@ -15021,7 +15033,16 @@ fn load_resources(
|
||||
let mut loaded_gui = None;
|
||||
let mut ui_ready = false;
|
||||
let mut pack_error = None;
|
||||
let ui = match UiResources::load(&root, vanilla_root) {
|
||||
// A dedicated server builds only the font (UiResources::server), as the
|
||||
// integrated server's game does: the full atlas and panorama are 144 MB.
|
||||
let load_ui = |root: &std::path::Path| {
|
||||
if headless {
|
||||
UiResources::server(root)
|
||||
} else {
|
||||
UiResources::load(root, vanilla_root)
|
||||
}
|
||||
};
|
||||
let ui = match load_ui(&root) {
|
||||
Ok(ui) => Some(ui),
|
||||
// Minecraft.rollbackResourcePacks: the packs' textures don't fit, start
|
||||
// with the game's own resources.
|
||||
@@ -15047,8 +15068,16 @@ fn load_resources(
|
||||
ui_ready = true;
|
||||
}
|
||||
progress.set(0);
|
||||
// Nor the block sprites' pixels, which only the renderer reads (32 MB).
|
||||
let reload = |assets: &Assets, root: &std::path::Path| {
|
||||
if headless {
|
||||
assets.reload_for_server(root)
|
||||
} else {
|
||||
assets.reload(root)
|
||||
}
|
||||
};
|
||||
let (assets, render) = match Assets::load(&root).and_then(|assets| {
|
||||
let render = assets.reload(&root)?;
|
||||
let render = reload(&assets, &root)?;
|
||||
Ok((assets, render))
|
||||
}) {
|
||||
Ok(bundle) => bundle,
|
||||
@@ -15059,7 +15088,7 @@ fn load_resources(
|
||||
pack_error = Some(e.to_string());
|
||||
root = vanilla_root.to_path_buf();
|
||||
let assets = Assets::load(vanilla_root)?;
|
||||
let render = assets.reload(vanilla_root)?;
|
||||
let render = reload(&assets, vanilla_root)?;
|
||||
(assets, render)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -15073,7 +15102,7 @@ fn load_resources(
|
||||
let render = Arc::new(render);
|
||||
// The menu bundle must come from the same root as the world bundle.
|
||||
if !ui_ready || root != requested_root {
|
||||
let ui = UiResources::load(&root, vanilla_root)?;
|
||||
let ui = load_ui(&root)?;
|
||||
if menu {
|
||||
horizon_client::telemetry::mark_ui_ready();
|
||||
log::info!(
|
||||
@@ -15170,6 +15199,9 @@ fn load_resources(
|
||||
prepared_at.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
// The block entity renderer's face visibility: the meshes' own, whose
|
||||
// shapes already hold their faces, not a second load of the same tables.
|
||||
let visibility = pack.context.tables.visibility.clone();
|
||||
let rest_loader = if menu {
|
||||
let resources = resources.clone();
|
||||
let root = root.clone();
|
||||
@@ -15184,6 +15216,7 @@ fn load_resources(
|
||||
&assets,
|
||||
&behaviour_json,
|
||||
&gameplay_projection,
|
||||
visibility,
|
||||
&progress,
|
||||
started,
|
||||
)
|
||||
@@ -15211,6 +15244,7 @@ fn load_resources(
|
||||
&assets,
|
||||
&behaviour_json,
|
||||
&gameplay_projection,
|
||||
visibility,
|
||||
progress,
|
||||
started,
|
||||
)?;
|
||||
@@ -15238,6 +15272,7 @@ fn load_world_rest(
|
||||
assets: &Assets,
|
||||
behaviour_json: &serde_json::Value,
|
||||
gameplay_projection: &[horizon_client::blocks::GameplayProjection],
|
||||
visibility: horizon_world::block_render::FaceVisibility,
|
||||
progress: &Progress,
|
||||
started: Instant,
|
||||
) -> Result<WorldRest> {
|
||||
@@ -15273,10 +15308,12 @@ fn load_world_rest(
|
||||
recipes,
|
||||
transformers,
|
||||
providers: Providers::load(root),
|
||||
block_entity_render: horizon_client::block_entity_render::BlockEntityRenderer::load(
|
||||
root,
|
||||
registry_owner.registry,
|
||||
)?,
|
||||
block_entity_render:
|
||||
horizon_client::block_entity_render::BlockEntityRenderer::load_with_visibility(
|
||||
root,
|
||||
registry_owner.registry,
|
||||
visibility,
|
||||
)?,
|
||||
animate,
|
||||
fog,
|
||||
music: horizon_client::music::MusicTables::load(root),
|
||||
@@ -16143,6 +16180,7 @@ pub fn run() -> Result<()> {
|
||||
&early_settings,
|
||||
started,
|
||||
false,
|
||||
options.dedicated.is_some(),
|
||||
&Progress::new(),
|
||||
&mut |_, _| {},
|
||||
)?;
|
||||
|
||||
@@ -115,7 +115,7 @@ pub(super) struct AdvState {
|
||||
pub(super) announced: Vec<Text>,
|
||||
/// The tree being read on another thread since the world opened
|
||||
/// (`prefetch_advancement_tree`), taken by the first `advancement_tree`.
|
||||
pub(super) pending_tree: Option<std::thread::JoinHandle<Tree>>,
|
||||
pub(super) pending_tree: Option<std::thread::JoinHandle<Arc<Tree>>>,
|
||||
/// `inventory_changed`'s criteria by item, built for `tree`.
|
||||
inventory_index: Option<(Arc<Tree>, Arc<adv::InventoryIndex>)>,
|
||||
}
|
||||
@@ -261,9 +261,9 @@ impl Game<'_> {
|
||||
if self.adv.tree.list.is_empty() {
|
||||
let tree = match self.adv.pending_tree.take().map(|h| h.join()) {
|
||||
Some(Ok(tree)) => tree,
|
||||
_ => Tree::load(&self.options.resources.join("data")),
|
||||
_ => Tree::shared(&self.options.resources.join("data")),
|
||||
};
|
||||
self.adv.tree = Arc::new(tree);
|
||||
self.adv.tree = tree;
|
||||
// With the tree, while the world loads: built on the first
|
||||
// inventory change it cost that tick ~10 ms.
|
||||
self.inventory_index();
|
||||
@@ -299,7 +299,7 @@ impl Game<'_> {
|
||||
let data = self.options.resources.join("data");
|
||||
self.adv.pending_tree = std::thread::Builder::new()
|
||||
.name("horizon-advancements".into())
|
||||
.spawn(move || Tree::load(&data))
|
||||
.spawn(move || Tree::shared(&data))
|
||||
.ok();
|
||||
}
|
||||
pub(super) fn load_advancements(&mut self) {
|
||||
|
||||
@@ -19,7 +19,6 @@ use super::{
|
||||
use horizon_client::{
|
||||
Result,
|
||||
audio::Audio,
|
||||
hud::HudAtlas,
|
||||
lan::{Lan, MAX_PLAYERS, NetConfig, hub::HubConfig},
|
||||
settings::Settings,
|
||||
title::WorldOptions,
|
||||
@@ -850,15 +849,7 @@ fn server_game(
|
||||
options: Options,
|
||||
settings: Settings,
|
||||
) -> Result<Game<'_>> {
|
||||
// It draws nothing: only the font, which books and chat measure with.
|
||||
let atlas = HudAtlas::fonts(&options.resources)?;
|
||||
let gui = UiResources {
|
||||
atlas,
|
||||
panorama: Arc::new(horizon_client::gfx::panorama::Faces {
|
||||
size: 1,
|
||||
pixels: [0u8; 4].repeat(6),
|
||||
}),
|
||||
};
|
||||
let gui = UiResources::server(&options.resources)?;
|
||||
let audio = Audio::silent(&options.resources);
|
||||
Game::new(res, pack, gui, options, settings, audio, None)
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ pub(super) fn spawn_loader(
|
||||
&settings,
|
||||
started,
|
||||
true,
|
||||
false,
|
||||
&progress,
|
||||
&mut |gui, root| {
|
||||
let _ = sender.send(LoaderMsg::Ui(gui, root.to_path_buf()));
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::{
|
||||
};
|
||||
|
||||
pub struct ServerAdvancements {
|
||||
tree: Tree,
|
||||
tree: std::sync::Arc<Tree>,
|
||||
kids: Vec<Vec<usize>>,
|
||||
layout: Vec<Option<(i32, f32)>>,
|
||||
/// Recipe files by id.
|
||||
@@ -35,7 +35,7 @@ pub struct FirstTick {
|
||||
|
||||
impl ServerAdvancements {
|
||||
pub fn load(resources: &Path) -> Self {
|
||||
let tree = Tree::load(&resources.join("data"));
|
||||
let tree = Tree::shared(&resources.join("data"));
|
||||
let kids = tree.children();
|
||||
let layout = tree.layout();
|
||||
let mut recipes = BTreeMap::new();
|
||||
|
||||
@@ -4112,6 +4112,9 @@ impl<G: ChunkGenerator> ChunkGenerator for Persistent<G> {
|
||||
fn prepare_loaded(&self, worker: &mut G::Worker, chunk: &Chunk) {
|
||||
self.inner.prepare_loaded(worker, chunk);
|
||||
}
|
||||
fn idle(&self, worker: &mut G::Worker) {
|
||||
self.inner.idle(worker);
|
||||
}
|
||||
fn proto(&self, worker: &mut G::Worker, x: i32, z: i32) -> crate::streaming::Proto {
|
||||
let mut proto = self.inner.proto(worker, x, z);
|
||||
if let Some(saved) = self
|
||||
|
||||
@@ -496,6 +496,9 @@ impl ChunkGenerator for VanillaTerrain {
|
||||
fn lod_sampler(self: Arc<Self>) -> Box<dyn LodSampler> {
|
||||
Box::new(VanillaLodSampler::new(self))
|
||||
}
|
||||
fn idle(&self, worker: &mut GeneratorWorker) {
|
||||
worker.release();
|
||||
}
|
||||
fn lod_surface(
|
||||
&self,
|
||||
worker: &mut GeneratorWorker,
|
||||
|
||||
@@ -14,6 +14,11 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Block states by block name and by tag name ([`FeatureBlockBindings::bind_tags`]).
|
||||
pub type BoundBlockTags = (
|
||||
Arc<BTreeMap<String, Arc<BTreeSet<BlockStateId>>>>,
|
||||
Arc<BTreeMap<String, Arc<BTreeSet<BlockStateId>>>>,
|
||||
);
|
||||
pub struct FeatureBlockBindings<'r> {
|
||||
registry: &'r BlockRegistry,
|
||||
pub(crate) simple_blocks: Option<(
|
||||
@@ -27,8 +32,8 @@ pub struct FeatureBlockBindings<'r> {
|
||||
biomes: Option<Arc<super::biome_predicate::BiomePredicateBindings>>,
|
||||
pub(crate) fluids: super::fluid_predicate::FluidPredicateBindings,
|
||||
pub(crate) codec: BlockStateJsonCodec<'r>,
|
||||
pub(crate) blocks: BTreeMap<String, Arc<BTreeSet<BlockStateId>>>,
|
||||
pub(crate) tags: BTreeMap<String, Arc<BTreeSet<BlockStateId>>>,
|
||||
pub(crate) blocks: Arc<BTreeMap<String, Arc<BTreeSet<BlockStateId>>>>,
|
||||
pub(crate) tags: Arc<BTreeMap<String, Arc<BTreeSet<BlockStateId>>>>,
|
||||
pub(crate) air: [BlockStateId; 3],
|
||||
tree_blocks: std::sync::OnceLock<Arc<super::tree_blocks::TreeBlocks>>,
|
||||
pub(crate) structures: Option<Arc<super::structure::StructureResources>>,
|
||||
@@ -126,8 +131,15 @@ impl<'r> FeatureBlockBindings<'r> {
|
||||
integers: BTreeSet<(String, String)>,
|
||||
tags: &BTreeMap<String, Vec<String>>,
|
||||
) -> Result<Self, DecodeError> {
|
||||
let codec = BlockStateJsonCodec::new(registry, integers)
|
||||
.map_err(|e| DecodeError::Registry(e.to_string()))?;
|
||||
let bound = Self::bind_tags(registry, tags)?;
|
||||
Self::with_bound_tags(registry, integers, bound)
|
||||
}
|
||||
/// Every block's states and every tag's, by name: what `with_tags` binds,
|
||||
/// to share between the bindings made over the same registry and tags.
|
||||
pub fn bind_tags(
|
||||
registry: &BlockRegistry,
|
||||
tags: &BTreeMap<String, Vec<String>>,
|
||||
) -> Result<BoundBlockTags, DecodeError> {
|
||||
let mut blocks = BTreeMap::<String, BTreeSet<BlockStateId>>::new();
|
||||
for id in 0..registry.len() {
|
||||
let id = BlockStateId(id as u32);
|
||||
@@ -153,6 +165,25 @@ impl<'r> FeatureBlockBindings<'r> {
|
||||
return Err(DecodeError::Field("duplicate block tag"));
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
Arc::new(
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|(name, states)| (name, Arc::new(states)))
|
||||
.collect(),
|
||||
),
|
||||
Arc::new(bound_tags),
|
||||
))
|
||||
}
|
||||
/// [`FeatureBlockBindings::with_tags`] over tags already bound by
|
||||
/// [`FeatureBlockBindings::bind_tags`].
|
||||
pub fn with_bound_tags(
|
||||
registry: &'r BlockRegistry,
|
||||
integers: BTreeSet<(String, String)>,
|
||||
(blocks, tags): BoundBlockTags,
|
||||
) -> Result<Self, DecodeError> {
|
||||
let codec = BlockStateJsonCodec::new(registry, integers)
|
||||
.map_err(|e| DecodeError::Registry(e.to_string()))?;
|
||||
let get = |name| {
|
||||
registry
|
||||
.default_state(name)
|
||||
@@ -167,11 +198,8 @@ impl<'r> FeatureBlockBindings<'r> {
|
||||
occlusion_faces: None,
|
||||
can_occlude: None,
|
||||
fluids: super::fluid_predicate::FluidPredicateBindings::new(registry)?,
|
||||
blocks: blocks
|
||||
.into_iter()
|
||||
.map(|(name, states)| (name, Arc::new(states)))
|
||||
.collect(),
|
||||
tags: bound_tags,
|
||||
blocks,
|
||||
tags,
|
||||
tree_blocks: std::sync::OnceLock::new(),
|
||||
structures: None,
|
||||
air: [
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::{
|
||||
|
||||
struct RegionBlockQueries {
|
||||
fluids: super::fluid_predicate::FluidPredicateBindings,
|
||||
tags: BTreeMap<String, Arc<BTreeSet<BlockStateId>>>,
|
||||
tags: Arc<BTreeMap<String, Arc<BTreeSet<BlockStateId>>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -184,6 +184,9 @@ pub struct GeneratorData {
|
||||
biome_carvers: Vec<Vec<Arc<WorldCarver>>>,
|
||||
carving_blocks: CarvingBlocks,
|
||||
pub(super) block_tags: BTreeMap<String, Vec<String>>,
|
||||
/// `block_tags` bound to block states, once for the feature bindings of
|
||||
/// decoration, structures and live features (`feature_block_bindings`).
|
||||
bound_block_tags: std::sync::OnceLock<super::block_feature::BoundBlockTags>,
|
||||
pub(super) biome_tags: BTreeMap<String, Vec<String>>,
|
||||
pub(super) feature_resources: Option<FeatureResources>,
|
||||
/// Templates, processor lists and per-state template transforms.
|
||||
@@ -557,6 +560,7 @@ impl GeneratorData {
|
||||
feature_resources,
|
||||
ring_cache: Mutex::new(Vec::new()),
|
||||
decorations: Mutex::new(Vec::new()),
|
||||
bound_block_tags: std::sync::OnceLock::new(),
|
||||
}))
|
||||
}
|
||||
/// The concentric ring positions (strongholds) of the generator for
|
||||
@@ -676,6 +680,21 @@ impl GeneratorData {
|
||||
pub(super) fn integers(&self) -> &BTreeSet<(String, String)> {
|
||||
&self.integers
|
||||
}
|
||||
/// Feature block bindings over this data's blocks and block tags. The tags'
|
||||
/// state sets are bound once and shared: each binding held 5 MB of them.
|
||||
pub(super) fn feature_block_bindings(
|
||||
&self,
|
||||
) -> Result<super::block_feature::FeatureBlockBindings<'static>, DecodeError> {
|
||||
use super::block_feature::FeatureBlockBindings;
|
||||
let bound = match self.bound_block_tags.get() {
|
||||
Some(bound) => bound.clone(),
|
||||
None => {
|
||||
let bound = FeatureBlockBindings::bind_tags(self.registry, &self.block_tags)?;
|
||||
self.bound_block_tags.get_or_init(|| bound).clone()
|
||||
}
|
||||
};
|
||||
FeatureBlockBindings::with_bound_tags(self.registry, self.integers.clone(), bound)
|
||||
}
|
||||
}
|
||||
|
||||
/// A generated chunk with its quart biomes. Blocks and heightmaps live in
|
||||
@@ -3071,6 +3090,22 @@ impl GeneratorWorker {
|
||||
pub fn set_structure_mobs(&mut self, mobs: Arc<dyn StructureMobs>) {
|
||||
self.state.structure_mobs = Some(mobs);
|
||||
}
|
||||
/// Give back the density scratch buffers and the lookup caches, which only
|
||||
/// speed up the next chunks (a generating worker holds megabytes of them).
|
||||
/// Results do not change: they are made again on demand.
|
||||
pub fn release(&mut self) {
|
||||
let state = &mut self.state;
|
||||
for context in [
|
||||
&mut state.terrain_context,
|
||||
&mut state.biome_context,
|
||||
&mut state.carver_context,
|
||||
] {
|
||||
context.clear_scratch();
|
||||
}
|
||||
state.carver_biomes = HashMap::new();
|
||||
state.structure_biomes = HashMap::new();
|
||||
state.heights = HashMap::new();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -134,10 +134,10 @@ impl Decoration {
|
||||
.collect(),
|
||||
&data.biome_tags,
|
||||
)?);
|
||||
let mut bindings =
|
||||
FeatureBlockBindings::with_tags(registry, data.integers().clone(), &data.block_tags)?
|
||||
.with_fluid_tags(&resources.fluid_tags)?
|
||||
.with_biomes(biomes.clone());
|
||||
let mut bindings = data
|
||||
.feature_block_bindings()?
|
||||
.with_fluid_tags(&resources.fluid_tags)?
|
||||
.with_biomes(biomes.clone());
|
||||
if let Some(structures) = &data.structures {
|
||||
bindings = bindings.with_structures(structures.clone());
|
||||
}
|
||||
|
||||
@@ -203,11 +203,7 @@ impl StructureState {
|
||||
for (i, def) in registry.structures.iter().enumerate() {
|
||||
by_step[def.step].push(i);
|
||||
}
|
||||
let mut bindings = FeatureBlockBindings::with_tags(
|
||||
data.registry(),
|
||||
data.integers().clone(),
|
||||
&data.block_tags,
|
||||
)?;
|
||||
let mut bindings = data.feature_block_bindings()?;
|
||||
// Processors that test what a block is made of read canOcclude.
|
||||
if let Some(blocks) = data
|
||||
.feature_resources
|
||||
|
||||
@@ -103,10 +103,10 @@ impl LiveFeatures {
|
||||
.collect(),
|
||||
&data.biome_tags,
|
||||
)?);
|
||||
let mut bindings =
|
||||
FeatureBlockBindings::with_tags(registry, data.integers().clone(), &data.block_tags)?
|
||||
.with_fluid_tags(&resources.fluid_tags)?
|
||||
.with_biomes(biomes.clone());
|
||||
let mut bindings = data
|
||||
.feature_block_bindings()?
|
||||
.with_fluid_tags(&resources.fluid_tags)?
|
||||
.with_biomes(biomes.clone());
|
||||
if let Some(structures) = &data.structures {
|
||||
bindings = bindings.with_structures(structures.clone());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user