use super::{Command, BLOCK_CACHE_SIZE}; use crate::{unwrap_or, opt_unwrap_or}; use crate::spatial::{Vec3, area_rel_block_overlap, area_abs_block_overlap}; use crate::map_database::MapDatabase; use crate::map_block::{MapBlock, MapBlockError, is_valid_generated, NodeMetadataList}; use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map}; use crate::instance::{ArgType, InstBundle}; use crate::utils::{CacheMap, query_keys}; use crate::time_keeper::TimeKeeper; type BlockResult = Option>; fn get_cached( db: &mut MapDatabase, cache: &mut CacheMap, key: i64 ) -> BlockResult { match cache.get(&key) { Some(data) => data.clone(), None => { let block = db.get_block(key).ok() .filter(|d| is_valid_generated(d)) .map(|d| MapBlock::deserialize(&d)); cache.insert(key, block.clone()); block } } } fn clone(inst: &mut InstBundle) { let src_area = inst.args.area.unwrap(); let offset = inst.args.offset.unwrap(); let dst_area = src_area + offset; let mut dst_keys = query_keys(&mut inst.db, &inst.status, &[], Some(dst_area), false, true); // Sort blocks according to offset such that we don't read blocks that // have already been written. let sort_dir = offset.map(|v| if v > 0 { -1 } else { 1 }); // Subtract one from inverted axes to keep values from overflowing. let sort_offset = sort_dir.map(|v| if v == -1 { -1 } else { 0 }); dst_keys.sort_unstable_by_key(|k| { (Vec3::from_block_key(*k) * sort_dir + sort_offset).to_block_key() }); let mut block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE); let mut tk = TimeKeeper::new(); inst.status.begin_editing(); for dst_key in dst_keys { inst.status.inc_done(); let (mut dst_block, mut dst_meta) = unwrap_or!( opt_unwrap_or!( get_cached(&mut inst.db, &mut block_cache, dst_key), continue ).and_then(|b| NodeMetadataList::deserialize(b.metadata.get_ref()) .map(|m| (b, m)) ), { inst.status.inc_failed(); continue; } ); let dst_pos = Vec3::from_block_key(dst_key); let dst_part_abs = area_abs_block_overlap(&dst_area, dst_pos) .unwrap(); let src_part_abs = dst_part_abs - offset; let src_blocks_needed = src_part_abs.to_touching_block_area(); for src_pos in src_blocks_needed.iterate() { if !src_pos.is_valid_block_pos() { continue; } let src_key = src_pos.to_block_key(); let (src_block, src_meta) = opt_unwrap_or!( get_cached(&mut inst.db, &mut block_cache, src_key) .map(Result::ok).flatten() .and_then(|b| NodeMetadataList::deserialize(b.metadata.get_ref()) .ok().map(|m| (b, m)) ), continue ); let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos) .unwrap(); let src_frag_rel = src_frag_abs - src_pos * 16; let dst_frag_rel = area_rel_block_overlap( &(src_frag_abs + offset), dst_pos).unwrap(); { let _t = tk.get_timer("merge"); merge_blocks(&src_block, &mut dst_block, src_frag_rel, dst_frag_rel); } { let _t = tk.get_timer("merge_meta"); merge_metadata(&src_meta, &mut dst_meta, src_frag_rel, dst_frag_rel); } } { let _t = tk.get_timer("name-ID map cleanup"); clean_name_id_map(&mut dst_block); } *dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version); inst.db.set_block(dst_key, &dst_block.serialize()).unwrap(); } inst.status.end_editing(); tk.print(&inst.status); } pub fn get_command() -> Command { Command { func: clone, verify_args: None, args: vec![ (ArgType::Area(true), "Area to clone"), (ArgType::Offset(true), "Vector to shift the area by") ], help: "Clone (copy) a given area to a new location." } }