Skip to content

Commit 2628c8d

Browse files
committed
feat: update command line usage
1 parent 6bfd726 commit 2628c8d

File tree

12 files changed

+229
-129
lines changed

12 files changed

+229
-129
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "minecraft_world_optimizer"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2021"
55

66
[dependencies]

README.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ The Minecraft World Optimizer has only been successfully tested on 1.20.6 and 1.
3636

3737
The Minecraft World Optimizer as successfully been tested on the following worlds:
3838

39-
- **Server World:** Backed-up world of my survival server with over 13.9 million generated chunks.
39+
- **Server Worlds:** Backed-up world of my survival server with over 17.9 million generated chunks.
4040
- **New World:** New amplified world pre-generated using Chunky with a square radius of 1024.
4141

42-
| World | Before Optimization | After Optimization | Size Reduction |
43-
|--------------|------------------------------------------|----------------------------------------|----------------|
44-
| Server World | 105,681,020 Kilobytes <br/> 24,381 Files | 16,145,728 Kilobytes <br/> 6,961 Files | **84.7%** |
45-
| New World | 235,204 Kilobytes <br/> 36 Files | 2,440 Kilobytes <br/> 4 Files | **98.9%** |
42+
| World | Before Optimization | After Optimization | Size Reduction |
43+
|---------------|------------------------------------------|----------------------------------------|----------------|
44+
| Server Worlds | 134,079,252 Kilobytes <br/> 29,861 Files | 27,220,248 Kilobytes <br/> 9,734 Files | **79.7%** |
45+
| New World | 235,204 Kilobytes <br/> 36 Files | 2,440 Kilobytes <br/> 4 Files | **98.9%** |
4646

4747
## Getting Started
4848

@@ -72,25 +72,33 @@ cargo install --git https://github.com/Quozul/minecraft_world_optimizer.git
7272
## Usage
7373

7474
```shell
75-
minecraft_world_optimizer [WORLD_PATH]...
75+
# View up-to-date usage of the command:
76+
❯ minecraft_world_optimizer --help
77+
❯ minecraft_world_optimizer <MODE> <WORLD_PATHS>...
7678
```
7779

78-
Replace <WORLD_PATH> with the path to your Minecraft world folder containing region files.
80+
Replace mode with one of the following:
81+
82+
- `check`: the program will only check for chunks and region files that can be deleted without actually deleting any
83+
data. This mode is around two times faster than the write mode as it does not perform any file system operations.
84+
- `write`: the program will delete unused chunks and region files.
85+
86+
Replace <WORLD_PATHS> with the path to your Minecraft world folder containing region files.
7987
It will detect the 3 vanilla dimensions and optimise them. Note that this has not been tested on modded worlds with
8088
multiple dimensions.
8189

8290
Example:
8391

8492
```shell
85-
minecraft_world_optimizer ~/.minecraft/saves/MyWorld
93+
minecraft_world_optimizer check ~/.minecraft/saves/MyWorld
8694
```
8795

88-
It can also be used to optimize a server's worlds as dimensions are split in multiple worlds:
96+
It can also be used to optimize server worlds as dimensions are split in multiple worlds:
8997

9098
```shell
91-
minecraft_world_optimizer /path/to/server/world*
99+
minecraft_world_optimizer check /path/to/server/world*
92100
# Or if your shell does not support wildcard:
93-
minecraft_world_optimizer /path/to/server/world /path/to/server/world_nether /path/to/server/world_the_end
101+
minecraft_world_optimizer check /path/to/server/world /path/to/server/world_nether /path/to/server/world_the_end
94102
```
95103

96104
## Contributing

src/cli.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1-
use clap::Parser;
1+
use clap::{Parser, ValueEnum};
22
use std::path::PathBuf;
33

