player position sync, refactor some stuff

This commit is contained in:
griffi-gh 2023-05-20 02:32:32 +02:00
parent af356565a3
commit 78689bbe1c
22 changed files with 151 additions and 84 deletions

1
Cargo.lock generated
View file

@ -954,6 +954,7 @@ dependencies = [
"rayon", "rayon",
"serde_json", "serde_json",
"shipyard", "shipyard",
"static_assertions",
"strum", "strum",
"uflow", "uflow",
"winapi", "winapi",

View file

@ -12,6 +12,9 @@ opt-level = 1
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 1 opt-level = 1
[profile.dev.package.uflow]
opt-level = 3
[profile.dev.package.glium] [profile.dev.package.glium]
opt-level = 3 opt-level = 3

View file

@ -8,7 +8,7 @@ publish = false
kubi-shared = { path = "../kubi-shared" } kubi-shared = { path = "../kubi-shared" }
kubi-logging = { path = "../kubi-logging" } kubi-logging = { path = "../kubi-logging" }
log = "*" log = "*"
shipyard = { git = "https://github.com/leudz/shipyard", rev = "a4f4d27edcf", features = ["thread_local"] } shipyard = { git = "https://github.com/leudz/shipyard", rev = "a4f4d27edcf", default-features = false, features = ["std", "proc", "thread_local"] }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
toml = "0.7" toml = "0.7"
glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] } glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] }
@ -23,6 +23,7 @@ postcard = { version = "1.0", features = ["alloc"] }
lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] } lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] }
[features] [features]
default = [] default = ["parallel"]
parallel = ["shipyard/parallel"]
safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"] safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"]
nightly = ["hashbrown/nightly", "rand/nightly", "rand/simd_support", "glam/core-simd", "kubi-shared/nightly"] nightly = ["hashbrown/nightly", "rand/nightly", "rand/simd_support", "glam/core-simd", "kubi-shared/nightly"]

View file

@ -1,11 +1,13 @@
use glam::Mat4; use glam::Mat4;
use shipyard::{Component, EntityId, Unique, AllStoragesView, UniqueView, NonSendSync, View, ViewMut, Get}; use shipyard::{Component, EntityId, Unique, AllStoragesView, UniqueView, NonSendSync, View, ViewMut, Get, IntoIter};
use hashbrown::HashMap; use hashbrown::HashMap;
use uflow::SendMode;
use std::net::SocketAddr; use std::net::SocketAddr;
use kubi_shared::{ use kubi_shared::{
networking::{ networking::{
client::{ClientIdMap, Client}, client::{ClientIdMap, Client},
messages::{ClientToServerMessage, C_POSITION_CHANGED} messages::{ClientToServerMessage, ServerToClientMessage, C_POSITION_CHANGED},
channels::CHANNEL_MOVE
}, },
transform::Transform transform::Transform
}; };
@ -35,7 +37,8 @@ pub fn sync_client_positions(
events: UniqueView<ServerEvents>, events: UniqueView<ServerEvents>,
addr_map: UniqueView<ClientAddressMap>, addr_map: UniqueView<ClientAddressMap>,
clients: View<Client>, clients: View<Client>,
mut transforms: ViewMut<Transform> mut transforms: ViewMut<Transform>,
addrs: View<ClientAddress>,
) { ) {
for event in &events.0 { for event in &events.0 {
let Some(message) = check_message_auth::<C_POSITION_CHANGED>(&server, event, &clients, &addr_map) else { let Some(message) = check_message_auth::<C_POSITION_CHANGED>(&server, event, &clients, &addr_map) else {
@ -44,9 +47,34 @@ pub fn sync_client_positions(
let ClientToServerMessage::PositionChanged { position, velocity: _, direction } = message.message else { let ClientToServerMessage::PositionChanged { position, velocity: _, direction } = message.message else {
unreachable!() unreachable!()
}; };
//Apply position to client
//log movement (annoying duh)
log::debug!("dbg: player moved id: {} coords: {} quat: {}", message.client_id, position, direction);
//Apply position to server-side client
let mut trans = (&mut transforms).get(message.entity_id).unwrap(); let mut trans = (&mut transforms).get(message.entity_id).unwrap();
trans.0 = Mat4::from_rotation_translation(direction, position); trans.0 = Mat4::from_rotation_translation(direction, position);
//Transmit the change to other players
for (other_client, other_client_address) in (&clients, &addrs).iter() {
if other_client.0 == message.client_id {
continue
}
let Some(client) = server.0.client(&other_client_address.0) else {
log::error!("Client with address not found");
continue
};
client.borrow_mut().send(
postcard::to_allocvec(
&ServerToClientMessage::PlayerPositionChanged {
client_id: message.client_id,
position,
direction
}
).unwrap().into_boxed_slice(),
CHANNEL_MOVE,
SendMode::Reliable
);
}
} }
} }

View file

@ -10,7 +10,7 @@ mod auth;
use config::read_config; use config::read_config;
use server::{bind_server, update_server, log_server_errors}; use server::{bind_server, update_server, log_server_errors};
use client::init_client_maps; use client::{init_client_maps, sync_client_positions};
use auth::authenticate_players; use auth::authenticate_players;
use world::{update_world, init_world}; use world::{update_world, init_world};
@ -30,6 +30,7 @@ fn update() -> Workload {
log_server_errors, log_server_errors,
authenticate_players, authenticate_players,
update_world, update_world,
sync_client_positions,
).into_workload() ).into_workload()
).into_sequential_workload() ).into_sequential_workload()
} }

