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 631df59083
5 changed files with 222 additions and 52 deletions

View File

@ -5,7 +5,8 @@ use crate::{
comp,
msg::{EcsCompPacket, EcsResPacket},
sys,
terrain::{TerrainChunk, TerrainMap},
terrain::{Block, TerrainChunk, TerrainMap},
vol::WriteVol,
};
use rayon::{ThreadPool, ThreadPoolBuilder};
use serde_derive::{Deserialize, Serialize};
@ -15,7 +16,11 @@ use specs::{
Component, DispatcherBuilder, Entity as EcsEntity,
};
use sphynx;
use std::{collections::HashSet, sync::Arc, time::Duration};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
use vek::*;
/// 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.
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 changed_chunks: HashSet<Vec2<i32>>,
pub modified_chunks: HashSet<Vec2<i32>>,
pub removed_chunks: HashSet<Vec2<i32>>,
}
impl Changes {
pub fn default() -> Self {
impl Default for ChunkChanges {
fn default() -> Self {
Self {
new_chunks: HashSet::new(),
changed_chunks: HashSet::new(),
modified_chunks: HashSet::new(),
removed_chunks: HashSet::new(),
}
}
}
pub fn cleanup(&mut self) {
impl ChunkChanges {
pub fn clear(&mut self) {
self.new_chunks.clear();
self.changed_chunks.clear();
self.modified_chunks.clear();
self.removed_chunks.clear();
}
}
@ -68,7 +97,6 @@ pub struct State {
ecs: sphynx::World<EcsCompPacket, EcsResPacket>,
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
thread_pool: Arc<ThreadPool>,
changes: Changes,
}
impl State {
@ -77,7 +105,6 @@ impl State {
Self {
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
changes: Changes::default(),
}
}
@ -92,7 +119,6 @@ impl State {
state_package,
),
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(DeltaTime(0.0));
ecs.add_resource(TerrainMap::new().unwrap());
ecs.add_resource(TerrainChange::default());
ecs.add_resource(ChunkChanges::default());
}
/// 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
/// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes {
&self.changes
pub fn chunk_changes(&self) -> Fetch<ChunkChanges> {
self.ecs.read_resource()
}
/// Get the current in-game time of day.
@ -197,12 +225,12 @@ impl State {
/// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainMap> {
self.ecs.read_resource::<TerrainMap>()
self.ecs.read_resource()
}
/// Get a writable reference to this state's terrain.
pub fn terrain_mut(&self) -> FetchMut<TerrainMap> {
self.ecs.write_resource::<TerrainMap>()
self.ecs.write_resource()
}
/// Removes every chunk of the terrain.
@ -226,9 +254,15 @@ impl State {
.insert(key, Arc::new(chunk))
.is_some()
{
self.changes.changed_chunks.insert(key);
self.ecs
.write_resource::<ChunkChanges>()
.modified_chunks
.insert(key);
} else {
self.changes.new_chunks.insert(key);
self.ecs
.write_resource::<ChunkChanges>()
.new_chunks
.insert(key);
}
}
@ -240,7 +274,10 @@ impl State {
.remove(key)
.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);
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.
pub fn cleanup(&mut self) {
// 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 crate::{
vol::{BaseVol, ReadVol, WriteVol},
vol::{BaseVol, ReadVol, VolSize, WriteVol},
volumes::chunk::{Chunk, ChunkErr},
};
use fxhash::FxHashMap;
@ -185,21 +185,24 @@ impl WriteVol for Chonk {
self.sub_chunks[sub_chunk_idx] = SubChunk::Hash(*cblock, map);
Ok(())
}
SubChunk::Hash(cblock, _map) if block == *cblock => Ok(()),
SubChunk::Hash(_cblock, map) if map.len() < 4096 => {
SubChunk::Hash(cblock, map) if block == *cblock => {
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);
Ok(())
}
SubChunk::Hash(cblock, map) => {
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 {
new_chunk
.set(map_pos.map(|e| e as i32), *map_block)
.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);
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)]
pub enum SubChunk {
Homogeneous(Block),
Hash(Block, FxHashMap<Vec3<u8>, Block>),
Heterogeneous(Chunk<Block, TerrainChunkSize, ()>),
Heterogeneous(Chunk<Block, SubChunkSize, ()>),
}
impl SubChunk {

View File

@ -7,7 +7,9 @@ use common::{
comp,
msg::ServerMsg,
npc::{get_npc_name, NpcKind},
state::TimeOfDay,
state::{TerrainChange, TimeOfDay},
terrain::Block,
vol::Vox,
};
use specs::{Builder, Entity as EcsEntity, Join};
use vek::*;
@ -104,6 +106,18 @@ lazy_static! {
"/players : Show the online players list",
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(
"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(
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 => {
server.clients.notify(
entity,
ServerMsg::Chat(format!("You don't have any position!")),
);
server
.clients
.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) {
for cmd in CHAT_COMMANDS.iter() {
server

View File

@ -17,7 +17,7 @@ use common::{
msg::{ClientMsg, ClientState, RequestStateError, ServerInfo, ServerMsg},
net::PostOffice,
state::{State, Uid},
terrain::{TerrainChunk, TerrainChunkSize},
terrain::{TerrainChunk, TerrainChunkSize, TerrainMap},
vol::VolSize,
};
use log::{debug, warn};
@ -246,9 +246,24 @@ impl Server {
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.
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;
// For each player with a position, calculate the distance.
@ -258,15 +273,9 @@ impl Server {
)
.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
.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)
{
should_drop = false;
@ -275,7 +284,7 @@ impl Server {
}
if should_drop {
chunks_to_remove.push(key);
chunks_to_remove.push(chunk_key);
}
});
for key in chunks_to_remove {
@ -285,6 +294,36 @@ impl Server {
// 6) Synchronise clients with the new state of the world.
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.
// Cleanup

View File

@ -88,12 +88,20 @@ impl Terrain {
let current_tick = client.get_tick();
// 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()
.changes()
.new_chunks
.chunk_changes()
.modified_chunks
.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!
// 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 {
let pos = pos + Vec2::new(i, j);
if !self.chunks.contains_key(&pos) {
if !self.chunks.contains_key(&pos) || modified {
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
@ -116,18 +124,21 @@ impl Terrain {
}
if neighbours {
self.mesh_todo.entry(pos).or_insert(ChunkMeshState {
self.mesh_todo.insert(
pos,
started_tick: current_tick,
active_worker: false,
});
ChunkMeshState {
pos,
started_tick: current_tick,
active_worker: false,
},
);
}
}
}
}
}
// 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.mesh_todo.remove(pos);
}
@ -218,6 +229,8 @@ impl Terrain {
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
// since it's either out of date or no longer needed.