vendored bevy_egui and removed legacy code :/

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2025-12-14 20:25:55 +00:00
parent b0f62dae38
commit 5493faa1f1
32 changed files with 4844 additions and 865 deletions

View File

@@ -3,6 +3,8 @@
//! This module handles the 3D camera setup for the cube demo.
use bevy::prelude::*;
use bevy::camera::RenderTarget;
use bevy::window::WindowRef;
pub struct CameraPlugin;
@@ -17,11 +19,19 @@ impl Plugin for CameraPlugin {
/// Camera is positioned at (4, 3, 6) looking at the cube's initial position (0,
/// 0.5, 0). This provides a good viewing angle to see the cube, ground plane,
/// and any movements.
///
/// libmarathon's debug_ui will automatically attach the primary egui context
/// to this camera via the setup_primary_egui_context_system.
fn setup_camera(mut commands: Commands) {
info!("Setting up camera");
commands.spawn((
Camera3d::default(),
Camera {
target: RenderTarget::Window(WindowRef::Primary),
..default()
},
Transform::from_xyz(4.0, 3.0, 6.0).looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::Y),
// PrimaryEguiContext will be auto-added by libmarathon
));
}

View File

@@ -1,11 +1,8 @@
//! Debug UI overlay using egui
use bevy::prelude::*;
use bevy_egui::{
egui,
EguiContexts,
EguiPrimaryContextPass,
};
use bevy::ecs::message::MessageWriter;
use libmarathon::debug_ui::{EguiContexts, EguiPrimaryContextPass};
use libmarathon::networking::{
EntityLockRegistry,
GossipBridge,
@@ -25,17 +22,15 @@ impl Plugin for DebugUiPlugin {
/// Render the debug UI panel
fn render_debug_ui(
mut contexts: EguiContexts,
mut egui_ctx: EguiContexts,
node_clock: Option<Res<NodeVectorClock>>,
gossip_bridge: Option<Res<GossipBridge>>,
lock_registry: Option<Res<EntityLockRegistry>>,
cube_query: Query<(&Transform, &NetworkedEntity), With<CubeMarker>>,
mut spawn_events: MessageWriter<SpawnCubeEvent>,
mut delete_events: MessageWriter<DeleteCubeEvent>,
) {
let Ok(ctx) = contexts.ctx_mut() else {
return;
};
) -> Result {
let ctx: &egui::Context = egui_ctx.ctx_mut()?;
egui::Window::new("Debug Info")
.default_pos([10.0, 10.0])
@@ -186,4 +181,6 @@ fn render_debug_ui(
ui.label("Scroll: Move cube (Z)");
ui.label("ESC: Deselect");
});
Ok(())
}

View File

@@ -21,8 +21,8 @@ use libmarathon::engine::InputEvent;
use libmarathon::platform::desktop;
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent as WinitWindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::event::{Event as WinitEvent, WindowEvent as WinitWindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy};
use winit::window::{Window as WinitWindow, WindowId, WindowAttributes};
// Re-export InputEventBuffer from the input module
@@ -124,6 +124,9 @@ impl AppHandler {
let physical_size = winit_window.inner_size();
let scale_factor = winit_window.scale_factor();
// Set the scale factor in the input bridge so mouse coords are converted correctly
desktop::set_scale_factor(scale_factor);
// Create window entity with all required components (use logical size)
let mut window = bevy::window::Window {
title: "Marathon".to_string(),
@@ -134,10 +137,13 @@ impl AppHandler {
mode: WindowMode::Windowed,
position: WindowPosition::Automatic,
focused: true,
// Let Window use default theme - will auto-detect system theme, egui will follow
..Default::default()
};
// Set scale factor explicitly
window.resolution.set_scale_factor(scale_factor as f32);
// Set scale factor using the proper API that applies to physical size
window
.resolution
.set_scale_factor_and_apply_to_physical_size(scale_factor as f32);
// Create WindowWrapper and RawHandleWrapper for renderer
let window_wrapper = WindowWrapper::new(winit_window.clone());
@@ -151,10 +157,9 @@ impl AppHandler {
)).id();
info!("Created window entity {}", window_entity);
// Send initialization events
// Send initialization event (only WindowCreated, like Bevy does)
// WindowResized and WindowScaleFactorChanged should only fire in response to actual winit events
send_window_created(&mut bevy_app, window_entity);
send_window_resized(&mut bevy_app, window_entity, physical_size, scale_factor);
send_scale_factor_changed(&mut bevy_app, window_entity, scale_factor);
// Now finish the app - the renderer will initialize with the window
bevy_app.finish();
@@ -187,9 +192,9 @@ impl AppHandler {
// Run one final update to process close event
bevy_app.update();
// Cleanup
bevy_app.finish();
bevy_app.cleanup();
// Don't call finish/cleanup - let Bevy's AppExit handle it
// bevy_app.finish();
// bevy_app.cleanup();
}
event_loop.exit();
@@ -240,7 +245,14 @@ impl ApplicationHandler for AppHandler {
}
WinitWindowEvent::Resized(physical_size) => {
// Notify Bevy of window resize
// Update the Bevy Window component's physical resolution
if let Some(mut window_component) = bevy_app.world_mut().get_mut::<Window>(*bevy_window_entity) {
window_component
.resolution
.set_physical_resolution(physical_size.width, physical_size.height);
}
// Notify Bevy systems of window resize
let scale_factor = window.scale_factor();
send_window_resized(bevy_app, *bevy_window_entity, physical_size, scale_factor);
}
@@ -269,6 +281,26 @@ impl ApplicationHandler for AppHandler {
window.request_redraw();
}
WinitWindowEvent::ScaleFactorChanged { scale_factor, .. } => {
// Update the Bevy Window component's scale factor
if let Some(mut window_component) = bevy_app.world_mut().get_mut::<Window>(*bevy_window_entity) {
let prior_factor = window_component.resolution.scale_factor();
// Use the proper API that applies to physical size
window_component
.resolution
.set_scale_factor_and_apply_to_physical_size(scale_factor as f32);
// Send scale factor changed event so camera system can update
send_scale_factor_changed(bevy_app, *bevy_window_entity, scale_factor);
info!(
"Scale factor changed from {} to {} for window {:?}",
prior_factor, scale_factor, bevy_window_entity
);
}
}
_ => {}
}
}
@@ -322,7 +354,8 @@ impl ApplicationHandler for AppHandler {
///
/// executor::run(app).expect("Failed to run executor");
/// ```
pub fn run(app: App) -> Result<(), Box<dyn std::error::Error>> {
pub fn run(mut app: App) -> Result<(), Box<dyn std::error::Error>> {
// Create event loop (using default type for now, WakeUp will be added when implementing battery mode)
let event_loop = EventLoop::new()?;
// TODO(@siennathesane): Add battery power detection and adaptive frame/tick rate limiting

View File

@@ -7,7 +7,7 @@ use bevy::prelude::*;
use bevy::input::keyboard::KeyboardInput;
use bevy::input::mouse::{MouseButtonInput, MouseWheel};
use bevy::window::CursorMoved;
use libmarathon::engine::{InputEvent, KeyCode as EngineKeyCode, MouseButton as EngineMouseButton, TouchPhase, Modifiers};
use libmarathon::engine::{InputEvent, InputEventBuffer, KeyCode as EngineKeyCode, MouseButton as EngineMouseButton, TouchPhase, Modifiers};
/// Convert Bevy's Vec2 to glam::Vec2
///
@@ -100,19 +100,6 @@ impl Plugin for DesktopInputBridgePlugin {
}
}
/// Buffer for InputEvents collected this frame
#[derive(Resource, Default)]
pub struct InputEventBuffer {
pub events: Vec<InputEvent>,
}
impl InputEventBuffer {
/// Get all events from this frame
pub fn events(&self) -> &[InputEvent] {
&self.events
}
}
/// Clear the buffer at the start of each frame
fn clear_buffer(mut buffer: ResMut<InputEventBuffer>) {
buffer.events.clear();
@@ -152,17 +139,21 @@ fn collect_mouse_buttons(
}
}
/// Collect mouse motion events (for drag tracking)
/// Collect mouse motion events (for hover and drag tracking)
fn collect_mouse_motion(
mut buffer: ResMut<InputEventBuffer>,
mut cursor_moved: MessageReader<CursorMoved>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
) {
// Only process if cursor actually moved
for event in cursor_moved.read() {
let cursor_pos = event.position;
// Generate drag events for currently pressed buttons
// ALWAYS send MouseMove for cursor tracking (hover, tooltips, etc.)
buffer.events.push(InputEvent::MouseMove {
pos: to_glam_vec2(cursor_pos),
});
// ALSO generate drag events for currently pressed buttons
if mouse_buttons.pressed(MouseButton::Left) {
buffer.events.push(InputEvent::Mouse {
pos: to_glam_vec2(cursor_pos),

View File

@@ -1,22 +1,5 @@
//! Input event buffer shared between executor and ECS
//! Input event buffer re-export
//!
//! InputEventBuffer is now defined in libmarathon::engine
use bevy::prelude::*;
use libmarathon::engine::InputEvent;
/// Input event buffer resource for Bevy ECS
#[derive(Resource, Default)]
pub struct InputEventBuffer {
pub events: Vec<InputEvent>,
}
impl InputEventBuffer {
/// Get all events from this frame
pub fn events(&self) -> &[InputEvent] {
&self.events
}
/// Clear the buffer
pub fn clear(&mut self) {
self.events.clear();
}
}
pub use libmarathon::engine::InputEventBuffer;

View File

@@ -5,7 +5,7 @@
use bevy::prelude::*;
use libmarathon::{
engine::{GameAction, InputController},
networking::{EntityLockRegistry, NetworkedEntity, NodeVectorClock},
networking::{EntityLockRegistry, NetworkedEntity, NetworkedSelection, NodeVectorClock},
};
use super::event_buffer::InputEventBuffer;
@@ -45,15 +45,17 @@ fn to_bevy_vec2(v: glam::Vec2) -> bevy::math::Vec2 {
fn handle_game_actions(
input_buffer: Res<InputEventBuffer>,
mut controller_res: ResMut<InputControllerResource>,
lock_registry: Res<EntityLockRegistry>,
mut lock_registry: ResMut<EntityLockRegistry>,
node_clock: Res<NodeVectorClock>,
mut cube_query: Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
mut cube_query: Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
camera_query: Query<(&Camera, &GlobalTransform)>,
window_query: Query<&Window>,
) {
let node_id = node_clock.node_id;
// Process all input events through the controller to get game actions
let mut all_actions = Vec::new();
for event in input_buffer.events() {
for event in input_buffer.events.iter() {
let actions = controller_res.controller.process_event(event);
all_actions.extend(actions);
}
@@ -61,6 +63,17 @@ fn handle_game_actions(
// Apply game actions to entities
for action in all_actions {
match action {
GameAction::SelectEntity { position } => {
apply_select_entity(
position,
&mut lock_registry,
node_id,
&mut cube_query,
&camera_query,
&window_query,
);
}
GameAction::MoveEntity { delta } => {
apply_move_entity(delta, &lock_registry, node_id, &mut cube_query);
}
@@ -84,17 +97,83 @@ fn handle_game_actions(
}
}
/// Apply SelectEntity action - raycast to find clicked cube and select it
fn apply_select_entity(
position: glam::Vec2,
lock_registry: &mut EntityLockRegistry,
node_id: uuid::Uuid,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
camera_query: &Query<(&Camera, &GlobalTransform)>,
window_query: &Query<&Window>,
) {
// Get the camera and window
let Ok((camera, camera_transform)) = camera_query.single() else {
return;
};
let Ok(window) = window_query.single() else {
return;
};
// Convert screen position to world ray
let Some(ray) = screen_to_world_ray(position, camera, camera_transform, window) else {
return;
};
// Find the closest cube hit by the ray
let mut closest_hit: Option<(uuid::Uuid, f32)> = None;
for (networked, transform, _) in cube_query.iter() {
// Test ray against cube AABB (1x1x1 cube)
if let Some(distance) = ray_aabb_intersection(
ray.origin,
ray.direction,
transform.translation,
Vec3::splat(0.5), // Half extents for 1x1x1 cube
) {
if closest_hit.map_or(true, |(_, d)| distance < d) {
closest_hit = Some((networked.network_id, distance));
}
}
}
// If we hit a cube, clear all selections and select this one
if let Some((hit_entity_id, _)) = closest_hit {
// Clear all previous selections and locks
for (networked, _, mut selection) in cube_query.iter_mut() {
selection.clear();
lock_registry.release(networked.network_id, node_id);
}
// Select and lock the clicked cube
for (networked, _, mut selection) in cube_query.iter_mut() {
if networked.network_id == hit_entity_id {
selection.add(hit_entity_id);
let _ = lock_registry.try_acquire(hit_entity_id, node_id);
info!("Selected cube {}", hit_entity_id);
break;
}
}
} else {
// Clicked on empty space - deselect all
for (networked, _, mut selection) in cube_query.iter_mut() {
selection.clear();
lock_registry.release(networked.network_id, node_id);
}
info!("Deselected all cubes");
}
}
/// Apply MoveEntity action to locked cubes
fn apply_move_entity(
delta: glam::Vec2,
lock_registry: &EntityLockRegistry,
node_id: uuid::Uuid,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
) {
let bevy_delta = to_bevy_vec2(delta);
let sensitivity = 0.01; // Scale factor
for (networked, mut transform) in cube_query.iter_mut() {
for (networked, mut transform, _) in cube_query.iter_mut() {
if lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
transform.translation.x += bevy_delta.x * sensitivity;
transform.translation.y -= bevy_delta.y * sensitivity; // Invert Y for screen coords
@@ -107,12 +186,12 @@ fn apply_rotate_entity(
delta: glam::Vec2,
lock_registry: &EntityLockRegistry,
node_id: uuid::Uuid,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
) {
let bevy_delta = to_bevy_vec2(delta);
let sensitivity = 0.01;
for (networked, mut transform) in cube_query.iter_mut() {
for (networked, mut transform, _) in cube_query.iter_mut() {
if lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
let rotation_x = Quat::from_rotation_y(bevy_delta.x * sensitivity);
let rotation_y = Quat::from_rotation_x(-bevy_delta.y * sensitivity);
@@ -126,11 +205,11 @@ fn apply_move_depth(
delta: f32,
lock_registry: &EntityLockRegistry,
node_id: uuid::Uuid,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
) {
let sensitivity = 0.1;
for (networked, mut transform) in cube_query.iter_mut() {
for (networked, mut transform, _) in cube_query.iter_mut() {
if lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
transform.translation.z += delta * sensitivity;
}
@@ -141,12 +220,99 @@ fn apply_move_depth(
fn apply_reset_entity(
lock_registry: &EntityLockRegistry,
node_id: uuid::Uuid,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
cube_query: &mut Query<(&NetworkedEntity, &mut Transform, &mut NetworkedSelection), With<crate::cube::CubeMarker>>,
) {
for (networked, mut transform) in cube_query.iter_mut() {
for (networked, mut transform, _) in cube_query.iter_mut() {
if lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
transform.translation = Vec3::ZERO;
transform.rotation = Quat::IDENTITY;
}
}
}
/// A 3D ray for raycasting
struct Ray {
origin: Vec3,
direction: Vec3,
}
/// Convert screen coordinates to a world-space ray from the camera
fn screen_to_world_ray(
screen_pos: glam::Vec2,
camera: &Camera,
camera_transform: &GlobalTransform,
window: &Window,
) -> Option<Ray> {
// Convert screen position to viewport position (0..1 range)
let viewport_pos = Vec2::new(screen_pos.x, screen_pos.y);
// Use Bevy's viewport_to_world method
let ray_bevy = camera.viewport_to_world(camera_transform, viewport_pos).ok()?;
Some(Ray {
origin: ray_bevy.origin,
direction: *ray_bevy.direction,
})
}
/// Test ray-AABB (axis-aligned bounding box) intersection
///
/// Returns the distance along the ray if there's an intersection, None otherwise.
fn ray_aabb_intersection(
ray_origin: Vec3,
ray_direction: Vec3,
aabb_center: Vec3,
aabb_half_extents: Vec3,
) -> Option<f32> {
// Calculate AABB min and max
let aabb_min = aabb_center - aabb_half_extents;
let aabb_max = aabb_center + aabb_half_extents;
// Slab method for ray-AABB intersection
let mut tmin = f32::NEG_INFINITY;
let mut tmax = f32::INFINITY;
for i in 0..3 {
let origin_component = ray_origin[i];
let dir_component = ray_direction[i];
let min_component = aabb_min[i];
let max_component = aabb_max[i];
if dir_component.abs() < f32::EPSILON {
// Ray is parallel to slab, check if origin is within slab
if origin_component < min_component || origin_component > max_component {
return None;
}
} else {
// Compute intersection t values for near and far plane
let inv_dir = 1.0 / dir_component;
let mut t1 = (min_component - origin_component) * inv_dir;
let mut t2 = (max_component - origin_component) * inv_dir;
// Ensure t1 is the near intersection
if t1 > t2 {
std::mem::swap(&mut t1, &mut t2);
}
// Update tmin and tmax
tmin = tmin.max(t1);
tmax = tmax.min(t2);
// Check for intersection failure
if tmin > tmax {
return None;
}
}
}
// If tmin is negative, the ray origin is inside the AABB
// Return tmax in that case, otherwise return tmin
if tmin < 0.0 {
if tmax < 0.0 {
return None; // AABB is behind the ray
}
Some(tmax)
} else {
Some(tmin)
}
}

View File

@@ -3,10 +3,9 @@
//! This demonstrates real-time CRDT synchronization with Apple Pencil input.
use bevy::prelude::*;
// use bevy_egui::EguiPlugin; // Disabled - needs WinitPlugin which we own directly
use libmarathon::{
engine::{EngineBridge, EngineCore},
persistence::{PersistenceConfig, PersistencePlugin},
persistence::PersistenceConfig,
};
use std::path::PathBuf;
@@ -21,14 +20,13 @@ mod session;
mod session_ui;
mod setup;
use debug_ui::DebugUiPlugin;
use engine_bridge::EngineBridgePlugin;
mod input;
use camera::*;
use cube::*;
use debug_ui::*;
use input::*;
use rendering::*;
use selection::*;
use session::*;
@@ -80,9 +78,8 @@ fn main() {
.disable::<bevy::gilrs::GilrsPlugin>() // We handle gamepad input ourselves
);
// app.add_plugins(EguiPlugin::default()); // Disabled - needs WinitPlugin
app.add_plugins(EngineBridgePlugin);
app.add_plugins(PersistencePlugin::with_config(
// Marathon core plugins (networking, debug UI, persistence)
app.add_plugins(libmarathon::MarathonPlugin::new(
db_path,
PersistenceConfig {
flush_interval_secs: 2,
@@ -91,13 +88,16 @@ fn main() {
..Default::default()
},
));
// App-specific bridge for polling engine events
app.add_plugins(EngineBridgePlugin);
app.add_plugins(CameraPlugin);
app.add_plugins(RenderingPlugin);
app.add_plugins(input::InputHandlerPlugin);
app.add_plugins(CubePlugin);
app.add_plugins(SelectionPlugin);
// app.add_plugins(DebugUiPlugin); // Disabled - uses egui
// app.add_plugins(SessionUiPlugin); // Disabled - uses egui
app.add_plugins(DebugUiPlugin);
app.add_plugins(SessionUiPlugin);
app.add_systems(Startup, initialize_offline_resources);
// Run with our executor (unbounded event loop)

View File

@@ -4,8 +4,8 @@
//! and shows connected peer information.
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPrimaryContextPass};
use libmarathon::{
debug_ui::{egui, EguiContexts, EguiPrimaryContextPass},
engine::{EngineBridge, EngineCommand},
networking::{CurrentSession, NodeVectorClock, SessionId},
};