View file

@ -35,7 +35,7 @@ pub fn bind_server(
endpoint_config: EndpointConfig { endpoint_config: EndpointConfig {
active_timeout_ms: config.server.timeout_ms, active_timeout_ms: config.server.timeout_ms,
keepalive: true, keepalive: true,
keepalive_interval_ms: 1000, keepalive_interval_ms: 300,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()

View file

@ -23,6 +23,7 @@ uflow = "0.7"
postcard = { version = "1.0", features = ["alloc"] } postcard = { version = "1.0", features = ["alloc"] }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] } lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] }
static_assertions = "1.1"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3" } winapi = { version = "0.3" }
@ -32,4 +33,4 @@ default = []
generate_visualizer_data = ["serde_json", "shipyard/serde1"] generate_visualizer_data = ["serde_json", "shipyard/serde1"]
safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"] safe_lz4 = ["lz4_flex/safe-encode", "lz4_flex/safe-decode"]
parallel = ["shipyard/parallel"] parallel = ["shipyard/parallel"]
nightly = ["hashbrown/nightly", "glam/core-simd", "kubi-shared/nightly"] nightly = ["hashbrown/nightly", "glam/core-simd", "static_assertions/nightly", "kubi-shared/nightly"]

View file

@ -6,5 +6,7 @@ out vec4 out_color;
uniform vec4 color; uniform vec4 color;
void main() { void main() {
// discard fully transparent pixels
if (color.w <= 0.) discard;
out_color = color; out_color = color;
} }

View file

@ -1,11 +0,0 @@
#version 300 es
precision highp float;
out vec4 color;
uniform vec4 u_color;
void main() {
color = u_color;
// color -= vec4(0, 0, 0, 0.1 * sin(gl_FragCoord.x) * cos(gl_FragCoord.y));
}

View file

@ -1,12 +0,0 @@
#version 300 es
precision highp float;
in vec3 position;
uniform ivec3 u_position;
uniform mat4 perspective;
uniform mat4 view;
void main() {
gl_Position = perspective * view * vec4(position + vec3(u_position), 1.);
}

View file

