mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
cb9230ec34
This PR redesigns Valence's architecture around the Bevy Entity Component System framework (`bevy_ecs` and `bevy_app`). Along the way, a large number of changes and improvements have been made. - Valence is now a Bevy plugin. This allows Valence to integrate with the wider Bevy ecosystem. - The `Config` trait has been replaced with the plugin struct which is much easier to configure. Async callbacks are grouped into their own trait. - `World` has been renamed to `Instance` to avoid confusion with `bevy_ecs::world::World`. - Entities, clients, player list, and inventories are all just ECS components/resources. There is no need for us to have our own generational arena/slotmap/etc for each one. - Client events use Bevy's event system. Users can read events with the `EventReader` system parameter. This also means that events are dispatched at an earlier stage of the program where access to the full server is available. There is a special "event loop" stage which is used primarily to avoid the loss of ordering information between events. - Chunks have been completely overhauled to be simpler and faster. The distinction between loaded and unloaded chunks has been mostly eliminated. The per-section bitset that tracked changes has been removed, which should further reduce memory usage. More operations on chunks are available such as removal and cloning. - The full client's game profile is accessible rather than just the textures. - Replaced `vek` with `glam` for parity with Bevy. - Basic inventory support has been added. - Various small changes to `valence_protocol`. - New Examples - The terrain and anvil examples are now fully asynchronous and will not block the main tick loop while chunks are loading. # TODOs - [x] Implement and dispatch client events. - ~~[ ] Finish implementing the new entity/chunk update algorithm.~~ New approach ended up being slower. And also broken. - [x] [Update rust-mc-bot to 1.19.3](https://github.com/Eoghanmc22/rust-mc-bot/pull/3). - [x] Use rust-mc-bot to test for and fix any performance regressions. Revert to old entity/chunk update algorithm if the new one turns out to be slower for some reason. - [x] Make inventories an ECS component. - [x] Make player lists an ECS ~~component~~ resource. - [x] Expose all properties of the client's game profile. - [x] Update the examples. - [x] Update `valence_anvil`. - ~~[ ] Update `valence_spatial_index` to use `glam` instead of `vek`.~~ Maybe later - [x] Make entity events use a bitset. - [x] Update docs. Closes #69 Closes #179 Closes #53 --------- Co-authored-by: Carson McManus <dyc3@users.noreply.github.com> Co-authored-by: AviiNL <me@avii.nl> Co-authored-by: Danik Vitek <x3665107@gmail.com> Co-authored-by: Snowiiii <71594357+Snowiiii@users.noreply.github.com>
138 lines
4.1 KiB
Rust
138 lines
4.1 KiB
Rust
use std::fs::create_dir_all;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::{ensure, Context};
|
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
use fs_extra::dir::CopyOptions;
|
|
use reqwest::IntoUrl;
|
|
use valence::instance::Chunk;
|
|
use valence_anvil::AnvilWorld;
|
|
use zip::ZipArchive;
|
|
|
|
criterion_group!(benches, criterion_benchmark);
|
|
criterion_main!(benches);
|
|
|
|
fn criterion_benchmark(c: &mut Criterion) {
|
|
let world_dir = get_world_asset(
|
|
"https://github.com/valence-rs/valence-test-data/archive/refs/heads/asset/sp_world_1.19.2.zip",
|
|
"1.19.2 benchmark world",
|
|
true
|
|
).expect("failed to get world asset");
|
|
|
|
let mut world = AnvilWorld::new(world_dir);
|
|
|
|
c.bench_function("Load square 10x10", |b| {
|
|
b.iter(|| {
|
|
let world = black_box(&mut world);
|
|
|
|
for z in -5..5 {
|
|
for x in -5..5 {
|
|
let nbt = world
|
|
.read_chunk(x, z)
|
|
.expect("failed to read chunk")
|
|
.expect("missing chunk at position")
|
|
.data;
|
|
|
|
let mut chunk = Chunk::new(24);
|
|
|
|
valence_anvil::to_valence(&nbt, &mut chunk, 4, |_| Default::default()).unwrap();
|
|
|
|
black_box(chunk);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/// Loads the asset. If the asset is already present on the system due to a
|
|
/// prior run, the cached asset is used instead. If the asset is not
|
|
/// cached yet, this function downloads the asset using the current thread.
|
|
/// This will block until the download is complete.
|
|
///
|
|
/// returns: `PathBuf` The reference to the asset on the file system
|
|
fn get_world_asset(
|
|
url: impl IntoUrl,
|
|
dest_path: impl AsRef<Path>,
|
|
remove_top_level_dir: bool,
|
|
) -> anyhow::Result<PathBuf> {
|
|
let url = url.into_url()?;
|
|
let dest_path = dest_path.as_ref();
|
|
|
|
let asset_cache_dir = Path::new(".asset_cache");
|
|
|
|
create_dir_all(asset_cache_dir).context("unable to create `.asset_cache` directory")?;
|
|
|
|
let final_path = asset_cache_dir.join(dest_path);
|
|
|
|
if final_path.exists() {
|
|
return Ok(final_path);
|
|
}
|
|
|
|
let mut response = reqwest::blocking::get(url.clone())?;
|
|
|
|
let cache_download_directory = asset_cache_dir.join("downloads");
|
|
|
|
create_dir_all(&cache_download_directory)
|
|
.context("unable to create `.asset_cache/downloads` directory")?;
|
|
|
|
let mut downloaded_zip_file =
|
|
tempfile::tempfile_in(&cache_download_directory).context("Could not create temp file")?;
|
|
|
|
println!("Downloading {dest_path:?} from {url}");
|
|
|
|
response
|
|
.copy_to(&mut downloaded_zip_file)
|
|
.context("could not write web contents to the temporary file")?;
|
|
|
|
let mut zip_archive = ZipArchive::new(downloaded_zip_file)
|
|
.context("unable to create zip archive from downloaded content")?;
|
|
|
|
if !remove_top_level_dir {
|
|
zip_archive
|
|
.extract(&final_path)
|
|
.context("unable to unzip downloaded contents")?;
|
|
|
|
return Ok(final_path);
|
|
}
|
|
|
|
let temp_dir = tempfile::tempdir_in(&cache_download_directory)
|
|
.context("unable to create temporary directory in `.asset_cache`")?;
|
|
|
|
zip_archive
|
|
.extract(&temp_dir)
|
|
.context("unable to unzip downloaded contents")?;
|
|
|
|
let mut entries = temp_dir.path().read_dir()?;
|
|
|
|
let top_level_dir = entries
|
|
.next()
|
|
.context("the downloaded zip file was empty")??;
|
|
|
|
ensure!(
|
|
entries.next().is_none(),
|
|
"found more than one entry in the top level directory of the Zip file"
|
|
);
|
|
|
|
ensure!(
|
|
top_level_dir.path().is_dir(),
|
|
"the only content in the zip archive is a file"
|
|
);
|
|
|
|
create_dir_all(&final_path).context("could not create a directory inside the asset cache")?;
|
|
|
|
let dir_entries = top_level_dir
|
|
.path()
|
|
.read_dir()?
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
let items_to_move: Vec<_> = dir_entries.into_iter().map(|d| d.path()).collect();
|
|
|
|
fs_extra::move_items(&items_to_move, &final_path, &CopyOptions::new())?;
|
|
|
|
// We keep the temporary directory around until we're done moving files out
|
|
// of it.
|
|
drop(temp_dir);
|
|
|
|
Ok(final_path)
|
|
}
|