Implement multiplayer #2

Merged
SonicSwordcane merged 21 commits from multiplayer into main 2024-11-30 00:31:10 +00:00
6 changed files with 364 additions and 478 deletions
Showing only changes of commit 83377ff5fa - Show all commits

329
src/app.rs Normal file
View File

@ -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;
}
}
}

View File

@ -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),
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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 {

View File

@ -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;