use std::{collections::HashMap, ffi::CString, str::FromStr, time::Instant};
use anyhow::Context;
use dirk_universe::{Entity, Universe, World, WorldId};
use dirk_world::player::{Player, PlayerId};
use tracing::info;
use dirk_logging::Logger;
pub struct Engine {
exit_consumer: dirk_events::Consumer<dirk_events::AppExit>,
event_manager: dirk_events::EventManager,
#[allow(unused)]
asset_registry: dirk_assets::AssetRegistry,
renderer: dirk_renderer::Renderer,
platform: dirk_platform::Platform,
universe: dirk_universe::Universe,
next_player_id: PlayerId,
players: HashMap<PlayerId, Player>,
is_requesting_exit: bool,
exit_error: Option<anyhow::Error>,
last_tick: Instant,
#[allow(unused)]
logger: Logger,
}
impl Engine {
pub fn init() -> anyhow::Result<Self> {
#[cfg(feature = "editor")]
info!("starting editor");
info!("initialising engine");
let logger = dirk_logging::Logger::new()
.write_fs(true)
.max_level(dirk_logging::LogLevel::Debug)
.init()
.context("initialising logger")?;
let event_manager = dirk_events::EventManager::new();
let asset_registry = dirk_assets::AssetRegistry::init(&event_manager)
.context("initialising asset registry")?;
let version = dirk_utils::Version::from_str(env!("CARGO_PKG_VERSION"))?;
let name = "DirkEngine";
let platform = dirk_platform::Platform::init(&event_manager).context("platform init")?;
let mut renderer = dirk_renderer::Renderer::init(
&dirk_renderer::RendererCreateInfo {
engine_name: CString::from_str(name)?,
engine_version: version,
app_name: CString::from_str(name)?,
app_version: version,
},
platform.main_window(),
&event_manager,
)
.context("renderer init")?;
let universe = Universe::builder()
.with_other(dirk_world::universe_builder(&event_manager))
.with_other(renderer.universe_builder())
.build();
info!("engine initialised");
Ok(Self {
exit_consumer: event_manager.subscribe(),
event_manager,
logger,
asset_registry,
platform,
renderer,
universe,
next_player_id: 0,
players: HashMap::new(),
is_requesting_exit: false,
exit_error: None,
last_tick: Instant::now(),
})
}
pub fn start(&mut self) -> anyhow::Result<()> {
info!("starting engine");
let world_id = self.create_test_world();
self.spawn_player(world_id);
for _ in 0..5 {
self.tick_inner().context("pre-start ticking")?;
}
Ok(())
}
pub fn tick(&mut self) -> bool {
if !match self.tick_inner() {
Ok(exit) => exit,
Err(err) => {
self.exit_error = Some(err.context("engine tick"));
false
}
} {
return false;
}
match self.render() {
Ok(()) => true,
Err(err) => {
self.exit_error = Some(err.context("rendering"));
false
}
}
}
fn tick_inner(&mut self) -> anyhow::Result<bool> {
let delta_time = self.capture_delta_time();
self.event_manager.dispatch_all();
std::thread::sleep(std::time::Duration::from_millis(100));
self.process_events();
if self.is_requesting_exit() {
return Ok(false);
}
self.platform.tick(delta_time);
self.universe.tick(delta_time);
self.asset_registry.tick();
self.renderer
.tick(delta_time, self.platform.windows_mut())
.context("renderer")?;
self.players
.values_mut()
.for_each(|player| player.tick(&mut self.universe));
Ok(!self.is_requesting_exit())
}
fn render(&mut self) -> anyhow::Result<()> {
Ok(self.renderer.render()?)
}
fn process_events(&mut self) {
if let Some(dirk_events::AppExit(msg)) = self.exit_consumer.try_consume() {
info!("App exit requested: {msg}");
self.exit(None);
}
}
pub fn is_requesting_exit(&self) -> bool {
self.is_requesting_exit || self.exit_error.is_some()
}
pub fn exit(&mut self, err: Option<anyhow::Error>) {
self.is_requesting_exit = true;
self.exit_error = err;
}
pub fn get_exit_error(&self) -> &Option<anyhow::Error> {
&self.exit_error
}
fn capture_delta_time(&mut self) -> f32 {
let current_time = Instant::now();
let delta = current_time.duration_since(self.last_tick).as_secs_f32();
self.last_tick = current_time;
delta
}
fn spawn_player(&mut self, world: WorldId) -> PlayerId {
let id = self.next_player_id;
self.next_player_id += 1;
let player = Player::spawn(
id,
&mut self.universe,
world,
self.platform.main_window().id(),
&self.event_manager,
);
self.players.insert(id, player);
id
}
#[allow(unused)]
fn kill_player(&mut self, id: PlayerId) {
let Some(player) = self.players.remove(&id) else {
return;
};
player.despawn(&mut self.universe);
}
fn create_test_world(&mut self) -> WorldId {
use dirk_world::components::{Renderable, Transform};
let duck_model = dirk_assets::AssetHandle::from_raw(
"models/Duck/Duck.dirkasset",
dirk_assets::AssetType::Model,
);
let shrek_model = dirk_assets::AssetHandle::from_raw(
"models/Shrek/Shrek.dirkasset",
dirk_assets::AssetType::Model,
);
let shrek_builder = Entity::builder()
.with_component(Transform {
location: glam::Vec3::ZERO,
rotation: glam::Vec3::ZERO,
scale: glam::Vec3::splat(1.),
})
.with_component(Renderable::new(shrek_model));
let duck_builder = Entity::builder()
.with_component(Transform {
location: glam::vec3(100., 0., 0.),
rotation: glam::Vec3::ZERO,
scale: glam::Vec3::splat(1.),
})
.with_component(Renderable::new(duck_model));
let world_builder = World::builder("test world")
.with_entity(shrek_builder)
.with_entity(duck_builder);
let world = self.universe.create_world(world_builder);
world.expect("world creation shouldn't fail")
}
}