Implement multiplayer #2
|
@ -0,0 +1,329 @@
|
||||||
|
use std::{collections::HashSet, num::NonZero, sync::Arc, thread};
|
||||||
|
|
||||||
|
use egui::{
|
||||||
|
ahash::{HashMap, HashMapExt},
|
||||||
|
Context, TextWrapMode, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo,
|
||||||
|
};
|
||||||
|
use gilrs::{EventType, Gilrs};
|
||||||
|
use winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
event::{KeyEvent, WindowEvent},
|
||||||
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||||
|
window::Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
controller::ControllerManager,
|
||||||
|
emulator::{EmulatorClient, SimId},
|
||||||
|
input::MappingProvider,
|
||||||
|
window::{AppWindow, GameWindow, InputWindow},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Application {
|
||||||
|
client: EmulatorClient,
|
||||||
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
|
mappings: MappingProvider,
|
||||||
|
controllers: ControllerManager,
|
||||||
|
viewports: HashMap<ViewportId, Viewport>,
|
||||||
|
focused: Option<ViewportId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application {
|
||||||
|
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
||||||
|
let mappings = MappingProvider::new();
|
||||||
|
let controllers = ControllerManager::new(client.clone(), &mappings);
|
||||||
|
{
|
||||||
|
let mappings = mappings.clone();
|
||||||
|
let proxy = proxy.clone();
|
||||||
|
thread::spawn(|| process_gamepad_input(mappings, proxy));
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
proxy,
|
||||||
|
mappings,
|
||||||
|
controllers,
|
||||||
|
viewports: HashMap::new(),
|
||||||
|
focused: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
self.viewports
|
||||||
|
.insert(viewport_id, Viewport::new(event_loop, window));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler<UserEvent> for Application {
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
let app = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player1);
|
||||||
|
let wrapper = Viewport::new(event_loop, Box::new(app));
|
||||||
|
self.focused = Some(wrapper.id());
|
||||||
|
self.viewports.insert(wrapper.id(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
match &event {
|
||||||
|
WindowEvent::KeyboardInput { event, .. } => {
|
||||||
|
self.controllers.handle_key_event(event);
|
||||||
|
viewport.handle_key_event(event);
|
||||||
|
}
|
||||||
|
WindowEvent::Focused(new_focused) => {
|
||||||
|
self.focused = new_focused.then_some(viewport_id);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match viewport.on_window_event(event) {
|
||||||
|
Some(Action::Redraw) => {
|
||||||
|
for viewport in self.viewports.values_mut() {
|
||||||
|
viewport.redraw(event_loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Action::Close) => {
|
||||||
|
self.viewports.remove(&viewport_id);
|
||||||
|
if viewport_id == ViewportId::ROOT {
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
UserEvent::GamepadEvent(event) => self.controllers.handle_gamepad_event(&event),
|
||||||
|
UserEvent::OpenInput => {
|
||||||
|
let input = InputWindow::new(self.mappings.clone());
|
||||||
|
self.open(event_loop, Box::new(input));
|
||||||
|
}
|
||||||
|
UserEvent::OpenPlayer2 => {
|
||||||
|
let p2 = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player2);
|
||||||
|
self.open(event_loop, Box::new(p2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
|
||||||
|
if let Some(viewport) = self.viewports.get(&ViewportId::ROOT) {
|
||||||
|
viewport.window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
pub fn new(event_loop: &ActiveEventLoop, mut app: Box<dyn AppWindow>) -> Self {
|
||||||
|
let mut painter = egui_wgpu::winit::Painter::new(
|
||||||
|
egui_wgpu::WgpuConfiguration::default(),
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ctx = Context::default();
|
||||||
|
ctx.style_mut(|s| {
|
||||||
|
s.wrap_mode = Some(TextWrapMode::Extend);
|
||||||
|
s.visuals.menu_rounding = Default::default();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut info = ViewportInfo::default();
|
||||||
|
let builder = app.initial_viewport();
|
||||||
|
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(painter.render_state().as_ref().unwrap());
|
||||||
|
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) -> Option<Action> {
|
||||||
|
let response = self.state.on_window_event(&self.window, &event);
|
||||||
|
egui_winit::update_viewport_info(
|
||||||
|
&mut self.info,
|
||||||
|
self.state.egui_ctx(),
|
||||||
|
&self.window,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
WindowEvent::RedrawRequested => Some(Action::Redraw),
|
||||||
|
WindowEvent::CloseRequested => Some(Action::Close),
|
||||||
|
WindowEvent::Resized(size) => {
|
||||||
|
let (Some(width), Some(height)) =
|
||||||
|
(NonZero::new(size.width), NonZero::new(size.height))
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
self.painter
|
||||||
|
.on_window_resized(ViewportId::ROOT, width, height);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ if response.repaint => Some(Action::Redraw),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_key_event(&mut self, event: &KeyEvent) {
|
||||||
|
self.app.handle_key_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.state
|
||||||
|
.handle_platform_output(&self.window, output.platform_output);
|
||||||
|
|
||||||
|
let Some(viewport_output) = output.viewport_output.remove(&ViewportId::ROOT) else {
|
||||||
|
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);
|
||||||
|
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),
|
||||||
|
OpenInput,
|
||||||
|
OpenPlayer2,
|
||||||
|
}
|
||||||
|
|
||||||
|
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>) {
|
||||||
|
let Ok(mut gilrs) = Gilrs::new() else {
|
||||||
|
eprintln!("could not connect gamepad listener");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
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.map_gamepad(SimId::Player1, &gamepad);
|
||||||
|
}
|
||||||
|
if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() {
|
||||||
|
// main thread has closed! we done
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,315 +0,0 @@
|
||||||
use std::{
|
|
||||||
collections::{hash_map::Entry, HashMap, HashSet},
|
|
||||||
num::NonZero,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use winit::event_loop::EventLoopProxy;
|
|
||||||
|
|
||||||
use crate::{emulator::EmulatorClient, window::WindowManager};
|
|
||||||
|
|
||||||
pub struct Application {
|
|
||||||
client: EmulatorClient,
|
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
|
||||||
state: Option<AppState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application {
|
|
||||||
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
|
||||||
Self {
|
|
||||||
client,
|
|
||||||
proxy,
|
|
||||||
state: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl winit::application::ApplicationHandler<UserEvent> for Application {
|
|
||||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
|
||||||
self.state = Some(AppState::new(
|
|
||||||
self.client.clone(),
|
|
||||||
self.proxy.clone(),
|
|
||||||
event_loop,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
window_id: winit::window::WindowId,
|
|
||||||
event: winit::event::WindowEvent,
|
|
||||||
) {
|
|
||||||
self.state
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.window_event(event_loop, window_id, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
_event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
_device_id: winit::event::DeviceId,
|
|
||||||
event: winit::event::DeviceEvent,
|
|
||||||
) {
|
|
||||||
self.state.as_mut().unwrap().device_event(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) {
|
|
||||||
self.state.as_mut().unwrap().user_event(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppState {
|
|
||||||
painter: egui_wgpu::winit::Painter,
|
|
||||||
ctx: egui::Context,
|
|
||||||
viewports: HashMap<egui::ViewportId, ViewportState>,
|
|
||||||
viewports_by_window: HashMap<winit::window::WindowId, egui::ViewportId>,
|
|
||||||
focused: Option<egui::ViewportId>,
|
|
||||||
screen: WindowManager,
|
|
||||||
}
|
|
||||||
impl AppState {
|
|
||||||
fn new(
|
|
||||||
client: EmulatorClient,
|
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
) -> Self {
|
|
||||||
let mut painter = egui_wgpu::winit::Painter::new(
|
|
||||||
egui_wgpu::WgpuConfiguration::default(),
|
|
||||||
1,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
let ctx = egui::Context::default();
|
|
||||||
ctx.style_mut(|s| {
|
|
||||||
s.wrap_mode = Some(egui::TextWrapMode::Extend);
|
|
||||||
s.visuals.menu_rounding = Default::default();
|
|
||||||
});
|
|
||||||
ctx.set_embed_viewports(false);
|
|
||||||
{
|
|
||||||
let proxy = proxy.clone();
|
|
||||||
ctx.set_request_repaint_callback(move |info| {
|
|
||||||
proxy.send_event(UserEvent::RepaintRequested(info)).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut screen = WindowManager::new(client, proxy);
|
|
||||||
let root_viewport = ViewportState::new(
|
|
||||||
egui::ViewportId::ROOT,
|
|
||||||
&ctx,
|
|
||||||
event_loop,
|
|
||||||
screen.initial_viewport(),
|
|
||||||
&mut painter,
|
|
||||||
);
|
|
||||||
screen.init_renderer(painter.render_state().as_ref().unwrap());
|
|
||||||
|
|
||||||
let mut viewports_by_window = HashMap::new();
|
|
||||||
viewports_by_window.insert(root_viewport.window.id(), egui::ViewportId::ROOT);
|
|
||||||
let mut viewports = HashMap::new();
|
|
||||||
viewports.insert(egui::ViewportId::ROOT, root_viewport);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
painter,
|
|
||||||
ctx,
|
|
||||||
viewports,
|
|
||||||
viewports_by_window,
|
|
||||||
focused: Some(egui::ViewportId::ROOT),
|
|
||||||
screen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
window_id: winit::window::WindowId,
|
|
||||||
event: winit::event::WindowEvent,
|
|
||||||
) {
|
|
||||||
let Some(&viewport_id) = self.viewports_by_window.get(&window_id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(viewport) = self.viewports.get_mut(&viewport_id) else {
|
|
||||||
panic!("Unrecognized viewport");
|
|
||||||
};
|
|
||||||
viewport.on_window_event(&event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
winit::event::WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
self.screen.handle_key_event(event);
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::RedrawRequested => {
|
|
||||||
pollster::block_on(
|
|
||||||
self.painter
|
|
||||||
.set_window(viewport_id, Some(viewport.window.clone())),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let cb = viewport.viewport_ui_cb.clone();
|
|
||||||
let mut input = viewport.state.take_egui_input(&viewport.window);
|
|
||||||
input.viewports = self
|
|
||||||
.viewports
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (*k, v.info.clone()))
|
|
||||||
.collect();
|
|
||||||
let output = self.ctx.run(input, |ctx| {
|
|
||||||
if let Some(cb) = cb.as_deref() {
|
|
||||||
cb(ctx)
|
|
||||||
} else {
|
|
||||||
self.screen.show(ctx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let clipped_primitives =
|
|
||||||
self.ctx.tessellate(output.shapes, output.pixels_per_point);
|
|
||||||
self.painter.paint_and_update_textures(
|
|
||||||
viewport_id,
|
|
||||||
output.pixels_per_point,
|
|
||||||
[0.0, 0.0, 0.0, 0.0],
|
|
||||||
&clipped_primitives,
|
|
||||||
&output.textures_delta,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut live_viewports = HashSet::default();
|
|
||||||
for (id, output) in output.viewport_output {
|
|
||||||
live_viewports.insert(id);
|
|
||||||
let viewport = match self.viewports.entry(id) {
|
|
||||||
Entry::Occupied(e) => e.into_mut(),
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
let mut v = ViewportState::new(
|
|
||||||
id,
|
|
||||||
&self.ctx,
|
|
||||||
event_loop,
|
|
||||||
output.builder,
|
|
||||||
&mut self.painter,
|
|
||||||
);
|
|
||||||
v.viewport_ui_cb = output.viewport_ui_cb;
|
|
||||||
self.viewports_by_window.insert(v.window.id(), id);
|
|
||||||
e.insert(v)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
egui_winit::process_viewport_commands(
|
|
||||||
&self.ctx,
|
|
||||||
&mut viewport.info,
|
|
||||||
output.commands,
|
|
||||||
&viewport.window,
|
|
||||||
&mut HashSet::default(),
|
|
||||||
);
|
|
||||||
if viewport.info.close_requested() {
|
|
||||||
live_viewports.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.viewports.retain(|k, v| {
|
|
||||||
if live_viewports.contains(k) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
self.viewports_by_window.remove(&v.window.id());
|
|
||||||
false
|
|
||||||
});
|
|
||||||
self.painter.gc_viewports(&live_viewports);
|
|
||||||
if !self.viewports.contains_key(&egui::ViewportId::ROOT) {
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::Resized(size) => {
|
|
||||||
let (Some(width), Some(height)) =
|
|
||||||
(NonZero::new(size.width), NonZero::new(size.height))
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.painter.on_window_resized(viewport_id, width, height);
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::Focused(new_focused) => {
|
|
||||||
self.focused = new_focused.then_some(viewport_id);
|
|
||||||
}
|
|
||||||
winit::event::WindowEvent::CloseRequested => {
|
|
||||||
if viewport_id == egui::ViewportId::ROOT {
|
|
||||||
event_loop.exit();
|
|
||||||
} else if let Some(viewport) = self.viewports.get_mut(&viewport_id) {
|
|
||||||
viewport.info.events.push(egui::ViewportEvent::Close);
|
|
||||||
self.ctx.request_repaint_of(viewport_id);
|
|
||||||
self.ctx.request_repaint_of(egui::ViewportId::ROOT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(&mut self, 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: UserEvent) {
|
|
||||||
match event {
|
|
||||||
UserEvent::GamepadEvent(event) => self.screen.handle_gamepad_event(event),
|
|
||||||
UserEvent::RepaintRequested(info) => {
|
|
||||||
let Some(viewport) = self.viewports.get(&info.viewport_id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
viewport.window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ViewportState {
|
|
||||||
info: egui::ViewportInfo,
|
|
||||||
state: egui_winit::State,
|
|
||||||
viewport_ui_cb: Option<Arc<egui::DeferredViewportUiCallback>>,
|
|
||||||
window: Arc<winit::window::Window>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewportState {
|
|
||||||
fn new(
|
|
||||||
id: egui::ViewportId,
|
|
||||||
ctx: &egui::Context,
|
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
viewport: egui::ViewportBuilder,
|
|
||||||
painter: &mut egui_wgpu::winit::Painter,
|
|
||||||
) -> Self {
|
|
||||||
let mut info = egui::ViewportInfo::default();
|
|
||||||
let window = Arc::new(egui_winit::create_window(ctx, event_loop, &viewport).unwrap());
|
|
||||||
egui_winit::update_viewport_info(&mut info, ctx, &window, true);
|
|
||||||
|
|
||||||
pollster::block_on(painter.set_window(id, Some(window.clone()))).unwrap();
|
|
||||||
let state = egui_winit::State::new(
|
|
||||||
ctx.clone(),
|
|
||||||
id,
|
|
||||||
event_loop,
|
|
||||||
Some(window.scale_factor() as f32),
|
|
||||||
event_loop.system_theme(),
|
|
||||||
painter.max_texture_side(),
|
|
||||||
);
|
|
||||||
Self {
|
|
||||||
info,
|
|
||||||
state,
|
|
||||||
viewport_ui_cb: None,
|
|
||||||
window,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_window_event(&mut self, event: &winit::event::WindowEvent) {
|
|
||||||
let response = self.state.on_window_event(&self.window, event);
|
|
||||||
if response.repaint {
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
egui_winit::update_viewport_info(
|
|
||||||
&mut self.info,
|
|
||||||
self.state.egui_ctx(),
|
|
||||||
&self.window,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UserEvent {
|
|
||||||
GamepadEvent(gilrs::Event),
|
|
||||||
RepaintRequested(egui::RequestRepaintInfo),
|
|
||||||
}
|
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{path::PathBuf, process};
|
use std::{path::PathBuf, process};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use application::Application;
|
use app::Application;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use emulator::EmulatorBuilder;
|
use emulator::EmulatorBuilder;
|
||||||
use thread_priority::{ThreadBuilder, ThreadPriority};
|
use thread_priority::{ThreadBuilder, ThreadPriority};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
mod application;
|
mod app;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod controller;
|
mod controller;
|
||||||
mod emulator;
|
mod emulator;
|
||||||
|
|
152
src/window.rs
152
src/window.rs
|
@ -1,157 +1,21 @@
|
||||||
use std::{
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc, Mutex,
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use egui::{Context, ViewportBuilder, ViewportId};
|
use egui::{Context, ViewportBuilder, ViewportId};
|
||||||
use game::GameWindow;
|
pub use game::GameWindow;
|
||||||
use game_screen::GameScreen;
|
pub use input::InputWindow;
|
||||||
use gilrs::{EventType, Gilrs};
|
use winit::event::KeyEvent;
|
||||||
use input::InputWindow;
|
|
||||||
use winit::{event::KeyEvent, event_loop::EventLoopProxy};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
application::UserEvent,
|
|
||||||
controller::ControllerManager,
|
|
||||||
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
|
||||||
input::MappingProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
mod game_screen;
|
mod game_screen;
|
||||||
mod input;
|
mod input;
|
||||||
|
|
||||||
pub struct WindowManager {
|
pub trait AppWindow {
|
||||||
p1: GameWindow,
|
|
||||||
p2: Arc<Mutex<GameWindow>>,
|
|
||||||
client: EmulatorClient,
|
|
||||||
input: ChildWindow<InputWindow>,
|
|
||||||
controllers: ControllerManager,
|
|
||||||
}
|
|
||||||
impl WindowManager {
|
|
||||||
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
|
||||||
let mappings = MappingProvider::new();
|
|
||||||
let controllers = ControllerManager::new(client.clone(), &mappings);
|
|
||||||
{
|
|
||||||
let mappings = mappings.clone();
|
|
||||||
thread::spawn(|| process_gamepad_input(mappings, proxy));
|
|
||||||
}
|
|
||||||
let input = ChildWindow::new(InputWindow::new(mappings));
|
|
||||||
let p1 = GameWindow::new(client.clone(), SimId::Player1, input.open.clone());
|
|
||||||
let p2 = GameWindow::new(client.clone(), SimId::Player2, input.open.clone());
|
|
||||||
Self {
|
|
||||||
p1,
|
|
||||||
p2: Arc::new(Mutex::new(p2)),
|
|
||||||
client,
|
|
||||||
input,
|
|
||||||
controllers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) {
|
|
||||||
GameScreen::init_pipeline(render_state);
|
|
||||||
self.p2.lock().unwrap().init_renderer(render_state);
|
|
||||||
self.p1.init_renderer(render_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initial_viewport(&self) -> ViewportBuilder {
|
|
||||||
self.p1.initial_viewport()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(&mut self, ctx: &Context) {
|
|
||||||
self.p1.show(ctx);
|
|
||||||
self.input.show(ctx);
|
|
||||||
if self.client.has_player_2() {
|
|
||||||
let (viewport_id, viewport) = {
|
|
||||||
let p2 = self.p2.lock().unwrap();
|
|
||||||
(p2.viewport_id(), p2.initial_viewport())
|
|
||||||
};
|
|
||||||
let client = self.client.clone();
|
|
||||||
let p2 = self.p2.clone();
|
|
||||||
ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| {
|
|
||||||
p2.lock().unwrap().show(ctx);
|
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
|
||||||
client.send_command(EmulatorCommand::StopSecondSim);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_key_event(&mut self, event: winit::event::KeyEvent) {
|
|
||||||
self.controllers.handle_key_event(&event);
|
|
||||||
self.input.handle_key_event(&event);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_gamepad_event(&mut self, event: gilrs::Event) {
|
|
||||||
self.controllers.handle_gamepad_event(&event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy<UserEvent>) {
|
|
||||||
let Ok(mut gilrs) = Gilrs::new() else {
|
|
||||||
eprintln!("could not connect gamepad listener");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
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.map_gamepad(SimId::Player1, &gamepad);
|
|
||||||
}
|
|
||||||
if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() {
|
|
||||||
// main thread has closed! we done
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait AppWindow {
|
|
||||||
fn viewport_id(&self) -> ViewportId;
|
fn viewport_id(&self) -> ViewportId;
|
||||||
fn initial_viewport(&self) -> ViewportBuilder;
|
fn initial_viewport(&self) -> ViewportBuilder;
|
||||||
fn show(&mut self, ctx: &Context);
|
fn show(&mut self, ctx: &Context);
|
||||||
|
fn on_init(&mut self, render_state: &egui_wgpu::RenderState) {
|
||||||
|
let _ = render_state;
|
||||||
|
}
|
||||||
|
fn on_destroy(&mut self) {}
|
||||||
fn handle_key_event(&mut self, event: &KeyEvent) {
|
fn handle_key_event(&mut self, event: &KeyEvent) {
|
||||||
let _ = event;
|
let _ = event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChildWindow<T: AppWindow> {
|
|
||||||
pub open: Arc<AtomicBool>,
|
|
||||||
window: Arc<Mutex<T>>,
|
|
||||||
}
|
|
||||||
impl<T: AppWindow + Send + 'static> ChildWindow<T> {
|
|
||||||
fn new(window: T) -> Self {
|
|
||||||
Self {
|
|
||||||
open: Arc::new(AtomicBool::new(false)),
|
|
||||||
window: Arc::new(Mutex::new(window)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show(&self, ctx: &Context) {
|
|
||||||
if !self.open.load(Ordering::Relaxed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (viewport_id, viewport) = {
|
|
||||||
let window = self.window.lock().unwrap();
|
|
||||||
(window.viewport_id(), window.initial_viewport())
|
|
||||||
};
|
|
||||||
let open = self.open.clone();
|
|
||||||
let window = self.window.clone();
|
|
||||||
ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| {
|
|
||||||
window.lock().unwrap().show(ctx);
|
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
|
||||||
open.store(false, Ordering::Relaxed);
|
|
||||||
ctx.request_repaint();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_key_event(&self, event: &KeyEvent) {
|
|
||||||
if self.open.load(Ordering::Relaxed) {
|
|
||||||
self.window.lock().unwrap().handle_key_event(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,40 +1,32 @@
|
||||||
use std::sync::{
|
use crate::{
|
||||||
atomic::{AtomicBool, Ordering},
|
app::UserEvent,
|
||||||
Arc,
|
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
|
|
||||||
use egui::{
|
use egui::{
|
||||||
menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder,
|
menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder,
|
||||||
ViewportCommand, ViewportId, WidgetText,
|
ViewportCommand, ViewportId, WidgetText,
|
||||||
};
|
};
|
||||||
|
use winit::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
use super::{game_screen::GameScreen, AppWindow};
|
use super::{game_screen::GameScreen, AppWindow};
|
||||||
|
|
||||||
pub struct GameWindow {
|
pub struct GameWindow {
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
input_window_open: Arc<AtomicBool>,
|
|
||||||
screen: Option<GameScreen>,
|
screen: Option<GameScreen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameWindow {
|
impl GameWindow {
|
||||||
pub fn new(client: EmulatorClient, sim_id: SimId, input_window_open: Arc<AtomicBool>) -> Self {
|
pub fn new(client: EmulatorClient, proxy: EventLoopProxy<UserEvent>, sim_id: SimId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
|
proxy,
|
||||||
sim_id,
|
sim_id,
|
||||||
input_window_open,
|
|
||||||
screen: None,
|
screen: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) {
|
|
||||||
let (screen, sink) = GameScreen::init(render_state);
|
|
||||||
self.client
|
|
||||||
.send_command(EmulatorCommand::SetRenderer(self.sim_id, sink));
|
|
||||||
self.screen = Some(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) {
|
fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) {
|
||||||
ui.menu_button("ROM", |ui| {
|
ui.menu_button("ROM", |ui| {
|
||||||
if ui.button("Open ROM").clicked() {
|
if ui.button("Open ROM").clicked() {
|
||||||
|
@ -101,7 +93,7 @@ impl GameWindow {
|
||||||
});
|
});
|
||||||
ui.menu_button("Input", |ui| {
|
ui.menu_button("Input", |ui| {
|
||||||
if ui.button("Bind Inputs").clicked() {
|
if ui.button("Bind Inputs").clicked() {
|
||||||
self.input_window_open.store(true, Ordering::Relaxed);
|
self.proxy.send_event(UserEvent::OpenInput).unwrap();
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -112,6 +104,7 @@ impl GameWindow {
|
||||||
{
|
{
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::StartSecondSim(None));
|
.send_command(EmulatorCommand::StartSecondSim(None));
|
||||||
|
self.proxy.send_event(UserEvent::OpenPlayer2).unwrap();
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if self.client.has_player_2() {
|
if self.client.has_player_2() {
|
||||||
|
@ -157,6 +150,19 @@ impl AppWindow for GameWindow {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_init(&mut self, render_state: &egui_wgpu::RenderState) {
|
||||||
|
let (screen, sink) = GameScreen::init(render_state);
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::SetRenderer(self.sim_id, sink));
|
||||||
|
self.screen = Some(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_destroy(&mut self) {
|
||||||
|
if self.sim_id == SimId::Player2 {
|
||||||
|
self.client.send_command(EmulatorCommand::StopSecondSim);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait UiExt {
|
trait UiExt {
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub struct GameScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameScreen {
|
impl GameScreen {
|
||||||
pub fn init_pipeline(render_state: &egui_wgpu::RenderState) {
|
fn init_pipeline(render_state: &egui_wgpu::RenderState) {
|
||||||
let device = &render_state.device;
|
let device = &render_state.device;
|
||||||
|
|
||||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
@ -101,6 +101,8 @@ impl GameScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(render_state: &egui_wgpu::RenderState) -> (Self, TextureSink) {
|
pub fn init(render_state: &egui_wgpu::RenderState) -> (Self, TextureSink) {
|
||||||
|
Self::init_pipeline(render_state);
|
||||||
|
|
||||||
let device = &render_state.device;
|
let device = &render_state.device;
|
||||||
let queue = &render_state.queue;
|
let queue = &render_state.queue;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue