mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
631df59083
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user