use std::{
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
};
use anyhow::{Error, Result};
use bevy::{
asset::{io::Reader, AssetLoader, LoadContext},
image::{CompressedImageFormats, ImageLoader, ImageLoaderSettings},
render::render_resource::TextureFormat,
utils::HashMap,
};
use event_listener::Event;
use seldom_singleton::AssetSingleton;
use crate::prelude::*;
pub(crate) fn plug(palette_path: PathBuf) -> impl Fn(&mut App) {
move |app| {
app.init_asset::<Palette>()
.init_asset_loader::<PaletteLoader>()
.add_systems(Startup, init_palette(palette_path.clone()))
.add_systems(
PreUpdate,
load_asset_palette.run_if(resource_exists::<LoadingAssetPaletteHandle>),
);
}
}
#[derive(Default)]
struct PaletteLoader;
impl AssetLoader for PaletteLoader {
type Asset = Palette;
type Settings = ImageLoaderSettings;
type Error = Error;
async fn load(
&self,
reader: &mut dyn Reader,
settings: &ImageLoaderSettings,
load_context: &mut LoadContext<'_>,
) -> Result<Palette> {
Ok(Palette::new(
&ImageLoader::new(CompressedImageFormats::NONE)
.load(reader, settings, load_context)
.await?,
))
}
fn extensions(&self) -> &[&str] {
&["palette.png"]
}
}
#[derive(Asset, Clone, TypePath, Debug)]
pub struct Palette {
pub(crate) size: UVec2,
pub(crate) colors: Vec<[u8; 3]>,
pub(crate) indices: HashMap<[u8; 3], u8>,
}
#[derive(Resource, Deref, DerefMut)]
pub struct PaletteHandle(pub Handle<Palette>);
pub(crate) type PaletteParam<'w> = AssetSingleton<'w, PaletteHandle>;
#[derive(Resource, Deref)]
struct LoadingAssetPaletteHandle(Handle<Palette>);
type LoadingAssetPaletteParam<'w> = AssetSingleton<'w, LoadingAssetPaletteHandle>;
impl Palette {
pub fn new(palette: &Image) -> Palette {
let (colors, _, _) = palette
.convert(TextureFormat::Rgba8UnormSrgb)
.unwrap()
.data
.iter()
.copied()
.fold(
(Vec::default(), [0, 0, 0], 0),
|(mut colors, mut color, i), value| {
if i == 3 {
if value != 0 {
colors.push(color);
}
(colors, [0, 0, 0], 0)
} else {
color[i] = value;
(colors, color, i + 1)
}
},
);
Palette {
size: UVec2::new(
palette.texture_descriptor.size.width,
palette.texture_descriptor.size.height,
),
indices: colors
.iter()
.enumerate()
.map(|(i, color)| (*color, i as u8))
.collect(),
colors,
}
}
}
fn init_palette(path: PathBuf) -> impl Fn(Commands, Res<AssetServer>) {
move |mut commands, assets| {
let palette = assets.load(path.clone());
commands.insert_resource(PaletteHandle(palette.clone()));
commands.insert_resource(LoadingAssetPaletteHandle(palette));
}
}
static mut ASSET_PALETTE: Option<Palette> = None;
static ASSET_PALETTE_INITIALIZED: AtomicBool = AtomicBool::new(false);
static ASSET_PALETTE_JUST_INITIALIZED: Event = Event::new();
#[allow(static_mut_refs)]
pub(crate) async fn asset_palette() -> &'static Palette {
if ASSET_PALETTE_INITIALIZED.load(Ordering::SeqCst) {
return unsafe { ASSET_PALETTE.as_ref() }.unwrap();
}
let just_initialized = ASSET_PALETTE_JUST_INITIALIZED.listen();
if ASSET_PALETTE_INITIALIZED.load(Ordering::SeqCst) {
return unsafe { ASSET_PALETTE.as_ref() }.unwrap();
}
just_initialized.await;
unsafe { ASSET_PALETTE.as_ref() }.unwrap()
}
fn load_asset_palette(palette: LoadingAssetPaletteParam, mut cmd: Commands) {
let Some(palette) = palette.get() else {
return;
};
if ASSET_PALETTE_INITIALIZED.load(Ordering::SeqCst) {
panic!("Tried to set the asset palette after it was initialized");
}
let palette = Some(palette.clone());
unsafe { ASSET_PALETTE = palette };
ASSET_PALETTE_INITIALIZED.store(true, Ordering::SeqCst);
ASSET_PALETTE_JUST_INITIALIZED.notify(usize::MAX);
cmd.remove_resource::<LoadingAssetPaletteHandle>();
}