44
#[derive(Parser)]
55
#[command(
66
name = "minecraft_world_optimizer",
77
version = "1.0",
8-
about = "Optimizing Minecraft region files by deleting unused chunks.",
8+
about = "Optimizing Minecraft worlds by deleting unused region files and chunks.",
99
long_about = None,
1010
)]
1111
pub struct Cli {
12-
/// Path to your Minecraft Worlds containing `level.dat` file.
13-
pub world_path: Vec<PathBuf>,
12+
/// What mode to run the program in
13+
#[arg(value_enum, required = true)]
14+
pub mode: Mode,
1415

15-
/// Optimizes the world, if not set, will only count regions and chunks to be deleted.
16-
#[arg(short, long)]
17-
pub write: bool,
16+
/// Path to your Minecraft Worlds containing `level.dat` file
17+
#[arg(required = true)]
18+
pub world_paths: Vec<PathBuf>,
19+
}
20+
21+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
22+
pub enum Mode {
23+
/// Only counts of region files and chunks that can be deleted without making any change to the world
24+
Check,
25+
26+
/// Optimizes the world by deleting unused region files and chunks.
27+
/// This is a destructive process, make sure to make a backup of your worlds before running.
28+
/// Also make sure the world is not loaded by the game as this will corrupt the world.
29+
Write,
1830
}

src/commands/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod optimize_result;
2+
pub mod read;
3+
pub mod write;

src/commands/optimize_result.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::fmt::Display;
2+
3+
#[derive(Default, Clone)]
4+
pub struct OptimizeResult {
5+
pub total_chunks: usize,
6+
pub deleted_chunks: usize,
7+
pub deleted_regions: usize,
8+
}
9+
10+
impl Display for OptimizeResult {
11+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
12+
write!(
13+
f,
14+
"Optimization Result:\n\
15+
Total Chunks: {}\n\
16+
Deleted Chunks: {}\n\
17+
Deleted Regions: {}",
18+
self.total_chunks, self.deleted_chunks, self.deleted_regions
19+
)
20+
}
21+
}
22+
23+
pub fn reduce_optimize_results(results: &mut [OptimizeResult]) -> OptimizeResult {
24+
results
25+
.iter_mut()
26+
.reduce(|acc, cur| {
27+
acc.deleted_regions += cur.deleted_regions;
28+
acc.total_chunks += cur.total_chunks;
29+
acc.deleted_chunks += cur.deleted_chunks;
30+
acc
31+
})
32+
.cloned()
33+
.unwrap_or_default()
34+
}

src/commands/read.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::commands::optimize_result::{reduce_optimize_results, OptimizeResult};
2+
use crate::region_loader::region::Region;
3+
use crate::world::get_region_files::get_region_files;
4+
use indicatif::{ProgressBar, ProgressStyle};
5+
use rayon::iter::ParallelIterator;
6+
use rayon::prelude::IntoParallelRefIterator;
7+
use std::error::Error;
8+
use std::path::PathBuf;
9+
10+
pub fn execute_read(world_paths: &Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
11+
let entries = get_region_files(world_paths)?;
12+
let pb = ProgressBar::new(entries.len() as u64);
13+
let style = ProgressStyle::with_template(
14+
"{percent}% {bar} {pos}/{len} [{elapsed_precise}>{eta_precise}, {per_sec}]",
15+
)
16+
.unwrap();
17+
pb.set_style(style);
18+
19+
let mut results = entries
20+
.par_iter()
21+
.map(|entry| {
22+
let result = optimize_read(entry);
23+
pb.inc(1);
24+
result
25+
})
26+
.flatten()
27+
.collect::<Vec<OptimizeResult>>();
28+
29+
let result = reduce_optimize_results(&mut results);
30+
println!("{result}");
31+
32+
Ok(())
33+
}
34+
35+
fn optimize_read(region_file_path: &PathBuf) -> std::io::Result<OptimizeResult> {
36+
let mut result = OptimizeResult::default();
37+
38+
match Region::from_file_name(region_file_path) {
39+
Ok(region) => {
40+
let chunks = region.get_chunks();
41+
result.total_chunks += chunks.len();
42+
43+
for chunk in chunks {
44+
if chunk.should_delete() {
45+
result.deleted_chunks += 1;
46+
}
47+
}
48+
if result.deleted_chunks >= result.total_chunks {
49+
result.deleted_regions += 1;
50+
}
51+
}
52+
Err(_) => {
53+
result.deleted_regions += 1;
54+
}
55+
}
56+
57+
Ok(result)
58+
}

