grow thread + communications (not tested)

This commit is contained in:
Arkitu 2025-06-25 19:32:12 +02:00
parent 1a68ab5af1
commit 6c74ed9272
6 changed files with 183 additions and 40 deletions

1
Cargo.lock generated
View File

@ -2555,6 +2555,7 @@ name = "forestiles"
version = "0.4.0"
dependencies = [
"android_logger",
"async-channel",
"bevy",
"bevy_editor_pls",
"console_error_panic_hook",

View File

@ -48,6 +48,7 @@ rand = { version = "0.8", features = ["small_rng"] }
voronoice = "0.2"
noise = "0.9"
bevy_editor_pls = { version = "0.11", git = "https://github.com/jakobhellermann/bevy_editor_pls.git", optional = true }
async-channel = "*"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"

View File

@ -25,7 +25,10 @@ use cells::{
};
use picking::*;
use crate::map::cells::grow::{grow_thread, GrowThread};
use crate::{
map::cells::grow::{grow_thread, GrowThread},
time::GameTime,
};
pub struct Plugin;
impl bevy::prelude::Plugin for Plugin {
@ -80,6 +83,7 @@ fn setup(
// mut materials: ResMut<Assets<StandardMaterial>>,
mut materials: ResMut<Assets<CellMaterial>>,
seed: Res<Seed>,
gt: Res<GameTime>,
) {
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
let mut sites = Vec::with_capacity(CELLS);
@ -246,11 +250,15 @@ fn setup(
});
}
cmds.insert_resource(GrowThread::new(
voronoi.clone(),
gt.secs.clone(),
cells,
rng,
));
cmds.insert_resource(Voronoi(voronoi));
cmds.insert_resource(CellsEntities(cells_entities));
let task_pool = AsyncComputeTaskPool::get();
cmds.insert_resource(GrowThread(task_pool.spawn(grow_thread())));
}
// TODO: update this to take chunks into account

View File

