Merge branch 'zesterer/small-fixes' into 'master'

Fixed chonk memory usage bug, added block manipulation

See merge request veloren/veloren!278
This commit is contained in:
Joshua Barretto 2019-07-01 15:22:04 +00:00
commit 3d2a6632ef
5 changed files with 222 additions and 52 deletions

View File

@ -5,7 +5,8 @@ use crate::{
comp, comp,
msg::{EcsCompPacket, EcsResPacket}, msg::{EcsCompPacket, EcsResPacket},
sys, sys,
terrain::{TerrainChunk, TerrainMap}, terrain::{Block, TerrainChunk, TerrainMap},
vol::WriteVol,
}; };
use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -15,7 +16,11 @@ use specs::{
Component, DispatcherBuilder, Entity as EcsEntity, Component, DispatcherBuilder, Entity as EcsEntity,
}; };
use sphynx; use sphynx;
use std::{collections::HashSet, sync::Arc, time::Duration}; use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
use vek::*; use vek::*;
/// How much faster should an in-game day be compared to a real day? /// How much faster should an in-game day be compared to a real day?
@ -40,24 +45,48 @@ pub struct DeltaTime(pub f32);
/// lag. Ideally, we'd avoid such a situation. /// lag. Ideally, we'd avoid such a situation.
const MAX_DELTA_TIME: f32 = 1.0; const MAX_DELTA_TIME: f32 = 1.0;
pub struct Changes { pub struct TerrainChange {
blocks: HashMap<Vec3<i32>, Block>,
}
impl Default for TerrainChange {
fn default() -> Self {
Self {
blocks: HashMap::new(),
}
}
}
impl TerrainChange {
pub fn set(&mut self, pos: Vec3<i32>, block: Block) {
self.blocks.insert(pos, block);
}
pub fn clear(&mut self) {
self.blocks.clear();
}
}
pub struct ChunkChanges {
pub new_chunks: HashSet<Vec2<i32>>, pub new_chunks: HashSet<Vec2<i32>>,
pub changed_chunks: HashSet<Vec2<i32>>, pub modified_chunks: HashSet<Vec2<i32>>,
pub removed_chunks: HashSet<Vec2<i32>>, pub removed_chunks: HashSet<Vec2<i32>>,
} }
impl Changes { impl Default for ChunkChanges {
pub fn default() -> Self { fn default() -> Self {
Self { Self {
new_chunks: HashSet::new(), new_chunks: HashSet::new(),
changed_chunks: HashSet::new(), modified_chunks: HashSet::new(),
removed_chunks: HashSet::new(), removed_chunks: HashSet::new(),
} }
} }
}
pub fn cleanup(&mut self) { impl ChunkChanges {
pub fn clear(&mut self) {
self.new_chunks.clear(); self.new_chunks.clear();
self.changed_chunks.clear(); self.modified_chunks.clear();
self.removed_chunks.clear(); self.removed_chunks.clear();
} }
} }
@ -68,7 +97,6 @@ pub struct State {
ecs: sphynx::World<EcsCompPacket, EcsResPacket>, ecs: sphynx::World<EcsCompPacket, EcsResPacket>,
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher // Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
thread_pool: Arc<ThreadPool>, thread_pool: Arc<ThreadPool>,
changes: Changes,
} }
impl State { impl State {
@ -77,7 +105,6 @@ impl State {
Self { Self {
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world), ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()), thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
changes: Changes::default(),
} }
} }
@ -92,7 +119,6 @@ impl State {
state_package, state_package,
), ),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()), thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
changes: Changes::default(),
} }
} }
@ -134,6 +160,8 @@ impl State {
ecs.add_resource(Time(0.0)); ecs.add_resource(Time(0.0));
ecs.add_resource(DeltaTime(0.0)); ecs.add_resource(DeltaTime(0.0));
ecs.add_resource(TerrainMap::new().unwrap()); ecs.add_resource(TerrainMap::new().unwrap());
ecs.add_resource(TerrainChange::default());
ecs.add_resource(ChunkChanges::default());
} }
/// Register a component with the state's ECS. /// Register a component with the state's ECS.
@ -172,8 +200,8 @@ impl State {
/// Get a reference to the `Changes` structure of the state. This contains /// Get a reference to the `Changes` structure of the state. This contains
/// information about state that has changed since the last game tick. /// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes { pub fn chunk_changes(&self) -> Fetch<ChunkChanges> {
&self.changes self.ecs.read_resource()
} }
/// Get the current in-game time of day. /// Get the current in-game time of day.
@ -197,12 +225,12 @@ impl State {
/// Get a reference to this state's terrain. /// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainMap> { pub fn terrain(&self) -> Fetch<TerrainMap> {
self.ecs.read_resource::<TerrainMap>() self.ecs.read_resource()
} }
/// Get a writable reference to this state's terrain. /// Get a writable reference to this state's terrain.
pub fn terrain_mut(&self) -> FetchMut<TerrainMap> { pub fn terrain_mut(&self) -> FetchMut<TerrainMap> {
self.ecs.write_resource::<TerrainMap>() self.ecs.write_resource()
} }
/// Removes every chunk of the terrain. /// Removes every chunk of the terrain.
@ -226,9 +254,15 @@ impl State {
.insert(key, Arc::new(chunk)) .insert(key, Arc::new(chunk))
.is_some() .is_some()
{ {
self.changes.changed_chunks.insert(key); self.ecs
.write_resource::<ChunkChanges>()
.modified_chunks
.insert(key);
} else { } else {
self.changes.new_chunks.insert(key); self.ecs
.write_resource::<ChunkChanges>()
.new_chunks
.insert(key);
} }
} }
@ -240,7 +274,10 @@ impl State {
.remove(key) .remove(key)
.is_some() .is_some()
{ {
self.changes.removed_chunks.insert(key); self.ecs
.write_resource::<ChunkChanges>()
.removed_chunks
.insert(key);
} }
} }
@ -262,11 +299,27 @@ impl State {
dispatch_builder.build().dispatch(&self.ecs.res); dispatch_builder.build().dispatch(&self.ecs.res);
self.ecs.maintain(); self.ecs.maintain();
// Apply terrain changes
let mut terrain = self.ecs.write_resource::<TerrainMap>();
let mut chunk_changes = self.ecs.write_resource::<ChunkChanges>();
self.ecs
.write_resource::<TerrainChange>()
.blocks
.drain()
.for_each(|(pos, block)| {
if terrain.set(pos, block).is_ok() {
chunk_changes.modified_chunks.insert(terrain.pos_key(pos));
} else {
warn!("Tried to modify block outside of terrain at {:?}", pos);
}
});
} }
/// Clean up the state after a tick. /// Clean up the state after a tick.
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// Clean up data structures from the last tick. // Clean up data structures from the last tick.
self.changes.cleanup(); self.ecs.write_resource::<TerrainChange>().clear();
self.ecs.write_resource::<ChunkChanges>().clear();
} }
} }

