Rust 中的简单事件调度程序

问题描述 投票:0回答:1

我对 Rust 有点陌生,我正在尝试开发一个简单的应用程序来使用 OpenGL 创建简单的对象(如立方体)。

到目前为止,我已经使用“gl”模块创建了 OpenGL 的包装器来初始化 vbos、vaos、程序、着色器等...

最初我使用“glutin”模块在主函数中处理我感兴趣的事件。 代码看起来像这样:

fn main() {
    let event_loop = EventLoop::new();
    let window = WindowBuilder::new().with_title("Rust OpenGL");

    let gl_context = ContextBuilder::new()
        .with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
        .build_windowed(window, &event_loop)
        .expect("Cannot create windowed context");

    let gl_context = unsafe {
        gl_context
            .make_current()
            .expect("Failed to make context current")
    };

    gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);

    let v1 = vec![
        Vertex((-0.5, -0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
        Vertex((0.5, -0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
        Vertex((0.5, 0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
        Vertex((-0.5, 0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
    ];
    let front_face = Square::new(&v1);

    let v2 = vec![
        Vertex((-0.5, -0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
        Vertex((0.5, -0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
        Vertex((0.5, 0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
        Vertex((-0.5, 0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
    ];
    let back_face = Square::new(&v2);

    let cube = Cube::from(&[front_face, back_face]);

    let mut rotation_angle: Rad<f32> = Deg(0.0).into();

    event_loop.run(move |event, target, control_flow| {
        *control_flow = ControlFlow::Wait;

        match event {
            Event::LoopDestroyed => (),
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                WindowEvent::Resized(physical_size) => gl_context.resize(physical_size),
                WindowEvent::KeyboardInput { input, .. } => {
                    if let Some(VirtualKeyCode::A) = input.virtual_keycode {
                        match input.state {
                            ElementState::Pressed => {
                                rotation_angle -= Deg(0.5).into();
                                gl_context.window().request_redraw();
                            },
                            _ => ()
                        }
                    }
                    if let Some(VirtualKeyCode::D) = input.virtual_keycode {
                        match input.state {
                            ElementState::Pressed => {
                                rotation_angle += Deg(0.5).into();
                                gl_context.window().request_redraw();
                            },
                            _ => ()
                        }
                    }
                }
                _ => (),
            },
            Event::RedrawRequested(_) => {
                println!("{:?}", rotation_angle);

                let rotation_matrix_y = Matrix4::from_axis_angle(Vector3::unit_y(), rotation_angle);
                let rotation_matrix_x = Matrix4::from_axis_angle(Vector3::unit_x(), Deg(30.0));
                let transformation_matrix = rotation_matrix_y * rotation_matrix_x;

                let transform_location = cube.gl_wrapper().program.get_uniform_location("Transform").unwrap();

                unsafe {
                    gl::UniformMatrix4fv(transform_location as GLint, 1, gl::FALSE, transformation_matrix.as_ptr());

                    gl::ClearColor(0.0,  0.0,  0.0,  1.0);
                    gl::Clear(gl::COLOR_BUFFER_BIT);

                    Renderer::draw(&cube);
                }
                gl_context.swap_buffers().unwrap();
            }
            _ => (),
        }
    });
}

在这个演示中,Renderer 结构体负责绘制简单的形状,通过按 A 和 D 键我可以旋转立方体;然后我稍微修改了代码,但我想重点关注事件处理部分。

正如您所见,代码很多,尤其是都在同一个函数(主函数)中。

我认为稍微重构一下这个混乱会很好,我想出了这个:

fn main() {
    let event_loop = EventLoop::new();
    let mut app = Application::new(&event_loop);

    event_loop.run(move |event, _, control_flow| {
        app.set_control_flow_reference(control_flow);
        app.set_control_flow(ControlFlow::Wait);

        let mut generic_dispatcher = GenericDispatcher::new(&mut app);
        generic_dispatcher.handle(event);
    });
}

#[derive(Debug)]
pub struct Application {
    control_flow: ControlFlow,
    gl_context: ContextWrapper<PossiblyCurrent, Window>
}

impl Application {
    pub fn new(event_loop: &EventLoop<()>) -> Self {
        let window_builder = WindowBuilder::new().with_title("Rust OpenGL");
        let context = ContextBuilder::new()
            .with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
            .build_windowed(window_builder, event_loop)
            .expect("Cannot create windowed context");

        let context = unsafe {
            context
                .make_current()
                .expect("Failed to make context current")
        };

        gl::load_with(|ptr| context.get_proc_address(ptr) as *const _);
        unsafe {
            info::log_error("loading gl");
        }

        Self {
            control_flow: ControlFlow::default(),
            gl_context: context,
        }
    }

    pub fn set_control_flow_reference(&mut self, new_control_flow_reference: &mut ControlFlow) {
        let mut control_flow_reference = &mut self.control_flow;
        control_flow_reference = new_control_flow_reference;
    }

    pub fn set_control_flow(&mut self, new_control_flow: ControlFlow) {
        let control_flow_reference = &mut self.control_flow;
        *control_flow_reference = new_control_flow;
    }

    pub fn gl_context(&self) -> &ContextWrapper<PossiblyCurrent, Window> {
        &self.gl_context
    }
}

pub trait Handler<E> {
    fn handle(&mut self, item: E);
}

pub trait Dispatcher<'a> {
    fn new(application: &'a mut Application) -> Self;
    fn application(&'a self) -> &'a Application;
}

pub struct GenericDispatcher<'a> {
    application: &'a mut Application,
    angle_x: Deg<f32>,
    angle_y: Deg<f32>,
    cube: Cube
}

impl<'a> Handler<Event<'_, ()>> for GenericDispatcher<'a> {
    fn handle(&mut self, item: Event<'_, ()>) {
        match item {
            Event::NewEvents(_) => {}
            Event::WindowEvent { window_id, event } => {
                let mut window_dispatcher = WindowDispatcher::new(self.application);
                window_dispatcher.handle(event);
            }
            Event::DeviceEvent { device_id, event } => {
                let mut device_dispatcher = DeviceDispatcher::new(self.application);
                device_dispatcher.handle(event);
            }
            Event::UserEvent(_) => {}
            Event::Suspended => {}
            Event::Resumed => {}
            Event::MainEventsCleared => {}
            Event::RedrawRequested(_) => {
                println!("angle_y: {:?}", self.angle_y);
                println!("angle_x: {:?}", self.angle_x);

                let size = self.application.gl_context().window().inner_size();
                let width = size.width;
                let height = size.height;
                let aspect_ratio = width as f32 / height as f32;

                let rotation_matrix_y = Matrix4::from_axis_angle(Vector3::unit_y(), self.angle_y);
                let rotation_matrix_x = Matrix4::from_axis_angle(Vector3::unit_x(), self.angle_x);
                let model_matrix = rotation_matrix_y * rotation_matrix_x;

                let model_location = self.cube.gl_wrapper().program.get_uniform_location("Model").unwrap();
                // let view_location = cube.gl_wrapper().program.get_uniform_location("View").unwrap();
                // let projection_location = cube.gl_wrapper().program.get_uniform_location("Projection").unwrap();

                unsafe {
                    gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_matrix.as_ptr());
                    // gl::UniformMatrix4fv(view_location as GLint, 1, gl::FALSE, view_matrix.as_ptr());
                    // gl::UniformMatrix4fv(projection_location as GLint, 1, gl::FALSE, projection_matrix.as_ptr());

                    gl::Enable(gl::DEPTH_TEST);
                    gl::DepthFunc(gl::LESS);
                    gl::ClearColor(0.0,  0.0,  0.0,  1.0);
                    gl::Clear(gl::COLOR_BUFFER_BIT);
                    gl::Clear(gl::DEPTH_BUFFER_BIT);

                    Renderer::draw(&self.cube);
                }
                self.application.gl_context().swap_buffers().unwrap();
            }
            Event::RedrawEventsCleared => {}
            Event::LoopDestroyed => {}
        }
    }
}

还有更多的调度程序结构体,但它们与通用调度程序相同,只是改变了handle方法。

这可行,但窗口会阻塞,我认为这是因为我只使用一个线程。

问题是我的应用程序结构包装器内的“glutin”模块中的 ContextWrapper 结构没有实现在线程之间共享数据的特征(同步和发送特征)。

我也尝试过这个:

struct EventQueue<T> {
    data: Mutex<VecDeque<T>>,
}

impl<T> EventQueue<T> {
    fn new() -> Self {
        Self {
            data: Mutex::new(VecDeque::new()),
        }
    }

    // Add an event to the queue
    fn push(&self, value: T) {
        self.data.lock().unwrap().push_back(value);
    }

    // Remove and return an event from the queue
    fn pop(&self) -> Option<T> {
        self.data.lock().unwrap().pop_front()
    }
}

fn main() {
    let event_loop = EventLoop::new();
    let mut app = Application::new(&event_loop);

    event_loop.run(move |event, _, control_flow| {
        app.set_control_flow_reference(control_flow);
        app.set_control_flow(ControlFlow::Wait);

        let event_queue = Arc::new(EventQueue::new());

        // Clone the event data before the closure
        let event_data = event.clone();

        // Spawn worker threads
        for _ in 0..4 {
            let event_queue = Arc::clone(&event_queue);
            thread::spawn(move || {
                loop {
                    if let Some(event) = event_queue.pop() {
                        println!("Event: {:?}", event);
                    }
                }
            });
        }

        // Push the cloned event data into the queue
        event_queue.push(event_data);
    });
}

但是我收到错误,该事件正在逃避关闭。

我该如何解决这个问题?我应该找到处理事件的解决方法吗? 对于任何错误以及我糟糕的编写代码/ Rust 知识,提前表示歉意。

rust opengl events dispatcher
1个回答
0
投票

这可行,但窗口会阻塞,我认为这是因为我只使用一个线程。

是的,它“确实”,但那是因为你告诉它的。

您不需要使用单独的线程。你只需要告诉 glutin 不要等待下一个事件。

您将

ControlFlow::Wait
分配给
control_flow
,字面意思是“等待,直到有新事件”。因此,如果您没有按任何按钮或移动鼠标,则“不会”发出新事件。相反,您想要的是
ControlFlow::Poll
,这会导致您的事件处理程序被连续调用,而不管是否有任何待处理的事件。

所以改变这个:

*control_flow = ControlFlow::Wait;

进入此:

*control_flow = ControlFlow::Poll;

现在,接下来你还需要告诉 glutin 发出一个新的

Event::RedrawRequested
。您可以通过拨打
window.request_redraw()
来完成此操作。请注意,这必须
Event::MainEventsCleared
中完成。

因此您需要将以下匹配臂添加到您的

match event

match event {
    ...
    Event::MainEventsCleared => {
        gl_context.window().request_redraw();
    }
    _ => (),
}

顺便说一句,您只提到了 Rust 新手,而不是 OpenGL 新手。然而,OpenGL 和多线程并没有真正很好地混合在一起。因此,除非您对此有经验,否则我将避免在混合中引入线程。

是的,您当然可以将线程用于非图形相关的东西。然而,线程可能也应该与事件循环保持一定的距离。 但是,我对明胶的记忆不够,无法对此给出明确的答案。

© www.soinside.com 2019 - 2024. All rights reserved.