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 anyhow::Result;
 | 
			
		||||
use application::Application;
 | 
			
		||||
use app::Application;
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use emulator::EmulatorBuilder;
 | 
			
		||||
use thread_priority::{ThreadBuilder, ThreadPriority};
 | 
			
		||||
use winit::event_loop::{ControlFlow, EventLoop};
 | 
			
		||||
 | 
			
		||||
mod application;
 | 
			
		||||
mod app;
 | 
			
		||||
mod audio;
 | 
			
		||||
mod controller;
 | 
			
		||||
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 game::GameWindow;
 | 
			
		||||
use game_screen::GameScreen;
 | 
			
		||||
use gilrs::{EventType, Gilrs};
 | 
			
		||||
use input::InputWindow;
 | 
			
		||||
use winit::{event::KeyEvent, event_loop::EventLoopProxy};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    application::UserEvent,
 | 
			
		||||
    controller::ControllerManager,
 | 
			
		||||
    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
			
		||||
    input::MappingProvider,
 | 
			
		||||
};
 | 
			
		||||
pub use game::GameWindow;
 | 
			
		||||
pub use input::InputWindow;
 | 
			
		||||
use winit::event::KeyEvent;
 | 
			
		||||
 | 
			
		||||
mod game;
 | 
			
		||||
mod game_screen;
 | 
			
		||||
mod input;
 | 
			
		||||
 | 
			
		||||
pub struct WindowManager {
 | 
			
		||||
    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 {
 | 
			
		||||
pub trait AppWindow {
 | 
			
		||||
    fn viewport_id(&self) -> ViewportId;
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder;
 | 
			
		||||
    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) {
 | 
			
		||||
        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::{
 | 
			
		||||
    atomic::{AtomicBool, Ordering},
 | 
			
		||||
    Arc,
 | 
			
		||||
use crate::{
 | 
			
		||||
    app::UserEvent,
 | 
			
		||||
    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
 | 
			
		||||
use egui::{
 | 
			
		||||
    menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder,
 | 
			
		||||
    ViewportCommand, ViewportId, WidgetText,
 | 
			
		||||
};
 | 
			
		||||
use winit::event_loop::EventLoopProxy;
 | 
			
		||||
 | 
			
		||||
use super::{game_screen::GameScreen, AppWindow};
 | 
			
		||||
 | 
			
		||||
pub struct GameWindow {
 | 
			
		||||
    client: EmulatorClient,
 | 
			
		||||
    proxy: EventLoopProxy<UserEvent>,
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    input_window_open: Arc<AtomicBool>,
 | 
			
		||||
    screen: Option<GameScreen>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
            client,
 | 
			
		||||
            proxy,
 | 
			
		||||
            sim_id,
 | 
			
		||||
            input_window_open,
 | 
			
		||||
            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) {
 | 
			
		||||
        ui.menu_button("ROM", |ui| {
 | 
			
		||||
            if ui.button("Open ROM").clicked() {
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +93,7 @@ impl GameWindow {
 | 
			
		|||
        });
 | 
			
		||||
        ui.menu_button("Input", |ui| {
 | 
			
		||||
            if ui.button("Bind Inputs").clicked() {
 | 
			
		||||
                self.input_window_open.store(true, Ordering::Relaxed);
 | 
			
		||||
                self.proxy.send_event(UserEvent::OpenInput).unwrap();
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -112,6 +104,7 @@ impl GameWindow {
 | 
			
		|||
            {
 | 
			
		||||
                self.client
 | 
			
		||||
                    .send_command(EmulatorCommand::StartSecondSim(None));
 | 
			
		||||
                self.proxy.send_event(UserEvent::OpenPlayer2).unwrap();
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
            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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ pub struct 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 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) {
 | 
			
		||||
        Self::init_pipeline(render_state);
 | 
			
		||||
 | 
			
		||||
        let device = &render_state.device;
 | 
			
		||||
        let queue = &render_state.queue;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue