grow thread + communications (not tested)
This commit is contained in:
parent
1a68ab5af1
commit
6c74ed9272
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2555,6 +2555,7 @@ name = "forestiles"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_logger",
|
"android_logger",
|
||||||
|
"async-channel",
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy_editor_pls",
|
"bevy_editor_pls",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
@ -48,6 +48,7 @@ rand = { version = "0.8", features = ["small_rng"] }
|
|||||||
voronoice = "0.2"
|
voronoice = "0.2"
|
||||||
noise = "0.9"
|
noise = "0.9"
|
||||||
bevy_editor_pls = { version = "0.11", git = "https://github.com/jakobhellermann/bevy_editor_pls.git", optional = true }
|
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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
|
16
src/map.rs
16
src/map.rs
@ -25,7 +25,10 @@ use cells::{
|
|||||||
};
|
};
|
||||||
use picking::*;
|
use picking::*;
|
||||||
|
|
||||||
use crate::map::cells::grow::{grow_thread, GrowThread};
|
use crate::{
|
||||||
|
map::cells::grow::{grow_thread, GrowThread},
|
||||||
|
time::GameTime,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Plugin;
|
pub struct Plugin;
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
impl bevy::prelude::Plugin for Plugin {
|
||||||
@ -80,6 +83,7 @@ fn setup(
|
|||||||
// mut materials: ResMut<Assets<StandardMaterial>>,
|
// mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut materials: ResMut<Assets<CellMaterial>>,
|
mut materials: ResMut<Assets<CellMaterial>>,
|
||||||
seed: Res<Seed>,
|
seed: Res<Seed>,
|
||||||
|
gt: Res<GameTime>,
|
||||||
) {
|
) {
|
||||||
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
|
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
|
||||||
let mut sites = Vec::with_capacity(CELLS);
|
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(Voronoi(voronoi));
|
||||||
cmds.insert_resource(CellsEntities(cells_entities));
|
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
|
// TODO: update this to take chunks into account
|
||||||
|
@ -39,32 +39,47 @@ pub enum CellKind {
|
|||||||
Grass { wealth: WealthType },
|
Grass { wealth: WealthType },
|
||||||
}
|
}
|
||||||
impl CellKind {
|
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 {
|
match self {
|
||||||
CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => unreachable!(),
|
CellKind::Forest { .. } => 100 * 365 * 24 * 60 * 60, // a forest takes 100 years to mature
|
||||||
CellKind::Forest { .. } => Duration::from_secs(100 * 365 * 24 * 60 * 60), // Let's say that a forest takes 100 years to mature
|
CellKind::Grass { .. } => (7 * 7 * 24 * 60 * 60), // grass takes 7 weeks to reach its max
|
||||||
CellKind::Grass { .. } => Duration::from_secs(7 * 7 * 24 * 60 * 60), // Let's say that 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 {
|
pub const fn can_place_animal(&self, kind: AnimalKind) -> bool {
|
||||||
match self {
|
match self {
|
||||||
CellKind::Sea => false,
|
CellKind::Sea => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn grass_wealth(&self) -> Option<usize> {
|
pub const fn grass_wealth(&self) -> Option<WealthType> {
|
||||||
match self {
|
match self {
|
||||||
CellKind::Grass { wealth } => Some(*wealth),
|
CellKind::Grass { wealth } => Some(*wealth),
|
||||||
CellKind::Forest { wealth } => Some(
|
CellKind::Forest { wealth } => Some(wealth.saturating_mul(
|
||||||
wealth.saturating_mul(
|
(CellKind::FOREST.growth_duration() / CellKind::GRASS.growth_duration())
|
||||||
(CellKind::Forest { wealth: 0 }
|
as WealthType,
|
||||||
.regen_full_growth_duration()
|
)),
|
||||||
.as_secs()
|
|
||||||
/ CellKind::Grass { wealth: 0 }
|
|
||||||
.regen_full_growth_duration()
|
|
||||||
.as_secs()) as usize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +120,7 @@ impl Cell {
|
|||||||
// pub chunk:
|
// pub chunk:
|
||||||
// };
|
// };
|
||||||
|
|
||||||
pub type WealthType = usize;
|
pub type WealthType = u16;
|
||||||
const fn wealth_to_unit(wealth: WealthType) -> f32 {
|
const fn wealth_to_unit(wealth: WealthType) -> f32 {
|
||||||
wealth as f32 / WealthType::MAX as f32
|
wealth as f32 / WealthType::MAX as f32
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::tasks::futures_lite::{future, FutureExt};
|
use bevy::tasks::futures_lite::{future, FutureExt};
|
||||||
use bevy::tasks::{block_on, AsyncComputeTaskPool, IoTaskPool, Task};
|
use bevy::tasks::{block_on, AsyncComputeTaskPool, IoTaskPool, Task};
|
||||||
|
use rand::rngs::SmallRng;
|
||||||
|
use rand::seq::IteratorRandom;
|
||||||
|
use rand::Rng;
|
||||||
use voronoice::Voronoi;
|
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;
|
use super::Cell;
|
||||||
|
|
||||||
@ -16,7 +21,23 @@ impl bevy::prelude::Plugin for Plugin {
|
|||||||
|
|
||||||
// Background task that manages terrain
|
// Background task that manages terrain
|
||||||
#[derive(Resource)]
|
#[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)]
|
#[derive(Component)]
|
||||||
struct GetChunkTask(Task<Vec<Cell>>);
|
struct GetChunkTask(Task<Vec<Cell>>);
|
||||||
@ -24,27 +45,115 @@ struct GetChunkTask(Task<Vec<Cell>>);
|
|||||||
fn start_grow_task(mut cmds: Commands) {}
|
fn start_grow_task(mut cmds: Commands) {}
|
||||||
|
|
||||||
fn rx_chunks_updates(
|
fn rx_chunks_updates(
|
||||||
mut chunks: Query<(&Chunk, &mut GetChunkTask, &mut MeshNeedsUpdate)>,
|
mut chunks: Query<(&mut MeshNeedsUpdate)>,
|
||||||
mut cells: Query<&mut Cell>,
|
mut cells: Query<(&mut Cell, &Parent)>,
|
||||||
cells_entities: Res<CellsEntities>,
|
cells_entities: Res<CellsEntities>,
|
||||||
|
grow_thread: Res<GrowThread>,
|
||||||
) {
|
) {
|
||||||
for (ch_id, mut task, mut needs_update) in chunks.iter_mut() {
|
while let Ok(c) = grow_thread.2.try_recv() {
|
||||||
let res = block_on(future::poll_once(&mut task.0));
|
let id = c.voronoi_id;
|
||||||
if let Some(updated_cells) = res {
|
let (mut cell, parent) = cells.get_mut(cells_entities.0[id]).unwrap();
|
||||||
if updated_cells.len() > 0 {
|
*cell = c;
|
||||||
needs_update.0 = true;
|
chunks.get(parent.get()).unwrap().0 = true;
|
||||||
}
|
|
||||||
for c in updated_cells.into_iter() {
|
|
||||||
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() })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>) {
|
// fn begin_generating_map_chunks(mut my_tasks: ResMut<MyMapGenTasks>) {
|
||||||
// let task_pool = AsyncComputeTaskPool::get();
|
// let task_pool = AsyncComputeTaskPool::get();
|
||||||
|
13
src/time.rs
13
src/time.rs
@ -1,4 +1,10 @@
|
|||||||
use std::time::Duration;
|
use std::{
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicU64, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
@ -7,7 +13,8 @@ impl bevy::prelude::Plugin for Plugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.insert_resource(GameTime {
|
app.insert_resource(GameTime {
|
||||||
current: Duration::ZERO,
|
current: Duration::ZERO,
|
||||||
speed: 24. * 60. * 60. * 10. * 10.,
|
secs: Arc::new(AtomicU64::new(0)),
|
||||||
|
speed: 3600. * 24. * 365.,
|
||||||
})
|
})
|
||||||
.add_systems(PreUpdate, update_time);
|
.add_systems(PreUpdate, update_time);
|
||||||
}
|
}
|
||||||
@ -16,10 +23,12 @@ impl bevy::prelude::Plugin for Plugin {
|
|||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct GameTime {
|
pub struct GameTime {
|
||||||
pub current: Duration,
|
pub current: Duration,
|
||||||
|
pub secs: Arc<AtomicU64>,
|
||||||
pub speed: f32, // = game time / real time
|
pub speed: f32, // = game time / real time
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_time(mut gt: ResMut<GameTime>, time: Res<Time>) {
|
fn update_time(mut gt: ResMut<GameTime>, time: Res<Time>) {
|
||||||
let speed = gt.speed;
|
let speed = gt.speed;
|
||||||
gt.current += Duration::from_secs_f32(time.delta_secs() * speed);
|
gt.current += Duration::from_secs_f32(time.delta_secs() * speed);
|
||||||
|
gt.secs.store(gt.current.as_secs(), Ordering::Release);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user