Простой диспетчер событий в Rust

Я пытаюсь разработать простое приложение для создания простых объектов (например, куба) с помощью OpenGL.

На данный момент я создал оболочку для OpenGL, используя модуль «gl» для инициализации vbos, vaos, программ, шейдеров и т. д.

Первоначально я обрабатывал интересующие меня события в основной функции, используя модуль «glutin». Код выглядел примерно так:

main.rs

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, я мог вращать куб соответственно влево и вправо; Затем я немного изменил код, но мне бы хотелось сосредоточиться на части обработки событий.

Как видите, кода очень много, особенно если все это находится в одной и той же функции — основной функции. Я так подумал, что было бы неплохо немного отрефакторить этот беспорядок, и придумал следующее:

main.rs

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

    event_loop.run(move |event, _, control_flow| {
        // Commenting these out as i'm not sure it works
        // app.set_control_flow_reference(control_flow);
        // app.set_control_flow(ControlFlow::Poll);
        
        *control_flow = ControlFlow::Poll;

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

application.rs

#[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
    }
}

обработчик.rs

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

generic.rs

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

// snip the impl for the new associated function

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 => {
                self.application.gl_context().window().request_redraw();
            }
            Event::RedrawRequested(_) => {
                println!("angle_y: {:?}", self.angle_y);
                println!("angle_x: {:?}", self.angle_x);

                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();

                unsafe {
                    gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_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 => {}
        }
    }
}

окно.рс

pub struct WindowDispatcher<'a> {
    application: &'a mut Application
}

impl<'a> Handler<WindowEvent<'_>> for WindowDispatcher<'a> {
    fn handle(&mut self, item: WindowEvent<'_>) {
        let mut keyboard_dispatcher = KeyboardDispatcher::new(self.application);
        match item {
            WindowEvent::Resized(physical_size) => {
                self.application.gl_context().resize(physical_size);
            }
            WindowEvent::Moved(_) => {}
            WindowEvent::CloseRequested => {
                self.application.set_control_flow(ControlFlow::Exit);
            }
            WindowEvent::Destroyed => {}
            WindowEvent::DroppedFile(_) => {}
            WindowEvent::HoveredFile(_) => {}
            WindowEvent::HoveredFileCancelled => {}
            WindowEvent::ReceivedCharacter(_) => {}
            WindowEvent::Focused(_) => {}
            WindowEvent::KeyboardInput { device_id, input, is_synthetic } => {
                keyboard_dispatcher.handle(input);
            }
            WindowEvent::ModifiersChanged(_) => {}
            WindowEvent::Ime(_) => {}
            WindowEvent::CursorMoved { device_id, position, modifiers } => {}
            WindowEvent::CursorEntered { device_id } => {}
            WindowEvent::CursorLeft { device_id } => {}
            WindowEvent::MouseWheel { device_id, delta, phase, modifiers } => {}
            WindowEvent::MouseInput { device_id, state, button, modifiers } => {}
            WindowEvent::TouchpadPressure { device_id, pressure, stage } => {}
            WindowEvent::AxisMotion { device_id, axis, value } => {}
            WindowEvent::Touch(_) => {}
            WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size } => {}
            WindowEvent::ThemeChanged(_) => {}
            WindowEvent::Occluded(_) => {}
        }
    }
}

клавиатура.rs

pub struct KeyboardDispatcher<'a> {
    application: &'a mut Application
}

impl<'a> Handler<KeyboardInput> for KeyboardDispatcher<'a> {
    fn handle(&mut self, item: KeyboardInput) {
        match item {
            KeyboardInput { scancode, state, virtual_keycode, modifiers } => {
                match state {
                    ElementState::Pressed => {
                        println!("{:?} pressed!", virtual_keycode.unwrap());
                    }
                    ElementState::Released => {
                        println!("{:?} released!", virtual_keycode.unwrap());
                    }
                }
            }
        }
    }
}

При выполнении программы я получаю только белое окно, ничего не рисуется и, похоже, не фиксируется ни одно событие. Я не могу даже переместить окно или закрыть его: чтобы остановить программу, мне приходится принудительно остановить выполнение.

Затем я попытался закомментировать создание WindowDispatcher и DeviceDispatcher и вызов Renderer::draw() внутри метода handle метода GenericDispatcher, чтобы увидеть, была ли в этом проблема:

impl<'a> Handler<Event<'_, ()>> for GenericDispatcher<'a> {
    fn handle(&mut self, item: Event<'_, ()>) {
            // snip
            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);
            }
            // snip
            Event::RedrawRequested(_) => {
                // snip
                unsafe {
                    gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_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);
                }
                // snip
        }
    }
}

Но я все еще сталкиваюсь с той же проблемой. Иногда, вообще не меняя код, получаю черное окно и печатаю углы, как и положено, но потом все снова зависает и приходится принудительно останавливать выполнение.

Как я могу это исправить?

Вы используете старую версию глютина? Я не могу разобраться в некоторых типах. Не могли бы вы включить список зависимостей?

vallentin 05.03.2024 11:38

