MapBlock caching part 2

master
random-geek 2021-02-08 23:51:04 -08:00
parent ef6fa6ca1a
commit 0f5bfc4971
10 changed files with 134 additions and 114 deletions

6
.gitignore vendored
View File

@ -1,2 +1,6 @@
/target /target
**/*.rs.bk **/*.rs.bk
# VSCode stuff
/.vscode
*.code_workspace

View File

@ -1,5 +0,0 @@
{
"cSpell.words": [
"minetest"
]
}

View File

@ -53,16 +53,17 @@ WorldEdit `//fixlight` command.
Usage: `clone --p1 x y z --p2 x y z --offset x y z` Usage: `clone --p1 x y z --p2 x y z --offset x y z`
Clone (copy) the given area to a new location. Clone (copy) a given area to a new location.
Arguments: Arguments:
- `--p1, --p2`: Area to copy from. - `--p1, --p2`: Area to clone.
- `--offset`: Offset to shift the area by. For example, to copy an area 50 - `--offset`: Vector to shift the area by. For example, to copy an area 50
nodes upward (positive Y direction), use `--offset 0 50 0`. nodes downward (negative Y direction), use `--offset 0 -50 0`. Directions may
be determined using Minetest's F5 debug menu.
This command copies nodes, param1, param2, and metadata. Nothing will be copied This command copies nodes, param1, param2, and metadata. Nothing will be copied
into mapblocks that are not yet generated. from or into mapblocks that are not yet generated.
### deleteblocks ### deleteblocks

View File

@ -1,23 +1,42 @@
use super::Command; use super::{Command, BLOCK_CACHE_SIZE};
use crate::unwrap_or; use crate::{unwrap_or, opt_unwrap_or};
use crate::spatial::{Vec3, area_rel_block_overlap, use crate::spatial::{Vec3, area_rel_block_overlap,
area_abs_block_overlap}; area_abs_block_overlap};
use crate::map_block::{MapBlock, is_valid_generated, NodeMetadataList}; 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::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
use crate::instance::{ArgType, InstBundle}; use crate::instance::{ArgType, InstBundle};
use crate::utils::{CacheMap, CachedMapDatabase, query_keys}; use crate::utils::{CacheMap, query_keys};
use crate::time_keeper::TimeKeeper; use crate::time_keeper::TimeKeeper;
// TODO: This and overlay--cache mapblocks in deserialized form. type BlockResult = Option<Result<MapBlock, MapBlockError>>;
fn get_cached(
db: &mut MapDatabase,
cache: &mut CacheMap<i64, BlockResult>,
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) { fn clone(inst: &mut InstBundle) {
let src_area = inst.args.area.unwrap(); let src_area = inst.args.area.unwrap();
let offset = inst.args.offset.unwrap(); let offset = inst.args.offset.unwrap();
let dst_area = src_area + offset; let dst_area = src_area + offset;
let mut keys = query_keys(&mut inst.db, &inst.status, let mut dst_keys = query_keys(&mut inst.db, &inst.status,
&[], Some(dst_area), false, true); &[], Some(dst_area), false, true);
// Sort blocks according to offset such that we don't read blocks that // Sort blocks according to offset such that we don't read blocks that
@ -26,25 +45,27 @@ fn clone(inst: &mut InstBundle) {
// Subtract one from inverted axes to keep values from overflowing. // Subtract one from inverted axes to keep values from overflowing.
let sort_offset = sort_dir.map(|v| if v == -1 { -1 } else { 0 }); let sort_offset = sort_dir.map(|v| if v == -1 { -1 } else { 0 });
keys.sort_unstable_by_key(|k| { dst_keys.sort_unstable_by_key(|k| {
(Vec3::from_block_key(*k) * sort_dir + sort_offset).to_block_key() (Vec3::from_block_key(*k) * sort_dir + sort_offset).to_block_key()
}); });
// let mut db = CachedMapDatabase::new(&mut inst.db, 256); let mut block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE);
let mut block_cache = CacheMap::<i64, MapBlock>::with_capacity(256);
let mut tk = TimeKeeper::new(); let mut tk = TimeKeeper::new();
inst.status.begin_editing(); inst.status.begin_editing();
for dst_key in keys { for dst_key in dst_keys {
inst.status.inc_done(); inst.status.inc_done();
let dst_data = inst.db.get_block(dst_key).unwrap(); let (mut dst_block, mut dst_meta) = unwrap_or!(
if !is_valid_generated(&dst_data) { opt_unwrap_or!(
continue; get_cached(&mut inst.db, &mut block_cache, dst_key),
} continue
let mut dst_block = MapBlock::deserialize(&dst_data).unwrap(); ).and_then(|b|
let mut dst_meta = NodeMetadataList::deserialize( NodeMetadataList::deserialize(b.metadata.get_ref())
dst_block.metadata.get_ref()).unwrap(); .map(|m| (b, m))
),
{ inst.status.inc_failed(); continue; }
);
let dst_pos = Vec3::from_block_key(dst_key); let dst_pos = Vec3::from_block_key(dst_key);
let dst_part_abs = area_abs_block_overlap(&dst_area, dst_pos) let dst_part_abs = area_abs_block_overlap(&dst_area, dst_pos)
@ -57,22 +78,15 @@ fn clone(inst: &mut InstBundle) {
continue; continue;
} }
let src_key = src_pos.to_block_key(); let src_key = src_pos.to_block_key();
let src_block = if let Some(block) = block_cache.get(&src_key) { let (src_block, src_meta) = opt_unwrap_or!(
let _t = tk.get_timer("get_block (cached)"); get_cached(&mut inst.db, &mut block_cache, src_key)
block.clone() .map(Result::ok).flatten()
} else { .and_then(|b|
let _t = tk.get_timer("get_block (database)"); NodeMetadataList::deserialize(b.metadata.get_ref())
let src_data = unwrap_or!(inst.db.get_block(src_key), .ok().map(|m| (b, m))
continue); ),
if !is_valid_generated(&src_data) { continue
continue; );
}
let src_block = MapBlock::deserialize(&src_data).unwrap();
block_cache.insert(src_key, src_block.clone());
src_block
};
let src_meta = NodeMetadataList::deserialize(
&src_block.metadata.get_ref()).unwrap();
let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos) let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos)
.unwrap(); .unwrap();
@ -112,8 +126,8 @@ pub fn get_command() -> Command {
verify_args: None, verify_args: None,
args: vec![ args: vec![
(ArgType::Area(true), "Area to clone"), (ArgType::Area(true), "Area to clone"),
(ArgType::Offset(true), "Vector to shift nodes by") (ArgType::Offset(true), "Vector to shift the area by")
], ],
help: "Clone a given area to a new location." help: "Clone (copy) a given area to a new location."
} }
} }

