196 lines
7.3 KiB
Rust
196 lines
7.3 KiB
Rust
use std::net::UdpSocket;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
use bevy::asset::{embedded_asset, embedded_path};
|
|
use bevy::prelude::*;
|
|
use bevy::tasks::{AsyncComputeTaskPool, Task};
|
|
use log::info;
|
|
use rand::rngs::SmallRng;
|
|
use rand::seq::IteratorRandom;
|
|
use rand::{rng, Rng};
|
|
use voronoice::Voronoi;
|
|
|
|
use crate::map::cells::{wealth_to_unit, WealthType};
|
|
use crate::map::{CellKind, CellsEntities, MeshNeedsUpdate};
|
|
|
|
use super::Cell;
|
|
|
|
pub struct Plugin;
|
|
impl bevy::prelude::Plugin for Plugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(Update, rx_chunks_updates);
|
|
// embedded_asset!(app, "../../../", "assets/models/tree.glb");
|
|
}
|
|
}
|
|
|
|
// Background task that manages terrain
|
|
#[derive(Resource)]
|
|
pub struct GrowThread(
|
|
pub Task<()>,
|
|
pub async_channel::Sender<Cell>,
|
|
pub async_channel::Receiver<Cell>,
|
|
);
|
|
impl GrowThread {
|
|
pub fn new(voronoi: Voronoi, gt: Arc<AtomicU64>, cells: Vec<Cell>, rng: SmallRng) -> Self {
|
|
let task_pool = AsyncComputeTaskPool::get();
|
|
let (tx_control, rx_control) = async_channel::unbounded();
|
|
let (tx_update, rx_update) = async_channel::unbounded();
|
|
Self(
|
|
task_pool.spawn(grow_thread(voronoi, gt, cells, rng, rx_control, tx_update)),
|
|
tx_control,
|
|
rx_update,
|
|
)
|
|
}
|
|
}
|
|
|
|
// #[derive(Component)]
|
|
// struct GetChunkTask(Task<Vec<Cell>>);
|
|
|
|
// const MAX_UPDATE_PER_FRAME: usize = 10000;
|
|
fn rx_chunks_updates(
|
|
mut chunks: Query<&mut MeshNeedsUpdate>,
|
|
mut cells: Query<(&mut Cell, &ChildOf, Entity)>,
|
|
cells_entities: Res<CellsEntities>,
|
|
grow_thread: Res<GrowThread>,
|
|
mut cmds: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
) {
|
|
while let Ok(c) = grow_thread.2.try_recv() {
|
|
let id = c.voronoi_id;
|
|
let (mut cell, parent, id) = cells.get_mut(cells_entities.0[id]).unwrap();
|
|
chunks.get_mut(parent.parent()).unwrap().0 += cell.kind.diff(&c.kind);
|
|
|
|
if matches!(c.kind, CellKind::Forest { .. })
|
|
&& !matches!(cell.kind, CellKind::Forest { .. })
|
|
{
|
|
// Spawn tree
|
|
cmds.spawn((
|
|
SceneRoot(
|
|
asset_server
|
|
.load(GltfAssetLabel::Scene(0).from_asset("embedded://models/tree.glb")),
|
|
),
|
|
ChildOf(id),
|
|
Transform::from_scale(Vec3::new(0.1, 0.1, 0.1)).with_rotation(
|
|
Quat::from_axis_angle(
|
|
Vec3::Y,
|
|
rng().random_range(-std::f32::consts::PI..std::f32::consts::PI),
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
*cell = c;
|
|
}
|
|
}
|
|
|
|
const GRASS_EXTEND_STEP: u64 = CellKind::GRASS.growth_duration();
|
|
const FOREST_EXTEND_STEP: u64 = CellKind::FOREST.growth_duration();
|
|
pub async fn grow_thread(
|
|
voronoi: Voronoi,
|
|
gt: Arc<AtomicU64>,
|
|
cells: Vec<Cell>,
|
|
mut rng: SmallRng,
|
|
rx_control: async_channel::Receiver<Cell>,
|
|
tx_update: async_channel::Sender<Cell>,
|
|
) {
|
|
// Add "modified" flag
|
|
let mut cells: Vec<(Cell, f32)> = cells.into_iter().map(|c| (c, 0.)).collect();
|
|
let mut last_grass_grow = 0;
|
|
let mut last_forest_grow = 0;
|
|
let mut last_grass_extend = 0;
|
|
let mut last_forest_extend = 0;
|
|
loop {
|
|
// Receive control from the game
|
|
while let Ok(c) = rx_control.try_recv() {
|
|
let id = c.voronoi_id;
|
|
cells[id] = (c, 1.);
|
|
}
|
|
|
|
let gt = gt.load(Ordering::Acquire);
|
|
|
|
// Update step
|
|
let grass_grow = gt.saturating_sub(last_grass_grow) * WealthType::MAX as u64
|
|
/ CellKind::GRASS.growth_duration();
|
|
let forest_grow = gt.saturating_sub(last_forest_grow) * WealthType::MAX as u64
|
|
/ CellKind::FOREST.growth_duration();
|
|
if grass_grow > 0 || forest_grow > 0 {
|
|
for (c, modified) in cells.iter_mut() {
|
|
if grass_grow > 0 {
|
|
if let CellKind::Grass { ref mut wealth } = c.kind {
|
|
*wealth = wealth.saturating_add(grass_grow as WealthType);
|
|
*modified += grass_grow as f32 / WealthType::MAX as f32;
|
|
}
|
|
}
|
|
if forest_grow > 0 {
|
|
if let CellKind::Forest { ref mut wealth } = c.kind {
|
|
*wealth = wealth.saturating_add(forest_grow as WealthType);
|
|
*modified += grass_grow as f32 / WealthType::MAX as f32;
|
|
}
|
|
}
|
|
}
|
|
// Don't make multiple updates at once to avoid problems and lags
|
|
} else if gt - last_grass_extend > GRASS_EXTEND_STEP {
|
|
for i in 0..cells.len() {
|
|
if cells[i].1 < 1. {
|
|
if let Some(wealth) = cells[i].0.kind.grass_wealth() {
|
|
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
|
|
let target = &mut cells
|
|
[voronoi.cell(i).iter_neighbors().choose(&mut rng).unwrap()];
|
|
|
|
if matches!(target.0.kind, CellKind::Dirt) {
|
|
target.0.kind = CellKind::Grass { wealth: 0 };
|
|
target.1 = 1.;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last_grass_extend += GRASS_EXTEND_STEP;
|
|
} else if gt - last_forest_extend > FOREST_EXTEND_STEP {
|
|
for i in 0..cells.len() {
|
|
if cells[i].1 < 1. {
|
|
if let CellKind::Forest { wealth } = cells[i].0.kind {
|
|
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
|
|
let target = &mut cells
|
|
[voronoi.cell(i).iter_neighbors().choose(&mut rng).unwrap()];
|
|
match target.0.kind {
|
|
CellKind::Dirt => {
|
|
target.0.kind = CellKind::Forest { wealth: 0 };
|
|
target.1 = 1.;
|
|
}
|
|
CellKind::Grass { wealth: w } => {
|
|
target.0.kind = CellKind::Forest {
|
|
wealth: (w as f32
|
|
* CellKind::GRASS.growth_duration() as f32
|
|
/ CellKind::FOREST.growth_duration() as f32)
|
|
as WealthType,
|
|
};
|
|
target.1 = 1.;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last_forest_extend += FOREST_EXTEND_STEP;
|
|
}
|
|
|
|
let grass_count = cells
|
|
.iter()
|
|
.filter(|(c, _)| matches!(c.kind, CellKind::Grass { .. }))
|
|
.count();
|
|
|
|
last_grass_grow += grass_grow * CellKind::GRASS.growth_duration() / WealthType::MAX as u64;
|
|
last_forest_grow += forest_grow * CellKind::FOREST.growth_duration();
|
|
|
|
// Send modifications
|
|
let modification_treshold = 1. - (1. / (tx_update.len() as f32));
|
|
for (c, m) in cells.iter_mut().filter(|(_, m)| *m > modification_treshold) {
|
|
tx_update.send(c.clone()).await.unwrap();
|
|
*m = 0.;
|
|
}
|
|
}
|
|
}
|