@vallentin Да, это так. Мне не удалось заставить работать более новую версию; [зависимости] cgmath = "0.18.0" gl = "0.14.0" Glutin = "0.29.1" thiserror = "1.0.56"

Luca 05.03.2024 11:47
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
2
137
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это работает, но окно блокируется, и я думаю, это потому, что я использую только один поток.

Да, так и есть, но это потому, что вы так сказали.

Вам не нужно использовать отдельные потоки. Вам просто нужно сказать Глютину не ждать следующего события.

Вы назначаете 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 и многопоточность не очень хорошо сочетаются. Так что, если у вас нет опыта в этом, я бы воздержался от добавления тем в эту смесь.

Да, вы, конечно, можете использовать потоки для вещей, не связанных с графикой. Однако потоки, вероятно, также должны оставаться в стороне от цикла событий. Однако я недостаточно помню о глютине, чтобы дать однозначный ответ на этот вопрос.

Большое спасибо за ваш ответ. Да, я тоже новичок в OpenGL. Я попробовал ваше решение, но выполнение программы все равно почему-то заблокировано.

Luca 05.03.2024 13:02

@Лука, пожалуйста! Не стесняйтесь обновлять код в вашем вопросе. Тогда я смогу взглянуть на него еще раз. Вы наверное просто что-то не туда добавили :)

vallentin 05.03.2024 13:05

@Лука Эй, извини за задержку. Итак, я еще раз проверил ваш код, я действительно его протестировал. С моей стороны все работает так, как ожидалось. Когда вы запускаете его, angle_x и angle_y не выводятся постоянно на консоль? Если нет, то у вас может быть что-то в WindowDispatcher, DeviceDispatcher или Renderer::draw(). Вы их не предоставили, поэтому я удалил их при тестировании. Делая это, и он постоянно рисует

vallentin 06.03.2024 01:33

Это странно. Я также закомментировал строки внутри метода handle структуры GenericDispatcher, где я создаю два диспетчера и вызов Renderer::draw(), но я все равно получаю замороженное окно, и на консоли ничего не печатается. При необходимости могу предоставить полный код.

Luca 06.03.2024 13:32

Собственно, иногда, вообще не меняя код, я получаю как и ожидалось черное окно, углы печатаются один раз, но после этого из окна вообще нет ответа, и мне приходится принудительно останавливать выполнение.

Luca 06.03.2024 13:39

@Лука, что ты делаешь в WindowDispatcher и DeviceDispatcher, там что-то похоже, может привести к блокировке

vallentin 06.03.2024 13:48
Ответ принят как подходящий

Кому интересно, мне удалось решить свою проблему.

Вот 5 ключевых шагов:

1. Исправить зависимости

Оказывается, вам нужна более свежая версия ящиков gl и glutin, а также ящики winit, glutin-winit и raw-window-handle.

2. Создавайте собственные события

Используйте перечисление для каждого события, которое может понадобиться вашему приложению.

pub enum CustomEvent {
    Log(String),
    // others
}

3. Создайте диспетчера

Используйте структуру для хранения полезных данных (например, прокси-сервера, используемого для отправки данных в приложение):

use winit::event_loop::EventLoopProxy;

pub struct Dispatcher {
    // Make sure to pass as generic the enum created in point 2.
    proxy: EventLoopProxy<CustomEvent>,
    // others
}

и реализовать методы для обработки событий:

    pub fn handle_key_pressed(&mut self, physical_key: PhysicalKey, logical_key: Key) {

        // Match physical and logical keys
        match logical_key {
            Key::Named(named) => match named {
                // Implement logic

                // Send event to the application
                self.proxy.
                    send_event(CustomEvent::Log("hello".to_string())
                    .unwrap();
            }
    }

    pub fn handle_mouse_pressed(&self) {
        println!("Mouse pressed");
    }

    // Implement other methods

4. Внедрите черту ApplicationHandler в свое приложение.

Определите структуру Application (или как вы хотите ее назвать) и реализуйте эту черту из winit::application:

use winit::application::ApplicationHandler;

pub struct Application {
   // your fields
}

В частности, обратите внимание на функцию user_event:


// Once again use as generic your CustomEvent enum
impl ApplicationHandler<CustomEvent> for Application {
    // Implement every method


    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: CustomEvent) {    
        // Match events received from the Dispatcher event proxy
        match event {
            CustomEvent::Log(str) => {
                println!("{str}");
            }
            // Implement your logic
        }
    }

}

5. Инициализируйте и запустите приложение.

// Initialize your app
let app = Application::new();

// Create the event loop (and for the 3rd time, make sure to use the your CustomEvent enum)
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();

// Create the event loop proxy
let proxy = event_loop.create_proxy();

// Create your dispatcher, which will need the proxy
let dispatcher = Dispatcher::new(proxy);

// Feel free to organize your code as you like (e.g. store the dispatcher inside the Application itself)

// Run the application
event_loop.run_app(app).unwrap();

Вот и все!

Спасибо всем за помощь.

Пожалуйста, знайте, что эта реализация может быть не самой лучшей или следовать идиоматическому Rust, я просто так это сделал.

Другие вопросы по теме