View File

@ -1,6 +1,6 @@
use super::{block::Block, TerrainChunkMeta, TerrainChunkSize}; use super::{block::Block, TerrainChunkMeta, TerrainChunkSize};
use crate::{ use crate::{
vol::{BaseVol, ReadVol, WriteVol}, vol::{BaseVol, ReadVol, VolSize, WriteVol},
volumes::chunk::{Chunk, ChunkErr}, volumes::chunk::{Chunk, ChunkErr},
}; };
use fxhash::FxHashMap; use fxhash::FxHashMap;
@ -185,21 +185,24 @@ impl WriteVol for Chonk {
self.sub_chunks[sub_chunk_idx] = SubChunk::Hash(*cblock, map); self.sub_chunks[sub_chunk_idx] = SubChunk::Hash(*cblock, map);
Ok(()) Ok(())
} }
SubChunk::Hash(cblock, _map) if block == *cblock => Ok(()), SubChunk::Hash(cblock, map) if block == *cblock => {
SubChunk::Hash(_cblock, map) if map.len() < 4096 => { map.remove(&rpos.map(|e| e as u8));
Ok(())
}
SubChunk::Hash(_cblock, map) if map.len() <= 4096 => {
map.insert(rpos.map(|e| e as u8), block); map.insert(rpos.map(|e| e as u8), block);
Ok(()) Ok(())
} }
SubChunk::Hash(cblock, map) => { SubChunk::Hash(cblock, map) => {
let mut new_chunk = Chunk::filled(*cblock, ()); let mut new_chunk = Chunk::filled(*cblock, ());
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope)
for (map_pos, map_block) in map { for (map_pos, map_block) in map {
new_chunk new_chunk
.set(map_pos.map(|e| e as i32), *map_block) .set(map_pos.map(|e| e as i32), *map_block)
.unwrap(); // Can't fail (I hope!) .unwrap(); // Can't fail (I hope!)
} }
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope)
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk); self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(()) Ok(())
} }
@ -222,11 +225,22 @@ impl WriteVol for Chonk {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubChunkSize;
impl VolSize for SubChunkSize {
const SIZE: Vec3<u32> = Vec3 {
x: TerrainChunkSize::SIZE.x,
y: TerrainChunkSize::SIZE.y,
z: SUB_CHUNK_HEIGHT,
};
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SubChunk { pub enum SubChunk {
Homogeneous(Block), Homogeneous(Block),
Hash(Block, FxHashMap<Vec3<u8>, Block>), Hash(Block, FxHashMap<Vec3<u8>, Block>),
Heterogeneous(Chunk<Block, TerrainChunkSize, ()>), Heterogeneous(Chunk<Block, SubChunkSize, ()>),
} }
impl SubChunk { impl SubChunk {

View File

@ -7,7 +7,9 @@ use common::{
comp, comp,
msg::ServerMsg, msg::ServerMsg,
npc::{get_npc_name, NpcKind}, npc::{get_npc_name, NpcKind},
state::TimeOfDay, state::{TerrainChange, TimeOfDay},
terrain::Block,
vol::Vox,
}; };
use specs::{Builder, Entity as EcsEntity, Join}; use specs::{Builder, Entity as EcsEntity, Join};
use vek::*; use vek::*;
@ -104,6 +106,18 @@ lazy_static! {
"/players : Show the online players list", "/players : Show the online players list",
handle_players, handle_players,
), ),
ChatCommand::new(
"solid",
"{}",
"/solid : Make the blocks around you solid",
handle_solid,
),
ChatCommand::new(
"empty",
"{}",
"/empty : Make the blocks around you empty",
handle_empty,
),
ChatCommand::new( ChatCommand::new(
"help", "", "/help: Display this message", handle_help) "help", "", "/help: Display this message", handle_help)
]; ];
@ -122,7 +136,7 @@ fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
} }
None => server.clients.notify( None => server.clients.notify(
entity, entity,
ServerMsg::Chat(String::from("Command 'jump' invalid in current state.")), ServerMsg::Chat(String::from("You have no position!")),
), ),
} }
} }
@ -240,10 +254,9 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
} }
}, },
None => { None => {
server.clients.notify( server
entity, .clients
ServerMsg::Chat(format!("You don't have any position!")), .notify(entity, ServerMsg::Chat(format!("You have no position!")));
);
} }
} }
} }
@ -315,6 +328,44 @@ fn handle_players(server: &mut Server, entity: EcsEntity, _args: String, _action
} }
} }
fn handle_solid(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match server.state.read_component_cloned::<comp::Pos>(entity) {
Some(current_pos) => {
server.state.ecs().write_resource::<TerrainChange>().set(
current_pos.0.map(|e| e.floor() as i32),
Block::new(1, Rgb::broadcast(255)),
);
}
None => server.clients.notify(
entity,
ServerMsg::Chat(String::from("You have no position!")),
),
}
}
fn handle_empty(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match server.state.read_component_cloned::<comp::Pos>(entity) {
Some(current_pos) => {
let mut terrain_change = server.state.ecs().write_resource::<TerrainChange>();
for i in -1..2 {
for j in -1..2 {
for k in -2..1 {
terrain_change.set(
current_pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k),
Block::empty(),
);
}
}
}
}
None => server.clients.notify(
entity,
ServerMsg::Chat(String::from("You have no position!")),
),
}
}
fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
for cmd in CHAT_COMMANDS.iter() { for cmd in CHAT_COMMANDS.iter() {
server server

View File

@ -17,7 +17,7 @@ use common::{
msg::{ClientMsg, ClientState, RequestStateError, ServerInfo, ServerMsg}, msg::{ClientMsg, ClientState, RequestStateError, ServerInfo, ServerMsg},
net::PostOffice, net::PostOffice,
state::{State, Uid}, state::{State, Uid},
terrain::{TerrainChunk, TerrainChunkSize}, terrain::{TerrainChunk, TerrainChunkSize, TerrainMap},
vol::VolSize, vol::VolSize,
}; };
use log::{debug, warn}; use log::{debug, warn};
@ -246,9 +246,24 @@ impl Server {
self.pending_chunks.remove(&key); self.pending_chunks.remove(&key);
} }
fn chunk_in_vd(
player_pos: Vec3<f32>,
chunk_pos: Vec2<i32>,
terrain: &TerrainMap,
vd: u32,
) -> bool {
let player_chunk_pos = terrain.pos_key(player_pos.map(|e| e as i32));
let adjusted_dist_sqr = Vec2::from(player_chunk_pos - chunk_pos)
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared();
adjusted_dist_sqr <= vd.pow(2)
}
// Remove chunks that are too far from players. // Remove chunks that are too far from players.
let mut chunks_to_remove = Vec::new(); let mut chunks_to_remove = Vec::new();
self.state.terrain().iter().for_each(|(key, _)| { self.state.terrain().iter().for_each(|(chunk_key, _)| {
let mut should_drop = true; let mut should_drop = true;
// For each player with a position, calculate the distance. // For each player with a position, calculate the distance.
@ -258,15 +273,9 @@ impl Server {
) )
.join() .join()
{ {
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
let adjusted_dist_sqr = Vec2::from(chunk_pos - key)
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared();
if player if player
.view_distance .view_distance
.map(|vd| adjusted_dist_sqr <= vd.pow(2)) .map(|vd| chunk_in_vd(pos.0, chunk_key, &self.state.terrain(), vd))
.unwrap_or(false) .unwrap_or(false)
{ {
should_drop = false; should_drop = false;
@ -275,7 +284,7 @@ impl Server {
} }
if should_drop { if should_drop {
chunks_to_remove.push(key); chunks_to_remove.push(chunk_key);
} }
}); });
for key in chunks_to_remove { for key in chunks_to_remove {
@ -285,6 +294,36 @@ impl Server {
// 6) Synchronise clients with the new state of the world. // 6) Synchronise clients with the new state of the world.
self.sync_clients(); self.sync_clients();
// Sync changed chunks
'chunk: for chunk_key in &self.state.chunk_changes().modified_chunks {
let terrain = self.state.terrain();
for (entity, player, pos) in (
&self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Player>(),
&self.state.ecs().read_storage::<comp::Pos>(),
)
.join()
{
if player
.view_distance
.map(|vd| chunk_in_vd(pos.0, *chunk_key, &terrain, vd))
.unwrap_or(false)
{
self.clients.notify(
entity,
ServerMsg::TerrainChunkUpdate {
key: *chunk_key,
chunk: Box::new(match self.state.terrain().get_key(*chunk_key) {
Some(chunk) => chunk.clone(),
None => break 'chunk,
}),
},
);
}
}
}
// 7) Finish the tick, pass control back to the frontend. // 7) Finish the tick, pass control back to the frontend.
// Cleanup // Cleanup

View File

@ -88,12 +88,20 @@ impl Terrain {
let current_tick = client.get_tick(); let current_tick = client.get_tick();
// Add any recently created or changed chunks to the list of chunks to be meshed. // Add any recently created or changed chunks to the list of chunks to be meshed.
for pos in client for (modified, pos) in client
.state() .state()
.changes() .chunk_changes()
.new_chunks .modified_chunks
.iter() .iter()
.chain(client.state().changes().changed_chunks.iter()) .map(|c| (true, c))
.chain(
client
.state()
.chunk_changes()
.new_chunks
.iter()
.map(|c| (false, c)),
)
{ {
// TODO: ANOTHER PROBLEM HERE! // TODO: ANOTHER PROBLEM HERE!
// What happens if the block on the edge of a chunk gets modified? We need to spawn // What happens if the block on the edge of a chunk gets modified? We need to spawn
@ -103,7 +111,7 @@ impl Terrain {
for j in -1..2 { for j in -1..2 {
let pos = pos + Vec2::new(i, j); let pos = pos + Vec2::new(i, j);
if !self.chunks.contains_key(&pos) { if !self.chunks.contains_key(&pos) || modified {
let mut neighbours = true; let mut neighbours = true;
for i in -1..2 { for i in -1..2 {
for j in -1..2 { for j in -1..2 {
@ -116,18 +124,21 @@ impl Terrain {
} }
if neighbours { if neighbours {
self.mesh_todo.entry(pos).or_insert(ChunkMeshState { self.mesh_todo.insert(
pos, pos,
started_tick: current_tick, ChunkMeshState {
active_worker: false, pos,
}); started_tick: current_tick,
active_worker: false,
},
);
} }
} }
} }
} }
} }
// Remove any models for chunks that have been recently removed. // Remove any models for chunks that have been recently removed.
for pos in &client.state().changes().removed_chunks { for pos in &client.state().chunk_changes().removed_chunks {
self.chunks.remove(pos); self.chunks.remove(pos);
self.mesh_todo.remove(pos); self.mesh_todo.remove(pos);
} }
@ -218,6 +229,8 @@ impl Terrain {
z_bounds: response.z_bounds, z_bounds: response.z_bounds,
}, },
); );
self.mesh_todo.remove(&response.pos);
} }
// Chunk must have been removed, or it was spawned on an old tick. Drop the mesh // Chunk must have been removed, or it was spawned on an old tick. Drop the mesh
// since it's either out of date or no longer needed. // since it's either out of date or no longer needed.