@ -19,20 +19,8 @@ uniform sampler2DArray tex;
void main() { void main() {
// base color from texture // base color from texture
color = texture(tex, vec3(v_uv, v_tex_index)); color = texture(tex, vec3(v_uv, v_tex_index));
// HACKY texture "antialiasing"
// color += (
// alpha_drop(color, texture(tex, vec3(v_uv + vec2(.000, .001), v_tex_index))) +
// alpha_drop(color, texture(tex, vec3(v_uv + vec2(.001, .000), v_tex_index))) +
// alpha_drop(color, texture(tex, vec3(v_uv + vec2(.001, .001), v_tex_index))) +
// alpha_drop(color, texture(tex, vec3(v_uv - vec2(.000, .001), v_tex_index))) +
// alpha_drop(color, texture(tex, vec3(v_uv - vec2(.001, .000), v_tex_index))) +
// alpha_drop(color, texture(tex, vec3(v_uv - vec2(.001, .001), v_tex_index)))
// ) / 6.;
// color /= 2.;
// discard fully transparent pixels // discard fully transparent pixels
if (color.w <= 0.0) { if (color.w <= 0.0) discard;
discard;
}
//basic "lighting" //basic "lighting"
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z); float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
color *= vec4(vec3(light), 1.); color *= vec4(vec3(light), 1.);

View file

@ -63,7 +63,7 @@ fn block_placement_system(
(ray.block_position, Block::Air) (ray.block_position, Block::Air)
}; };
//queue place //queue place
block_event_queue.push(QueuedBlock { block_event_queue.0.push(QueuedBlock {
position: place_position, position: place_position,
block_type: place_block, block_type: place_block,
soft: place_block != Block::Air, soft: place_block != Block::Air,

View file

@ -69,9 +69,9 @@ use rendering::{
init_window_size, init_window_size,
update_window_size, update_window_size,
primitives::init_primitives, primitives::init_primitives,
world::{draw_world, draw_current_chunk_border},
selection_box::render_selection_box, selection_box::render_selection_box,
world::draw_world, entities::render_entities,
world::draw_current_chunk_border,
}; };
use block_placement::update_block_placement; use block_placement::update_block_placement;
use delta_time::{DeltaTime, init_delta_time}; use delta_time::{DeltaTime, init_delta_time};
@ -107,6 +107,7 @@ fn startup() -> Workload {
init_delta_time, init_delta_time,
).into_sequential_workload() ).into_sequential_workload()
} }
fn update() -> Workload { fn update() -> Workload {
( (
update_window_size, update_window_size,
@ -143,6 +144,7 @@ fn update() -> Workload {
disconnect_on_exit.run_if(is_multiplayer), disconnect_on_exit.run_if(is_multiplayer),
).into_sequential_workload() ).into_sequential_workload()
} }
fn render() -> Workload { fn render() -> Workload {
( (
clear_background, clear_background,
@ -150,10 +152,12 @@ fn render() -> Workload {
draw_world, draw_world,
draw_current_chunk_border, draw_current_chunk_border,
render_selection_box, render_selection_box,
render_entities,
).into_sequential_workload().run_if(is_ingame), ).into_sequential_workload().run_if(is_ingame),
render_gui, render_gui,
).into_sequential_workload() ).into_sequential_workload()
} }
fn after_frame_end() -> Workload { fn after_frame_end() -> Workload {
( (
clear_events, clear_events,

View file

@ -1,7 +1,10 @@
use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator}; use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator};
use glium::glutin::event_loop::ControlFlow; use glium::glutin::event_loop::ControlFlow;
use std::net::SocketAddr; use std::net::SocketAddr;
use uflow::{client::{Client, Config as ClientConfig, Event as ClientEvent}, EndpointConfig}; use uflow::{
client::{Client, Config as ClientConfig, Event as ClientEvent},
EndpointConfig
};
use kubi_shared::networking::{ use kubi_shared::networking::{
messages::ServerToClientMessage, messages::ServerToClientMessage,
state::ClientJoinState, state::ClientJoinState,
@ -71,7 +74,7 @@ fn connect_client(
endpoint_config: EndpointConfig { endpoint_config: EndpointConfig {
active_timeout_ms: 10000, active_timeout_ms: 10000,
keepalive: true, keepalive: true,
keepalive_interval_ms: 1000, keepalive_interval_ms: 300,
..Default::default() ..Default::default()
}, },
}).expect("Client connection failed"); }).expect("Client connection failed");
@ -128,8 +131,8 @@ pub fn update_networking() -> Workload {
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>), ).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>),
( (
( (
receive_player_connect_events receive_player_connect_events,
), ).into_workload(),
( (
recv_block_place_events, recv_block_place_events,
receive_player_movement_events, receive_player_movement_events,

View file

@ -4,7 +4,7 @@ use uflow::{SendMode, client::Event as ClientEvent};
use kubi_shared::{ use kubi_shared::{
transform::Transform, transform::Transform,
networking::{ networking::{
messages::{ClientToServerMessage, ServerToClientMessage, S_PLAYER_POSITION_CHANGED}, messages::{ClientToServerMessage, ServerToClientMessage, S_PLAYER_POSITION_CHANGED, S_PLAYER_CONNECTED},
channels::CHANNEL_MOVE, channels::CHANNEL_MOVE,
client::ClientIdMap, client::ClientIdMap,
}, },
@ -83,7 +83,7 @@ pub fn receive_player_connect_events(
let ClientEvent::Receive(data) = &event.0 else { let ClientEvent::Receive(data) = &event.0 else {
return None return None
}; };
if !event.is_message_of_type::<S_PLAYER_POSITION_CHANGED>() { if !event.is_message_of_type::<S_PLAYER_CONNECTED>() {
return None return None
}; };
let Ok(parsed_message) = postcard::from_bytes(data) else { let Ok(parsed_message) = postcard::from_bytes(data) else {

View file

@ -86,6 +86,6 @@ pub fn recv_block_place_events(
let ServerToClientMessage::QueueBlock { item } = parsed_message else { let ServerToClientMessage::QueueBlock { item } = parsed_message else {
unreachable!() unreachable!()
}; };
queue.push(item); queue.0.push(item);
} }
} }

View file

@ -37,18 +37,19 @@ impl AssetPaths for BlockTexture {
} }
#[derive(Unique)] #[derive(Unique)]
#[repr(transparent)]
pub struct BlockTexturesPrefab(pub SrgbTexture2dArray); pub struct BlockTexturesPrefab(pub SrgbTexture2dArray);
#[derive(Unique)] #[derive(Unique)]
#[repr(transparent)]
pub struct ChunkShaderPrefab(pub Program); pub struct ChunkShaderPrefab(pub Program);
#[derive(Unique)] #[derive(Unique)]
pub struct SelBoxShaderPrefab(pub Program); #[repr(transparent)]
#[derive(Unique)]
pub struct ColoredShaderPrefab(pub Program); pub struct ColoredShaderPrefab(pub Program);
#[derive(Unique)] #[derive(Unique)]
#[repr(transparent)]
pub struct ProgressbarShaderPrefab(pub Program); pub struct ProgressbarShaderPrefab(pub Program);
pub fn load_prefabs( pub fn load_prefabs(
@ -73,14 +74,6 @@ pub fn load_prefabs(
&renderer.display &renderer.display
) )
)); ));
storages.add_unique_non_send_sync(SelBoxShaderPrefab(
include_shader_prefab!(
"selection_box",
"../shaders/selection_box.vert",
"../shaders/selection_box.frag",
&renderer.display
)
));
storages.add_unique_non_send_sync(ColoredShaderPrefab( storages.add_unique_non_send_sync(ColoredShaderPrefab(
include_shader_prefab!( include_shader_prefab!(
"colored", "colored",

View file

@ -14,14 +14,18 @@ use crate::{events::WindowResizedEvent, settings::{GameSettings, FullscreenMode}
pub mod primitives; pub mod primitives;
pub mod world; pub mod world;
pub mod selection_box; pub mod selection_box;
pub mod entities;
#[derive(Unique)] #[derive(Unique)]
#[repr(transparent)]
pub struct RenderTarget(pub glium::Frame); pub struct RenderTarget(pub glium::Frame);
#[derive(Unique)] #[derive(Unique)]
#[repr(transparent)]
pub struct BackgroundColor(pub Vec3); pub struct BackgroundColor(pub Vec3);
#[derive(Unique, Clone, Copy)] #[derive(Unique, Clone, Copy)]
#[repr(transparent)]
pub struct WindowSize(pub UVec2); pub struct WindowSize(pub UVec2);
#[derive(Unique)] #[derive(Unique)]
@ -32,7 +36,6 @@ impl Renderer {
pub fn init(event_loop: &EventLoop<()>, settings: &GameSettings) -> Self { pub fn init(event_loop: &EventLoop<()>, settings: &GameSettings) -> Self {
log::info!("initializing display"); log::info!("initializing display");
let wb = WindowBuilder::new() let wb = WindowBuilder::new()
.with_title("kubi") .with_title("kubi")
.with_maximized(true) .with_maximized(true)

View file

@ -0,0 +1,59 @@
use shipyard::{NonSendSync, UniqueViewMut, UniqueView, View, IntoIter, IntoWithId};
use glium::{DepthTest, Depth, PolygonMode, BackfaceCullingMode, DrawParameters, Surface, uniform};
use kubi_shared::{entity::Entity, transform::Transform};
use crate::{
prefabs::ColoredShaderPrefab,
camera::Camera,
settings::GameSettings,
player::MainPlayer
};
use super::{
RenderTarget,
primitives::cube::CubePrimitive
};
// TODO: entity models
pub fn render_entities(
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
buffers: NonSendSync<UniqueView<CubePrimitive>>,
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
camera: View<Camera>,
settings: UniqueView<GameSettings>,
entities: View<Entity>,
transform: View<Transform>,
) {
let (camera_id, camera) = camera.iter().with_id().next().expect("No cameras in the scene");
let draw_parameters = DrawParameters {
depth: Depth {
test: DepthTest::IfLess,
write: true,
..Default::default()
},
multisampling: settings.msaa.is_some(),
polygon_mode: PolygonMode::Fill,
backface_culling: BackfaceCullingMode::CullClockwise,
..Default::default()
};
let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d();
for (entity_id, (_, trans)) in (&entities, &transform).iter().with_id() {
//skip rendering camera holder (as the entity would block the view)
if entity_id == camera_id { continue }
//render entity
target.0.draw(
&buffers.0,
&buffers.1,
&program.0,
&uniform! {
color: [1.0, 1.0, 1.0, 1.0_f32],
model: trans.0.to_cols_array_2d(),
view: view,
perspective: perspective,
},
&draw_parameters
).unwrap();
}
}

View file

@ -1,3 +1,4 @@
use glam::{Mat4, Vec3, Quat};
use shipyard::{View, IntoIter, NonSendSync, UniqueViewMut, UniqueView}; use shipyard::{View, IntoIter, NonSendSync, UniqueViewMut, UniqueView};
use glium::{ use glium::{
Surface, Surface,
@ -8,18 +9,21 @@ use glium::{
}; };
use crate::{ use crate::{
world::raycast::LookingAtBlock, world::raycast::LookingAtBlock,
camera::Camera, prefabs::SelBoxShaderPrefab camera::Camera,
prefabs::ColoredShaderPrefab
}; };
use super::{ use super::{
RenderTarget, RenderTarget,
primitives::cube::CubePrimitive, primitives::cube::CubePrimitive,
}; };
const SMOL: f32 = 0.0001;
pub fn render_selection_box( pub fn render_selection_box(
lookat: View<LookingAtBlock>, lookat: View<LookingAtBlock>,
camera: View<Camera>, camera: View<Camera>,
mut target: NonSendSync<UniqueViewMut<RenderTarget>>, mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
program: NonSendSync<UniqueView<SelBoxShaderPrefab>>, program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
buffers: NonSendSync<UniqueView<CubePrimitive>>, buffers: NonSendSync<UniqueView<CubePrimitive>>,
) { ) {
let camera = camera.iter().next().unwrap(); let camera = camera.iter().next().unwrap();
@ -32,8 +36,12 @@ pub fn render_selection_box(
&buffers.1, &buffers.1,
&program.0, &program.0,
&uniform! { &uniform! {
u_color: [0., 0., 0., 0.5_f32], color: [0., 0., 0., 0.5_f32],
u_position: lookat.block_position.to_array(), model: Mat4::from_scale_rotation_translation(
Vec3::splat(1. + SMOL * 2.),
Quat::default(),
lookat.block_position.as_vec3() - Vec3::splat(SMOL)
).to_cols_array_2d(),
perspective: camera.perspective_matrix.to_cols_array_2d(), perspective: camera.perspective_matrix.to_cols_array_2d(),
view: camera.view_matrix.to_cols_array_2d(), view: camera.view_matrix.to_cols_array_2d(),
}, },
@ -41,7 +49,8 @@ pub fn render_selection_box(
backface_culling: BackfaceCullingMode::CullClockwise, backface_culling: BackfaceCullingMode::CullClockwise,
blend: Blend::alpha_blending(), blend: Blend::alpha_blending(),
depth: Depth { depth: Depth {
test: DepthTest::IfLessOrEqual, //this may be unreliable! //this may be unreliable... unless scale is applied! hacky...
test: DepthTest::IfLessOrEqual,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()

View file

@ -191,7 +191,7 @@ fn process_completed_tasks(
let mut ops: usize = 0; let mut ops: usize = 0;
while let Some(res) = task_manager.receive() { while let Some(res) = task_manager.receive() {
match res { match res {
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued } => { ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => {
//check if chunk exists //check if chunk exists
let Some(chunk) = world.chunks.get_mut(&position) else { let Some(chunk) = world.chunks.get_mut(&position) else {
log::warn!("blocks data discarded: chunk doesn't exist"); log::warn!("blocks data discarded: chunk doesn't exist");
@ -213,10 +213,8 @@ fn process_completed_tasks(
chunk.current_state = CurrentChunkState::Loaded; chunk.current_state = CurrentChunkState::Loaded;
//push queued blocks //push queued blocks
//TODO use extend queue.0.append(&mut queued);
for item in queued { drop(queued); //`queued` is empty after `append`
queue.push(item);
}
//increase ops counter //increase ops counter
ops += 1; ops += 1;

View file

@ -4,16 +4,12 @@ use shipyard::{UniqueViewMut, Unique};
use super::ChunkStorage; use super::ChunkStorage;
#[derive(Unique, Default, Clone)] #[derive(Unique, Default, Clone)]
pub struct BlockUpdateQueue { #[repr(transparent)]
queue: Vec<QueuedBlock> pub struct BlockUpdateQueue(pub Vec<QueuedBlock>);
}
impl BlockUpdateQueue { impl BlockUpdateQueue {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn push(&mut self, event: QueuedBlock) {
self.queue.push(event)
}
} }
pub fn apply_queued_blocks( pub fn apply_queued_blocks(
@ -21,7 +17,7 @@ pub fn apply_queued_blocks(
mut world: UniqueViewMut<ChunkStorage> mut world: UniqueViewMut<ChunkStorage>
) { ) {
//maybe i need to check for desired/current state here before marking as dirty? //maybe i need to check for desired/current state here before marking as dirty?
queue.queue.retain(|&event| { queue.0.retain(|&event| {
if let Some(block) = world.get_block_mut(event.position) { if let Some(block) = world.get_block_mut(event.position) {
if event.soft && *block != Block::Air { if event.soft && *block != Block::Air {
return false return false