src/commands/write.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::commands::optimize_result::{reduce_optimize_results, OptimizeResult};
2+
use crate::region_loader::region::Region;
3+
use crate::world::get_region_files::get_region_files;
4+
use indicatif::{ProgressBar, ProgressStyle};
5+
use rayon::iter::ParallelIterator;
6+
use rayon::prelude::IntoParallelRefIterator;
7+
use std::error::Error;
8+
use std::path::PathBuf;
9+
10+
pub fn execute_write(world_paths: &Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
11+
let entries = get_region_files(world_paths)?;
12+
let pb = ProgressBar::new(entries.len() as u64);
13+
let style = ProgressStyle::with_template(
14+
"{percent}% {bar} {pos}/{len} [{elapsed_precise}>{eta_precise}, {per_sec}]",
15+
)
16+
.unwrap();
17+
pb.set_style(style);
18+
19+
let mut results = entries
20+
.par_iter()
21+
.map(|entry| {
22+
let result = optimize_write(entry);
23+
pb.inc(1);
24+
result
25+
})
26+
.flatten()
27+
.collect::<Vec<OptimizeResult>>();
28+
29+
let result = reduce_optimize_results(&mut results);
30+
println!("{result}");
31+
32+
Ok(())
33+
}
34+
35+
fn optimize_write(region_file_path: &PathBuf) -> std::io::Result<OptimizeResult> {
36+
let mut result = OptimizeResult::default();
37+
38+
match Region::from_file_name(region_file_path) {
39+
Ok(mut region) => {
40+
let chunks = region.get_chunks();
41+
result.total_chunks += chunks.len();
42+
43+
let mut chunks_to_delete = Vec::new();
44+
for chunk in chunks {
45+
if chunk.should_delete() {
46+
chunks_to_delete.push(chunk.clone());
47+
}
48+
}
49+
result.deleted_chunks += chunks_to_delete.len();
50+
51+
for chunk in &chunks_to_delete {
52+
region.remove_chunk(chunk);
53+
}
54+
55+
if region.is_empty() {
56+
result.deleted_regions += 1;
57+
std::fs::remove_file(region_file_path)?;
58+
} else {
59+
let bytes = region.to_bytes();
60+
std::fs::write(region_file_path, bytes)?;
61+
}
62+
}
63+
Err(_) => {
64+
result.deleted_regions += 1;
65+
std::fs::remove_file(region_file_path)?;
66+
}
67+
}
68+
69+
Ok(result)
70+
}

src/main.rs

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,23 @@
11
mod cli;
2+
mod commands;
23
mod nbt;
3-
mod optimizer;
44
mod region_loader;
55
mod world;
66

7-
use crate::cli::Cli;
8-
use crate::optimizer::{optimize_region_file, OptimizeResult};
9-
use crate::world::get_region_files::get_region_files;
10-
use crate::world::validate::validate_worlds;
7+
use crate::cli::{Cli, Mode};
8+
use crate::commands::read::execute_read;
9+
use crate::commands::write::execute_write;
1110
use clap::Parser;
12-
use indicatif::{ProgressBar, ProgressStyle};
13-
use rayon::prelude::*;
1411

1512
fn main() {
1613
let cli = Cli::parse();
1714

18-
match validate_worlds(&cli.world_path) {
19-
Err(err) => {
20-
eprintln!("{err}");
21-
}
22-
Ok(_) => {
23-
let entries = cli
24-
.world_path
25-
.iter()
26-
.flat_map(|world| get_region_files(world))
27-
.collect::<Vec<_>>();
15+
let result = match cli.mode {
16+
Mode::Write => execute_write(&cli.world_paths),
17+
Mode::Check => execute_read(&cli.world_paths),
18+
};
2819

29-
let pb = ProgressBar::new(entries.len() as u64);
30-
let style = ProgressStyle::with_template(
31-
"{percent}% {bar} {pos}/{len} [{elapsed_precise}>{eta_precise}, {per_sec}]",
32-
)
33-
.unwrap();
34-
pb.set_style(style);
35-
36-
let mut results = entries
37-
.par_iter()
38-
.map(|entry| {
39-
let result = optimize_region_file(entry, cli.write);
40-
pb.inc(1);
41-
result
42-
})
43-
.flatten()
44-
.collect::<Vec<OptimizeResult>>();
45-
46-
let result = results.iter_mut().reduce(|acc, cur| {
47-
acc.deleted_regions += cur.deleted_regions;
48-
acc.total_chunks += cur.total_chunks;
49-
acc.deleted_chunks += cur.deleted_chunks;
50-
acc
51-
});
52-
53-
println!("{:?}", result);
54-
}
20+
if let Err(err) = result {
21+
eprintln!("{err}");
5522
}
5623
}

0 commit comments

Comments
 (0)