View File

@ -16,6 +16,9 @@ mod set_param2;
mod vacuum; mod vacuum;
pub const BLOCK_CACHE_SIZE: usize = 1024;
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) -> anyhow::Result<()>>,

View File

@ -1,11 +1,14 @@
use super::Command; use super::{Command, BLOCK_CACHE_SIZE};
use crate::opt_unwrap_or;
use crate::spatial::{Vec3, Area, area_rel_block_overlap, use crate::spatial::{Vec3, Area, area_rel_block_overlap,
area_abs_block_overlap, area_contains_block, area_touches_block}; area_abs_block_overlap, area_contains_block, area_touches_block};
use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::instance::{ArgType, InstArgs, InstBundle};
use crate::map_block::{MapBlock, NodeMetadataList, is_valid_generated}; use crate::map_database::MapDatabase;
use crate::map_block::{MapBlock, MapBlockError, NodeMetadataList,
is_valid_generated};
use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map}; use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
use crate::utils::query_keys; use crate::utils::{query_keys, CacheMap};
fn verify_args(args: &InstArgs) -> anyhow::Result<()> { fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
@ -97,6 +100,26 @@ fn overlay_no_offset(inst: &mut InstBundle) {
} }
type BlockResult = Option<Result<MapBlock, MapBlockError>>;
fn get_cached(
db: &mut MapDatabase,
cache: &mut CacheMap<i64, BlockResult>,
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
}
}
}
/// Overlay with offset, with or without area. /// Overlay with offset, with or without area.
#[inline] #[inline]
fn overlay_with_offset(inst: &mut InstBundle) { fn overlay_with_offset(inst: &mut InstBundle) {
@ -106,21 +129,27 @@ fn overlay_with_offset(inst: &mut InstBundle) {
let idb = inst.idb.as_mut().unwrap(); let idb = inst.idb.as_mut().unwrap();
// Get keys from output database. // Get keys from output database.
let keys = query_keys(&mut inst.db, &inst.status, let dst_keys = query_keys(&mut inst.db, &inst.status,
&[], dst_area, inst.args.invert, true); &[], dst_area, inst.args.invert, true);
inst.status.begin_editing();
for key in keys { let mut src_block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE);
inst.status.begin_editing();
for dst_key in dst_keys {
inst.status.inc_done(); inst.status.inc_done();
let dst_pos = Vec3::from_block_key(key); let dst_pos = Vec3::from_block_key(dst_key);
let dst_data = inst.db.get_block(key).unwrap(); let dst_data = opt_unwrap_or!(
if !is_valid_generated(&dst_data) { inst.db.get_block(dst_key).ok().filter(|d| is_valid_generated(d)),
continue; continue
} );
let mut dst_block = MapBlock::deserialize(&dst_data).unwrap(); let (mut dst_block, mut dst_meta) = opt_unwrap_or!(
let mut dst_meta = NodeMetadataList::deserialize( MapBlock::deserialize(&dst_data).ok().and_then(|b|
dst_block.metadata.get_ref()).unwrap(); NodeMetadataList::deserialize(b.metadata.get_ref())
.ok().map(|m| (b, m))
),
{ inst.status.inc_failed(); continue; }
);
let dst_part_abs = dst_area.map_or( let dst_part_abs = dst_area.map_or(
Area::new(dst_pos * 16, dst_pos * 16 + 15), Area::new(dst_pos * 16, dst_pos * 16 + 15),
@ -133,17 +162,16 @@ fn overlay_with_offset(inst: &mut InstBundle) {
if !src_pos.is_valid_block_pos() { if !src_pos.is_valid_block_pos() {
continue; continue;
} }
let src_data = match idb.get_block(src_pos.to_block_key()) { let src_key = src_pos.to_block_key();
Ok(d) => if is_valid_generated(&d) { let (src_block, src_meta) = opt_unwrap_or!(
d get_cached(idb, &mut src_block_cache, src_key)
} else { .map(Result::ok).flatten()
continue .and_then(|b|
}, NodeMetadataList::deserialize(b.metadata.get_ref())
Err(_) => continue .ok().map(|m| (b, m))
}; ),
let src_block = MapBlock::deserialize(&src_data).unwrap(); continue
let src_meta = NodeMetadataList::deserialize( );
src_block.metadata.get_ref()).unwrap();
let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos) let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos)
.unwrap(); .unwrap();
@ -159,7 +187,7 @@ fn overlay_with_offset(inst: &mut InstBundle) {
clean_name_id_map(&mut dst_block); clean_name_id_map(&mut dst_block);
*dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version); *dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version);
inst.db.set_block(key, &dst_block.serialize()).unwrap(); inst.db.set_block(dst_key, &dst_block.serialize()).unwrap();
} }
inst.status.end_editing(); inst.status.end_editing();
@ -186,6 +214,6 @@ pub fn get_command() -> Command {
(ArgType::Invert, "Overlay all nodes outside the given area"), (ArgType::Invert, "Overlay all nodes outside the given area"),
(ArgType::Offset(false), "Vector to offset nodes by"), (ArgType::Offset(false), "Vector to offset nodes by"),
], ],
help: "Copy part or all of one map into another." help: "Copy part or all of one world/map into another."
} }
} }

