use bevy_ecs::message::Messages;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::*;
use bevy_ecs::system::*;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;
use tracing::trace;
use windows::Win32::Foundation::HWND;
pub trait VsyncTick {
fn try_tick_on_vsync(&self) -> bool;
}
impl VsyncTick for Rc<RefCell<EcsWorld>> {
fn try_tick_on_vsync(&self) -> bool {
let result = match self.try_borrow_mut() {
Ok(mut world) => {
let result = world.try_tick_on_vsync();
#[cfg(debug_assertions)]
if result {
use crate::win_thread_mgr::DEBUG_WNDPROC_TICK_COUNT;
use std::sync::atomic::Ordering;
DEBUG_WNDPROC_TICK_COUNT.fetch_add(1, Ordering::Relaxed);
}
result
}
Err(_) => {
false
}
};
crate::ecs::window::flush_window_pos_commands();
result
}
}
#[derive(Resource, Default, Debug)]
pub struct FrameCount(pub u32);
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Input;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Update;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreLayout;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Layout;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PostLayout;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct UISetup;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct GraphicsSetup;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Draw;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreRenderSurface;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RenderSurface;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Composition;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CommitComposition;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FrameFinalize;
pub struct EcsWorld {
world: World,
has_systems: bool,
message_window: Option<HWND>,
frame_count: u64,
last_log_time: Option<Instant>,
}
impl EcsWorld {
pub fn new() -> Self {
let mut world = World::new();
world.insert_resource(crate::ecs::app::App::new());
world.insert_resource(FrameCount::default());
world.insert_resource(crate::ecs::layout::taffy::TaffyLayoutResource::default());
world.insert_resource(crate::ecs::graphics::SurfaceCreationStats::default());
if let Ok(wic_core) = crate::ecs::widget::bitmap_source::WicCore::new() {
world.insert_resource(wic_core);
}
world.insert_resource(crate::ecs::graphics::FrameTime::new());
world.insert_resource(crate::ecs::widget::bitmap_source::WintfTaskPool::new());
crate::ecs::layout::initialize_layout_root(&mut world);
world.insert_resource(crate::ecs::drag::DragAccumulatorResource::new());
world.insert_resource(crate::ecs::drag::WindowDragContextResource::new());
world.init_resource::<Messages<crate::ecs::drag::DragStartEvent>>();
world.init_resource::<Messages<crate::ecs::drag::DragEvent>>();
world.init_resource::<Messages<crate::ecs::drag::DragEndEvent>>();
{
world.init_resource::<Schedules>();
let mut schedules = world.resource_mut::<Schedules>();
schedules.insert(Schedule::new(Input));
schedules.insert(Schedule::new(Update));
schedules.insert(Schedule::new(PreLayout));
schedules.insert(Schedule::new(Layout));
schedules.insert(Schedule::new(PostLayout));
{
let mut sc = Schedule::new(UISetup);
sc.set_executor_kind(ExecutorKind::SingleThreaded);
schedules.insert(sc);
}
schedules.insert(Schedule::new(GraphicsSetup));
schedules.insert(Schedule::new(Draw));
schedules.insert(Schedule::new(PreRenderSurface));
schedules.insert(Schedule::new(RenderSurface));
schedules.insert(Schedule::new(Composition));
schedules.insert(Schedule::new(CommitComposition));
schedules.insert(Schedule::new(FrameFinalize));
}
{
let mut schedules = world.resource_mut::<Schedules>();
schedules.add_systems(
Input,
crate::ecs::widget::bitmap_source::systems::drain_task_pool_commands,
);
schedules.add_systems(
Input,
crate::ecs::pointer::dispatch_pointer_events
.after(crate::ecs::widget::bitmap_source::systems::drain_task_pool_commands),
);
schedules.add_systems(
Input,
crate::ecs::drag::dispatch_drag_events
.after(crate::ecs::pointer::dispatch_pointer_events),
);
schedules.add_systems(
Input,
crate::ecs::drag::cleanup_drag_state.after(crate::ecs::drag::dispatch_drag_events),
);
#[cfg(debug_assertions)]
schedules.add_systems(
Input,
(
crate::ecs::pointer::debug_pointer_state_changes,
crate::ecs::pointer::debug_pointer_leave,
)
.after(crate::ecs::pointer::dispatch_pointer_events),
);
schedules.add_systems(UISetup, crate::ecs::window_system::create_windows);
schedules.add_systems(UISetup, crate::ecs::graphics::apply_window_pos_changes);
schedules.add_systems(
Update,
(
crate::ecs::layout::detect_display_change_system,
crate::ecs::layout::update_monitor_layout_system
.after(crate::ecs::layout::detect_display_change_system),
crate::ecs::graphics::invalidate_dependent_components
.after(crate::ecs::layout::update_monitor_layout_system),
crate::ecs::widget::text::update_typewriters
.after(crate::ecs::graphics::invalidate_dependent_components),
)
.chain(),
);
schedules.add_systems(
PreLayout,
(
crate::ecs::graphics::init_graphics_core,
crate::ecs::graphics::visual_resource_management_system
.after(crate::ecs::graphics::init_graphics_core),
crate::ecs::graphics::visual_hierarchy_sync_system
.after(crate::ecs::graphics::visual_resource_management_system),
),
);
schedules.add_systems(
Layout,
(
crate::ecs::layout::build_taffy_styles_system,
crate::ecs::layout::sync_taffy_tree_system
.after(crate::ecs::layout::build_taffy_styles_system),
crate::ecs::layout::compute_taffy_layout_system
.after(crate::ecs::layout::sync_taffy_tree_system),
crate::ecs::layout::update_arrangements_system
.after(crate::ecs::layout::compute_taffy_layout_system),
crate::ecs::layout::cleanup_removed_entities_system,
)
.chain(),
);
schedules.add_systems(
PostLayout,
(
crate::ecs::layout::sync_window_arrangement_from_window_pos,
crate::ecs::layout::sync_simple_arrangements,
crate::ecs::layout::mark_dirty_arrangement_trees
.after(crate::ecs::layout::sync_simple_arrangements),
crate::ecs::layout::propagate_global_arrangements
.after(crate::ecs::layout::mark_dirty_arrangement_trees),
crate::ecs::layout::window_pos_sync_system
.after(crate::ecs::layout::propagate_global_arrangements),
)
.chain(),
);
schedules.add_systems(
GraphicsSetup,
(
crate::ecs::graphics::init_window_graphics,
crate::ecs::graphics::window_visual_integration_system
.after(crate::ecs::graphics::init_window_graphics),
)
.chain(),
);
schedules.add_systems(
Draw,
(
crate::ecs::graphics::resolve_inherited_brushes,
crate::ecs::widget::shapes::rectangle::draw_rectangles
.after(crate::ecs::graphics::resolve_inherited_brushes),
crate::ecs::widget::text::draw_labels
.after(crate::ecs::graphics::resolve_inherited_brushes),
crate::ecs::widget::text::invalidate_typewriter_layout_on_arrangement_change
.after(crate::ecs::graphics::resolve_inherited_brushes),
crate::ecs::widget::text::init_typewriter_layout
.after(crate::ecs::widget::text::invalidate_typewriter_layout_on_arrangement_change),
crate::ecs::widget::text::draw_typewriters
.after(crate::ecs::widget::text::init_typewriter_layout),
crate::ecs::widget::text::draw_typewriter_backgrounds
.after(crate::ecs::widget::text::draw_typewriters),
crate::ecs::widget::bitmap_source::draw_bitmap_sources
.after(crate::ecs::graphics::resolve_inherited_brushes),
crate::ecs::widget::bitmap_source::generate_alpha_mask_system
.after(crate::ecs::widget::bitmap_source::draw_bitmap_sources),
crate::ecs::graphics::deferred_surface_creation_system
.after(crate::ecs::widget::bitmap_source::generate_alpha_mask_system),
crate::ecs::graphics::cleanup_surface_on_commandlist_removed
.after(crate::ecs::graphics::deferred_surface_creation_system),
)
.chain(),
);
schedules.add_systems(PreRenderSurface, crate::ecs::graphics::mark_dirty_surfaces);
schedules.add_systems(RenderSurface, crate::ecs::graphics::render_surface);
schedules.add_systems(
Composition,
crate::ecs::graphics::visual_property_sync_system,
);
schedules.add_systems(CommitComposition, crate::ecs::graphics::commit_composition);
schedules.add_systems(
FrameFinalize,
crate::ecs::pointer::clear_transient_pointer_state,
);
schedules.add_systems(
FrameFinalize,
(
|world: &mut World| {
world
.resource_mut::<Messages<crate::ecs::drag::DragStartEvent>>()
.update()
},
|world: &mut World| {
world
.resource_mut::<Messages<crate::ecs::drag::DragEvent>>()
.update()
},
|world: &mut World| {
world
.resource_mut::<Messages<crate::ecs::drag::DragEndEvent>>()
.update()
},
),
);
}
Self {
world,
has_systems: true, message_window: None,
frame_count: 0,
last_log_time: None,
}
}
pub fn set_message_window(&mut self, hwnd: HWND) {
self.message_window = Some(hwnd);
if let Some(mut app) = self.world.get_resource_mut::<crate::ecs::app::App>() {
app.set_message_window(hwnd);
}
}
pub fn message_window(&self) -> Option<HWND> {
self.message_window
}
pub fn schedules_mut(&mut self) -> Mut<'_, Schedules> {
self.has_systems = true;
self.world.resource_mut::<Schedules>()
}
pub fn add_systems<M>(
&mut self,
schedule: impl ScheduleLabel,
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
) {
self.has_systems = true;
self.world
.resource_mut::<Schedules>()
.add_systems(schedule, systems);
}
pub fn world(&self) -> &World {
&self.world
}
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
pub fn spawn<F, Fut>(&self, f: F)
where
F: FnOnce(crate::ecs::widget::bitmap_source::CommandSender) -> Fut + Send + 'static,
Fut: std::future::Future<Output = ()> + Send,
{
if let Some(task_pool) = self
.world
.get_resource::<crate::ecs::widget::bitmap_source::WintfTaskPool>()
{
task_pool.spawn(f);
}
}
fn measure_and_log_framerate(&mut self) {
self.frame_count += 1;
let now = Instant::now();
if let Some(last_log) = self.last_log_time {
let elapsed = now.duration_since(last_log);
if elapsed.as_secs() >= 10 {
let fps = self.frame_count as f64 / elapsed.as_secs_f64();
let avg_frame_time = elapsed.as_secs_f64() * 1000.0 / self.frame_count as f64;
trace!(
fps,
frame_count = self.frame_count,
elapsed_secs = format_args!("{:.2}", elapsed.as_secs_f64()),
avg_frame_time_ms = format_args!("{:.2}", avg_frame_time),
"Frame rate measurement"
);
self.frame_count = 0;
self.last_log_time = Some(now);
}
} else {
self.last_log_time = Some(now);
}
}
pub fn try_tick_world(&mut self) -> bool {
self.measure_and_log_framerate();
if !self.has_systems {
return false;
}
if let Some(mut frame_count) = self.world.get_resource_mut::<FrameCount>() {
frame_count.0 += 1;
}
crate::ecs::pointer::transfer_buffers_to_world(&mut self.world);
let _ = self.world.try_run_schedule(Input);
let _ = self.world.try_run_schedule(Update);
let _ = self.world.try_run_schedule(PreLayout);
let _ = self.world.try_run_schedule(Layout);
let _ = self.world.try_run_schedule(PostLayout);
let _ = self.world.try_run_schedule(UISetup);
let _ = self.world.try_run_schedule(GraphicsSetup);
let _ = self.world.try_run_schedule(Draw);
let _ = self.world.try_run_schedule(PreRenderSurface);
let _ = self.world.try_run_schedule(RenderSurface);
let _ = self.world.try_run_schedule(Composition);
let _ = self.world.try_run_schedule(CommitComposition);
let _ = self.world.try_run_schedule(FrameFinalize);
crate::ecs::nchittest_cache::clear_nchittest_cache();
true
}
pub fn try_tick_on_vsync(&mut self) -> bool {
use crate::win_thread_mgr::{LAST_VSYNC_TICK, VSYNC_TICK_COUNT};
use std::sync::atomic::Ordering;
let current_tick = VSYNC_TICK_COUNT.load(Ordering::Relaxed);
let last_tick = LAST_VSYNC_TICK.load(Ordering::Relaxed);
if current_tick == last_tick {
return false;
}
LAST_VSYNC_TICK.store(current_tick, Ordering::Relaxed);
self.try_tick_world()
}
}
impl Default for EcsWorld {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for EcsWorld {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EcsWorld").finish_non_exhaustive()
}
}