use crate::bevy_ryot::map::MapTiles;
use crate::bevy_ryot::{GameObjectBundle, LoadObjects};
use crate::lmdb::{DatabaseName, Item, ItemRepository, ItemsFromHeedLmdb, SerdePostcard};
use crate::position::Sector;
use crate::prelude::GameObjectId;
use crate::{helpers::execute, lmdb, Layer};
use bevy::prelude::*;
use heed::types::Bytes;
use heed::Env;
use log::error;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub struct LmdbPlugin;
impl Plugin for LmdbPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<LmdbEnv>()
.init_resource::<LmdbCompactor>()
.add_systems(Startup, init_tiles_db.map(drop));
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct LmdbEnv(pub Option<Env>);
impl Default for LmdbEnv {
fn default() -> Self {
Self(Some(
lmdb::create_env(lmdb::get_storage_path()).expect("Failed to create LMDB env"),
))
}
}
#[derive(Debug, Resource, Clone)]
pub struct LmdbCompactor {
pub timer: Timer,
pub is_running: Arc<AtomicBool>,
}
impl Default for LmdbCompactor {
fn default() -> Self {
Self {
timer: Timer::new(std::time::Duration::from_secs(5 * 60), TimerMode::Repeating),
is_running: Arc::new(AtomicBool::new(false)),
}
}
}
pub fn compact_map(time: Res<Time>, env: Res<LmdbEnv>, mut lmdb_compactor: ResMut<LmdbCompactor>) {
if !lmdb_compactor.timer.tick(time.delta()).finished() {
return;
}
let Some(env) = &env.0 else {
return;
};
let env_clone = env.clone();
let is_running = lmdb_compactor.is_running.clone();
execute(async move {
let can_run = is_running
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
.is_ok();
if can_run {
lmdb::compact(env_clone).unwrap();
is_running.store(false, Ordering::SeqCst);
}
});
}
pub fn read_area(
tiles: Res<MapTiles<Entity>>,
env: ResMut<LmdbEnv>,
mut last_area: Local<Sector>,
sector_query: Query<&Sector, (With<Camera>, Changed<Sector>)>,
mut object_loaded_event_sender: EventWriter<LoadObjects>,
) {
let Some(env) = &env.0 else {
return;
};
let Ok(sector) = sector_query.get_single() else {
return;
};
let sector = *sector * 1.5;
for area in *last_area - sector {
load_area(env.clone(), area, &tiles, &mut object_loaded_event_sender);
}
*last_area = sector;
}
pub fn reload_visible_area(
tiles: Res<MapTiles<Entity>>,
env: ResMut<LmdbEnv>,
sector_query: Query<&Sector, With<Camera>>,
mut object_loaded_event_sender: EventWriter<LoadObjects>,
) {
let Some(env) = &env.0 else {
return;
};
for sector in sector_query.iter() {
load_area(
env.clone(),
*sector,
&tiles,
&mut object_loaded_event_sender,
);
}
}
pub fn load_area(
env: Env,
sector: Sector,
tiles: &Res<MapTiles<Entity>>,
object_loaded_event_sender: &mut EventWriter<LoadObjects>,
) {
let item_repository = ItemsFromHeedLmdb::new(env);
match item_repository.get_for_area(§or) {
Ok(area) => {
let mut bundles = vec![];
for tile in area {
for (layer, item) in tile.items {
if let Some(tile) = tiles.get(&tile.position) {
if tile.peek_for_layer(layer).is_some() {
continue;
}
}
bundles.push(GameObjectBundle::new(
GameObjectId::Object(item.id as u32),
tile.position,
layer,
));
}
}
object_loaded_event_sender.send(LoadObjects(bundles));
}
Err(e) => {
error!("Failed to read area: {}", e);
}
}
}
fn init_tiles_db(lmdb_env: Res<LmdbEnv>) -> color_eyre::Result<()> {
let Some(env) = &lmdb_env.0 else {
return Ok(());
};
let (wtxn, _) =
lmdb::rw::<Bytes, SerdePostcard<HashMap<Layer, Item>>>(env, DatabaseName::Tiles)?;
wtxn.commit()?;
Ok(())
}