@ -39,32 +39,47 @@ pub enum CellKind {
Grass { wealth: WealthType },
}
impl CellKind {
pub const fn regen_full_growth_duration(&self) -> Duration {
pub const FOREST: CellKind = CellKind::Forest { wealth: 0 };
pub const GRASS: CellKind = CellKind::Grass { wealth: 0 };
pub const fn growth_duration(&self) -> u64 {
match self {
CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => unreachable!(),
CellKind::Forest { .. } => Duration::from_secs(100 * 365 * 24 * 60 * 60), // Let's say that a forest takes 100 years to mature
CellKind::Grass { .. } => Duration::from_secs(7 * 7 * 24 * 60 * 60), // Let's say that grass takes 7 weeks to reach its max
CellKind::Forest { .. } => 100 * 365 * 24 * 60 * 60, // a forest takes 100 years to mature
CellKind::Grass { .. } => (7 * 7 * 24 * 60 * 60), // grass takes 7 weeks to reach its max
_ => unreachable!(),
}
}
/// Time before trying to extend to one neighbor
pub const fn extend_duration(&self) -> usize {
match self {
CellKind::Grass { wealth } => {
// "Grass can expand into adjacent dirt at a rate of 2.5 to 7.5 cm (1 to 3 inches) per month.", DeepSeek
59_388_377
* (CELL_AREA as usize).isqrt()
* WealthType::MAX.saturating_div(*wealth) as usize
}
CellKind::Forest { wealth } => {
// Arbitrary value : 1m per 10 years
(10 * 365 * 24 * 3600)
* (CELL_AREA as usize).isqrt()
* WealthType::MAX.saturating_div(*wealth) as usize
}
_ => unreachable!(),
}
}
pub const fn can_place_animal(&self, kind: AnimalKind) -> bool {
match self {
CellKind::Sea => false,
_ => true,
}
}
pub const fn grass_wealth(&self) -> Option<usize> {
pub const fn grass_wealth(&self) -> Option<WealthType> {
match self {
CellKind::Grass { wealth } => Some(*wealth),
CellKind::Forest { wealth } => Some(
wealth.saturating_mul(
(CellKind::Forest { wealth: 0 }
.regen_full_growth_duration()
.as_secs()
/ CellKind::Grass { wealth: 0 }
.regen_full_growth_duration()
.as_secs()) as usize,
),
),
CellKind::Forest { wealth } => Some(wealth.saturating_mul(
(CellKind::FOREST.growth_duration() / CellKind::GRASS.growth_duration())
as WealthType,
)),
_ => None,
}
}
@ -105,7 +120,7 @@ impl Cell {
// pub chunk:
// };
pub type WealthType = usize;
pub type WealthType = u16;
const fn wealth_to_unit(wealth: WealthType) -> f32 {
wealth as f32 / WealthType::MAX as f32
}

View File

@ -1,11 +1,16 @@
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use bevy::prelude::*;
use bevy::tasks::futures_lite::{future, FutureExt};
use bevy::tasks::{block_on, AsyncComputeTaskPool, IoTaskPool, Task};
use rand::rngs::SmallRng;
use rand::seq::IteratorRandom;
use rand::Rng;
use voronoice::Voronoi;
use crate::map::{CellsEntities, Chunk, MeshNeedsUpdate};
use crate::map::cells::{wealth_to_unit, WealthType};
use crate::map::{CellKind, CellsEntities, Chunk, MeshNeedsUpdate};
use super::Cell;
@ -16,7 +21,23 @@ impl bevy::prelude::Plugin for Plugin {
// Background task that manages terrain
#[derive(Resource)]
pub struct GrowThread(pub Task<()>);
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>, mut 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>>);
@ -24,27 +45,115 @@ struct GetChunkTask(Task<Vec<Cell>>);
fn start_grow_task(mut cmds: Commands) {}
fn rx_chunks_updates(
mut chunks: Query<(&Chunk, &mut GetChunkTask, &mut MeshNeedsUpdate)>,
mut cells: Query<&mut Cell>,
mut chunks: Query<(&mut MeshNeedsUpdate)>,
mut cells: Query<(&mut Cell, &Parent)>,
cells_entities: Res<CellsEntities>,
grow_thread: Res<GrowThread>,
) {
for (ch_id, mut task, mut needs_update) in chunks.iter_mut() {
let res = block_on(future::poll_once(&mut task.0));
if let Some(updated_cells) = res {
if updated_cells.len() > 0 {
needs_update.0 = true;
}
for c in updated_cells.into_iter() {
while let Ok(c) = grow_thread.2.try_recv() {
let id = c.voronoi_id;
*cells.get_mut(cells_entities.0[id]).unwrap() = c;
}
let task_pool = IoTaskPool::get();
task.0 = task_pool.spawn(async { Vec::new() })
}
let (mut cell, parent) = cells.get_mut(cells_entities.0[id]).unwrap();
*cell = c;
chunks.get(parent.get()).unwrap().0 = true;
}
}
pub async fn grow_thread(voronoi: Voronoi, current_frame: Arc<AtomicU>) {}
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>,
mut cells: Vec<Cell>,
mut rng: SmallRng,
mut rx_control: async_channel::Receiver<Cell>,
mut tx_update: async_channel::Sender<Cell>,
) {
// Add "modified" flag
let mut cells: Vec<(Cell, bool)> = cells.into_iter().map(|c| (c, false)).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, false);
}
let gt = gt.load(Ordering::Acquire);
// Update step
let grass_grow = ((gt - last_grass_grow) / CellKind::GRASS.growth_duration()) as usize;
let forest_grow = ((gt - last_forest_grow) / CellKind::FOREST.growth_duration()) as usize;
if grass_grow > 0 || forest_grow > 0 {
for (c, modified) in cells.iter_mut() {
if grass_grow > 0 {
if let CellKind::Grass { mut wealth } = c.kind {
wealth = wealth.saturating_add(grass_grow as WealthType);
*modified = true;
}
}
if forest_grow > 0 {
if let CellKind::Forest { mut wealth } = c.kind {
wealth = wealth.saturating_add(forest_grow as WealthType);
*modified = true;
}
}
}
// 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 {
if let Some(wealth) = cells[i].0.kind.grass_wealth() {
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
let mut 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 = true;
}
}
}
}
}
} else if gt - last_forest_extend > FOREST_EXTEND_STEP {
for i in 0..cells.len() {
if !cells[i].1 {
if let CellKind::Forest { wealth } = cells[i].0.kind {
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
let mut 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 = true;
}
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 = true;
}
_ => {}
}
}
}
}
}
}
// Send modifications
for (c, _) in cells.iter().filter(|(_, m)| *m) {
tx_update.send(c.clone()).await;
}
}
}
// fn begin_generating_map_chunks(mut my_tasks: ResMut<MyMapGenTasks>) {
// let task_pool = AsyncComputeTaskPool::get();

View File

@ -1,4 +1,10 @@
use std::time::Duration;
use std::{
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
time::Duration,
};
use bevy::prelude::*;
@ -7,7 +13,8 @@ impl bevy::prelude::Plugin for Plugin {
fn build(&self, app: &mut App) {
app.insert_resource(GameTime {
current: Duration::ZERO,
speed: 24. * 60. * 60. * 10. * 10.,
secs: Arc::new(AtomicU64::new(0)),
speed: 3600. * 24. * 365.,
})
.add_systems(PreUpdate, update_time);
}
@ -16,10 +23,12 @@ impl bevy::prelude::Plugin for Plugin {
#[derive(Resource)]
pub struct GameTime {
pub current: Duration,
pub secs: Arc<AtomicU64>,
pub speed: f32, // = game time / real time
}
fn update_time(mut gt: ResMut<GameTime>, time: Res<Time>) {
let speed = gt.speed;
gt.current += Duration::from_secs_f32(time.delta_secs() * speed);
gt.secs.store(gt.current.as_secs(), Ordering::Release);
}