163 lines
5.4 KiB
Rust
163 lines
5.4 KiB
Rust
use std::{fs, time::Duration};
|
|
|
|
use anyhow::Result;
|
|
use egui::{Button, CentralPanel, Checkbox, Label, ViewportBuilder, ViewportId};
|
|
use egui_notify::{Anchor, Toast, Toasts};
|
|
|
|
use crate::{
|
|
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId},
|
|
profiler::{Profiler, ProfilerStatus},
|
|
window::AppWindow,
|
|
};
|
|
|
|
pub struct ProfileWindow {
|
|
sim_id: SimId,
|
|
client: EmulatorClient,
|
|
profiler: Profiler,
|
|
toasts: Toasts,
|
|
}
|
|
|
|
impl ProfileWindow {
|
|
pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
|
Self {
|
|
sim_id,
|
|
client: client.clone(),
|
|
profiler: Profiler::new(sim_id, client),
|
|
toasts: Toasts::new()
|
|
.with_anchor(Anchor::BottomLeft)
|
|
.with_margin((10.0, 10.0).into())
|
|
.reverse(true),
|
|
}
|
|
}
|
|
|
|
pub fn launch(&mut self) {
|
|
self.profiler.enable();
|
|
}
|
|
|
|
fn finish_recording(&mut self) {
|
|
let pause = matches!(self.client.emulator_state(), EmulatorState::Running);
|
|
if pause {
|
|
self.client.send_command(EmulatorCommand::Pause);
|
|
}
|
|
match self.try_finish_recording() {
|
|
Ok(Some(path)) => {
|
|
let mut toast = Toast::info(format!("Saved to {path}"));
|
|
toast.duration(Some(Duration::from_secs(5)));
|
|
self.toasts.add(toast);
|
|
}
|
|
Ok(None) => {}
|
|
Err(error) => {
|
|
let mut toast = Toast::error(format!("{error:#}"));
|
|
toast.duration(Some(Duration::from_secs(5)));
|
|
self.toasts.add(toast);
|
|
}
|
|
}
|
|
if pause {
|
|
self.client.send_command(EmulatorCommand::Resume);
|
|
}
|
|
}
|
|
|
|
fn try_finish_recording(&mut self) -> Result<Option<String>> {
|
|
let bytes_receiver = self.profiler.finish_recording();
|
|
let file = rfd::FileDialog::new()
|
|
.add_filter("Profiler files", &["json"])
|
|
.set_file_name("profile.json")
|
|
.save_file();
|
|
if let Some(path) = file {
|
|
let bytes = pollster::block_on(bytes_receiver)?;
|
|
fs::write(&path, bytes)?;
|
|
Ok(Some(path.display().to_string()))
|
|
} else {
|
|
self.profiler.cancel_recording();
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AppWindow for ProfileWindow {
|
|
fn viewport_id(&self) -> ViewportId {
|
|
ViewportId::from_hash_of(format!("Profile-{}", self.sim_id))
|
|
}
|
|
|
|
fn sim_id(&self) -> SimId {
|
|
self.sim_id
|
|
}
|
|
|
|
fn initial_viewport(&self) -> ViewportBuilder {
|
|
ViewportBuilder::default()
|
|
.with_title(format!("Profiler ({})", self.sim_id))
|
|
.with_inner_size((300.0, 200.0))
|
|
}
|
|
|
|
fn show(&mut self, ctx: &egui::Context) {
|
|
let status = self.profiler.status();
|
|
let recording = matches!(status, ProfilerStatus::Recording);
|
|
CentralPanel::default().show(ctx, |ui| {
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.spacing_mut().item_spacing.x = 0.0;
|
|
ui.add(
|
|
Label::new(
|
|
"Use this tool to record performance profiles of your game, for use in ",
|
|
)
|
|
.wrap_mode(egui::TextWrapMode::Wrap),
|
|
);
|
|
ui.hyperlink("https://profiler.firefox.com");
|
|
ui.label(".");
|
|
});
|
|
ui.horizontal_wrapped(|ui| {
|
|
ui.spacing_mut().item_spacing.x = 0.0;
|
|
ui.add(
|
|
Label::new("For more instructions, see ").wrap_mode(egui::TextWrapMode::Wrap),
|
|
);
|
|
ui.hyperlink_to(
|
|
"the Lemur wiki",
|
|
"https://git.virtual-boy.com/PVB/lemur/wiki/Profiling-with-Lemur",
|
|
);
|
|
ui.label(".");
|
|
});
|
|
ui.separator();
|
|
|
|
let mut enabled = status.enabled();
|
|
let enabled_checkbox = Checkbox::new(&mut enabled, "Enable profiling");
|
|
if ui.add_enabled(!recording, enabled_checkbox).changed() {
|
|
if enabled {
|
|
self.profiler.enable();
|
|
} else {
|
|
self.profiler.disable();
|
|
}
|
|
}
|
|
if !enabled {
|
|
ui.label("Enabling profiling will restart your current game.");
|
|
} else {
|
|
ui.horizontal(|ui| {
|
|
if !recording {
|
|
let record_button = Button::new("Record");
|
|
let can_record = matches!(status, ProfilerStatus::Enabled);
|
|
if ui.add_enabled(can_record, record_button).clicked() {
|
|
self.profiler.start_recording();
|
|
}
|
|
} else {
|
|
if ui.button("Finish recording").clicked() {
|
|
self.finish_recording();
|
|
}
|
|
if ui.button("Cancel recording").clicked() {
|
|
self.profiler.cancel_recording();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
match &status {
|
|
ProfilerStatus::Recording => {
|
|
ui.label("Recording...");
|
|
}
|
|
ProfilerStatus::Error(message) => {
|
|
ui.label(message);
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
self.toasts.show(ctx);
|
|
}
|
|
}
|