use crate::{
PColor,
pico8::{Pico8State, Palettes, GfxSprite, Gfx, GfxDirty},
};
use super::canvas;
use bevy::prelude::*;
use mashmap::MashMap;
mod counter;
use counter::DrawCounter;
static DRAW_COUNTER: DrawCounter = DrawCounter::new(1);
const MAX_EXPECTED_CLEARABLES: f32 = 1000.0;
pub(crate) fn plugin(app: &mut App) {
app
.register_type::<Clearable>()
.add_event::<ClearEvent>()
.init_resource::<ClearCache>()
.add_systems(Last, (handle_overflow).chain())
.add_observer(handle_clear_event);
}
#[derive(Debug, Event, Clone, Copy)]
pub struct ClearEvent {
color: PColor
}
impl ClearEvent {
pub fn new(color: PColor) -> Self {
Self {
color
}
}
}
#[derive(Resource, Default)]
pub(crate) struct ClearCache(MashMap<u64, Entity>);
impl ClearCache {
pub fn insert(&mut self, clearable: &Clearable, id: Entity) -> bool {
assert!(matches!(clearable.state, ClearState::Visible));
match clearable.hash {
Some(hash) => {
self.0.insert(hash, id);
true
}
None => false,
}
}
pub fn take(&mut self, hash: &u64) -> Option<Entity> {
self.0.remove_one(hash)
}
pub fn remove(&mut self, clearable: &Clearable, id: Entity) -> bool {
let result = self.0
.drain_key_if(&clearable.hash.unwrap(), |v| *v == id)
.next()
.is_some();
if matches!(clearable.state, ClearState::Visible) {
}
result
}
}
#[derive(Debug, Component, Clone, Copy, Reflect)]
#[component(on_remove = on_remove_hook)]
pub struct Clearable {
pub(crate) draw_count: usize,
pub time_to_live: u8,
pub hash: Option<u64>,
pub state: ClearState,
}
#[derive(Debug, Component, Clone, Copy, Reflect)]
pub enum ClearState {
Visible,
Hidden { time_to_live: u8 },
}
impl ClearState {
pub fn decrement_ttl(&mut self) -> Option<u8> {
match self {
ClearState::Visible => None,
ClearState::Hidden { time_to_live } => {
let last_ttl = *time_to_live;
*time_to_live = time_to_live.saturating_sub(1);
Some(last_ttl)
}
}
}
}
fn on_remove_hook(mut world: bevy::ecs::world::DeferredWorld, id: Entity, _comp_id: bevy::ecs::component::ComponentId) {
let Some(clearable) = world.get::<Clearable>(id).copied() else { return; };
let Some(mut cache) = world.get_resource_mut::<ClearCache>() else { return; };
info!("Removing clearable {id} from cache.");
cache.remove(&clearable, id);
}
impl Default for Clearable {
fn default() -> Self {
Clearable {
draw_count: DRAW_COUNTER.increment(),
time_to_live: 0,
hash: None,
state: ClearState::Visible,
}
}
}
impl Clearable {
pub fn new(time_to_live: u8) -> Self {
Clearable {
draw_count: DRAW_COUNTER.increment(),
time_to_live,
hash: None,
state: ClearState::Visible,
}
}
pub fn is_cached(&self) -> bool {
!matches!(self.state, ClearState::Visible)
}
pub fn mark_cached(&mut self) -> bool {
if !self.is_cached() {
self.state = ClearState::Hidden { time_to_live: self.time_to_live };
true
} else {
false
}
}
pub fn with_hash(mut self, hash: u64) -> Self {
self.hash = Some(hash);
self
}
pub fn suggest_z(&self) -> f32 {
1.0 + self.draw_count as f32 / MAX_EXPECTED_CLEARABLES
}
pub fn resurrect(&mut self) {
self.state = ClearState::Visible;
self.draw_count = DRAW_COUNTER.increment();
}
}
fn handle_overflow(mut query: Query<&mut Clearable>) {
if DRAW_COUNTER.overflowed() {
for mut clearable in &mut query {
clearable.draw_count = 0;
}
DRAW_COUNTER.reset_overflowed()
}
}
fn handle_clear_event(
trigger: Trigger<ClearEvent>,
mut query: Query<(Entity, &mut Clearable, &mut Visibility)>,
mut commands: Commands,
mut state: ResMut<Pico8State>,
mut cache: ResMut<ClearCache>,
mut gfxs: ResMut<Assets<Gfx>>,
one_color: Single<&mut Sprite, With<canvas::OneColorBackground>>,
background: Single<(Entity, &GfxSprite, &mut GfxDirty, &mut canvas::Background), With<canvas::Background>>,
palettes: Res<Palettes>,
background_dirty: Local<bool>,
) {
state.draw_state.clear_screen();
let mut sprite = one_color.into_inner();
match palettes.get_color(trigger.color, state.palette) {
Ok(color) => {
sprite.color = color;
}
Err(e) => {
error!("Could not clear to color: {e}");
sprite.color = Srgba::rgb(1.0, 0.0, 1.0).into(); }
}
let (background_id, gfx_sprite, mut gfx_dirty, background) = background.into_inner();
if gfx_dirty.0 {
if let Some(gfx) = gfxs.get_mut(&gfx_sprite.image) {
trace!("Clearing Background pixels.");
gfx.data.set_elements(0x00);
}
gfx_dirty.0 = false;
}
for (id, mut clearable, mut visibility) in &mut query {
match clearable.state.decrement_ttl() {
Some(ttl) if ttl == 0 => {
commands.entity(id).despawn_recursive();
}
Some(_ttl) => {
}
None => {
if clearable.time_to_live == 0 || clearable.hash.is_none() {
commands.entity(id).despawn_recursive();
} else {
*visibility = Visibility::Hidden;
if cache.insert(&clearable, id) {
assert!(clearable.mark_cached());
} else {
panic!();
}
}
}
}
}
DRAW_COUNTER.set(1);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test0() {
static COUNTER: DrawCounter = DrawCounter::new(0);
assert_eq!(COUNTER.increment(), 0);
assert_eq!(COUNTER.increment(), 1);
assert_eq!(COUNTER.get(), 2);
}
}