use std::sync::Arc;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
use crate::assets::{Assets, FontId};
use crate::camera::Camera;
use crate::draw2d::SpriteId;
use crate::draw2d::{Color, Draw2d};
use crate::ecs::{MeshId, TextureId};
use crate::effect_pass::EffectPass;
use crate::geometry::PendingGeometry;
use crate::gpu::GpuContext;
use crate::hot_shader::{HotEffectPass, HotPostProcessPass, HotWorldPostProcessPass};
use crate::input::Input;
use crate::mesh::{Mesh, Transform};
use crate::picking::{self, PickResult, Ray, RayHit};
use crate::post_process::{PostProcessPass, WorldPostProcessPass};
use crate::render_graph::{
EffectNode, HotEffectNode, HotPostProcessNode, HotWorldPostProcessNode, MeshNode, MeshQueue,
PostProcessNode, RenderGraph, WorldPostProcessNode,
};
use crate::texture::{Sprite, Texture};
use glam::{Quat, Vec3};
use std::cell::RefCell;
use std::rc::Rc;
pub struct MeshLoader<'a> {
gpu: &'a GpuContext,
mesh_queue: &'a Rc<RefCell<MeshQueue>>,
pending: PendingGeometry,
}
impl<'a> MeshLoader<'a> {
pub fn centered(mut self) -> Self {
self.pending = self.pending.centered();
self
}
pub fn normalized(mut self) -> Self {
self.pending = self.pending.normalized();
self
}
pub fn scaled(mut self, factor: f32) -> Self {
self.pending = self.pending.scaled(factor);
self
}
pub fn translated(mut self, offset: Vec3) -> Self {
self.pending = self.pending.translated(offset);
self
}
pub fn smooth_normals(mut self) -> Self {
self.pending = self.pending.smooth_normals();
self
}
pub fn upright(mut self) -> Self {
self.pending = self.pending.upright();
self
}
pub fn rotated_by(mut self, rotation: Quat) -> Self {
self.pending = self.pending.rotated_by(rotation);
self
}
pub fn build(self) -> Result<MeshId, crate::geometry::GeometryError> {
let mesh = self.pending.upload(self.gpu)?;
Ok(self.mesh_queue.borrow_mut().add_mesh(mesh))
}
pub fn unwrap(self) -> MeshId {
self.build().expect("Failed to load geometry")
}
pub fn expect(self, msg: &str) -> MeshId {
self.build().expect(msg)
}
}
pub struct SetupContext<'a> {
pub gpu: &'a GpuContext,
pub assets: &'a mut Assets,
pub draw: &'a mut Draw2d,
pub world: &'a mut hecs::World,
default_font: &'a mut Option<FontId>,
graph_builder: &'a mut Option<RenderGraph>,
mesh_queue: &'a Rc<RefCell<MeshQueue>>,
}
impl<'a> SetupContext<'a> {
pub fn default_font(&mut self, size: f32) -> FontId {
let font = self.assets.default_font(self.gpu, size);
*self.default_font = Some(font);
font
}
pub fn background_color(&mut self, color: Color) -> &mut Self {
let shader = format!(
r#"
@group(0) @binding(0) var<uniform> u: Uniforms;
struct Uniforms {{
resolution: vec2f,
time: f32,
}}
@vertex
fn vs(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {{
var pos = array<vec2f, 3>(
vec2f(-1.0, -1.0),
vec2f(3.0, -1.0),
vec2f(-1.0, 3.0)
);
return vec4f(pos[vertex_index], 0.0, 1.0);
}}
@fragment
fn fs() -> @location(0) vec4f {{
return vec4f({:.6}, {:.6}, {:.6}, {:.6});
}}
"#,
color.r, color.g, color.b, color.a
);
let effect = EffectPass::new(self.gpu, &shader);
self.add_node(EffectNode::new(effect));
self
}
pub fn effect(&mut self, shader: &str) -> &mut Self {
let effect = EffectPass::new(self.gpu, shader);
self.add_node(EffectNode::new(effect));
self
}
pub fn effect_world(&mut self, shader: &str) -> &mut Self {
let effect = EffectPass::new_world(self.gpu, shader);
self.add_node(EffectNode::new(effect));
self
}
pub fn post_process(&mut self, shader: &str) -> &mut Self {
let pass = PostProcessPass::new(self.gpu, shader);
self.add_node(PostProcessNode::new(pass));
self
}
pub fn post_process_world(&mut self, shader: &str) -> &mut Self {
let pass = WorldPostProcessPass::new(self.gpu, shader);
self.add_node(WorldPostProcessNode::new(pass));
self
}
pub fn hot_effect(&mut self, path: &str) -> &mut Self {
match HotEffectPass::new(self.gpu, path) {
Ok(effect) => self.add_node(HotEffectNode::new(effect)),
Err(e) => eprintln!("[hot-reload] Failed to load shader '{}': {}", path, e),
}
self
}
pub fn hot_effect_world(&mut self, path: &str) -> &mut Self {
match HotEffectPass::new_world(self.gpu, path) {
Ok(effect) => self.add_node(HotEffectNode::new(effect)),
Err(e) => eprintln!("[hot-reload] Failed to load shader '{}': {}", path, e),
}
self
}
pub fn hot_post_process(&mut self, path: &str) -> &mut Self {
match HotPostProcessPass::new(self.gpu, path) {
Ok(pass) => self.add_node(HotPostProcessNode::new(pass)),
Err(e) => eprintln!("[hot-reload] Failed to load shader '{}': {}", path, e),
}
self
}
pub fn hot_post_process_world(&mut self, path: &str) -> &mut Self {
match HotWorldPostProcessPass::new(self.gpu, path) {
Ok(pass) => self.add_node(HotWorldPostProcessNode::new(pass)),
Err(e) => eprintln!("[hot-reload] Failed to load shader '{}': {}", path, e),
}
self
}
fn add_node<N: crate::render_graph::RenderNode + 'static>(&mut self, node: N) {
if self.graph_builder.is_none() {
*self.graph_builder = Some(RenderGraph::builder().node(node).build(self.gpu));
} else {
let old = self.graph_builder.take().unwrap();
*self.graph_builder = Some(old.with_node(node, self.gpu));
}
}
pub fn enable_mesh_rendering(&mut self) -> &mut Self {
let mesh_node = MeshNode::new(self.gpu, Rc::clone(self.mesh_queue));
self.add_node(mesh_node);
self
}
pub fn mesh_cube(&mut self) -> MeshId {
let mesh = Mesh::cube(self.gpu);
self.mesh_queue.borrow_mut().add_mesh(mesh)
}
pub fn mesh_sphere(&mut self, segments: u32, rings: u32) -> MeshId {
let mesh = Mesh::sphere(self.gpu, segments, rings);
self.mesh_queue.borrow_mut().add_mesh(mesh)
}
pub fn mesh_plane(&mut self, size: f32) -> MeshId {
let mesh = Mesh::plane(self.gpu, size);
self.mesh_queue.borrow_mut().add_mesh(mesh)
}
pub fn add_mesh(&mut self, mesh: Mesh) -> MeshId {
self.mesh_queue.borrow_mut().add_mesh(mesh)
}
pub fn load(&self, path: &str) -> MeshLoader<'_> {
MeshLoader {
gpu: self.gpu,
mesh_queue: self.mesh_queue,
pending: PendingGeometry::from_file(path),
}
}
pub fn load_stl_bytes(&self, bytes: &[u8]) -> MeshLoader<'_> {
MeshLoader {
gpu: self.gpu,
mesh_queue: self.mesh_queue,
pending: PendingGeometry::from_stl_bytes(bytes),
}
}
pub fn add_texture(&mut self, texture: Texture) -> TextureId {
self.mesh_queue.borrow_mut().add_texture(texture)
}
pub fn texture_from_file(&mut self, path: &str) -> Result<TextureId, image::ImageError> {
let texture = Texture::from_file(self.gpu, path)?;
Ok(self.add_texture(texture))
}
pub fn texture_from_bytes(
&mut self,
bytes: &[u8],
label: &str,
) -> Result<TextureId, image::ImageError> {
let texture = Texture::from_bytes(self.gpu, bytes, label)?;
Ok(self.add_texture(texture))
}
pub fn texture_blocky_noise(&mut self, size: u32, seed: u32) -> TextureId {
let texture = Texture::blocky_noise(self.gpu, size, seed);
self.add_texture(texture)
}
pub fn texture_blocky_grass(&mut self, size: u32, seed: u32) -> TextureId {
let texture = Texture::blocky_grass(self.gpu, size, seed);
self.add_texture(texture)
}
pub fn texture_blocky_stone(&mut self, size: u32, seed: u32) -> TextureId {
let texture = Texture::blocky_stone(self.gpu, size, seed);
self.add_texture(texture)
}
pub fn add_sprite(&mut self, sprite: Sprite) -> SpriteId {
self.draw.add_sprite(sprite)
}
pub fn sprite_from_file(&mut self, path: &str) -> Result<SpriteId, image::ImageError> {
let sprite = Sprite::from_file(self.gpu, path)?;
Ok(self.add_sprite(sprite))
}
pub fn sprite_from_file_nearest(&mut self, path: &str) -> Result<SpriteId, image::ImageError> {
let sprite = Sprite::from_file_nearest(self.gpu, path)?;
Ok(self.add_sprite(sprite))
}
pub fn sprite_from_bytes(
&mut self,
bytes: &[u8],
label: &str,
) -> Result<SpriteId, image::ImageError> {
let sprite = Sprite::from_bytes(self.gpu, bytes, label)?;
Ok(self.add_sprite(sprite))
}
pub fn sprite_from_bytes_nearest(
&mut self,
bytes: &[u8],
label: &str,
) -> Result<SpriteId, image::ImageError> {
let sprite = Sprite::from_bytes_nearest(self.gpu, bytes, label)?;
Ok(self.add_sprite(sprite))
}
pub fn sprite_blocky_noise(&mut self, size: u32, seed: u32) -> SpriteId {
let data = generate_blocky_noise_data(size, seed);
let sprite = Sprite::from_rgba_nearest(self.gpu, &data, size, size, "Blocky Noise Sprite");
self.add_sprite(sprite)
}
}
fn generate_blocky_noise_data(size: u32, seed: u32) -> Vec<u8> {
let mut data = vec![0u8; (size * size * 4) as usize];
let colors: &[[u8; 3]] = &[
[139, 90, 43], [128, 128, 128], [85, 85, 85], [160, 120, 60], [100, 70, 40], [90, 90, 90], [120, 100, 70], [70, 60, 50], ];
fn hash(x: u32, y: u32, seed: u32) -> u32 {
let mut h = seed;
h = h.wrapping_add(x.wrapping_mul(374761393));
h = h.wrapping_add(y.wrapping_mul(668265263));
h ^= h >> 13;
h = h.wrapping_mul(1274126177);
h ^= h >> 16;
h
}
for y in 0..size {
for x in 0..size {
let idx = ((y * size + x) * 4) as usize;
let h = hash(x, y, seed);
let color_idx = (h % colors.len() as u32) as usize;
let base = colors[color_idx];
let variation = ((hash(x + 1000, y + 1000, seed) % 30) as i32) - 15;
data[idx] = (base[0] as i32 + variation).clamp(0, 255) as u8;
data[idx + 1] = (base[1] as i32 + variation).clamp(0, 255) as u8;
data[idx + 2] = (base[2] as i32 + variation).clamp(0, 255) as u8;
data[idx + 3] = 255; }
}
data
}
pub struct Frame<'a> {
pub gpu: &'a GpuContext,
pub assets: &'a Assets,
pub draw: &'a mut Draw2d,
pub camera: &'a mut Camera,
pub input: &'a Input,
pub world: &'a mut hecs::World,
pub time: f32,
pub dt: f32,
pub(crate) default_font: Option<FontId>,
pub(crate) mesh_queue: Rc<RefCell<MeshQueue>>,
pub(crate) window: &'a Window,
pub(crate) scene_switch: Option<(String, crate::scene::Transition)>,
}
impl Frame<'_> {
pub fn fps(&self) -> f32 {
if self.dt > 0.0 { 1.0 / self.dt } else { 0.0 }
}
pub fn width(&self) -> u32 {
self.gpu.width()
}
pub fn height(&self) -> u32 {
self.gpu.height()
}
pub fn capture_cursor(&self) {
use winit::window::CursorGrabMode;
let _ = self
.window
.set_cursor_grab(CursorGrabMode::Locked)
.or_else(|_| self.window.set_cursor_grab(CursorGrabMode::Confined));
self.window.set_cursor_visible(false);
}
pub fn release_cursor(&self) {
use winit::window::CursorGrabMode;
let _ = self.window.set_cursor_grab(CursorGrabMode::None);
self.window.set_cursor_visible(true);
}
pub fn switch_to(&mut self, scene_name: impl Into<String>) {
self.switch_to_with(scene_name, crate::scene::Transition::instant());
}
pub fn switch_to_with(
&mut self,
scene_name: impl Into<String>,
transition: crate::scene::Transition,
) {
self.scene_switch = Some((scene_name.into(), transition));
}
pub fn text(&mut self, x: f32, y: f32, text: &str) {
self.text_color(x, y, text, Color::WHITE)
}
pub fn text_color(&mut self, x: f32, y: f32, text: &str, color: Color) {
let font = self
.default_font
.expect("No default font set. Call ctx.default_font() in setup.");
self.draw.text(self.assets, font, x, y, text, color);
}
pub fn rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
self.draw.rect(x, y, w, h, color);
}
pub fn panel(&mut self, x: f32, y: f32, w: f32, h: f32) -> f32 {
self.draw.panel(x, y, w, h).draw(self.assets);
y
}
pub fn panel_titled(&mut self, x: f32, y: f32, w: f32, h: f32, title: &str) -> f32 {
let font = self
.default_font
.expect("Panel with title requires default font. Call ctx.default_font() in setup.");
self.draw
.panel(x, y, w, h)
.title(title, font)
.draw(self.assets);
y + 22.0 }
pub fn set_camera(&mut self, camera: Camera) {
*self.camera = camera;
}
pub fn mesh(&mut self, mesh: MeshId) -> MeshBuilder<'_> {
MeshBuilder {
queue: &self.mesh_queue,
mesh,
transform: Transform::default(),
color: Color::WHITE,
texture: None,
}
}
pub fn draw_mesh(&mut self, mesh: MeshId, transform: Transform, color: Color) {
self.mesh_queue.borrow_mut().draw(mesh, transform, color);
}
pub fn draw_mesh_white(&mut self, mesh: MeshId, transform: Transform) {
self.draw_mesh(mesh, transform, Color::WHITE);
}
pub fn draw_mesh_textured(
&mut self,
mesh: MeshId,
transform: Transform,
color: Color,
texture: TextureId,
) {
self.mesh_queue
.borrow_mut()
.draw_textured(mesh, transform, color, texture);
}
pub fn draw_mesh_textured_white(
&mut self,
mesh: MeshId,
transform: Transform,
texture: TextureId,
) {
self.draw_mesh_textured(mesh, transform, Color::WHITE, texture);
}
pub fn sprite(&mut self, sprite_id: SpriteId, x: f32, y: f32) {
self.draw.sprite(sprite_id, x, y, Color::WHITE);
}
pub fn sprite_tinted(&mut self, sprite_id: SpriteId, x: f32, y: f32, tint: Color) {
self.draw.sprite(sprite_id, x, y, tint);
}
pub fn sprite_scaled(&mut self, sprite_id: SpriteId, x: f32, y: f32, w: f32, h: f32) {
self.draw.sprite_scaled(sprite_id, x, y, w, h, Color::WHITE);
}
pub fn sprite_scaled_tinted(
&mut self,
sprite_id: SpriteId,
x: f32,
y: f32,
w: f32,
h: f32,
tint: Color,
) {
self.draw.sprite_scaled(sprite_id, x, y, w, h, tint);
}
pub fn sprite_region(
&mut self,
sprite_id: SpriteId,
x: f32,
y: f32,
w: f32,
h: f32,
src_x: f32,
src_y: f32,
src_w: f32,
src_h: f32,
) {
self.draw.sprite_region(
sprite_id,
x,
y,
w,
h,
src_x,
src_y,
src_w,
src_h,
Color::WHITE,
);
}
pub fn sprite_region_tinted(
&mut self,
sprite_id: SpriteId,
x: f32,
y: f32,
w: f32,
h: f32,
src_x: f32,
src_y: f32,
src_w: f32,
src_h: f32,
tint: Color,
) {
self.draw
.sprite_region(sprite_id, x, y, w, h, src_x, src_y, src_w, src_h, tint);
}
pub fn render_world(&mut self) {
use crate::ecs::RenderMesh;
use crate::mesh::Transform;
for (_, (transform, render_mesh)) in self.world.query::<(&Transform, &RenderMesh)>().iter()
{
if let Some(texture) = render_mesh.texture {
self.mesh_queue.borrow_mut().draw_textured(
render_mesh.mesh,
*transform,
render_mesh.color,
texture,
);
} else {
self.mesh_queue
.borrow_mut()
.draw(render_mesh.mesh, *transform, render_mesh.color);
}
}
}
pub fn mouse_ray(&self) -> Ray {
let mouse = self.input.mouse_position();
let aspect = self.gpu.width() as f32 / self.gpu.height() as f32;
Ray::from_screen(
mouse.x,
mouse.y,
self.gpu.width() as f32,
self.gpu.height() as f32,
self.camera.view_matrix(),
self.camera
.projection_matrix(aspect, self.camera.near, self.camera.far),
)
}
pub fn pick_collider(&self) -> PickResult {
let ray = self.mouse_ray();
picking::raycast(self.world, &ray)
}
pub fn pick_collider_all(&self) -> Vec<RayHit> {
let ray = self.mouse_ray();
picking::raycast_all(self.world, &ray)
}
pub fn raycast(&self, ray: &Ray) -> PickResult {
picking::raycast(self.world, ray)
}
pub fn raycast_all(&self, ray: &Ray) -> Vec<RayHit> {
picking::raycast_all(self.world, ray)
}
}
pub struct MeshBuilder<'a> {
queue: &'a Rc<RefCell<MeshQueue>>,
mesh: MeshId,
transform: Transform,
color: Color,
texture: Option<TextureId>,
}
impl MeshBuilder<'_> {
pub fn at(mut self, x: f32, y: f32, z: f32) -> Self {
self.transform = Transform::from_position(glam::Vec3::new(x, y, z));
self
}
pub fn position(mut self, pos: glam::Vec3) -> Self {
self.transform = Transform::from_position(pos);
self
}
pub fn transform(mut self, transform: Transform) -> Self {
self.transform = transform;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn texture(mut self, texture: TextureId) -> Self {
self.texture = Some(texture);
self
}
pub fn draw(self) {
let mut queue = self.queue.borrow_mut();
if let Some(texture) = self.texture {
queue.draw_textured(self.mesh, self.transform, self.color, texture);
} else {
queue.draw(self.mesh, self.transform, self.color);
}
}
}
pub struct AppConfig {
pub title: String,
pub width: u32,
pub height: u32,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
title: "Hoplite".to_string(),
width: 800,
height: 600,
}
}
}
impl AppConfig {
pub fn new() -> Self {
Self::default()
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn size(mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self
}
}
pub fn run<S, F>(setup: S)
where
S: FnOnce(&mut SetupContext) -> F + 'static,
F: FnMut(&mut Frame) + 'static,
{
run_with_config(AppConfig::default(), setup);
}
pub fn run_with_config<S, F>(config: AppConfig, setup: S)
where
S: FnOnce(&mut SetupContext) -> F + 'static,
F: FnMut(&mut Frame) + 'static,
{
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = HopliteApp::Pending {
config,
setup: Some(Box::new(move |gpu, assets, draw, mesh_queue, world| {
let mut default_font = None;
let mut graph_builder = None;
let mut ctx = SetupContext {
gpu,
assets,
draw,
world,
default_font: &mut default_font,
graph_builder: &mut graph_builder,
mesh_queue,
};
let frame_fn = setup(&mut ctx);
(
Box::new(frame_fn) as Box<dyn FnMut(&mut Frame)>,
default_font,
graph_builder,
)
})),
};
event_loop.run_app(&mut app).unwrap();
}
pub struct SceneSetupContext<'a> {
pub base: SetupContext<'a>,
scene_manager: &'a mut crate::scene::SceneManager,
}
impl<'a> SceneSetupContext<'a> {
pub fn default_font(&mut self, size: f32) -> FontId {
self.base.default_font(size)
}
pub fn mesh_cube(&mut self) -> MeshId {
self.base.mesh_cube()
}
pub fn mesh_sphere(&mut self, segments: u32, rings: u32) -> MeshId {
self.base.mesh_sphere(segments, rings)
}
pub fn mesh_plane(&mut self, size: f32) -> MeshId {
self.base.mesh_plane(size)
}
pub fn add_mesh(&mut self, mesh: Mesh) -> MeshId {
self.base.add_mesh(mesh)
}
pub fn add_texture(&mut self, texture: crate::texture::Texture) -> TextureId {
self.base.add_texture(texture)
}
pub fn texture_from_file(&mut self, path: &str) -> Result<TextureId, image::ImageError> {
self.base.texture_from_file(path)
}
pub fn texture_blocky_noise(&mut self, size: u32, seed: u32) -> TextureId {
self.base.texture_blocky_noise(size, seed)
}
pub fn texture_blocky_grass(&mut self, size: u32, seed: u32) -> TextureId {
self.base.texture_blocky_grass(size, seed)
}
pub fn texture_blocky_stone(&mut self, size: u32, seed: u32) -> TextureId {
self.base.texture_blocky_stone(size, seed)
}
pub fn sprite_from_file(&mut self, path: &str) -> Result<SpriteId, image::ImageError> {
self.base.sprite_from_file(path)
}
pub fn scene<S, F>(&mut self, name: &str, setup: S) -> crate::scene::scene::SceneBuilder<'_>
where
S: FnOnce(&mut crate::scene::SceneSetupContext) -> F,
F: FnMut(&mut Frame) + 'static,
{
let mut render_graph = None;
let mesh_queue = Rc::clone(self.base.mesh_queue);
let mut scene_ctx =
crate::scene::SceneSetupContext::new(self.base.gpu, &mut render_graph, &mesh_queue);
let frame_fn = setup(&mut scene_ctx);
let scene = crate::scene::Scene::new(
crate::scene::SceneId::new(name),
render_graph,
mesh_queue,
Box::new(frame_fn),
);
let scene_id = scene.id.clone();
self.scene_manager.register(scene);
self.scene_manager.scene_builder(scene_id)
}
pub fn start_scene(&mut self, name: &str) {
self.scene_manager.set_active(name);
}
pub fn gpu(&self) -> &GpuContext {
self.base.gpu
}
}
pub fn run_with_scenes<S>(setup: S)
where
S: FnOnce(&mut SceneSetupContext) + 'static,
{
run_with_scenes_config(AppConfig::default(), setup);
}
pub fn run_with_scenes_config<S>(config: AppConfig, setup: S)
where
S: FnOnce(&mut SceneSetupContext) + 'static,
{
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let scene_setup: SceneSetupFn = Box::new(move |gpu, assets, draw, mesh_queue, world| {
let mut default_font = None;
let mut graph_builder = None;
let mut scene_manager = crate::scene::SceneManager::new();
scene_manager.init_gpu_resources(gpu);
{
let base_ctx = SetupContext {
gpu,
assets,
draw,
world,
default_font: &mut default_font,
graph_builder: &mut graph_builder,
mesh_queue,
};
let mut ctx = SceneSetupContext {
base: base_ctx,
scene_manager: &mut scene_manager,
};
setup(&mut ctx);
}
(scene_manager, default_font)
});
let mut app = HopliteSceneApp::Pending {
config,
setup: Some(scene_setup),
};
event_loop.run_app(&mut app).unwrap();
}
type SceneSetupFn = Box<
dyn FnOnce(
&GpuContext,
&mut Assets,
&mut Draw2d,
&Rc<RefCell<MeshQueue>>,
&mut hecs::World,
) -> (crate::scene::SceneManager, Option<FontId>),
>;
enum HopliteSceneApp {
Pending {
config: AppConfig,
setup: Option<SceneSetupFn>,
},
Running {
window: Arc<Window>,
gpu: GpuContext,
assets: Assets,
draw_2d: Draw2d,
input: Input,
world: hecs::World,
scene_manager: crate::scene::SceneManager,
default_font: Option<FontId>,
mesh_queue: Rc<RefCell<MeshQueue>>,
start_time: Instant,
last_frame: Instant,
},
}
impl ApplicationHandler for HopliteSceneApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if let HopliteSceneApp::Pending { config, setup } = self {
let window_attrs = WindowAttributes::default()
.with_title(&config.title)
.with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height));
let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
let gpu = GpuContext::new(window.clone());
let mut assets = Assets::new();
let mut draw_2d = Draw2d::new(&gpu);
let mesh_queue = Rc::new(RefCell::new(MeshQueue::new()));
let mut world = hecs::World::new();
let setup_fn = setup.take().unwrap();
let (scene_manager, default_font) =
setup_fn(&gpu, &mut assets, &mut draw_2d, &mesh_queue, &mut world);
*self = HopliteSceneApp::Running {
window,
gpu,
assets,
draw_2d,
input: Input::new(),
world,
scene_manager,
default_font,
mesh_queue,
start_time: Instant::now(),
last_frame: Instant::now(),
};
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let HopliteSceneApp::Running {
window,
gpu,
assets,
draw_2d,
input,
world,
scene_manager,
default_font,
mesh_queue,
start_time,
last_frame,
} = self
else {
return;
};
input.handle_event(&event);
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(size) => {
gpu.resize(size.width, size.height);
}
WindowEvent::RedrawRequested => {
let now = Instant::now();
let time = start_time.elapsed().as_secs_f32();
let dt = now.duration_since(*last_frame).as_secs_f32();
*last_frame = now;
draw_2d.clear();
draw_2d.update_font_bind_groups(gpu, assets);
mesh_queue.borrow_mut().clear_queue();
scene_manager.update(time);
scene_manager.run_frame(
gpu,
assets,
draw_2d,
input,
world,
time,
dt,
mesh_queue,
window,
*default_font,
);
scene_manager.render(gpu, time, draw_2d, assets);
input.begin_frame();
window.request_redraw();
}
_ => {}
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
let HopliteSceneApp::Running { input, .. } = self else {
return;
};
if let winit::event::DeviceEvent::MouseMotion { delta } = event {
input.handle_raw_mouse_motion(delta.0 as f32, delta.1 as f32);
}
}
}
type SetupFn = Box<
dyn FnOnce(
&GpuContext,
&mut Assets,
&mut Draw2d,
&Rc<RefCell<MeshQueue>>,
&mut hecs::World,
) -> (
Box<dyn FnMut(&mut Frame)>,
Option<FontId>,
Option<RenderGraph>,
),
>;
enum HopliteApp {
Pending {
config: AppConfig,
setup: Option<SetupFn>,
},
Running {
window: Arc<Window>,
gpu: GpuContext,
assets: Assets,
draw_2d: Draw2d,
camera: Camera,
input: Input,
world: hecs::World,
frame_fn: Box<dyn FnMut(&mut Frame)>,
default_font: Option<FontId>,
render_graph: Option<RenderGraph>,
mesh_queue: Rc<RefCell<MeshQueue>>,
start_time: Instant,
last_frame: Instant,
},
}
impl ApplicationHandler for HopliteApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if let HopliteApp::Pending { config, setup } = self {
let window_attrs = WindowAttributes::default()
.with_title(&config.title)
.with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height));
let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
let gpu = GpuContext::new(window.clone());
let mut assets = Assets::new();
let mut draw_2d = Draw2d::new(&gpu);
let mesh_queue = Rc::new(RefCell::new(MeshQueue::new()));
let mut world = hecs::World::new();
let setup_fn = setup.take().unwrap();
let (frame_fn, default_font, render_graph) =
setup_fn(&gpu, &mut assets, &mut draw_2d, &mesh_queue, &mut world);
*self = HopliteApp::Running {
window,
gpu,
assets,
draw_2d,
camera: Camera::new(),
input: Input::new(),
world,
frame_fn,
default_font,
render_graph,
mesh_queue,
start_time: Instant::now(),
last_frame: Instant::now(),
};
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let HopliteApp::Running {
window,
gpu,
assets,
draw_2d,
camera,
input,
world,
frame_fn,
default_font,
render_graph,
mesh_queue,
start_time,
last_frame,
} = self
else {
return;
};
input.handle_event(&event);
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(size) => {
gpu.resize(size.width, size.height);
}
WindowEvent::RedrawRequested => {
let now = Instant::now();
let time = start_time.elapsed().as_secs_f32();
let dt = now.duration_since(*last_frame).as_secs_f32();
*last_frame = now;
draw_2d.clear();
draw_2d.update_font_bind_groups(gpu, assets);
mesh_queue.borrow_mut().clear_queue();
let mut frame = Frame {
gpu,
assets,
draw: draw_2d,
camera,
input,
world,
time,
dt,
default_font: *default_font,
mesh_queue: Rc::clone(mesh_queue),
window,
scene_switch: None, };
frame_fn(&mut frame);
if let Some(graph) = render_graph {
graph.execute_with_ui(gpu, time, camera, |gpu, pass| {
draw_2d.render(gpu, pass, assets);
});
} else {
render_2d_only(gpu, draw_2d, assets);
}
input.begin_frame();
window.request_redraw();
}
_ => {}
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
let HopliteApp::Running { input, .. } = self else {
return;
};
if let winit::event::DeviceEvent::MouseMotion { delta } = event {
input.handle_raw_mouse_motion(delta.0 as f32, delta.1 as f32);
}
}
}
fn render_2d_only(gpu: &GpuContext, draw_2d: &Draw2d, assets: &Assets) {
let output = gpu.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = gpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("2D Only Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("2D Only Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
draw_2d.render(gpu, &mut render_pass, assets);
}
gpu.queue.submit(std::iter::once(encoder.finish()));
output.present();
}