Skip to main content

diene_engine_runtime/
lib.rs

1//! Engine runtime with renderer backend selection policy.
2
3#![forbid(unsafe_code)]
4
5use common::logging::macros::{debug, info};
6use engine_core::app::{ApplicationHost, ApplicationHostBuildError, ApplicationHostError, WindowError};
7use engine_renderer_api::{BoxedRenderer, RenderWindow, RendererFactory};
8use engine_renderer_vulkan::renderer::{VulkanRendererBuilder, VulkanRendererError};
9use thiserror::Error;
10
11/// Errors returned by the public application runtime.
12#[derive(Debug, Error)]
13#[non_exhaustive]
14pub enum ApplicationError {
15    /// Renderer backend operation failed.
16    #[error("renderer failed: {0}")]
17    Renderer(#[source] VulkanRendererError),
18
19    /// Window or event loop operation failed.
20    #[error("window failed: {0}")]
21    Window(#[from] WindowError),
22
23    /// Application configuration is invalid.
24    #[error("application build failed: {0}")]
25    Build(#[from] ApplicationHostBuildError),
26}
27
28impl From<ApplicationHostError<VulkanRendererError>> for ApplicationError {
29    fn from(error: ApplicationHostError<VulkanRendererError>) -> Self {
30        match error {
31            ApplicationHostError::Renderer(error) => Self::Renderer(error),
32            ApplicationHostError::Window(error) => Self::Window(error),
33            ApplicationHostError::Build(error) => Self::Build(error),
34        }
35    }
36}
37
38/// Renderer backend selection mode.
39#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
40#[non_exhaustive]
41pub enum RendererBackend {
42    /// Chooses the best supported backend compiled into this binary.
43    #[default]
44    Auto,
45
46    /// Forces the Vulkan backend.
47    Vulkan,
48}
49
50/// Public engine application that owns renderer backend selection policy.
51#[derive(Debug)]
52pub struct Application {
53    host: RuntimeApplicationHost,
54}
55
56type RuntimeApplicationHost = ApplicationHost<RendererBackendSelector>;
57
58/// Configures an [`Application`].
59#[derive(Debug, Default)]
60pub struct ApplicationBuilder {
61    name: Option<String>,
62    renderer_backend: RendererBackend,
63    vsync: bool,
64}
65
66impl ApplicationBuilder {
67    /// Sets the human-readable application name.
68    #[must_use]
69    pub fn with_name(mut self, name: impl Into<String>) -> Self {
70        self.name = Some(name.into());
71        self
72    }
73
74    /// Selects the renderer backend policy.
75    #[must_use]
76    pub fn with_renderer_backend(mut self, backend: RendererBackend) -> Self {
77        self.renderer_backend = backend;
78        self
79    }
80
81    /// Enables or disables vertical synchronization.
82    #[must_use]
83    pub fn with_vsync(mut self, on: bool) -> Self {
84        self.vsync = on;
85        self
86    }
87
88    /// Builds the application.
89    pub fn build(self) -> Result<Application, ApplicationError> {
90        debug!("building runtime application with backend {:?}", self.renderer_backend);
91
92        let selector = RendererBackendSelector { renderer_backend: self.renderer_backend, vsync: self.vsync };
93
94        let mut builder = ApplicationHost::builder(selector);
95
96        if let Some(name) = self.name {
97            builder = builder.with_name(name);
98        }
99
100        Ok(Application { host: builder.build()? })
101    }
102}
103
104impl Application {
105    /// Creates a builder for configuring an [`Application`].
106    pub fn builder() -> ApplicationBuilder {
107        ApplicationBuilder::default()
108    }
109
110    /// Returns the human-readable application name.
111    pub fn name(&self) -> &str {
112        self.host.name()
113    }
114
115    /// Runs the application until the event loop exits.
116    pub fn run(self) -> Result<(), ApplicationError> {
117        self.host.run().map_err(Into::into)
118    }
119}
120
121#[derive(Clone, Copy, Debug)]
122struct RendererBackendSelector {
123    renderer_backend: RendererBackend,
124    vsync: bool,
125}
126
127impl RendererFactory for RendererBackendSelector {
128    type Error = VulkanRendererError;
129
130    fn create_renderer(&mut self, window: &dyn RenderWindow) -> Result<BoxedRenderer<Self::Error>, Self::Error> {
131        match self.renderer_backend {
132            RendererBackend::Auto | RendererBackend::Vulkan => {
133                info!(
134                    "selected renderer backend vulkan with {} policy",
135                    if matches!(self.renderer_backend, RendererBackend::Auto) {
136                        "auto"
137                    } else {
138                        "forced"
139                    }
140                );
141                create_vulkan_renderer(window, self.vsync)
142            }
143        }
144    }
145}
146
147fn create_vulkan_renderer(window: &dyn RenderWindow, vsync: bool) -> Result<BoxedRenderer<VulkanRendererError>, VulkanRendererError> {
148    debug!("creating vulkan renderer");
149
150    let renderer = VulkanRendererBuilder::default().with_vsync(vsync).build(window)?;
151
152    Ok(Box::new(renderer))
153}