use crate::plugin::{Plugin, PluginDyn, PluginGroup};
use crate::{Engine, EngineBuilder};
#[cfg(all(feature = "render", feature = "winit"))]
use astrelis_render::{WindowContextDescriptor, WindowManager};
#[cfg(feature = "winit")]
use astrelis_winit::{
app::{App, AppCtx, run_app},
window::{WindowDescriptor, WinitPhysicalSize},
};
#[cfg(feature = "winit")]
type AppFactory = Box<dyn FnOnce(&mut AppCtx, &Engine) -> Box<dyn App>>;
struct StoredPlugins {
plugins: std::cell::RefCell<Vec<Box<dyn PluginDyn>>>,
}
impl PluginGroup for StoredPlugins {
fn name(&self) -> &'static str {
"StoredPlugins"
}
fn plugins(&self) -> Vec<Box<dyn PluginDyn>> {
self.plugins.borrow_mut().drain(..).collect()
}
}
pub struct ApplicationBuilder {
title: String,
size: (u32, u32),
plugins: Vec<Box<dyn PluginDyn>>,
#[cfg(all(feature = "render", feature = "winit"))]
window_descriptor: Option<WindowContextDescriptor>,
create_window: bool,
}
impl Default for ApplicationBuilder {
fn default() -> Self {
Self::new()
}
}
impl ApplicationBuilder {
pub fn new() -> Self {
Self {
title: "Astrelis Application".to_string(),
size: (1280, 720),
plugins: Vec::new(),
#[cfg(all(feature = "render", feature = "winit"))]
window_descriptor: None,
create_window: true,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn with_size(mut self, width: u32, height: u32) -> Self {
self.size = (width, height);
self
}
pub fn add_plugin(mut self, plugin: impl Plugin + 'static) -> Self {
self.plugins.push(Box::new(plugin));
self
}
pub fn add_plugins(mut self, group: impl PluginGroup) -> Self {
for plugin in group.plugins() {
self.plugins.push(plugin);
}
self
}
#[cfg(all(feature = "render", feature = "winit"))]
pub fn with_window_descriptor(mut self, descriptor: WindowContextDescriptor) -> Self {
self.window_descriptor = Some(descriptor);
self
}
pub fn without_window(mut self) -> Self {
self.create_window = false;
self
}
#[cfg(feature = "winit")]
pub fn run<T>(self, factory: impl FnOnce(&mut AppCtx, &Engine) -> T + 'static)
where
T: App + 'static,
{
self.run_internal(factory)
}
#[cfg(feature = "winit")]
fn run_internal<T>(self, factory: impl FnOnce(&mut AppCtx, &Engine) -> T + 'static)
where
T: App + 'static,
{
let title = self.title;
let size = self.size;
#[cfg(all(feature = "render", feature = "winit"))]
let window_descriptor = self.window_descriptor;
let create_window = self.create_window;
let mut builder = EngineBuilder::new();
if !self.plugins.is_empty() {
use std::cell::RefCell;
builder = builder.add_plugins(StoredPlugins {
plugins: RefCell::new(self.plugins),
});
}
let engine = builder.build();
use std::cell::RefCell;
thread_local! {
static APP_BUILDER_DATA: RefCell<Option<AppBuilderData>> = const { RefCell::new(None) };
}
#[allow(dead_code)] struct AppBuilderData {
engine: Engine,
title: String,
size: (u32, u32),
#[cfg(all(feature = "render", feature = "winit"))]
window_descriptor: Option<WindowContextDescriptor>,
create_window: bool,
factory: AppFactory,
}
APP_BUILDER_DATA.with(|data| {
*data.borrow_mut() = Some(AppBuilderData {
engine,
title,
size,
#[cfg(all(feature = "render", feature = "winit"))]
window_descriptor,
create_window,
factory: Box::new(move |ctx, engine| Box::new(factory(ctx, engine))),
});
});
fn app_builder_factory(ctx: &mut AppCtx) -> Box<dyn App> {
use std::cell::RefCell;
thread_local! {
static APP_BUILDER_DATA: RefCell<Option<AppBuilderData>> = const { RefCell::new(None) };
}
struct AppBuilderData {
engine: Engine,
title: String,
size: (u32, u32),
#[cfg(all(feature = "render", feature = "winit"))]
window_descriptor: Option<WindowContextDescriptor>,
create_window: bool,
factory: AppFactory,
}
let mut data = APP_BUILDER_DATA
.with(|d| d.borrow_mut().take())
.expect("ApplicationBuilder data not found");
#[cfg(all(feature = "render", feature = "winit"))]
{
if data.create_window
&& let Some(window_manager) = data.engine.get_mut::<WindowManager>()
{
let descriptor = WindowDescriptor {
title: data.title.clone(),
size: Some(WinitPhysicalSize::new(
data.size.0 as f32,
data.size.1 as f32,
)),
..Default::default()
};
if let Some(window_desc) = data.window_descriptor.take() {
if let Err(e) = window_manager.create_window_with_descriptor(
ctx,
descriptor,
window_desc,
) {
tracing::error!("Failed to create window with descriptor: {}", e);
}
} else if let Err(e) = window_manager.create_window(ctx, descriptor) {
tracing::error!("Failed to create window: {}", e);
}
}
}
#[cfg(not(all(feature = "render", feature = "winit")))]
{
let _ = data.create_window; }
(data.factory)(ctx, &data.engine)
}
run_app(app_builder_factory)
}
pub fn build_engine(self) -> Engine {
let mut builder = EngineBuilder::new();
if !self.plugins.is_empty() {
use std::cell::RefCell;
builder = builder.add_plugins(StoredPlugins {
plugins: RefCell::new(self.plugins),
});
}
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_application_builder_defaults() {
let builder = ApplicationBuilder::new();
assert_eq!(builder.title, "Astrelis Application");
assert_eq!(builder.size, (1280, 720));
assert!(builder.plugins.is_empty());
assert!(builder.create_window);
}
#[test]
fn test_application_builder_with_title() {
let builder = ApplicationBuilder::new().with_title("Test Game");
assert_eq!(builder.title, "Test Game");
}
#[test]
fn test_application_builder_with_size() {
let builder = ApplicationBuilder::new().with_size(1920, 1080);
assert_eq!(builder.size, (1920, 1080));
}
#[test]
fn test_application_builder_without_window() {
let builder = ApplicationBuilder::new().without_window();
assert!(!builder.create_window);
}
#[test]
fn test_application_builder_add_plugin() {
use crate::FnPlugin;
let builder = ApplicationBuilder::new().add_plugin(FnPlugin::new("test", |_| {}));
assert_eq!(builder.plugins.len(), 1);
}
#[test]
fn test_application_builder_build_engine() {
use crate::FnPlugin;
let engine = ApplicationBuilder::new()
.add_plugin(FnPlugin::new("test", |resources| {
resources.insert(42i32);
}))
.build_engine();
assert_eq!(*engine.get::<i32>().unwrap(), 42);
}
#[test]
fn test_application_builder_chaining() {
let builder = ApplicationBuilder::new()
.with_title("Test")
.with_size(800, 600)
.without_window();
assert_eq!(builder.title, "Test");
assert_eq!(builder.size, (800, 600));
assert!(!builder.create_window);
}
}