From c516af0605f09fbb47096444fca7a375b13ca956 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Sun, 17 Mar 2024 00:53:25 +0100 Subject: [PATCH] wip: app redesign --- recipes.json | 6 +- violet-core/src/layout/cache.rs | 2 +- violet-core/src/layout/mod.rs | 63 +----- violet-core/src/layout/stack.rs | 6 +- violet-core/src/state/dedup.rs | 7 +- violet-core/src/systems.rs | 2 - violet-core/src/time/mod.rs | 8 +- violet-core/src/types.rs | 2 +- violet-core/src/widget/basic.rs | 9 +- violet-core/src/widget/interactive/slider.rs | 4 +- violet-demo/Cargo.toml | 3 + violet-wgpu/src/app.rs | 211 ++++++++++++------- violet-wgpu/src/graphics/gpu.rs | 1 - violet-wgpu/src/graphics/texture.rs | 2 +- violet-wgpu/src/renderer/debug_renderer.rs | 1 - violet-wgpu/src/renderer/mod.rs | 1 - violet-wgpu/src/renderer/window_renderer.rs | 9 +- violet-wgpu/src/text.rs | 2 +- 18 files changed, 161 insertions(+), 178 deletions(-) diff --git a/recipes.json b/recipes.json index d8affb8..050b0e5 100644 --- a/recipes.json +++ b/recipes.json @@ -55,7 +55,11 @@ ] }, "build web": { - "cmd": "wasm-pack build --target web", + "cmd": "wasm-pack build --target web --dev", + "cwd": "./violet-demo/" + }, + "build web profile": { + "cmd": "wasm-pack build --target web --profiling", "cwd": "./violet-demo/" }, "host": { diff --git a/violet-core/src/layout/cache.rs b/violet-core/src/layout/cache.rs index ab2fa1b..d626ca3 100644 --- a/violet-core/src/layout/cache.rs +++ b/violet-core/src/layout/cache.rs @@ -69,7 +69,7 @@ impl LayoutCache { pub(crate) fn insert_query_row(&mut self, value: CachedValue) { self.query_row = Some(value); if let Some(f) = self.on_invalidated.as_ref() { - // f(LayoutUpdate::SizeQueryUpdate) + f(LayoutUpdate::SizeQueryUpdate) } } diff --git a/violet-core/src/layout/mod.rs b/violet-core/src/layout/mod.rs index 0b07e8a..78f2332 100644 --- a/violet-core/src/layout/mod.rs +++ b/violet-core/src/layout/mod.rs @@ -12,7 +12,7 @@ use crate::{ self, anchor, aspect_ratio, children, layout, max_size, maximize, min_size, offset, padding, size, size_resolver, }, - layout::cache::{validate_cached_layout, validate_cached_query, CachedValue, LAYOUT_TOLERANCE}, + layout::cache::{validate_cached_layout, validate_cached_query, CachedValue}, Edges, Rect, }; @@ -223,67 +223,6 @@ impl SizingHints { } } -fn validate_sizing(entity: &EntityRef, sizing: &Sizing, limits: LayoutLimits) { - const TOLERANCE: f32 = 0.2; - if sizing.min.size().x > limits.max_size.x + TOLERANCE - || sizing.min.size().y > limits.max_size.y + TOLERANCE - { - tracing::error!( - %entity, - min_size = %sizing.min.size(), - max_size = %limits.max_size, - "Minimum size exceeds size limit", - ); - } - - if sizing.preferred.size().x > limits.max_size.x + TOLERANCE - || sizing.preferred.size().y > limits.max_size.y + TOLERANCE - { - tracing::error!( - %entity, - preferred_size = %sizing.preferred.size(), - ?limits, - "Preferred size exceeds size limit", - ); - } - - if sizing.min.size().x + TOLERANCE < limits.min_size.x - || sizing.min.size().y + TOLERANCE < limits.min_size.y - { - tracing::error!( - %entity, - min_size = %sizing.min.size(), - ?limits, - "Minimum size is less than size limit", - ); - } -} - -fn validate_block(entity: &EntityRef, block: &Block, limits: LayoutLimits) { - const TOLERANCE: f32 = 0.2; - if block.rect.size().x > limits.max_size.x + TOLERANCE - || block.rect.size().y > limits.max_size.y + TOLERANCE - { - tracing::error!( - %entity, - rect_size = %block.rect.size(), - max_size = %limits.max_size, - "Widget size exceeds size limit", - ); - } - - if block.rect.size().x + TOLERANCE < limits.min_size.x - || block.rect.size().y + TOLERANCE < limits.min_size.y - { - tracing::error!( - %entity, - rect_size = %block.rect.size(), - min_size = %limits.min_size, - "Widget size is less than size limit", - ); - } -} - pub(crate) fn query_size(world: &World, entity: &EntityRef, args: QueryArgs) -> Sizing { puffin::profile_function!(format!("{entity} {args:?}")); // assert!(limits.min_size.x <= limits.max_size.x); diff --git a/violet-core/src/layout/stack.rs b/violet-core/src/layout/stack.rs index e16879e..a674fef 100644 --- a/violet-core/src/layout/stack.rs +++ b/violet-core/src/layout/stack.rs @@ -8,9 +8,7 @@ use crate::{ Edges, Rect, }; -use super::{ - apply_layout, resolve_pos, Alignment, Block, Direction, LayoutLimits, QueryArgs, Sizing, -}; +use super::{apply_layout, resolve_pos, Alignment, Block, LayoutLimits, QueryArgs, Sizing}; #[derive(Debug)] pub struct StackableBounds { @@ -203,7 +201,7 @@ impl StackLayout { }, ); - maximize = maximize + sizing.maximize; + maximize += sizing.maximize; hints = hints.combine(sizing.hints); diff --git a/violet-core/src/state/dedup.rs b/violet-core/src/state/dedup.rs index 2e43da1..e9172fd 100644 --- a/violet-core/src/state/dedup.rs +++ b/violet-core/src/state/dedup.rs @@ -1,9 +1,6 @@ -use std::{future::ready, sync::Arc}; +use std::future::ready; -use futures::{FutureExt, StreamExt}; -use futures_signals::signal::{Mutable, SignalExt}; -use parking_lot::Mutex; -use tracing::info; +use futures::StreamExt; use super::{State, StateSink, StateStream, StateStreamRef}; diff --git a/violet-core/src/systems.rs b/violet-core/src/systems.rs index 2e88154..2070e83 100644 --- a/violet-core/src/systems.rs +++ b/violet-core/src/systems.rs @@ -11,12 +11,10 @@ use flax::{ entity_ids, events::{EventData, EventSubscriber}, filter::Or, - sink::Sink, BoxedSystem, CommandBuffer, Dfs, DfsBorrow, Entity, Fetch, FetchExt, FetchItem, Query, QueryBorrow, System, World, }; use glam::Vec2; -use tracing::info; use crate::{ components::{ diff --git a/violet-core/src/time/mod.rs b/violet-core/src/time/mod.rs index 969da60..14c6bf9 100644 --- a/violet-core/src/time/mod.rs +++ b/violet-core/src/time/mod.rs @@ -11,7 +11,6 @@ use std::{ }; use futures::{ - channel::oneshot, task::{ArcWake, AtomicWaker}, Future, }; @@ -226,14 +225,17 @@ impl Timers { #[cfg(target_arch = "wasm32")] struct TickFuture { inner: Arc, - timeout: Option<(oneshot::Receiver<()>, gloo_timers::callback::Timeout)>, + timeout: Option<( + futures::channel::oneshot::Receiver<()>, + gloo_timers::callback::Timeout, + )>, } #[cfg(target_arch = "wasm32")] impl TickFuture { fn new(inner: Arc, timeout: Option) -> Self { let timeout = if let Some(timeout) = timeout { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = futures::channel::oneshot::channel(); let timeout = gloo_timers::callback::Timeout::new( timeout.as_millis().try_into().unwrap(), diff --git a/violet-core/src/types.rs b/violet-core/src/types.rs index ef8e201..e696ec4 100644 --- a/violet-core/src/types.rs +++ b/violet-core/src/types.rs @@ -218,7 +218,7 @@ impl Rect { } #[must_use] - pub(crate) fn clamp_size(&self, min: Vec2, max: Vec2) -> Self { + pub fn clamp_size(&self, min: Vec2, max: Vec2) -> Self { let size = self.size().clamp(min, max); Self { min: self.min, diff --git a/violet-core/src/widget/basic.rs b/violet-core/src/widget/basic.rs index e44b539..6a2d436 100644 --- a/violet-core/src/widget/basic.rs +++ b/violet-core/src/widget/basic.rs @@ -4,14 +4,9 @@ use palette::Srgba; use crate::{ assets::AssetKey, - components::{ - self, aspect_ratio, color, draw_shape, font_size, min_size, size, text, text_wrap, - }, + components::{self, color, draw_shape, font_size, text, text_wrap}, shape, - style::{ - colors::REDWOOD_DEFAULT, spacing_large, spacing_small, SizeExt, StyleExt, ValueOrRef, - WidgetSize, - }, + style::{colors::REDWOOD_DEFAULT, spacing_small, SizeExt, StyleExt, ValueOrRef, WidgetSize}, text::{TextSegment, Wrap}, unit::Unit, Scope, Widget, diff --git a/violet-core/src/widget/interactive/slider.rs b/violet-core/src/widget/interactive/slider.rs index 4288267..718b2d9 100644 --- a/violet-core/src/widget/interactive/slider.rs +++ b/violet-core/src/widget/interactive/slider.rs @@ -5,7 +5,6 @@ use futures::{stream::BoxStream, StreamExt}; use futures_signals::signal::Mutable; use glam::Vec2; use palette::Srgba; -use web_time::Duration; use winit::event::ElementState; use crate::{ @@ -14,10 +13,9 @@ use crate::{ layout::Alignment, state::{State, StateDuplex, StateStream}, style::{interactive_active, interactive_inactive, spacing_small, SizeExt, StyleExt}, - time::sleep, to_owned, unit::Unit, - utils::{throttle, zip_latest}, + utils::zip_latest, widget::{row, ContainerStyle, Positioned, Rectangle, Stack, StreamWidget, Text}, Scope, StreamEffect, Widget, }; diff --git a/violet-demo/Cargo.toml b/violet-demo/Cargo.toml index 1798624..faa1d55 100644 --- a/violet-demo/Cargo.toml +++ b/violet-demo/Cargo.toml @@ -29,3 +29,6 @@ wasm-bindgen-futures = "0.4" itertools.workspace = true tracing-tree.workspace = true puffin.workspace = true + +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = false diff --git a/violet-wgpu/src/app.rs b/violet-wgpu/src/app.rs index 56440b2..92eecf4 100644 --- a/violet-wgpu/src/app.rs +++ b/violet-wgpu/src/app.rs @@ -6,10 +6,9 @@ use flax::{components::name, Entity, Schedule, World}; use glam::{vec2, Vec2}; use parking_lot::Mutex; use winit::{ - dpi::PhysicalSize, - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::EventLoopBuilder, - keyboard::Key, + dpi::{LogicalSize, PhysicalSize}, + event::{Event, WindowEvent}, + event_loop::{EventLoopBuilder, EventLoopWindowTarget}, window::WindowBuilder, }; @@ -77,9 +76,9 @@ impl App { } pub fn run(self, root: impl Widget) -> anyhow::Result<()> { - let mut ex = Executor::new(); + let executor = Executor::new(); - let spawner = ex.spawner(); + let spawner = executor.spawner(); let mut frame = Frame::new(spawner, AssetCache::new(), World::new()); @@ -121,8 +120,6 @@ impl App { let window = builder.build(&event_loop)?; - let mut window_size = window.inner_size(); - let mut input_state = InputState::new(Vec2::ZERO); let stylesheet = setup_stylesheet().spawn(frame.world_mut()); @@ -130,12 +127,10 @@ impl App { // Mount the root widget let root = frame.new_root(Canvas { stylesheet, - size: vec2(window_size.width as f32, window_size.height as f32), + size: vec2(0.0, 0.0), root, }); - let mut stats = AppStats::new(60); - tracing::info!("creating gpu"); let window = Arc::new(window); @@ -162,9 +157,7 @@ impl App { } })); - let mut renderer = None; - - let mut schedule = Schedule::new() + let schedule = Schedule::new() .with_system(templating_system(root, layout_changes_tx)) .flush() .with_system(hydrate_text()) @@ -177,71 +170,57 @@ impl App { .with_system(transform_system()); let start_time = Instant::now(); - let mut cur_time = start_time; #[cfg(not(target_arch = "wasm32"))] let _puffin_server = setup_puffin(); - let mut minimized = true; - - event_loop.run(move |event, ctl| match event { + let mut instance = AppInstance { + frame, + renderer: None, + root, + scale_factor: window.scale_factor(), + stats: AppStats::new(60), + current_time: start_time, + start_time, + executor, + schedule, + window_size: window.inner_size(), + }; + + let on_event = move |event, ctl: &EventLoopWindowTarget<()>| match event { Event::AboutToWait => { puffin::profile_scope!("AboutToWait"); if let Some(mut window_renderer) = renderer_rx.try_recv().ok().flatten() { - window_renderer.resize(window_size); - renderer = Some(window_renderer); - } - - if minimized { - return; + window_renderer.resize(instance.window_size, instance.scale_factor); + instance.renderer = Some(window_renderer); } - let new_time = Instant::now(); - - let frame_time = new_time.duration_since(cur_time); - - cur_time = new_time; - - // tracing::info!(?dt, fps = 1.0 / delta_time); - - stats.record_frame(frame_time); - - ex.tick(&mut frame); - - update_animations(&mut frame, cur_time - start_time); + instance.update(); - { - puffin::profile_scope!("Schedule"); - schedule.execute_seq(&mut frame.world).unwrap(); + if let Err(err) = instance.draw() { + tracing::error!("Failed to draw to window: {err:?}"); } - if let Some(renderer) = &mut renderer { - puffin::profile_scope!("Draw"); - if let Err(err) = renderer.draw(&mut frame) { - tracing::error!("Failed to draw to window: {err:?}"); - } + if !instance.is_minimized() { + let report = instance.stats.report(); + window.set_title(&format!( + "Violet - {:>4.1?} {:>4.1?} {:>4.1?}", + report.min_frame_time, report.average_frame_time, report.max_frame_time, + )); } - - let report = stats.report(); - window.set_title(&format!( - "Violet - {:>4.1?} {:>4.1?} {:>4.1?}", - report.min_frame_time, report.average_frame_time, report.max_frame_time, - )); puffin::GlobalProfiler::lock().new_frame(); } Event::WindowEvent { window_id, event } => match event { WindowEvent::RedrawRequested => { puffin::profile_scope!("RedrawRequested"); - if let Some(renderer) = &mut renderer { - if let Err(err) = renderer.draw(&mut frame) { - tracing::error!("Failed to draw to window: {err:?}"); - } + if let Err(err) = instance.draw() { + tracing::error!("Failed to draw to window: {err:?}"); } } WindowEvent::MouseInput { state, button, .. } => { puffin::profile_scope!("MouseInput"); - input_state.on_mouse_input(&mut frame, state, button); + input_state.on_mouse_input(&mut instance.frame, state, button); } WindowEvent::ModifiersChanged(modifiers) => { puffin::profile_scope!("ModifiersChanged"); @@ -249,34 +228,26 @@ impl App { } WindowEvent::KeyboardInput { event, .. } => { puffin::profile_scope!("KeyboardInput", format!("{event:?}")); - input_state.on_keyboard_input(&mut frame, event) + input_state.on_keyboard_input(&mut instance.frame, event) } WindowEvent::CursorMoved { position, .. } => { puffin::profile_scope!("CursorMoved"); - input_state - .on_cursor_move(&mut frame, vec2(position.x as f32, position.y as f32)) + input_state.on_cursor_move( + &mut instance.frame, + vec2(position.x as f32, position.y as f32), + ) + } + WindowEvent::ScaleFactorChanged { + scale_factor: s, .. + } => { + tracing::info!("Scale factor changed to {s}"); + instance.scale_factor = s; + + let size = instance.window_size; + instance.on_resize(size); } WindowEvent::Resized(size) => { - puffin::profile_scope!("Resized"); - minimized = size.width == 0 || size.height == 0; - - window_size = size; - - frame - .world_mut() - .set( - root, - components::rect(), - Rect { - min: vec2(0.0, 0.0), - max: vec2(size.width as f32, size.height as f32), - }, - ) - .unwrap(); - - if let Some(renderer) = &mut renderer { - renderer.resize(size); - } + instance.on_resize(size); } WindowEvent::CloseRequested => { ctl.exit(); @@ -288,10 +259,90 @@ impl App { event => { tracing::trace!(?event, "Event") } - })?; + }; + + // #[cfg(not(target_arch = "wasm32"))] + // { + // event_loop.run(on_event)?; + // } + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::EventLoopExtWebSys; + event_loop.spawn(on_event); + } + + Ok(()) + } +} + +pub struct AppInstance { + frame: Frame, + renderer: Option, + root: Entity, + scale_factor: f64, + stats: AppStats, + current_time: Instant, + start_time: Instant, + executor: Executor, + schedule: Schedule, + window_size: PhysicalSize, +} + +impl AppInstance { + pub fn on_resize(&mut self, size: PhysicalSize) { + self.window_size = size; + + tracing::info!(?size, self.scale_factor, "Resizing window"); + + let logical_size: LogicalSize = size.to_logical(self.scale_factor); + + self.frame + .world_mut() + .set( + self.root, + components::rect(), + Rect::from_size(vec2(logical_size.width, logical_size.height)), + ) + .unwrap(); + + if let Some(renderer) = &mut self.renderer { + renderer.resize(size, self.scale_factor); + } + } + + pub fn update(&mut self) { + if self.is_minimized() { + return; + } + + let new_time = Instant::now(); + + let frame_time = new_time.duration_since(self.current_time); + + self.current_time = new_time; + self.stats.record_frame(frame_time); + + self.executor.tick(&mut self.frame); + + update_animations(&mut self.frame, self.current_time - self.start_time); + + { + self.schedule.execute_seq(&mut self.frame.world).unwrap(); + } + } + + pub fn draw(&mut self) -> anyhow::Result<()> { + puffin::profile_function!(); + if let Some(renderer) = &mut self.renderer { + puffin::profile_scope!("Draw"); + renderer.draw(&mut self.frame)?; + } Ok(()) } + pub fn is_minimized(&self) -> bool { + self.window_size.width == 0 || self.window_size.height == 0 + } } #[cfg(not(target_arch = "wasm32"))] diff --git a/violet-wgpu/src/graphics/gpu.rs b/violet-wgpu/src/graphics/gpu.rs index 3e6bca2..2339c2c 100644 --- a/violet-wgpu/src/graphics/gpu.rs +++ b/violet-wgpu/src/graphics/gpu.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use glam::UVec2; use wgpu::{Adapter, Backends, SurfaceConfiguration, SurfaceError, SurfaceTexture, TextureFormat}; use winit::{dpi::PhysicalSize, window::Window}; diff --git a/violet-wgpu/src/graphics/texture.rs b/violet-wgpu/src/graphics/texture.rs index 9e4510d..07f736e 100644 --- a/violet-wgpu/src/graphics/texture.rs +++ b/violet-wgpu/src/graphics/texture.rs @@ -61,7 +61,7 @@ impl Texture { // The layout of the texture wgpu::ImageDataLayout { offset: 0, - bytes_per_row: Some(format.block_size(None).unwrap() * dimensions.0), + bytes_per_row: Some(format.block_copy_size(None).unwrap() * dimensions.0), rows_per_image: Some(dimensions.1), }, size, diff --git a/violet-wgpu/src/renderer/debug_renderer.rs b/violet-wgpu/src/renderer/debug_renderer.rs index 2b9cffb..2528fb4 100644 --- a/violet-wgpu/src/renderer/debug_renderer.rs +++ b/violet-wgpu/src/renderer/debug_renderer.rs @@ -4,7 +4,6 @@ use flax::{fetch::entity_refs, Entity, Query}; use glam::{vec2, vec3, vec4, Mat4, Quat, Vec3, Vec4}; use image::DynamicImage; use itertools::Itertools; -use palette::bool_mask::Select; use violet_core::{ assets::Asset, components::screen_rect, diff --git a/violet-wgpu/src/renderer/mod.rs b/violet-wgpu/src/renderer/mod.rs index dc4052c..fb53254 100644 --- a/violet-wgpu/src/renderer/mod.rs +++ b/violet-wgpu/src/renderer/mod.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use bytemuck::Zeroable; use flax::{ - components::child_of, entity_ids, fetch::{entity_refs, EntityRefs, NthRelation}, CommandBuffer, Component, Entity, EntityRef, Fetch, Query, QueryBorrow, RelationExt, World, diff --git a/violet-wgpu/src/renderer/window_renderer.rs b/violet-wgpu/src/renderer/window_renderer.rs index 4297d87..08dad6d 100644 --- a/violet-wgpu/src/renderer/window_renderer.rs +++ b/violet-wgpu/src/renderer/window_renderer.rs @@ -6,7 +6,7 @@ use glam::Mat4; use parking_lot::Mutex; use puffin::profile_scope; use wgpu::{Operations, RenderPassDescriptor, StoreOp, SurfaceError}; -use winit::dpi::PhysicalSize; +use winit::dpi::{LogicalSize, PhysicalSize}; use violet_core::{layout::cache::LayoutUpdate, Frame}; @@ -51,9 +51,10 @@ impl WindowRenderer { } } - pub fn resize(&mut self, new_size: PhysicalSize) { - let w = new_size.width as f32; - let h = new_size.height as f32; + pub fn resize(&mut self, new_size: PhysicalSize, scale_factor: f64) { + let logical_size: LogicalSize = new_size.to_logical(scale_factor); + let w = logical_size.width; + let h = logical_size.height; self.ctx.globals.projview = Mat4::orthographic_lh(0.0, w, h, 0.0, 0.0, 1000.0); self.ctx diff --git a/violet-wgpu/src/text.rs b/violet-wgpu/src/text.rs index 6649adf..057a026 100644 --- a/violet-wgpu/src/text.rs +++ b/violet-wgpu/src/text.rs @@ -10,7 +10,7 @@ use parking_lot::Mutex; use violet_core::{ components::font_size, - layout::{Direction, LayoutLimits, QueryArgs, SizeResolver, SizingHints}, + layout::{LayoutLimits, QueryArgs, SizeResolver, SizingHints}, text::{LayoutGlyphs, LayoutLineGlyphs, TextSegment}, Rect, };