View File

@ -317,10 +317,12 @@ pub fn spawn_compute_thread(args: InstArgs)
-> (std::thread::JoinHandle<()>, StatusClient) -> (std::thread::JoinHandle<()>, StatusClient)
{ {
let (status_tx, status_rx) = status_channel(); let (status_tx, status_rx) = status_channel();
// Clone within this thread to avoid issue #39364 (hopefully).
let status_tx_2 = status_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.clone()).unwrap_or_else( compute_thread(args, status_tx_2).unwrap_or_else(
|err| status_tx.log_error(&err.to_string()) |err| status_tx.log_error(&err.to_string())
); );
}) })

View File

@ -22,7 +22,7 @@ use node_timer::{serialize_timers, deserialize_timers};
pub use name_id_map::NameIdMap; pub use name_id_map::NameIdMap;
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum MapBlockError { pub enum MapBlockError {
InvalidVersion, InvalidVersion,
DataError, DataError,

View File

@ -5,8 +5,7 @@ use memmem::{Searcher, TwoWaySearcher};
use byteorder::{WriteBytesExt, BigEndian}; use byteorder::{WriteBytesExt, BigEndian};
use crate::instance::{InstState, StatusServer}; use crate::instance::{InstState, StatusServer};
use crate::map_block::MapBlock; use crate::map_database::MapDatabase;
use crate::map_database::{MapDatabase, DBError};
use crate::spatial::{Area, Vec3}; use crate::spatial::{Area, Vec3};
@ -101,36 +100,6 @@ impl<K: Eq + std::hash::Hash + Clone, V> CacheMap<K, V> {
} }
pub struct CachedMapDatabase<'a, 'b> {
db: &'a mut MapDatabase<'b>,
cache: CacheMap<i64, Option<MapBlock>>
}
impl<'a, 'b> CachedMapDatabase<'a, 'b> {
pub fn new(db: &'a mut MapDatabase<'b>, cap: usize) -> Self {
Self { db, cache: CacheMap::with_capacity(cap) }
}
pub fn get_block(&mut self, key: i64) -> Option<MapBlock> {
if let Some(block) = self.cache.get(&key) {
block.clone()
} else {
let data = self.db.get_block(key).ok();
let block = match data {
Some(d) => MapBlock::deserialize(&d).ok(),
None => None
};
self.cache.insert(key, block.clone());
block
}
}
pub fn set_block(&mut self, key: i64, data: &[u8]) -> Result<(), DBError> {
self.db.set_block(key, data)
}
}
pub fn to_bytes(s: &String) -> Vec<u8> { pub fn to_bytes(s: &String) -> Vec<u8> {
s.as_bytes().to_vec() s.as_bytes().to_vec()
} }
@ -155,6 +124,17 @@ macro_rules! unwrap_or {
} }
#[macro_export]
macro_rules! opt_unwrap_or {
($res:expr, $alt:expr) => {
match $res {
Some(val) => val,
None => $alt
}
}
}
pub fn fmt_duration(dur: Duration) -> String { pub fn fmt_duration(dur: Duration) -> String {
let s = dur.as_secs(); let s = dur.as_secs();
if s < 3600 { if s < 3600 {

View File

@ -1,7 +0,0 @@
{
"folders": [
{
"path": "."
}
]
}