CHUNK GENERATION IS REAL

This commit is contained in:
2024-06-21 00:36:37 -04:00
parent aa466a248c
commit b8ed5297fa
43 changed files with 1341 additions and 517 deletions
+182
View File
@@ -0,0 +1,182 @@
use std::collections::{HashMap, HashSet, VecDeque};
use bevy_ecs::{entity::Entity, system::Commands};
use nalgebra::Vector3;
use ndarray::{s, Array3, Axis};
use simdnoise::NoiseBuilder;
use crate::{
client::render::voxel::VoxelColor,
common::component::{chunk, ChunkBundle, ChunkData, ChunkMesh, ChunkPos},
util::{
oct_tree::OctTree,
thread::{ExitType, ThreadChannel, ThreadHandle},
},
};
pub struct ChunkManager {
handles: Vec<ThreadHandle<ChunkLoaderMsg, ServerChunkMsg>>,
i: usize,
n: usize,
map: HashMap<ChunkPos, Entity>,
generating: HashSet<ChunkPos>,
}
impl ChunkManager {
pub fn new() -> Self {
let n = 4;
Self {
handles: std::iter::repeat_with(|| ThreadHandle::spawn(chunk_loader_main))
.take(n)
.collect(),
i: 0,
n,
map: HashMap::new(),
generating: HashSet::new(),
}
}
pub fn entity_at(&self, pos: &ChunkPos) -> Option<&Entity> {
self.map.get(pos)
}
pub fn is_generating(&self, pos: &ChunkPos) -> bool {
self.generating.contains(pos)
}
pub fn queue(&mut self, pos: ChunkPos) {
if !self.is_generating(&pos) {
self.handles[self.i].send(ChunkLoaderMsg::Generate(pos));
self.i = (self.i + 1) % self.n;
self.generating.insert(pos);
}
}
pub fn update(&mut self, commands: &mut Commands) {
for msg in self.handles.iter_mut().flat_map(|h| h.recv()) {
match msg {
ServerChunkMsg::ChunkGenerated(chunk) => {
let id = commands
.spawn(ChunkBundle {
pos: chunk.pos,
data: chunk.data,
mesh: chunk.mesh,
})
.id();
self.map.insert(chunk.pos, id);
self.generating.remove(&chunk.pos);
}
}
}
}
}
pub struct GeneratedChunk {
pub pos: ChunkPos,
pub data: ChunkData,
pub mesh: ChunkMesh,
}
impl Drop for ChunkManager {
fn drop(&mut self) {
for h in &mut self.handles {
h.send(ChunkLoaderMsg::Exit);
h.join();
}
}
}
enum ServerChunkMsg {
ChunkGenerated(GeneratedChunk),
}
enum ChunkLoaderMsg {
Generate(ChunkPos),
Exit,
}
impl ExitType for ChunkLoaderMsg {
fn exit() -> Self {
Self::Exit
}
}
fn chunk_loader_main(channel: ThreadChannel<ServerChunkMsg, ChunkLoaderMsg>) {
let mut to_generate = VecDeque::new();
'outer: loop {
let msg = channel.recv_wait();
match msg {
ChunkLoaderMsg::Generate(pos) => {
to_generate.push_back(pos);
}
ChunkLoaderMsg::Exit => {
break 'outer;
}
}
if let Some(pos) = to_generate.pop_front() {
let data = generate(pos);
let mesh = ChunkMesh::from_data(&data);
let data = if pos.y > 0 || pos.y < -1 {
ChunkData::empty()
} else {
ChunkData::from_tree(OctTree::from_arr(data.slice(s![
1..data.len_of(Axis(0)) - 1,
1..data.len_of(Axis(1)) - 1,
1..data.len_of(Axis(2)) - 1
])))
};
channel.send(ServerChunkMsg::ChunkGenerated(GeneratedChunk {
pos,
data,
mesh,
}));
}
}
}
fn generate(pos: ChunkPos) -> Array3<VoxelColor> {
let shape = [chunk::SIDE_LENGTH + 2; 3];
if pos.y > 0 {
return Array3::from_elem(shape, VoxelColor::none());
}
if pos.y < -1 {
return Array3::from_elem(shape, VoxelColor::none());
}
let posf: Vector3<f32> = (pos.cast() * chunk::SIDE_LENGTH as f32) - Vector3::from_element(1.0);
let (a, b, c, d) = (0.0, 50.0, 100.0, 127.0);
let (noise, ..) = NoiseBuilder::gradient_2d_offset(
posf.x,
chunk::SIDE_LENGTH + 2,
posf.z,
chunk::SIDE_LENGTH + 2,
)
.with_seed(0)
.with_freq(0.005)
.generate();
Array3::from_shape_fn(shape, |(x, y, z)| {
let y = y as f32 + posf.y;
let n = (noise[x + z * (chunk::SIDE_LENGTH + 2)] + 0.022) * (1.0 / 0.044) * d;
if y < n.max(b) {
if y < b {
VoxelColor {
r: 100,
g: 100,
b: 255,
a: 255,
}
} else if y < c {
VoxelColor {
r: 100,
g: 255,
b: 100,
a: 255,
}
} else {
VoxelColor {
r: 150,
g: 150,
b: 150,
a: 255,
}
}
} else {
VoxelColor::none()
}
})
}
+2
View File
@@ -0,0 +1,2 @@
mod load;
pub use load::*;
+80
View File
@@ -0,0 +1,80 @@
use std::collections::{hash_map, HashMap};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{component::Component, entity::Entity, system::Resource};
use crate::common::{ClientChannel, ClientMessage, ServerMessage};
pub enum ServerClient {
Local(ClientChannel),
}
impl ServerClient {
pub fn recv(&mut self) -> Vec<ServerMessage> {
match self {
Self::Local(ch) => ch.recv().collect(),
}
}
pub fn send(&self, msg: ClientMessage) {
match self {
Self::Local(ch) => ch.send(msg),
}
}
}
#[derive(Deref, DerefMut)]
pub struct ServerClients {
map: HashMap<Entity, ServerClient>,
}
impl ServerClients {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn add(&mut self, id: Entity, client: ServerClient) {
self.map.insert(id, client);
}
}
impl<'a> IntoIterator for &'a mut ServerClients {
type Item = (&'a Entity, &'a mut ServerClient);
type IntoIter = hash_map::IterMut<'a, Entity, ServerClient>;
fn into_iter(self) -> Self::IntoIter {
self.map.iter_mut()
}
}
// I don't think it's worth putting a reciever in here rn
// and moving that stuff into the ecs but we'll see
#[derive(Component)]
pub struct ClientComponent {
send: Vec<ClientMessage>,
}
impl ClientComponent {
pub fn new() -> Self {
Self { send: Vec::new() }
}
pub fn send(&mut self, msg: ClientMessage) {
self.send.push(msg);
}
pub fn take(&mut self) -> Vec<ClientMessage> {
std::mem::take(&mut self.send)
}
}
#[derive(Resource)]
pub struct ClientBroadcast(Vec<ClientMessage>);
impl ClientBroadcast {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn send(&mut self, msg: ClientMessage) {
self.0.push(msg);
}
pub fn take(&mut self) -> Vec<ClientMessage> {
std::mem::take(&mut self.0)
}
}
+107 -43
View File
@@ -1,21 +1,29 @@
mod chunk;
mod client;
mod rsc;
mod system;
mod test;
use crate::{
sync::{ClientChannel, ClientMessage, ClientSender, ServerMessage},
world::{
component::{Orientation, Pos, Synced, VoxelGrid, VoxelGridBundle},
generation::generate,
pub use client::*;
use crate::common::{
component::{
ChunkBundle, ChunkData, ChunkMap, ChunkMesh, ChunkPos, Orientation, PlayerBundle, Pos,
VoxelGrid, VoxelGridBundle,
},
ClientChannel, ClientMessage, ServerMessage,
};
use bevy_ecs::{entity::Entity, query::With, system::SystemId, world::World};
use bevy_ecs::{entity::Entity, system::SystemId, world::World};
use chunk::ChunkManager;
use client::{ClientBroadcast, ServerClient, ServerClients};
use rsc::UPDATE_TIME;
use std::time::{Duration, Instant};
use test::spawn_test_stuff;
pub struct Server {
update_time: Duration,
target: Instant,
client: ClientChannel,
clients: ServerClients,
world: World,
systems: ServerSystems,
mov: Vec<Entity>,
@@ -24,23 +32,27 @@ pub struct Server {
pub struct ServerSystems {
sync_pos: SystemId,
sync_chunks: SystemId,
}
impl ServerSystems {
pub fn new(world: &mut World) -> Self {
Self {
sync_pos: world.register_system(system::sync::pos),
sync_chunks: world.register_system(system::sync::chunks),
}
}
}
impl Server {
pub fn new(client: ClientChannel) -> Self {
pub fn new() -> Self {
let mut world = World::new();
world.insert_resource(ClientSender(client.sender()));
world.insert_resource(ClientBroadcast::new());
world.insert_resource(ChunkMap::new());
world.insert_non_send_resource(ChunkManager::new());
let systems = ServerSystems::new(&mut world);
Self {
client,
clients: ServerClients::new(),
world,
systems,
target: Instant::now(),
@@ -50,58 +62,110 @@ impl Server {
}
}
pub fn from_client(client: ClientChannel) -> Self {
let mut s = Self::new();
s.add_client(ServerClient::Local(client));
s
}
pub fn add_client(&mut self, client: ServerClient) {
let id = self.world.spawn(ClientComponent::new()).id();
self.clients.add(id, client);
}
pub fn start(ch: ClientChannel) {
Self::new(ch).run();
Self::from_client(ch).run();
}
pub fn run(&mut self) {
generate(&mut self.world);
spawn_test_stuff(&mut self.world);
loop {
self.recv();
let now = Instant::now();
if now >= self.target {
self.target += self.update_time;
let mut q = self.world.query::<(Entity, &mut Pos)>();
for (e, mut p) in q.iter_mut(&mut self.world) {
if self.mov.contains(&e) {
p.x += 0.1;
}
}
self.world.run_system(self.systems.sync_pos).unwrap();
self.world.clear_trackers();
self.tick();
}
if self.stop {
break;
}
self.send();
}
}
pub fn tick(&mut self) {
let mut q = self.world.query::<(Entity, &mut Pos)>();
for (e, mut p) in q.iter_mut(&mut self.world) {
if self.mov.contains(&e) {
p.x += 0.1;
}
}
self.world.run_system(self.systems.sync_pos).unwrap();
self.world.run_system(self.systems.sync_chunks).unwrap();
self.world.clear_trackers();
}
pub fn recv(&mut self) {
for msg in self.client.recv() {
match msg {
ServerMessage::LoadWorld => {
let mut q = self
.world
.query_filtered::<(Entity, &Pos, &Orientation, &VoxelGrid), With<Synced>>();
// ePOG
for (e, p, o, g) in q.iter(&self.world) {
self.client.send(ClientMessage::SpawnVoxelGrid(
e,
VoxelGridBundle {
pos: *p,
orientation: *o,
grid: g.clone(),
},
))
for (id, client) in &mut self.clients {
for msg in client.recv() {
match msg {
ServerMessage::Join => {
let mut q = self
.world
.query::<(Entity, &Pos, &Orientation, &VoxelGrid)>();
// ePOG
for (e, p, o, g) in q.iter(&self.world) {
client.send(ClientMessage::SpawnVoxelGrid(
e,
VoxelGridBundle {
pos: *p,
orientation: *o,
grid: g.clone(),
},
))
}
let mut q = self
.world
.query::<(Entity, &ChunkPos, &ChunkData, &ChunkMesh)>();
for (e, p, c, m) in q.iter(&self.world) {
client.send(ClientMessage::LoadChunk(
e,
ChunkBundle {
pos: *p,
data: c.clone(),
mesh: m.clone(),
},
))
}
self.world.entity_mut(*id).insert(PlayerBundle::new());
}
ServerMessage::SpawnVoxelGrid(grid) => {
let e = self.world.spawn(grid.clone()).id();
self.mov.push(e);
self.world
.resource_mut::<ClientBroadcast>()
.send(ClientMessage::SpawnVoxelGrid(e, grid));
}
ServerMessage::Stop => {
self.stop = true;
}
}
ServerMessage::SpawnVoxelGrid(grid) => {
let e = self.world.spawn((grid.clone(), Synced)).id();
self.mov.push(e);
self.client.send(ClientMessage::SpawnVoxelGrid(e, grid));
}
ServerMessage::Stop => {
self.stop = true;
}
}
}
pub fn send(&mut self) {
let msgs = self.world.resource_mut::<ClientBroadcast>().take();
for msg in &msgs {
for (_, client) in &mut self.clients {
client.send(msg.clone());
}
}
let mut q = self.world.query::<(Entity, &mut ClientComponent)>();
for (e, mut c) in q.iter_mut(&mut self.world) {
if let Some(sc) = self.clients.get(&e) {
for msg in c.take() {
sc.send(msg);
}
}
}
+68 -5
View File
@@ -1,9 +1,72 @@
use bevy_ecs::{entity::Entity, query::Changed, system::{Query, Res}};
use bevy_ecs::{
entity::Entity,
query::{Changed, With},
system::{Commands, NonSendMut, Query, ResMut},
};
use nalgebra::Vector3;
use crate::{sync::{ClientMessage, ClientSender}, world::component::{Pos, Synced}};
use crate::{
common::{
component::{
chunk::{self, ChunkBundle, LoadedChunks},
ChunkData, ChunkMesh, ChunkPos, Player, Pos,
},
ClientMessage,
},
server::{chunk::ChunkManager, client::ClientBroadcast, ClientComponent},
};
pub fn pos(query: Query<(Entity, &Synced, &Pos), Changed<Pos>>, client: Res<ClientSender>) {
for (e, _, pos) in query.iter() {
client.send(ClientMessage::PosUpdate(e, *pos));
pub fn pos(query: Query<(Entity, &Pos), Changed<Pos>>, mut clients: ResMut<ClientBroadcast>) {
for (e, pos) in query.iter() {
clients.send(ClientMessage::PosUpdate(e, *pos));
}
}
pub fn chunks(
mut players: Query<(&Pos, &mut LoadedChunks, &mut ClientComponent), With<Player>>,
chunks: Query<(&ChunkPos, &ChunkData, &ChunkMesh)>,
mut loader: NonSendMut<ChunkManager>,
mut commands: Commands,
) {
for (pos, mut loaded, mut client) in &mut players {
let fp = **pos / chunk::SIDE_LENGTH as f32;
let player_chunk = Vector3::new(
fp.x.floor() as i32,
fp.y.floor() as i32,
fp.z.floor() as i32,
);
let radius: i32 = 5;
let width = radius * 2 - 1;
let mut desired = Vec::new();
for i in 0..width.pow(3) {
let pos = Vector3::new(i % width, (i / width) % width, i / (width.pow(2)))
- Vector3::from_element(radius - 1);
let dist = pos.cast::<f32>().norm();
if dist < radius as f32 {
desired.push((dist, pos));
}
}
desired.sort_by(|(da, ..), (db, ..)| da.total_cmp(db));
for (_, pos) in desired {
let coords = pos - player_chunk;
let pos = ChunkPos(coords);
if !loaded.contains(&pos) {
if let Some(id) = loader.entity_at(&pos) {
let (pos, data, mesh) = chunks.get(*id).unwrap();
client.send(ClientMessage::LoadChunk(
*id,
ChunkBundle {
pos: *pos,
data: data.clone(),
mesh: mesh.clone(),
},
));
loaded.insert(*pos);
} else {
loader.queue(pos);
}
}
}
}
loader.update(&mut commands);
}
+75
View File
@@ -0,0 +1,75 @@
use crate::client::render::voxel::VoxelColor;
use crate::common::component::{VoxelGrid, VoxelGridBundle};
use bevy_ecs::world::World;
use nalgebra::{Rotation3, UnitVector3, Vector3};
use ndarray::Array3;
pub fn spawn_test_stuff(world: &mut World) {
let dim = (15, 10, 10);
world.spawn(VoxelGridBundle {
pos: Vector3::new(0.0, 0.0, 20.0).into(),
orientation: Rotation3::from_axis_angle(&Vector3::y_axis(), 0.5).into(),
grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| {
if x == z && x == y {
VoxelColor::white()
} else if z == 3 {
VoxelColor {
r: (x as f32 / dim.0 as f32 * 255.0) as u8,
g: (y as f32 / dim.1 as f32 * 255.0) as u8,
b: 100,
a: 255,
}
} else if z == 0 {
VoxelColor {
r: (x as f32 / dim.0 as f32 * 255.0) as u8,
g: (y as f32 / dim.1 as f32 * 255.0) as u8,
b: 0,
a: 100,
}
} else {
VoxelColor::none()
}
})),
});
// let dim = (1000, 2, 1000);
// world.spawn((
// VoxelGridBundle {
// pos: Vector3::new(0.0, -2.1, 0.0).into(),
// orientation: Rotation3::identity().into(),
// grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| {
// if y == 0 {
// VoxelColor::random()
// } else if (y == dim.1 - 1) && (x == 0 || x == dim.0 - 1 || z == 0 || z == dim.2 - 1)
// {
// VoxelColor {
// r: 255,
// g: 0,
// b: 255,
// a: 255,
// }
// } else {
// VoxelColor::none()
// }
// })),
// },
// Synced,
// ));
let dim = (3, 3, 3);
world.spawn(VoxelGridBundle {
pos: Vector3::new(0.0, 0.0, 16.5).into(),
orientation: (Rotation3::from_axis_angle(&Vector3::y_axis(), std::f32::consts::PI / 4.0)
* Rotation3::from_axis_angle(
&UnitVector3::new_normalize(Vector3::new(1.0, 0.0, 1.0)),
std::f32::consts::PI / 4.0,
))
.into(),
grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(..)| VoxelColor {
r: 255,
g: 0,
b: 255,
a: 255,
})),
});
}