lemur/src/app.rs

544 lines
18 KiB
Rust
Raw Normal View History

use std::{collections::HashSet, num::NonZero, sync::Arc, thread, time::Duration};
2024-11-28 15:27:18 +00:00
use egui::{
ahash::{HashMap, HashMapExt},
2024-12-08 21:00:55 +00:00
Context, FontData, FontDefinitions, FontFamily, IconData, TextWrapMode, ViewportBuilder,
ViewportCommand, ViewportId, ViewportInfo,
2024-11-28 15:27:18 +00:00
};
use gilrs::{EventType, Gilrs};
2025-01-15 04:33:30 +00:00
use tracing::{error, warn};
2024-11-28 15:27:18 +00:00
use winit::{
application::ApplicationHandler,
2024-11-29 23:54:26 +00:00
event::WindowEvent,
2024-11-28 15:27:18 +00:00
event_loop::{ActiveEventLoop, EventLoopProxy},
window::Window,
};
use crate::{
controller::ControllerManager,
emulator::{EmulatorClient, EmulatorCommand, SimId},
2024-11-28 15:27:18 +00:00
input::MappingProvider,
2025-02-15 17:56:58 +00:00
memory::MemoryClient,
2024-12-12 04:44:14 +00:00
persistence::Persistence,
vram::VramProcessor,
2025-02-02 20:16:11 +00:00
window::{
2025-02-02 23:33:59 +00:00
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, GameWindow, GdbServerWindow,
2025-02-13 04:59:48 +00:00
InputWindow, ObjectWindow,
2025-02-02 20:16:11 +00:00
},
2024-11-28 15:27:18 +00:00
};
2024-12-08 21:00:55 +00:00
fn load_icon() -> anyhow::Result<IconData> {
let bytes = include_bytes!("../assets/lemur-256x256.png");
let img = image::load_from_memory_with_format(bytes, image::ImageFormat::Png)?;
let rgba = img.into_rgba8();
Ok(IconData {
width: rgba.width(),
height: rgba.height(),
rgba: rgba.into_vec(),
})
}
2024-11-28 15:27:18 +00:00
pub struct Application {
2024-12-08 21:00:55 +00:00
icon: Option<Arc<IconData>>,
wgpu: WgpuState,
2024-11-28 15:27:18 +00:00
client: EmulatorClient,
proxy: EventLoopProxy<UserEvent>,
mappings: MappingProvider,
controllers: ControllerManager,
2025-02-15 17:56:58 +00:00
memory: Arc<MemoryClient>,
vram: VramProcessor,
2024-12-15 04:08:44 +00:00
persistence: Persistence,
2024-11-28 15:27:18 +00:00
viewports: HashMap<ViewportId, Viewport>,
focused: Option<ViewportId>,
init_debug_port: Option<u16>,
2024-11-28 15:27:18 +00:00
}
impl Application {
pub fn new(
client: EmulatorClient,
proxy: EventLoopProxy<UserEvent>,
debug_port: Option<u16>,
) -> Self {
let wgpu = WgpuState::new();
2024-12-08 21:00:55 +00:00
let icon = load_icon().ok().map(Arc::new);
2024-12-12 04:44:14 +00:00
let persistence = Persistence::new();
2024-12-15 04:08:44 +00:00
let mappings = MappingProvider::new(persistence.clone());
2024-11-28 15:27:18 +00:00
let controllers = ControllerManager::new(client.clone(), &mappings);
2025-02-15 17:56:58 +00:00
let memory = Arc::new(MemoryClient::new(client.clone()));
let vram = VramProcessor::new();
2024-11-28 15:27:18 +00:00
{
let mappings = mappings.clone();
let proxy = proxy.clone();
thread::spawn(|| process_gamepad_input(mappings, proxy));
}
Self {
2024-12-08 21:00:55 +00:00
icon,
wgpu,
2024-11-28 15:27:18 +00:00
client,
proxy,
mappings,
memory,
vram,
2024-11-28 15:27:18 +00:00
controllers,
2024-12-15 04:08:44 +00:00
persistence,
2024-11-28 15:27:18 +00:00
viewports: HashMap::new(),
focused: None,
init_debug_port: debug_port,
2024-11-28 15:27:18 +00:00
}
}
fn open(&mut self, event_loop: &ActiveEventLoop, window: Box<dyn AppWindow>) {
let viewport_id = window.viewport_id();
if self.viewports.contains_key(&viewport_id) {
return;
}
2024-12-08 21:00:55 +00:00
self.viewports.insert(
viewport_id,
Viewport::new(event_loop, &self.wgpu, self.icon.clone(), window),
2024-12-08 21:00:55 +00:00
);
2024-11-28 15:27:18 +00:00
}
}
impl ApplicationHandler<UserEvent> for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
2025-01-05 16:24:59 +00:00
if let Some(port) = self.init_debug_port {
let mut server =
GdbServerWindow::new(SimId::Player1, self.client.clone(), self.proxy.clone());
server.launch(port);
self.open(event_loop, Box::new(server));
}
2024-12-15 04:08:44 +00:00
let app = GameWindow::new(
self.client.clone(),
self.proxy.clone(),
self.persistence.clone(),
SimId::Player1,
);
self.open(event_loop, Box::new(app));
2024-11-28 15:27:18 +00:00
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
let Some(viewport) = self
.viewports
.values_mut()
.find(|v| v.window.id() == window_id)
else {
return;
};
let viewport_id = viewport.id();
2024-11-28 17:39:08 +00:00
let mut queue_redraw = false;
let mut inactive_viewports = HashSet::new();
let (consumed, action) = viewport.on_window_event(&event);
if !consumed {
match event {
WindowEvent::KeyboardInput { event, .. } => {
if !viewport.app.handle_key_event(&event) {
self.controllers.handle_key_event(&event);
}
}
WindowEvent::Focused(new_focused) => {
self.focused = new_focused.then_some(viewport_id);
}
_ => {}
}
}
match action {
2024-11-28 15:27:18 +00:00
Some(Action::Redraw) => {
for viewport in self.viewports.values_mut() {
2024-11-28 17:39:08 +00:00
match viewport.redraw(event_loop) {
Some(Action::Redraw) => {
queue_redraw = true;
}
Some(Action::Close) => {
inactive_viewports.insert(viewport.id());
}
None => {}
}
2024-11-28 15:27:18 +00:00
}
}
Some(Action::Close) => {
2024-11-28 17:39:08 +00:00
inactive_viewports.insert(viewport_id);
2024-11-28 15:27:18 +00:00
}
None => {}
}
2024-11-28 17:39:08 +00:00
self.viewports
.retain(|k, _| !inactive_viewports.contains(k));
match self.viewports.get(&ViewportId::ROOT) {
Some(viewport) => {
if queue_redraw {
viewport.window.request_redraw();
}
}
None => event_loop.exit(),
}
2024-11-28 15:27:18 +00:00
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
if let winit::event::DeviceEvent::MouseMotion { delta } = event {
let Some(viewport) = self
.focused
.as_ref()
.and_then(|id| self.viewports.get_mut(id))
else {
return;
};
viewport.state.on_mouse_motion(delta);
}
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
match event {
2024-11-29 23:54:26 +00:00
UserEvent::GamepadEvent(event) => {
if let Some(viewport) = self
2024-11-29 23:54:26 +00:00
.focused
.as_ref()
.and_then(|id| self.viewports.get_mut(id))
{
if viewport.app.handle_gamepad_event(&event) {
return;
}
}
self.controllers.handle_gamepad_event(&event);
2024-11-29 23:54:26 +00:00
}
2024-12-15 05:00:22 +00:00
UserEvent::OpenAbout => {
let about = AboutWindow;
self.open(event_loop, Box::new(about));
}
2025-02-02 20:16:11 +00:00
UserEvent::OpenCharacterData(sim_id) => {
2025-02-15 17:56:58 +00:00
let vram = CharacterDataWindow::new(sim_id, &self.memory, &mut self.vram);
2025-02-02 20:16:11 +00:00
self.open(event_loop, Box::new(vram));
}
2025-02-02 23:33:59 +00:00
UserEvent::OpenBgMap(sim_id) => {
2025-02-15 17:56:58 +00:00
let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.vram);
2025-02-02 23:33:59 +00:00
self.open(event_loop, Box::new(bgmap));
}
2025-02-13 04:59:48 +00:00
UserEvent::OpenObjects(sim_id) => {
2025-02-15 17:56:58 +00:00
let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.vram);
2025-02-13 04:59:48 +00:00
self.open(event_loop, Box::new(objects));
}
2024-12-31 02:55:30 +00:00
UserEvent::OpenDebugger(sim_id) => {
2025-01-05 16:24:59 +00:00
let debugger =
GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone());
2024-12-31 02:55:30 +00:00
self.open(event_loop, Box::new(debugger));
}
2024-11-28 15:27:18 +00:00
UserEvent::OpenInput => {
let input = InputWindow::new(self.mappings.clone());
self.open(event_loop, Box::new(input));
}
UserEvent::OpenPlayer2 => {
2024-12-15 04:08:44 +00:00
let p2 = GameWindow::new(
self.client.clone(),
self.proxy.clone(),
self.persistence.clone(),
SimId::Player2,
);
2024-11-28 15:27:18 +00:00
self.open(event_loop, Box::new(p2));
}
2025-01-10 04:00:46 +00:00
UserEvent::Quit(sim_id) => {
self.viewports
.retain(|_, viewport| viewport.app.sim_id() != sim_id);
if !self.viewports.contains_key(&ViewportId::ROOT) {
event_loop.exit();
}
2025-01-05 16:24:59 +00:00
}
2024-11-28 15:27:18 +00:00
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(viewport) = self.viewports.get(&ViewportId::ROOT) {
viewport.window.request_redraw();
}
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
let (sender, receiver) = oneshot::channel();
if self.client.send_command(EmulatorCommand::Exit(sender)) {
2025-01-15 04:33:30 +00:00
if let Err(error) = receiver.recv_timeout(Duration::from_secs(5)) {
error!(%error, "could not gracefully exit.");
}
}
}
2024-11-28 15:27:18 +00:00
}
struct WgpuState {
instance: Arc<wgpu::Instance>,
adapter: Arc<wgpu::Adapter>,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
}
impl WgpuState {
fn new() -> Self {
2025-02-15 23:22:21 +00:00
#[allow(unused_variables)]
let egui_wgpu::WgpuConfiguration {
wgpu_setup:
egui_wgpu::WgpuSetup::CreateNew {
supported_backends,
device_descriptor,
..
},
..
} = egui_wgpu::WgpuConfiguration::default()
else {
panic!("required fields not found")
};
#[cfg(windows)]
let supported_backends = wgpu::util::backend_bits_from_env()
.unwrap_or((wgpu::Backends::PRIMARY | wgpu::Backends::GL) - wgpu::Backends::VULKAN);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: supported_backends,
..wgpu::InstanceDescriptor::default()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
}))
.expect("could not create adapter");
let trace_path = std::env::var("WGPU_TRACE");
let (device, queue) = pollster::block_on(adapter.request_device(
&(*device_descriptor)(&adapter),
trace_path.ok().as_ref().map(std::path::Path::new),
))
.expect("could not request device");
Self {
instance: Arc::new(instance),
adapter: Arc::new(adapter),
device: Arc::new(device),
queue: Arc::new(queue),
}
}
}
2024-11-28 15:27:18 +00:00
struct Viewport {
painter: egui_wgpu::winit::Painter,
ctx: Context,
info: ViewportInfo,
commands: Vec<ViewportCommand>,
builder: ViewportBuilder,
window: Arc<Window>,
state: egui_winit::State,
app: Box<dyn AppWindow>,
}
impl Viewport {
2024-12-08 21:00:55 +00:00
pub fn new(
event_loop: &ActiveEventLoop,
wgpu: &WgpuState,
2024-12-08 21:00:55 +00:00
icon: Option<Arc<IconData>>,
mut app: Box<dyn AppWindow>,
) -> Self {
2024-11-28 15:27:18 +00:00
let ctx = Context::default();
2024-12-01 04:14:01 +00:00
let mut fonts = FontDefinitions::default();
2024-11-28 17:39:08 +00:00
fonts.font_data.insert(
"Selawik".into(),
2024-12-26 04:29:27 +00:00
Arc::new(FontData::from_static(include_bytes!(
"../assets/selawik.ttf"
))),
2024-11-28 17:39:08 +00:00
);
fonts
.families
.get_mut(&FontFamily::Proportional)
.unwrap()
.insert(0, "Selawik".into());
ctx.set_fonts(fonts);
2024-11-28 15:27:18 +00:00
ctx.style_mut(|s| {
s.wrap_mode = Some(TextWrapMode::Extend);
s.visuals.menu_rounding = Default::default();
});
2024-12-15 05:00:22 +00:00
egui_extras::install_image_loaders(&ctx);
2024-11-28 15:27:18 +00:00
let wgpu_config = egui_wgpu::WgpuConfiguration {
2025-01-07 18:06:37 +00:00
present_mode: wgpu::PresentMode::AutoNoVsync,
wgpu_setup: egui_wgpu::WgpuSetup::Existing {
instance: wgpu.instance.clone(),
adapter: wgpu.adapter.clone(),
device: wgpu.device.clone(),
queue: wgpu.queue.clone(),
},
2025-01-07 18:06:37 +00:00
..egui_wgpu::WgpuConfiguration::default()
};
let mut painter =
egui_wgpu::winit::Painter::new(ctx.clone(), wgpu_config, 1, None, false, true);
2024-12-26 04:29:27 +00:00
2024-11-28 15:27:18 +00:00
let mut info = ViewportInfo::default();
2024-12-08 21:00:55 +00:00
let mut builder = app.initial_viewport();
if let Some(icon) = icon {
builder = builder.with_icon(icon);
}
2024-11-28 15:27:18 +00:00
let (window, state) = create_window_and_state(&ctx, event_loop, &builder, &mut painter);
egui_winit::update_viewport_info(&mut info, &ctx, &window, true);
app.on_init(&ctx, painter.render_state().as_ref().unwrap());
2024-11-28 15:27:18 +00:00
Self {
painter,
ctx,
info,
commands: vec![],
builder,
window,
state,
app,
}
}
pub fn id(&self) -> ViewportId {
self.app.viewport_id()
}
pub fn on_window_event(&mut self, event: &WindowEvent) -> (bool, Option<Action>) {
let response = self.state.on_window_event(&self.window, event);
2024-11-28 15:27:18 +00:00
egui_winit::update_viewport_info(
&mut self.info,
self.state.egui_ctx(),
&self.window,
false,
);
let action = match event {
2024-11-28 15:27:18 +00:00
WindowEvent::RedrawRequested => Some(Action::Redraw),
WindowEvent::CloseRequested => Some(Action::Close),
WindowEvent::Resized(size) => {
if let (Some(width), Some(height)) =
2024-11-28 15:27:18 +00:00
(NonZero::new(size.width), NonZero::new(size.height))
{
self.painter
.on_window_resized(ViewportId::ROOT, width, height);
}
2024-11-28 15:27:18 +00:00
None
}
_ if response.repaint => Some(Action::Redraw),
_ => None,
};
(response.consumed, action)
2024-11-28 15:27:18 +00:00
}
fn redraw(&mut self, event_loop: &ActiveEventLoop) -> Option<Action> {
let mut input = self.state.take_egui_input(&self.window);
input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect();
let mut output = self.ctx.run(input, |ctx| {
self.app.show(ctx);
});
let clipped_primitives = self.ctx.tessellate(output.shapes, output.pixels_per_point);
self.painter.paint_and_update_textures(
ViewportId::ROOT,
output.pixels_per_point,
[0.0, 0.0, 0.0, 0.0],
&clipped_primitives,
&output.textures_delta,
2024-12-26 04:29:27 +00:00
vec![],
2024-11-28 15:27:18 +00:00
);
self.state
.handle_platform_output(&self.window, output.platform_output);
2024-11-28 17:39:08 +00:00
let Some(mut viewport_output) = output.viewport_output.remove(&ViewportId::ROOT) else {
2024-11-28 15:27:18 +00:00
return Some(Action::Close);
};
let (mut deferred_commands, recreate) = self.builder.patch(viewport_output.builder);
if recreate {
let (window, state) =
create_window_and_state(&self.ctx, event_loop, &self.builder, &mut self.painter);
egui_winit::update_viewport_info(&mut self.info, &self.ctx, &window, true);
self.window = window;
self.state = state;
}
self.commands.append(&mut deferred_commands);
2024-11-28 17:39:08 +00:00
self.commands.append(&mut viewport_output.commands);
2024-11-28 15:27:18 +00:00
egui_winit::process_viewport_commands(
&self.ctx,
&mut self.info,
std::mem::take(&mut self.commands),
&self.window,
&mut HashSet::default(),
);
if self.info.close_requested() {
Some(Action::Close)
} else {
Some(Action::Redraw)
}
}
}
impl Drop for Viewport {
fn drop(&mut self) {
self.app.on_destroy();
}
}
#[derive(Debug)]
pub enum UserEvent {
GamepadEvent(gilrs::Event),
2024-12-15 05:00:22 +00:00
OpenAbout,
2025-02-02 20:16:11 +00:00
OpenCharacterData(SimId),
2025-02-02 23:33:59 +00:00
OpenBgMap(SimId),
2025-02-13 04:59:48 +00:00
OpenObjects(SimId),
2024-12-31 02:55:30 +00:00
OpenDebugger(SimId),
2024-11-28 15:27:18 +00:00
OpenInput,
OpenPlayer2,
2025-01-10 04:00:46 +00:00
Quit(SimId),
2024-11-28 15:27:18 +00:00
}
pub enum Action {
Redraw,
Close,
}
fn create_window_and_state(
ctx: &Context,
event_loop: &ActiveEventLoop,
builder: &ViewportBuilder,
painter: &mut egui_wgpu::winit::Painter,
) -> (Arc<Window>, egui_winit::State) {
pollster::block_on(painter.set_window(ViewportId::ROOT, None)).unwrap();
let window = Arc::new(egui_winit::create_window(ctx, event_loop, builder).unwrap());
pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone()))).unwrap();
let state = egui_winit::State::new(
ctx.clone(),
ViewportId::ROOT,
event_loop,
Some(window.scale_factor() as f32),
event_loop.system_theme(),
painter.max_texture_side(),
);
(window, state)
}
fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy<UserEvent>) {
2025-01-15 04:33:30 +00:00
let mut gilrs = match Gilrs::new() {
Ok(gilrs) => gilrs,
Err(error) => {
warn!(%error, "could not connect gamepad listener");
return;
}
2024-11-28 15:27:18 +00:00
};
while let Some(event) = gilrs.next_event_blocking(None) {
if event.event == EventType::Connected {
let Some(gamepad) = gilrs.connected_gamepad(event.id) else {
continue;
};
mappings.handle_gamepad_connect(&gamepad);
}
if event.event == EventType::Disconnected {
mappings.handle_gamepad_disconnect(event.id);
2024-11-28 15:27:18 +00:00
}
if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() {
// main thread has closed! we done
return;
}
}
}