const MAX_GENERATION_THREADS: usize = 4;
fn gen_pool() -> &'static rayon::ThreadPool {
static POOL: OnceLock<rayon::ThreadPool> = OnceLock::new();
POOL.get_or_init(|| {
rayon::ThreadPoolBuilder::new()
.num_threads(MAX_GENERATION_THREADS)
.thread_name(|i| format!("texture-gen-{i}"))
.build()
.expect("failed to build texture generation thread pool")
})
}
use std::sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
mpsc,
};
use bevy::{
asset::Assets,
ecs::{
component::Component,
entity::Entity,
system::{Commands, Query, ResMut},
},
image::Image,
};
use crate::{
ashlar::{AshlarConfig, AshlarGenerator},
asphalt::{AsphaltConfig, AsphaltGenerator},
bark::{BarkConfig, BarkGenerator},
brick::{BrickConfig, BrickGenerator},
cobblestone::{CobblestoneConfig, CobblestoneGenerator},
concrete::{ConcreteConfig, ConcreteGenerator},
corrugated::{CorrugatedConfig, CorrugatedGenerator},
encaustic::{EncausticConfig, EncausticGenerator},
generator::{
GeneratedHandles, TextureError, TextureGenerator, TextureMap, map_to_images,
map_to_images_card,
},
ground::{GroundConfig, GroundGenerator},
iron_grille::{IronGrilleConfig, IronGrilleGenerator},
leaf::{LeafConfig, LeafGenerator},
marble::{MarbleConfig, MarbleGenerator},
metal::{MetalConfig, MetalGenerator},
pavers::{PaversConfig, PaversGenerator},
plank::{PlankConfig, PlankGenerator},
rock::{RockConfig, RockGenerator},
shingle::{ShingleConfig, ShingleGenerator},
stained_glass::{StainedGlassConfig, StainedGlassGenerator},
stucco::{StuccoConfig, StuccoGenerator},
thatch::{ThatchConfig, ThatchGenerator},
twig::{TwigConfig, TwigGenerator},
wainscoting::{WainscotingConfig, WainscotingGenerator},
window::{WindowConfig, WindowGenerator},
};
#[derive(Component)]
pub struct PendingTexture {
pub(crate) rx: std::sync::Mutex<mpsc::Receiver<Result<TextureMap, TextureError>>>,
cancelled: Arc<AtomicBool>,
is_card: bool,
}
impl Drop for PendingTexture {
fn drop(&mut self) {
self.cancelled.store(true, Ordering::Relaxed);
}
}
#[cfg(not(target_arch = "wasm32"))]
fn spawn_task<F>(f: F, is_card: bool) -> PendingTexture
where
F: FnOnce() -> Result<TextureMap, TextureError> + Send + 'static,
{
let cancelled = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&cancelled);
let (tx, rx) = mpsc::sync_channel(1);
gen_pool().spawn(move || {
if !flag.load(Ordering::Relaxed) {
tx.send(f()).ok();
}
});
PendingTexture {
rx: std::sync::Mutex::new(rx),
cancelled,
is_card,
}
}
#[cfg(target_arch = "wasm32")]
fn spawn_task<F>(f: F, is_card: bool) -> PendingTexture
where
F: FnOnce() -> Result<TextureMap, TextureError> + Send + 'static,
{
use bevy::tasks::AsyncComputeTaskPool;
let cancelled = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&cancelled);
let (tx, rx) = mpsc::sync_channel(1);
AsyncComputeTaskPool::get()
.spawn(async move {
if !flag.load(Ordering::Relaxed) {
tx.send(f()).ok();
}
})
.detach();
PendingTexture {
rx: std::sync::Mutex::new(rx),
cancelled,
is_card,
}
}
impl PendingTexture {
pub fn bark(config: BarkConfig, width: u32, height: u32) -> Self {
let generator = BarkGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn rock(config: RockConfig, width: u32, height: u32) -> Self {
let generator = RockGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn ground(config: GroundConfig, width: u32, height: u32) -> Self {
let generator = GroundGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn leaf(config: LeafConfig, width: u32, height: u32) -> Self {
let generator = LeafGenerator::new(config);
spawn_task(move || generator.generate(width, height), true)
}
pub fn twig(config: TwigConfig, width: u32, height: u32) -> Self {
let generator = TwigGenerator::new(config);
spawn_task(move || generator.generate(width, height), true)
}
pub fn brick(config: BrickConfig, width: u32, height: u32) -> Self {
let generator = BrickGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn window(config: WindowConfig, width: u32, height: u32) -> Self {
let generator = WindowGenerator::new(config);
spawn_task(move || generator.generate(width, height), true)
}
pub fn plank(config: PlankConfig, width: u32, height: u32) -> Self {
let generator = PlankGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn shingle(config: ShingleConfig, width: u32, height: u32) -> Self {
let generator = ShingleGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn stucco(config: StuccoConfig, width: u32, height: u32) -> Self {
let generator = StuccoGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn concrete(config: ConcreteConfig, width: u32, height: u32) -> Self {
let generator = ConcreteGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn metal(config: MetalConfig, width: u32, height: u32) -> Self {
let generator = MetalGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn pavers(config: PaversConfig, width: u32, height: u32) -> Self {
let generator = PaversGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn ashlar(config: AshlarConfig, width: u32, height: u32) -> Self {
let generator = AshlarGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn cobblestone(config: CobblestoneConfig, width: u32, height: u32) -> Self {
let generator = CobblestoneGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn thatch(config: ThatchConfig, width: u32, height: u32) -> Self {
let generator = ThatchGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn marble(config: MarbleConfig, width: u32, height: u32) -> Self {
let generator = MarbleGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn corrugated(config: CorrugatedConfig, width: u32, height: u32) -> Self {
let generator = CorrugatedGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn asphalt(config: AsphaltConfig, width: u32, height: u32) -> Self {
let generator = AsphaltGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn wainscoting(config: WainscotingConfig, width: u32, height: u32) -> Self {
let generator = WainscotingGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
pub fn stained_glass(config: StainedGlassConfig, width: u32, height: u32) -> Self {
let generator = StainedGlassGenerator::new(config);
spawn_task(move || generator.generate(width, height), true)
}
pub fn iron_grille(config: IronGrilleConfig, width: u32, height: u32) -> Self {
let generator = IronGrilleGenerator::new(config);
spawn_task(move || generator.generate(width, height), true)
}
pub fn encaustic(config: EncausticConfig, width: u32, height: u32) -> Self {
let generator = EncausticGenerator::new(config);
spawn_task(move || generator.generate(width, height), false)
}
}
#[derive(Component)]
pub struct TextureReady(pub GeneratedHandles);
pub fn poll_texture_tasks(
mut commands: Commands,
tasks: Query<(Entity, &PendingTexture)>,
mut images: ResMut<Assets<Image>>,
) {
for (entity, pending) in &tasks {
let poll = pending
.rx
.lock()
.expect("texture thread poisoned")
.try_recv();
match poll {
Ok(Ok(map)) => {
let handles = if pending.is_card {
map_to_images_card(map, &mut images)
} else {
map_to_images(map, &mut images)
};
commands
.entity(entity)
.remove::<PendingTexture>()
.insert(TextureReady(handles));
}
Ok(Err(e)) => {
bevy::log::error!("Texture generation failed: {e}");
commands.entity(entity).remove::<PendingTexture>();
}
Err(mpsc::TryRecvError::Disconnected) => {
bevy::log::error!("Texture generation thread panicked");
commands.entity(entity).remove::<PendingTexture>();
}
Err(mpsc::TryRecvError::Empty) => {}
}
}
}