Completion Part 2
This commit is contained in:
parent
a593f141ff
commit
b1f3f66006
19
Manual.md
19
Manual.md
@ -20,11 +20,12 @@ SQLite format maps are currently supported.
|
|||||||
|
|
||||||
## General usage
|
## General usage
|
||||||
|
|
||||||
`mapeditr [-h] <map> <subcommand>`
|
`mapeditr [-h] [-y] <map> <subcommand>`
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
- `-h`: Show a help message and exit.
|
- `-h, --help`: Print help information and exit.
|
||||||
|
- `-y, --yes`: Skip the default confirmation prompt (for those who feel brave).
|
||||||
- `<map>`: Path to the Minetest world/map to edit; this can be either a world
|
- `<map>`: Path to the Minetest world/map to edit; this can be either a world
|
||||||
directory or a `map.sqlite` file within a world folder. This file will be
|
directory or a `map.sqlite` file within a world folder. This file will be
|
||||||
modified, so *always* shut down the game/server before executing any command.
|
modified, so *always* shut down the game/server before executing any command.
|
||||||
@ -75,8 +76,8 @@ Arguments:
|
|||||||
|
|
||||||
- `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks
|
- `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks
|
||||||
fully within this area will be deleted.
|
fully within this area will be deleted.
|
||||||
- `--invert`: Delete all mapblocks fully *outside* the given area. Use with
|
- `--invert`: If present, delete all mapblocks fully *outside* the given area.
|
||||||
caution; you could erase a large portion of your world!
|
Use with caution; you could erase a large portion of your world!
|
||||||
|
|
||||||
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
|
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
|
||||||
will be invoked where the blocks were deleted, and this sometimes causes
|
will be invoked where the blocks were deleted, and this sometimes causes
|
||||||
@ -91,11 +92,11 @@ contents) are also deleted.
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
- `--node`: Only delete metadata of nodes with the given name. If not
|
- `--node`: (Optional) Name of node to delete metadata from. If not specified,
|
||||||
specified, metadata will be deleted in all matching nodes.
|
metadata will be deleted from any node.
|
||||||
- `--p1, --p2`: Area in which to delete metadata. If not specified, metadata
|
- `--p1, --p2`: (Optional) Area in which to delete metadata. If not specified,
|
||||||
will be deleted everywhere.
|
metadata will be deleted everywhere.
|
||||||
- `--invert`: Only delete metadata *outside* the given area.
|
- `--invert`: If present, delete metadata *outside* the given area.
|
||||||
|
|
||||||
### deleteobjects
|
### deleteobjects
|
||||||
|
|
||||||
|
@ -139,6 +139,12 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
|
|||||||
For additional information, see the manual.")
|
For additional information, see the manual.")
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
|
.arg(Arg::with_name("yes")
|
||||||
|
.long("yes")
|
||||||
|
.short("y")
|
||||||
|
.global(true)
|
||||||
|
.help("Skip the default confirmation prompt.")
|
||||||
|
)
|
||||||
// TODO: Move map arg to subcommands?
|
// TODO: Move map arg to subcommands?
|
||||||
.arg(Arg::with_name("map")
|
.arg(Arg::with_name("map")
|
||||||
.required(true)
|
.required(true)
|
||||||
@ -153,6 +159,7 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
|
|||||||
let sub_matches = matches.subcommand_matches(&sub_name).unwrap();
|
let sub_matches = matches.subcommand_matches(&sub_name).unwrap();
|
||||||
|
|
||||||
Ok(InstArgs {
|
Ok(InstArgs {
|
||||||
|
do_confirmation: !matches.is_present("yes"),
|
||||||
command: sub_name,
|
command: sub_name,
|
||||||
map_path: matches.value_of("map").unwrap().to_string(),
|
map_path: matches.value_of("map").unwrap().to_string(),
|
||||||
input_map_path: sub_matches.value_of("input_map").map(str::to_string),
|
input_map_path: sub_matches.value_of("input_map").map(str::to_string),
|
||||||
@ -214,7 +221,7 @@ fn print_editing_status(done: usize, total: usize, real_start: Instant,
|
|||||||
let num_bars = (progress * TOTAL_BARS as f32) as usize;
|
let num_bars = (progress * TOTAL_BARS as f32) as usize;
|
||||||
let bars = "=".repeat(num_bars);
|
let bars = "=".repeat(num_bars);
|
||||||
|
|
||||||
eprint!(
|
print!(
|
||||||
"\r[{bars:<total_bars$}] {progress:.1}% | {elapsed} elapsed \
|
"\r[{bars:<total_bars$}] {progress:.1}% | {elapsed} elapsed \
|
||||||
| {remaining} remaining",
|
| {remaining} remaining",
|
||||||
bars=bars,
|
bars=bars,
|
||||||
@ -228,7 +235,7 @@ fn print_editing_status(done: usize, total: usize, real_start: Instant,
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
eprint!("\rProcessing... {} elapsed", fmt_duration(real_elapsed));
|
print!("\rProcessing... {} elapsed", fmt_duration(real_elapsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
@ -239,13 +246,21 @@ fn print_log(log_type: LogType, msg: String) {
|
|||||||
let prefix = format!("{}: ", log_type);
|
let prefix = format!("{}: ", log_type);
|
||||||
let indented = msg.lines().collect::<Vec<_>>()
|
let indented = msg.lines().collect::<Vec<_>>()
|
||||||
.join(&format!( "\n{}", " ".repeat(prefix.len()) ));
|
.join(&format!( "\n{}", " ".repeat(prefix.len()) ));
|
||||||
eprintln!("{}{}", prefix, indented);
|
println!("{}{}", prefix, indented);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn get_confirmation() -> bool {
|
||||||
|
print!("Proceed? (Y/n): ");
|
||||||
|
let mut result = String::new();
|
||||||
|
std::io::stdin().read_line(&mut result).unwrap();
|
||||||
|
result.trim().to_ascii_lowercase() == "y"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn run_cmd_line() {
|
pub fn run_cmd_line() {
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use crate::instance::{InstState, InstEvent, spawn_compute_thread};
|
use crate::instance::{InstState, ServerEvent, spawn_compute_thread};
|
||||||
|
|
||||||
let args = match parse_cmd_line_args() {
|
let args = match parse_cmd_line_args() {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
@ -265,13 +280,24 @@ pub fn run_cmd_line() {
|
|||||||
let mut cur_state = InstState::Ignore;
|
let mut cur_state = InstState::Ignore;
|
||||||
let mut need_newline = false;
|
let mut need_newline = false;
|
||||||
|
|
||||||
|
let newline_if = |condition: &mut bool| {
|
||||||
|
if *condition {
|
||||||
|
println!();
|
||||||
|
*condition = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
loop { /* Main command-line logging loop */
|
loop { /* Main command-line logging loop */
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let mut forced_update = InstState::Ignore;
|
let mut forced_update = InstState::Ignore;
|
||||||
|
|
||||||
match status.event_rx.recv_timeout(TICK) {
|
match status.receiver().recv_timeout(TICK) {
|
||||||
Ok(event) => match event {
|
Ok(event) => match event {
|
||||||
InstEvent::NewState(new_state) => {
|
ServerEvent::Log(log_type, msg) => {
|
||||||
|
newline_if(&mut need_newline);
|
||||||
|
print_log(log_type, msg);
|
||||||
|
},
|
||||||
|
ServerEvent::NewState(new_state) => {
|
||||||
// Force progress updates at the beginning and end of
|
// Force progress updates at the beginning and end of
|
||||||
// querying/editing stages.
|
// querying/editing stages.
|
||||||
if (cur_state == InstState::Ignore) !=
|
if (cur_state == InstState::Ignore) !=
|
||||||
@ -290,13 +316,10 @@ pub fn run_cmd_line() {
|
|||||||
}
|
}
|
||||||
cur_state = new_state;
|
cur_state = new_state;
|
||||||
},
|
},
|
||||||
InstEvent::Log(log_type, msg) => {
|
ServerEvent::ConfirmRequest => {
|
||||||
if need_newline {
|
newline_if(&mut need_newline);
|
||||||
eprintln!();
|
status.confirm(get_confirmation());
|
||||||
need_newline = false;
|
},
|
||||||
}
|
|
||||||
print_log(log_type, msg);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Compute thread has exited; break out of the loop.
|
// Compute thread has exited; break out of the loop.
|
||||||
@ -311,8 +334,8 @@ pub fn run_cmd_line() {
|
|||||||
if forced_update == InstState::Querying
|
if forced_update == InstState::Querying
|
||||||
|| (cur_state == InstState::Querying && timed_update_ready)
|
|| (cur_state == InstState::Querying && timed_update_ready)
|
||||||
{
|
{
|
||||||
eprint!("\rQuerying mapblocks... {} found.",
|
print!("\rQuerying mapblocks... {} found.",
|
||||||
status.get().blocks_total);
|
status.get_status().blocks_total);
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
last_update = now;
|
last_update = now;
|
||||||
need_newline = true;
|
need_newline = true;
|
||||||
@ -320,8 +343,7 @@ pub fn run_cmd_line() {
|
|||||||
else if forced_update == InstState::Editing
|
else if forced_update == InstState::Editing
|
||||||
|| (cur_state == InstState::Editing && timed_update_ready)
|
|| (cur_state == InstState::Editing && timed_update_ready)
|
||||||
{
|
{
|
||||||
let s = status.get();
|
let s = status.get_status();
|
||||||
// TODO: Update duration format? e.g. 1m 42s remaining
|
|
||||||
print_editing_status(s.blocks_done, s.blocks_total,
|
print_editing_status(s.blocks_done, s.blocks_total,
|
||||||
querying_start, editing_start, s.show_progress);
|
querying_start, editing_start, s.show_progress);
|
||||||
last_update = now;
|
last_update = now;
|
||||||
@ -329,9 +351,8 @@ pub fn run_cmd_line() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print a newline after the last querying/editing message.
|
// Print a newline after the last querying/editing message.
|
||||||
if need_newline && cur_state == InstState::Ignore {
|
if cur_state == InstState::Ignore {
|
||||||
eprintln!();
|
newline_if(&mut need_newline);
|
||||||
need_newline = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{Command, BLOCK_CACHE_SIZE};
|
use super::{Command, ArgResult, BLOCK_CACHE_SIZE};
|
||||||
|
|
||||||
use crate::{unwrap_or, opt_unwrap_or};
|
use crate::{unwrap_or, opt_unwrap_or};
|
||||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
@ -10,7 +10,7 @@ use crate::instance::{ArgType, InstBundle, InstArgs};
|
|||||||
use crate::utils::{CacheMap, query_keys};
|
use crate::utils::{CacheMap, query_keys};
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
let map_area = Area::new(
|
let map_area = Area::new(
|
||||||
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
||||||
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
||||||
@ -19,10 +19,10 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
|||||||
if map_area.intersection(args.area.unwrap() + args.offset.unwrap())
|
if map_area.intersection(args.area.unwrap() + args.offset.unwrap())
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
anyhow::bail!("Destination area is outside map bounds.");
|
return ArgResult::error("Destination area is outside map bounds.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
ArgResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ pub fn get_command() -> Command {
|
|||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(true), "Area containing mapblocks to delete"),
|
(ArgType::Area(true), "Area containing mapblocks to delete"),
|
||||||
(ArgType::Invert,
|
(ArgType::Invert,
|
||||||
"Delete all mapblocks fully *outside* the given area.")
|
"If present, delete all mapblocks fully *outside* the given \
|
||||||
|
area.")
|
||||||
],
|
],
|
||||||
help: "Delete all mapblocks inside or outside an area."
|
help: "Delete all mapblocks inside or outside an area."
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
use super::Command;
|
use super::{Command, ArgResult};
|
||||||
|
|
||||||
use crate::unwrap_or;
|
use crate::unwrap_or;
|
||||||
use crate::spatial::Vec3;
|
use crate::spatial::Vec3;
|
||||||
use crate::instance::{ArgType, InstBundle};
|
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||||
use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt};
|
use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt};
|
||||||
use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num};
|
use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num};
|
||||||
|
|
||||||
|
|
||||||
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
|
if !args.area.is_some() && !args.node.is_some() {
|
||||||
|
return ArgResult::warning(
|
||||||
|
"No area or node specified. ALL metadata will be deleted!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgResult::Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn delete_metadata(inst: &mut InstBundle) {
|
fn delete_metadata(inst: &mut InstBundle) {
|
||||||
let node = inst.args.node.as_ref().map(to_bytes);
|
let node = inst.args.node.as_ref().map(to_bytes);
|
||||||
|
|
||||||
@ -71,14 +81,13 @@ fn delete_metadata(inst: &mut InstBundle) {
|
|||||||
pub fn get_command() -> Command {
|
pub fn get_command() -> Command {
|
||||||
Command {
|
Command {
|
||||||
func: delete_metadata,
|
func: delete_metadata,
|
||||||
verify_args: None,
|
verify_args: Some(verify_args),
|
||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(false), "Area in which to delete metadata"),
|
(ArgType::Area(false), "Area in which to delete metadata"),
|
||||||
(ArgType::Invert, "Delete all metadata outside the given area."),
|
(ArgType::Invert,
|
||||||
(ArgType::Node(false),
|
"If present, delete metadata *outside* the given area."),
|
||||||
"Node to delete metadata from. If not specified, all metadata \
|
(ArgType::Node(false), "Name of node to delete metadata from")
|
||||||
will be deleted.")
|
|
||||||
],
|
],
|
||||||
help: "Delete node metadata."
|
help: "Delete node metadata of certain nodes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,7 @@ fn delete_objects(inst: &mut InstBundle) {
|
|||||||
// is specified.
|
// is specified.
|
||||||
let search_item = inst.args.items.as_ref().and_then(|items| items.get(0))
|
let search_item = inst.args.items.as_ref().and_then(|items| items.get(0))
|
||||||
.map(to_bytes);
|
.map(to_bytes);
|
||||||
let item_searcher = search_item.as_ref()
|
let item_searcher = search_item.as_ref().map(|s| TwoWaySearcher::new(s));
|
||||||
.map(|s| TwoWaySearcher::new(s));
|
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
||||||
@ -71,8 +70,9 @@ fn delete_objects(inst: &mut InstBundle) {
|
|||||||
let mut count: u64 = 0;
|
let mut count: u64 = 0;
|
||||||
for key in keys {
|
for key in keys {
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
let data = unwrap_or!(inst.db.get_block(key), continue);
|
let data = inst.db.get_block(key).unwrap();
|
||||||
let mut block = unwrap_or!(MapBlock::deserialize(&data), continue);
|
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||||
|
{ inst.status.inc_failed(); continue; });
|
||||||
|
|
||||||
let mut modified = false;
|
let mut modified = false;
|
||||||
for i in (0..block.static_objects.len()).rev() {
|
for i in (0..block.static_objects.len()).rev() {
|
||||||
|
@ -19,9 +19,30 @@ mod vacuum;
|
|||||||
pub const BLOCK_CACHE_SIZE: usize = 1024;
|
pub const BLOCK_CACHE_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
|
||||||
|
pub enum ArgResult {
|
||||||
|
Ok,
|
||||||
|
Warning(String),
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgResult {
|
||||||
|
/// Create a new ArgResult::Warning from a &str.
|
||||||
|
#[inline]
|
||||||
|
pub fn warning(msg: &str) -> Self {
|
||||||
|
Self::Warning(msg.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new ArgResult::Error from a &str.
|
||||||
|
#[inline]
|
||||||
|
pub fn error(msg: &str) -> Self {
|
||||||
|
Self::Error(msg.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
pub func: fn(&mut InstBundle),
|
pub func: fn(&mut InstBundle),
|
||||||
pub verify_args: Option<fn(&InstArgs) -> anyhow::Result<()>>,
|
pub verify_args: Option<fn(&InstArgs) -> ArgResult>,
|
||||||
pub help: &'static str,
|
pub help: &'static str,
|
||||||
pub args: Vec<(ArgType, &'static str)>
|
pub args: Vec<(ArgType, &'static str)>
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{Command, BLOCK_CACHE_SIZE};
|
use super::{Command, ArgResult, BLOCK_CACHE_SIZE};
|
||||||
|
|
||||||
use crate::{unwrap_or, opt_unwrap_or};
|
use crate::{unwrap_or, opt_unwrap_or};
|
||||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
@ -10,11 +10,11 @@ use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
|
|||||||
use crate::utils::{query_keys, CacheMap};
|
use crate::utils::{query_keys, CacheMap};
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
if args.invert
|
if args.invert
|
||||||
&& args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some()
|
&& args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some()
|
||||||
{
|
{
|
||||||
anyhow::bail!("Inverted selections cannot be offset.");
|
return ArgResult::error("Inverted selections cannot be offset.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0));
|
let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0));
|
||||||
@ -26,10 +26,10 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
|||||||
if map_area.intersection(args.area.unwrap_or(map_area) + offset)
|
if map_area.intersection(args.area.unwrap_or(map_area) + offset)
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
anyhow::bail!("Destination area is outside map bounds.");
|
return ArgResult::error("Destination area is outside map bounds.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
ArgResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::Command;
|
use super::{Command, ArgResult};
|
||||||
|
|
||||||
use crate::unwrap_or;
|
use crate::unwrap_or;
|
||||||
use crate::spatial::Vec3;
|
use crate::spatial::Vec3;
|
||||||
@ -129,14 +129,15 @@ fn replace_in_inv(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
if args.new_item.is_none() && !args.delete_item && !args.delete_meta {
|
if args.new_item.is_none() && !args.delete_item && !args.delete_meta {
|
||||||
anyhow::bail!(
|
return ArgResult::error(
|
||||||
"new_item is required unless --delete or --deletemeta is used.")
|
"new_item is required unless --delete or --deletemeta is used.");
|
||||||
} else if args.new_item.is_some() && args.delete_item {
|
} else if args.new_item.is_some() && args.delete_item {
|
||||||
anyhow::bail!("Cannot delete items if new_item is specified.");
|
return ArgResult::error(
|
||||||
|
"Cannot delete items if new_item is specified.");
|
||||||
}
|
}
|
||||||
Ok(())
|
ArgResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::Command;
|
use super::{Command, ArgResult};
|
||||||
|
|
||||||
use crate::unwrap_or;
|
use crate::unwrap_or;
|
||||||
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||||
@ -127,10 +127,12 @@ fn replace_nodes(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
anyhow::ensure!(args.node != args.new_node,
|
if args.node == args.new_node {
|
||||||
"node and new_node must be different.");
|
return ArgResult::error("node and new_node must be different.");
|
||||||
Ok(())
|
}
|
||||||
|
|
||||||
|
ArgResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::Command;
|
use super::{Command, ArgResult};
|
||||||
|
|
||||||
use crate::unwrap_or;
|
use crate::unwrap_or;
|
||||||
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||||
@ -107,10 +107,12 @@ fn set_param2(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> ArgResult {
|
||||||
anyhow::ensure!(args.area.is_some() || args.node.is_some(),
|
if args.area.is_none() && args.node.is_none() {
|
||||||
"An area and/or node must be provided.");
|
return ArgResult::error("An area and/or node must be provided.");
|
||||||
Ok(())
|
}
|
||||||
|
|
||||||
|
ArgResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
127
src/instance.rs
127
src/instance.rs
@ -7,6 +7,7 @@ use anyhow::Context;
|
|||||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
use crate::map_database::MapDatabase;
|
use crate::map_database::MapDatabase;
|
||||||
use crate::commands;
|
use crate::commands;
|
||||||
|
use crate::commands::ArgResult;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -32,6 +33,7 @@ pub enum ArgType {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InstArgs {
|
pub struct InstArgs {
|
||||||
|
pub do_confirmation: bool,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub map_path: String,
|
pub map_path: String,
|
||||||
pub input_map_path: Option<String>,
|
pub input_map_path: Option<String>,
|
||||||
@ -86,6 +88,7 @@ impl InstStatus {
|
|||||||
|
|
||||||
pub enum LogType {
|
pub enum LogType {
|
||||||
Info,
|
Info,
|
||||||
|
Warning,
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,22 +96,29 @@ impl std::fmt::Display for LogType {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Info => write!(f, "info"),
|
Self::Info => write!(f, "info"),
|
||||||
|
Self::Warning => write!(f, "warning"),
|
||||||
Self::Error => write!(f, "error")
|
Self::Error => write!(f, "error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub enum InstEvent {
|
pub enum ServerEvent {
|
||||||
|
Log(LogType, String),
|
||||||
NewState(InstState),
|
NewState(InstState),
|
||||||
Log(LogType, String)
|
ConfirmRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum ClientEvent {
|
||||||
|
ConfirmResponse(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct StatusServer {
|
pub struct StatusServer {
|
||||||
status: Arc<Mutex<InstStatus>>,
|
status: Arc<Mutex<InstStatus>>,
|
||||||
event_tx: mpsc::Sender<InstEvent>
|
event_tx: mpsc::Sender<ServerEvent>,
|
||||||
|
event_rx: mpsc::Receiver<ClientEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusServer {
|
impl StatusServer {
|
||||||
@ -118,7 +128,7 @@ impl StatusServer {
|
|||||||
|
|
||||||
pub fn set_state(&self, new_state: InstState) {
|
pub fn set_state(&self, new_state: InstState) {
|
||||||
self.status.lock().unwrap().state = new_state;
|
self.status.lock().unwrap().state = new_state;
|
||||||
self.event_tx.send(InstEvent::NewState(new_state)).unwrap();
|
self.event_tx.send(ServerEvent::NewState(new_state)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_total(&self, total: usize) {
|
pub fn set_total(&self, total: usize) {
|
||||||
@ -146,8 +156,18 @@ impl StatusServer {
|
|||||||
self.set_state(InstState::Ignore);
|
self.set_state(InstState::Ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log<S: AsRef<str>>(&self, lt: LogType, msg: S) {
|
pub fn get_confirmation(&self) -> bool {
|
||||||
self.event_tx.send(InstEvent::Log(lt, msg.as_ref().to_string()))
|
self.event_tx.send(ServerEvent::ConfirmRequest).unwrap();
|
||||||
|
while let Ok(event) = self.event_rx.recv() {
|
||||||
|
match event {
|
||||||
|
ClientEvent::ConfirmResponse(res) => return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log<S: AsRef<str>>(&self, lt: LogType, msg: S) {
|
||||||
|
self.event_tx.send(ServerEvent::Log(lt, msg.as_ref().to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +175,10 @@ impl StatusServer {
|
|||||||
self.log(LogType::Info, msg);
|
self.log(LogType::Info, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn log_warning<S: AsRef<str>>(&self, msg: S) {
|
||||||
|
self.log(LogType::Warning, msg);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log_error<S: AsRef<str>>(&self, msg: S) {
|
pub fn log_error<S: AsRef<str>>(&self, msg: S) {
|
||||||
self.log(LogType::Error, msg);
|
self.log(LogType::Error, msg);
|
||||||
}
|
}
|
||||||
@ -162,14 +186,44 @@ impl StatusServer {
|
|||||||
|
|
||||||
|
|
||||||
pub struct StatusClient {
|
pub struct StatusClient {
|
||||||
pub event_rx: mpsc::Receiver<InstEvent>,
|
status: Arc<Mutex<InstStatus>>,
|
||||||
status: Arc<Mutex<InstStatus>>
|
event_tx: mpsc::Sender<ClientEvent>,
|
||||||
|
event_rx: mpsc::Receiver<ServerEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusClient {
|
impl StatusClient {
|
||||||
pub fn get(&self) -> InstStatus {
|
pub fn get_status(&self) -> InstStatus {
|
||||||
self.status.lock().unwrap().clone()
|
self.status.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn receiver(&self) -> &mpsc::Receiver<ServerEvent> {
|
||||||
|
&self.event_rx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm(&self, choice: bool) {
|
||||||
|
self.event_tx.send(ClientEvent::ConfirmResponse(choice)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn status_link() -> (StatusServer, StatusClient) {
|
||||||
|
let status1 = Arc::new(Mutex::new(InstStatus::new()));
|
||||||
|
let status2 = status1.clone();
|
||||||
|
let (s_event_tx, s_event_rx) = mpsc::channel();
|
||||||
|
let (c_event_tx, c_event_rx) = mpsc::channel();
|
||||||
|
(
|
||||||
|
StatusServer {
|
||||||
|
status: status1,
|
||||||
|
event_tx: s_event_tx,
|
||||||
|
event_rx: c_event_rx,
|
||||||
|
},
|
||||||
|
StatusClient {
|
||||||
|
status: status2,
|
||||||
|
event_tx: c_event_tx,
|
||||||
|
event_rx: s_event_rx,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -181,17 +235,6 @@ pub struct InstBundle<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn status_channel() -> (StatusServer, StatusClient) {
|
|
||||||
let status1 = Arc::new(Mutex::new(InstStatus::new()));
|
|
||||||
let status2 = status1.clone();
|
|
||||||
let (event_tx, event_rx) = mpsc::channel();
|
|
||||||
(
|
|
||||||
StatusServer {status: status1, event_tx},
|
|
||||||
StatusClient {status: status2, event_rx}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||||
// TODO: Complete verifications.
|
// TODO: Complete verifications.
|
||||||
|
|
||||||
@ -280,14 +323,17 @@ fn open_map(path: PathBuf, flags: sqlite::OpenFlags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn compute_thread(args: InstArgs, status: StatusServer)
|
fn compute_thread(args: InstArgs, status: StatusServer) -> anyhow::Result<()> {
|
||||||
-> anyhow::Result<()>
|
|
||||||
{
|
|
||||||
verify_args(&args)?;
|
verify_args(&args)?;
|
||||||
|
|
||||||
let commands = commands::get_commands();
|
let commands = commands::get_commands();
|
||||||
|
let mut cmd_warning = None;
|
||||||
if let Some(cmd_verify) = commands[args.command.as_str()].verify_args {
|
if let Some(cmd_verify) = commands[args.command.as_str()].verify_args {
|
||||||
cmd_verify(&args)?
|
cmd_warning = match cmd_verify(&args) {
|
||||||
|
ArgResult::Ok => None,
|
||||||
|
ArgResult::Warning(w) => Some(w),
|
||||||
|
ArgResult::Error(e) => anyhow::bail!(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_conn = open_map(PathBuf::from(&args.map_path),
|
let db_conn = open_map(PathBuf::from(&args.map_path),
|
||||||
@ -303,14 +349,27 @@ fn compute_thread(args: InstArgs, status: StatusServer)
|
|||||||
Some(conn) => Some(MapDatabase::new(conn)?),
|
Some(conn) => Some(MapDatabase::new(conn)?),
|
||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
// TODO: Standard warning?
|
|
||||||
let func = commands[args.command.as_str()].func;
|
let func = commands[args.command.as_str()].func;
|
||||||
let mut inst = InstBundle {args, status, db, idb};
|
let mut inst = InstBundle {args, status, db, idb};
|
||||||
func(&mut inst);
|
|
||||||
|
// Issue warnings and confirmation prompt.
|
||||||
|
if inst.args.do_confirmation {
|
||||||
|
inst.status.log_warning(
|
||||||
|
"This tool can permanently damage your Minetest world.\n\
|
||||||
|
Always EXIT Minetest and BACK UP the map database before use.");
|
||||||
|
}
|
||||||
|
if let Some(w) = cmd_warning {
|
||||||
|
inst.status.log_warning(w);
|
||||||
|
}
|
||||||
|
if inst.args.do_confirmation && !inst.status.get_confirmation() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
func(&mut inst); // The real thing!
|
||||||
|
|
||||||
let fails = inst.status.get_status().blocks_failed;
|
let fails = inst.status.get_status().blocks_failed;
|
||||||
if fails > 0 {
|
if fails > 0 {
|
||||||
// TODO: log_warning
|
|
||||||
inst.status.log_info(format!(
|
inst.status.log_info(format!(
|
||||||
"Skipped {} invalid/unsupported mapblocks.", fails));
|
"Skipped {} invalid/unsupported mapblocks.", fails));
|
||||||
}
|
}
|
||||||
@ -327,16 +386,18 @@ fn compute_thread(args: InstArgs, status: StatusServer)
|
|||||||
pub fn spawn_compute_thread(args: InstArgs)
|
pub fn spawn_compute_thread(args: InstArgs)
|
||||||
-> (std::thread::JoinHandle<()>, StatusClient)
|
-> (std::thread::JoinHandle<()>, StatusClient)
|
||||||
{
|
{
|
||||||
let (status_tx, status_rx) = status_channel();
|
let (status_server, status_client) = status_link();
|
||||||
// Clone within this thread to avoid issue #39364 (hopefully).
|
// Clone within this thread to avoid issue #39364 (hopefully).
|
||||||
let status_tx_2 = status_tx.clone();
|
let raw_event_tx = status_server.event_tx.clone();
|
||||||
let h = std::thread::Builder::new()
|
let h = std::thread::Builder::new()
|
||||||
.name("compute".to_string())
|
.name("compute".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
compute_thread(args, status_tx_2).unwrap_or_else(
|
compute_thread(args, status_server).unwrap_or_else(
|
||||||
|err| status_tx.log_error(&err.to_string())
|
// TODO: Find a cleaner way to do this.
|
||||||
|
|err| raw_event_tx.send(
|
||||||
|
ServerEvent::Log(LogType::Error, err.to_string())).unwrap()
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
(h, status_rx)
|
(h, status_client)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user