rust 无限循环和窗口冻结使用egui,时雄和std::sync::mpsc

brc7rcf0  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(208)

在通过通道传递消息时,我试图对接收到的响应进行调试,但不幸的是,我的窗口冻结,当试图打印调试时,table对象的列表无限打印。
我试图挽救局面的努力已被证明是徒劳的。
之后的sql查询的目标是加载表的列表及其关系,我已经将其分配给Diagram.tables并传递消息通道
在尝试接收通道self.handle_responses();并尝试通过时雄运行时执行查询函数之后,
我终于把表的列表翻了一遍
并将该列表添加到可以呈现的方形向量。
不幸的是,表的列表不停地循环,甚至挂起了Windows。
我以为表向量会重复一次,我可以利用它,但不成功。
这是从eframe template修改而来的完整代码app.rs

use std::sync::mpsc::{self, Receiver, Sender};

use crate::meta::{get_metadata, Table};
use egui::{Color32, FontId, Sense, Vec2};
use emath::{Align2, Pos2};
use std::fmt;
use tokio::runtime;
use tokio_postgres::NoTls;

pub const INIT_POS: Pos2 = egui::pos2(10.0, 15.0);
pub const ATTR_SIZE: Vec2 = egui::vec2(150.0, 25.0);

pub enum TaskMessage {
    //Applicaple to any scenario, behaves almost like a callback
    Generic(Box<dyn FnOnce(&mut Diagram) + Send>),
}


#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default = "Diagram::new")]
pub struct Diagram {
    pub shapes: Vec<Square>,
    pub tables: Vec<Table>,
    canvas_size: Vec2,
    #[serde(skip)]
    task_reciever: Receiver<TaskMessage>,
spawing them

    #[serde(skip)]
    _task_sender: Sender<TaskMessage>,
}

impl Diagram {
    fn new() -> Self {
        let (_task_sender, task_reciever) = mpsc::channel::<TaskMessage>();
        Self {
            shapes: vec![],
            tables: vec![],
            canvas_size: Vec2::splat(400.0),
            task_reciever,
            _task_sender,
        }
    }

    pub fn handle_responses(&mut self) {
        while let Ok(response) = self.task_reciever.try_recv() {
            match response {
                TaskMessage::Generic(gen_function) => {
                    gen_function(self);
                }
            }
        }
    }

}

impl egui::Widget for &mut Diagram {
    fn ui(self, ui: &mut egui::Ui) -> egui::Response {
        egui::Frame::canvas(ui.style())
            .show(ui, |ui| {
                egui::ScrollArea::new([true; 2]).show(ui, |ui| {
                    let sender = self._task_sender.clone();
                    let other_ctx = ui.ctx().clone();

                    self.handle_responses();
                    tokio::task::spawn(async {
                        let schema = "public".to_string();

                        get_metadata(schema, other_ctx, sender).await
                    });

                    eprintln!("{:#?}", self.tables);
                    for table in &self.tables {
                        if let Some(table_name) =
                            table.table.get("table_name").and_then(|v| v.as_str())
                        {
                            // println!("{:#?}", table_name);
                            let square = Square::new(table_name.to_string());
                            self.shapes.push(square);
                        }
                    }

                    for shape in self.shapes.iter_mut() {
                        shape.render(ui);
                    }
                    ui.allocate_at_least(self.canvas_size, Sense::hover());
                });
                // ui.ctx().set_debug_on_hover(true);
            })
            .response
    }
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct Square {
    position: egui::Pos2,
    dimension: egui::Vec2,
    label: String,
}

impl Square {
    fn new(label: String) -> Self {
        Self {
            position: egui::pos2(INIT_POS.x, INIT_POS.y),
            dimension: egui::vec2(ATTR_SIZE.x, ATTR_SIZE.y),
            attributes: vec![InnerSquare::new()],
            label,
        }
    }
    fn render(&mut self, ui: &mut egui::Ui) {
        let square_body = egui::Rect::from_min_size(self.position, self.dimension);
        //"finalized rect" which is offset properly
        let transformed_rect = {
            let resp = ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::click());
            let relative_to_screen = egui::emath::RectTransform::from_to(
                egui::Rect::from_min_size(Pos2::ZERO, resp.rect.size()),
                resp.rect,
            );
            relative_to_screen.transform_rect(square_body)
        };

        let frame = {
            let rounding_radius = 2.0;
            let fill = egui::Color32::LIGHT_GREEN;
            let stroke = egui::epaint::Stroke::new(2.0, Color32::DARK_BLUE);
            egui::Frame::none()
                .rounding(rounding_radius)
                .fill(fill)
                .stroke(stroke)
                .inner_margin(10.0)
        };
        //Creates a new ui where our square is supposed to appear
        ui.allocate_ui_at_rect(transformed_rect, |ui| {
            frame.show(ui, |ui| {
                //draw each attribute
                ui.label(
                    egui::RichText::new(&self.label)
                        .heading()
                        .color(egui::Color32::BLACK),
                );
                for inner_square in self.attributes.iter_mut() {
                    inner_square.render(ui);
                }
            });
        });
    }
}

#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
pub struct TemplateApp {
    diagram: Diagram,
    label: String,
    #[serde(skip)]
    value: f32,
    //this is what the ui thread will just to catch returns of tasks, in this case it's the std
    //mpsc channels, but any channel which has smiliar behaviour works
}

impl Default for TemplateApp {
    fn default() -> Self {
        Self {
            // Example stuff:
            label: "Hello World!".to_owned(),
            value: 2.7,
            diagram: Diagram::new(),
        }
    }
}

impl TemplateApp {
    /// Called once before the first frame.
    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
        Default::default()
    }
}

impl eframe::App for TemplateApp {
    /// Called by the frame work to save state before shutdown.
    fn save(&mut self, storage: &mut dyn eframe::Storage) {
        eframe::set_value(storage, eframe::APP_KEY, self);
    }

    /// Called each time the UI needs repainting, which may be many times per second.
    /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        let Self {
            label,
            diagram,
            value,
        } = self;

        // #[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages!
        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
            // The top panel is often a good place for a menu bar:
            egui::menu::bar(ui, |ui| {
                ui.menu_button("File", |ui| {
                    if ui.button("Quit").clicked() {
                        _frame.close();
                    }
                });
            });
        });

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(&mut self.diagram) // The central panel the region left after adding TopPanel's and SidePanel's
        });

        if false {
            egui::Window::new("Window").show(ctx, |ui| {
                ui.label("Panel Area");
            });
        }
    }
}

以及负责从tokio-postgre查询数据库的代码
//meta.rs

use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::error::Error;
use std::fmt;
use std::ptr::null;
use std::sync::mpsc::Sender;
use std::{thread, time};
use tokio::runtime::Runtime;
use tokio_postgres::tls::NoTlsStream;
use tokio_postgres::{Client, Connection, Error as TokioError, NoTls, Row, Socket};

// Define a custom error enum
#[derive(Debug)]
pub enum MetadataError {
    JsonResponseNotFound,
    RowNotFound,
    // PostgresError(PostgresError),
    TokioError(TokioError),
}

impl fmt::Display for MetadataError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MetadataError::JsonResponseNotFound => write!(f, "No JSON response found"),
            MetadataError::RowNotFound => write!(f, "No row found in the result"),
            // MetadataError::PostgresError(ref err) => err.fmt(f),
            MetadataError::TokioError(ref err) => err.fmt(f),
        }
    }
}

impl From<tokio_postgres::Error> for MetadataError {
    fn from(error: tokio_postgres::Error) -> MetadataError {
        MetadataError::TokioError(error)
    }
}

impl Error for MetadataError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match *self {
            MetadataError::JsonResponseNotFound | MetadataError::RowNotFound => None,
            // MetadataError::PostgresError(ref err) => Some(err),
            MetadataError::TokioError(ref err) => Some(err),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Table {
    pub table: serde_json::Value,
}

impl From<Row> for Table {
    fn from(row: Row) -> Self {
        Self { table: row.get(0) }
    }
}

pub async fn get_metadata(
    schema: String,
    ctx: egui::Context,
    sender: Sender<crate::app::TaskMessage>,
) -> Result<(), MetadataError> {
    let qry = r#"
        select row_to_json(Tb) 
    --- table listing query
    "#;
    let (client, connection) = tokio_postgres::connect(
        "postgresql://postgres:chou1979@localhost/authenticate",
        NoTls,
    )
    .await
    .unwrap();
    // The connection object performs the actual communication with the database,
    // so spawn it off to run on its own.
    let conn = tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });

    let stmt = client.prepare(qry).await?;
    let rows = client.query(&stmt, &[&schema]).await?;

    let finalized: Vec<Table> = rows.into_iter().map(Table::from).collect();
    conn.abort();
    // println!("{:#?}", finalized);

    let gen_function = move |diagram: &mut crate::app::Diagram| {
        diagram.tables = finalized
    };
    // sender.send(Box::new(gen_function)).unwrap();
    sender
        .send(crate::app::TaskMessage::Generic(Box::new(gen_function)))
        .unwrap();
    ctx.request_repaint();

    Ok(())
}
kcwpcxri

kcwpcxri1#

egui是一个即时模式的GUI库,这意味着它将在每一帧重新渲染所有内容。他们在这里解释了这是如何工作的。它们具体指出:
由于即时模式GUI在每个帧都进行完整布局,因此布局代码需要快速。如果你有一个非常复杂的GUI,这可能会加重CPU的负担。特别是,在滚动区域中具有非常大的UI(具有非常长的滚动)可能会很慢,因为内容需要在每一帧布局。
如果您的表列表特别长,这肯定是一个问题,因此值得考虑是否要使用即时模式GUI库。关于docs
即时模式起源于游戏,屏幕上的所有内容都是以显示刷新率绘制的,即每秒60+帧。在即时模式GUI中,整个界面以相同的高速率进行布局和绘制。这使得即时模式GUI特别适合于高度交互的应用程序。[.]简而言之就是:即时模式GUI库更易于使用,但功能较弱。
但是,您当前面临的问题并不一定是必须显示所有表,而是还需要通过调用get_metadata并执行数据库查询来在每一帧刷新所有表。这太过分了,我想知道您是否需要立即刷新元数据。即使您希望不断刷新元数据,也可能希望通过重用相同的数据库连接而不是每次都创建一个新连接来实现。
简而言之,您可能只需要在启动时运行get_metadata函数。或者,您可能希望以不同于刷新率的预定时间间隔运行它。例如,你可以像这样在一个单独的线程上运行它:

let qry = r#"
        select row_to_json(Tb) 
    --- table listing query
    "#;
    let (client, connection) = tokio_postgres::connect(
        "postgresql://postgres:chou1979@localhost/authenticate",
        NoTls,
    )
    .await
    .unwrap();
    // The connection object performs the actual communication with the database,
    // so spawn it off to run on its own.
    let conn = tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    
    let interval = Duration::from_secs(1);
    let mut next_time = Instant::now() + interval;
    loop {
      let stmt = client.prepare(qry).await?;
      let rows = client.query(&stmt, &[&schema]).await?;

      let finalized: Vec<Table> = rows.into_iter().map(Table::from).collect();
      conn.abort();
      // println!("{:#?}", finalized);

      let gen_function = move |diagram: &mut crate::app::Diagram| {
          diagram.tables = finalized
      };
      // sender.send(Box::new(gen_function)).unwrap();
      sender
          .send(crate::app::TaskMessage::Generic(Box::new(gen_function)))
          .unwrap();
      ctx.request_repaint();

      sleep(next_time - Instant::now());
      next_time += interval;
    }
    Ok(())

相关问题