mirror of https://codeberg.org/topola/topola.git
Add profiling capabilities (including GUI debug profiler)
This commit is contained in:
parent
8df8d760c3
commit
a7fcf57a75
|
|
@ -51,6 +51,7 @@ tr-menu-debug-show-bboxes = Show BBoxes
|
|||
tr-menu-debug-show-primitive-indices = Show Primitive Indices
|
||||
tr-menu-debug-show-bend-endpoint-tangents = Show Bend Endpoint Tangents
|
||||
tr-menu-debug-fix-step-rate = Fix Step Rate
|
||||
tr-menu-debug-profiler = Profiler
|
||||
tr-menu-debug-step-rate = Step Rate
|
||||
tr-menu-debug-step-rate-unit = steps/s
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ edition = "2024"
|
|||
[features]
|
||||
default = ["xdg-portal"]
|
||||
gtk3 = ["rfd/gtk3"]
|
||||
profiler = ["puffin", "puffin_egui", "puffin_http", "topola/profiler"]
|
||||
xdg-portal = ["rfd/xdg-portal"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
|
@ -41,6 +42,9 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
futures-lite = "2.6"
|
||||
env_logger = "0.11"
|
||||
puffin = { version = "0.20", optional = true }
|
||||
puffin_egui = { version = "0.30", optional = true }
|
||||
puffin_http = { version = "0.17", optional = true }
|
||||
|
||||
# Web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ impl eframe::App for App {
|
|||
|
||||
/// Called each time the UI has to be repainted.
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
crate::profiler::begin_frame();
|
||||
crate::profile_function!();
|
||||
|
||||
self.menu_bar.update(
|
||||
ctx,
|
||||
&mut self.translator,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ impl Controller {
|
|||
step_rate: Option<f64>,
|
||||
dt: f64,
|
||||
) -> bool {
|
||||
crate::profile_function!();
|
||||
let instant = Instant::now();
|
||||
|
||||
if step_rate.is_some() {
|
||||
|
|
@ -61,17 +62,18 @@ impl Controller {
|
|||
}
|
||||
|
||||
pub fn step(&mut self, _tr: &Translator) -> ControlFlow<()> {
|
||||
crate::profile_function!();
|
||||
self.master_interactor.step(self.workspace.board_mut())
|
||||
}
|
||||
|
||||
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
|
||||
crate::profile_function!();
|
||||
let Self {
|
||||
workspace,
|
||||
appearance_panel,
|
||||
master_interactor: _,
|
||||
dt_accum,
|
||||
} = self;
|
||||
|
||||
appearance_panel.update(ctx, workspace.board_mut());
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +86,7 @@ impl Controller {
|
|||
scene_hovered: bool,
|
||||
ui: &mut egui::Ui,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
let board_master =
|
||||
if let MasterInteractor::Autoplacer(interactor) = &self.master_interactor {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ impl Display {
|
|||
viewport: &Viewport,
|
||||
workspace: &Controller,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace);
|
||||
self.display_repulsions(ui, viewport, workspace);
|
||||
self.display_attractions(ui, viewport, workspace);
|
||||
|
|
@ -40,6 +42,7 @@ impl Display {
|
|||
viewport: &Viewport,
|
||||
workspace: &Controller,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
let board = workspace.workspace.board();
|
||||
let layout = board.layout();
|
||||
|
||||
|
|
@ -162,6 +165,7 @@ impl Display {
|
|||
}
|
||||
|
||||
fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
|
||||
crate::profile_function!();
|
||||
let board = workspace.workspace.board();
|
||||
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW);
|
||||
|
||||
|
|
@ -203,6 +207,7 @@ impl Display {
|
|||
}
|
||||
|
||||
fn display_retentions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
|
||||
crate::profile_function!();
|
||||
let board = workspace.workspace.board();
|
||||
let layout = board.layout();
|
||||
let stroke = egui::Stroke::new(
|
||||
|
|
@ -244,6 +249,7 @@ impl Display {
|
|||
}
|
||||
|
||||
fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) {
|
||||
crate::profile_function!();
|
||||
let board = workspace.workspace.board();
|
||||
let layout = board.layout();
|
||||
let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE);
|
||||
|
|
@ -375,6 +381,7 @@ impl Display {
|
|||
viewport: &Viewport,
|
||||
workspace: &Controller,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
let board = workspace.workspace.board();
|
||||
let layout = board.layout();
|
||||
|
||||
|
|
@ -452,6 +459,7 @@ impl Display {
|
|||
viewport: &Viewport,
|
||||
workspace: &Controller,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -522,6 +530,7 @@ impl Display {
|
|||
_viewport: &Viewport,
|
||||
workspace: &Controller,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else {
|
||||
return;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mod controller;
|
|||
mod display;
|
||||
mod layers_panel;
|
||||
mod menu_bar;
|
||||
mod profiler;
|
||||
mod translator;
|
||||
mod viewport;
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ use crate::app::App;
|
|||
fn main() -> eframe::Result {
|
||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||
|
||||
profiler::enable();
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([400.0, 300.0])
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ use crate::{
|
|||
pub struct MenuBar {
|
||||
pub fix_step_rate: bool,
|
||||
pub step_rate: f64,
|
||||
#[serde(default)]
|
||||
pub show_profiler: bool,
|
||||
}
|
||||
|
||||
impl MenuBar {
|
||||
|
|
@ -34,6 +36,7 @@ impl MenuBar {
|
|||
Self {
|
||||
fix_step_rate: false,
|
||||
step_rate: 1.0,
|
||||
show_profiler: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +47,8 @@ impl MenuBar {
|
|||
content_sender: Sender<Result<DsnFile, ParseErrorContext>>,
|
||||
controller: Option<&mut Controller>,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let mut actions = Actions::new(tr);
|
||||
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
|
|
@ -76,6 +81,15 @@ impl MenuBar {
|
|||
)),
|
||||
);
|
||||
});
|
||||
|
||||
#[cfg(feature = "profiler")]
|
||||
{
|
||||
ui.separator();
|
||||
|
||||
if ui.button(tr.text("tr-menu-debug-profiler")).clicked() {
|
||||
self.show_profiler = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
|
@ -118,5 +132,7 @@ impl MenuBar {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
crate::profiler::profiler_window(ctx, &mut self.show_profiler);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
static PUFFIN_SERVER: std::sync::OnceLock<puffin_http::Server> = std::sync::OnceLock::new();
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
pub fn enable() {
|
||||
let server_addr = format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT);
|
||||
PUFFIN_SERVER.get_or_init(|| {
|
||||
eprintln!("Run this to view profiler data: puffin_viewer {server_addr}");
|
||||
puffin_http::Server::new(&server_addr).expect("puffin_http server")
|
||||
});
|
||||
puffin::set_scopes_on(true);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", not(feature = "profiler")))]
|
||||
pub fn enable() {}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
pub fn begin_frame() {
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", not(feature = "profiler")))]
|
||||
pub fn begin_frame() {}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
pub fn profiler_window(ctx: &egui::Context, open: &mut bool) {
|
||||
if *open {
|
||||
*open = puffin_egui::profiler_window(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", not(feature = "profiler")))]
|
||||
pub fn profiler_window(_ctx: &egui::Context, _open: &mut bool) {}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! profile_scope {
|
||||
($name:expr) => {
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
puffin::profile_scope!($name);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! profile_function {
|
||||
() => {
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
puffin::profile_function!();
|
||||
};
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ impl Viewport {
|
|||
menu_bar: &MenuBar,
|
||||
controller: Option<&mut Controller>,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
ui.ctx().request_repaint();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ description = "Work-in-progress free and open-source topological (rubberband) ro
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
profiler = ["puffin"]
|
||||
|
||||
[dependencies]
|
||||
bidimap = "0.7"
|
||||
dearcut = { version = "0.3", features = ["serde", "undoredo"] }
|
||||
|
|
@ -24,6 +27,7 @@ spade = "2.15"
|
|||
specctra = { path = "../specctra" }
|
||||
stable-vec = "0.4"
|
||||
undoredo.workspace = true
|
||||
puffin = { version = "0.20", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
walkdir = "2.5"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ impl AutoplacerMasterInteractor {
|
|||
|
||||
impl Interactor for AutoplacerMasterInteractor {
|
||||
fn step(&mut self, board: &mut Board) -> ControlFlow<()> {
|
||||
crate::profile_function!();
|
||||
self.autoplacer.step(board)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use undoredo::{FlushDelta, ResetDelta};
|
|||
|
||||
use crate::{
|
||||
board::{Board, BoardDelta},
|
||||
layout::compounds::ComponentId,
|
||||
layout::{Layout, compounds::ComponentId},
|
||||
orientation::Orientation,
|
||||
selections::ComponentSelection,
|
||||
vector::Vector2,
|
||||
|
|
@ -54,6 +54,8 @@ impl Autoplacer {
|
|||
}
|
||||
|
||||
pub fn step(&mut self, board: &mut Board) -> ControlFlow<()> {
|
||||
crate::profile_function!();
|
||||
|
||||
if self.step_counter < self.schedule.max_steps {
|
||||
let control_flow = self.step_with_params(
|
||||
board,
|
||||
|
|
@ -88,37 +90,59 @@ impl Autoplacer {
|
|||
board: &mut Board,
|
||||
params: AutoplacerStepParams,
|
||||
) -> ControlFlow<()> {
|
||||
for &component in self.components.iter() {
|
||||
//self.step_component_with_params(component, params);
|
||||
//let last_cost = self.cost(board, params);
|
||||
let last_cost = self.component_cost(board, component, params);
|
||||
crate::profile_function!();
|
||||
|
||||
let dx_gaussian = Normal::new(0.0, params.std_dev).unwrap();
|
||||
let dy_gaussian = Normal::new(0.0, params.std_dev).unwrap();
|
||||
|
||||
//let dx = dx_gaussian.sample(&mut self.rng);
|
||||
//let dy = dy_gaussian.sample(&mut self.rng);
|
||||
let dx = dx_gaussian.sample(&mut rand::rng());
|
||||
let dy = dy_gaussian.sample(&mut rand::rng());
|
||||
|
||||
board.move_resolved_components_by(&[component], Vector2::new(dx as i64, dy as i64));
|
||||
|
||||
//let new_cost = self.cost(board, params);
|
||||
let new_cost = self.component_cost(board, component, params);
|
||||
let delta_cost = new_cost - last_cost;
|
||||
|
||||
if delta_cost < 0.0
|
||||
|| rand::rng().random::<f64>() < f64::exp(-delta_cost / params.temperature)
|
||||
{
|
||||
self.origin_delta = self.origin_delta.clone().merge_delta(board.flush_delta());
|
||||
} else {
|
||||
board.reset_delta();
|
||||
}
|
||||
for i in 0..self.components.len() {
|
||||
let component = self.components[i];
|
||||
self.step_component(board, component, params);
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn step_component(
|
||||
&mut self,
|
||||
board: &mut Board,
|
||||
component: ComponentId,
|
||||
params: AutoplacerStepParams,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let last_cost = self.component_cost(board, component, params);
|
||||
let translation = self.sample_move(params);
|
||||
board.move_resolved_components_by(&[component], translation);
|
||||
let new_cost = self.component_cost(board, component, params);
|
||||
let delta_cost = new_cost - last_cost;
|
||||
|
||||
if delta_cost < 0.0
|
||||
|| rand::rng().random::<f64>() < f64::exp(-delta_cost / params.temperature)
|
||||
{
|
||||
self.accept_move(board);
|
||||
} else {
|
||||
self.reject_move(board);
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_move(&self, params: AutoplacerStepParams) -> Vector2<i64> {
|
||||
crate::profile_function!();
|
||||
let dx_gaussian = Normal::new(0.0, params.std_dev).unwrap();
|
||||
let dy_gaussian = Normal::new(0.0, params.std_dev).unwrap();
|
||||
Vector2::new(
|
||||
dx_gaussian.sample(&mut rand::rng()) as i64,
|
||||
dy_gaussian.sample(&mut rand::rng()) as i64,
|
||||
)
|
||||
}
|
||||
|
||||
fn accept_move(&mut self, board: &mut Board) {
|
||||
crate::profile_function!();
|
||||
self.origin_delta = self.origin_delta.clone().merge_delta(board.flush_delta());
|
||||
}
|
||||
|
||||
fn reject_move(&mut self, board: &mut Board) {
|
||||
crate::profile_function!();
|
||||
board.reset_delta();
|
||||
}
|
||||
|
||||
/*fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 {
|
||||
self.components
|
||||
.iter()
|
||||
|
|
@ -130,26 +154,42 @@ impl Autoplacer {
|
|||
&self,
|
||||
board: &Board,
|
||||
component: ComponentId,
|
||||
params: AutoplacerStepParams,
|
||||
_params: AutoplacerStepParams,
|
||||
) -> f64 {
|
||||
let layout = board.layout();
|
||||
crate::profile_function!();
|
||||
|
||||
let repulsion_cost: i64 = layout
|
||||
.locate_component_repulsions(component, Orientation::Oblique)
|
||||
.map(|vector| vector.x.abs() + vector.y.abs())
|
||||
.sum();
|
||||
let attraction_cost: f64 = layout
|
||||
.component_attractions(component)
|
||||
.map(|vector| 1.0 / (1.0 + (vector.x.abs() + vector.y.abs()) as f64))
|
||||
.sum();
|
||||
let retention_cost: i64 = layout
|
||||
.component_retentions(component)
|
||||
.map(|vector| 100 * (vector.x.abs() + vector.y.abs()))
|
||||
.sum();
|
||||
let layout = board.layout();
|
||||
let repulsion_cost = self.repulsion_cost(layout, component);
|
||||
let attraction_cost = self.attraction_cost(layout, component);
|
||||
let retention_cost = self.retention_cost(layout, component);
|
||||
|
||||
repulsion_cost as f64 + attraction_cost + retention_cost as f64
|
||||
}
|
||||
|
||||
fn repulsion_cost(&self, layout: &Layout, component: ComponentId) -> i64 {
|
||||
crate::profile_function!();
|
||||
layout
|
||||
.locate_component_repulsions(component, Orientation::Oblique)
|
||||
.map(|vector| vector.x.abs() + vector.y.abs())
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn attraction_cost(&self, layout: &Layout, component: ComponentId) -> f64 {
|
||||
crate::profile_function!();
|
||||
layout
|
||||
.component_attractions(component)
|
||||
.map(|vector| 1.0 / (1.0 + (vector.x.abs() + vector.y.abs()) as f64))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn retention_cost(&self, layout: &Layout, component: ComponentId) -> i64 {
|
||||
crate::profile_function!();
|
||||
layout
|
||||
.component_retentions(component)
|
||||
.map(|vector| 100 * (vector.x.abs() + vector.y.abs()))
|
||||
.sum()
|
||||
}
|
||||
|
||||
/*fn step_component_with_params(
|
||||
&mut self,
|
||||
component: ComponentId,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ impl Board {
|
|||
selection: &[ComponentId],
|
||||
translation: Vector2<i64>,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
self.layout.move_components_by(selection, translation);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ impl Pin {
|
|||
|
||||
impl Layout {
|
||||
pub fn pin_centroid(&self, pin_id: PinId) -> Vector2<i64> {
|
||||
crate::profile_function!();
|
||||
let pin = self.pin(pin_id);
|
||||
let mut sum = Vector2::new(0, 0);
|
||||
let mut count = 0;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ impl Polygon {
|
|||
}
|
||||
|
||||
pub fn center(&self) -> Vector2<i64> {
|
||||
crate::profile_function!();
|
||||
Vector2::<i64>::polygon_centroid(&self.vertices)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
pub fn move_components_by(&mut self, ids: &[ComponentId], translation: Vector2<i64>) {
|
||||
crate::profile_function!();
|
||||
for id in ids {
|
||||
let component = self.components[id.index()].clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
mod profiler;
|
||||
|
||||
mod autoplacer;
|
||||
mod autorouter;
|
||||
pub mod board;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! profile_scope {
|
||||
($name:expr) => {
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
puffin::profile_scope!($name);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! profile_function {
|
||||
() => {
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "profiler"))]
|
||||
puffin::profile_function!();
|
||||
};
|
||||
}
|
||||
|
|
@ -247,6 +247,7 @@ macro_rules! impl_polygon_centroid {
|
|||
($type:ty) => {
|
||||
impl Vector2<$type> {
|
||||
pub fn polygon_centroid(polygon: &[Vector2<$type>]) -> Self {
|
||||
crate::profile_function!();
|
||||
let mut sum = Vector2::new(0 as $type, 0 as $type);
|
||||
|
||||
for vertex in polygon.iter() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue