简体   繁体   中英

Need help creating a plugin system for the game engine

Github repository

A little about how the plugin system should roughly work.

The Plugin trait implements the build() method, which is called when the plugin is loaded. The build() method takes App struct, where App is the application structure.

Now my code looks cumbersome. So the question is, are there any other options?

pub trait Component {
    fn start(&mut self);
    fn update(&mut self);
}

pub struct App {
    runners: Vec<Box<dyn Fn(&mut App)>>,
    components: Vec<Box<dyn Component>>,
}

&mut App

Initially, the build() method took &mut App , but this did not allow me to do the following:

impl Plugin for WinitWSIPlugin {
    fn build(&self, app: &mut App) {
        app.set_runner(move || {
            // Error here
            app.open_window(...);
        });
    }
}

As I understood the compiler did not allow me to do this, because I passed a reference to the App structure, which may soon be irrelevant.

Rc<RefCell>

In the following implementation, I passed Rc<RefCell<App>> , but calling borrow_mut() to call open_window(...) had a panic, even though I had not manually called borrow_mut() before.

impl Plugin for WinitWSIPlugin {
    fn build(&self, app: Rc<RefCell<App>>) {
        app.clone().borrow().set_runner(move || {
            let AppSystem::Windowed { info } = app.borrow().system();
            let mut winit_windows = WinitWindows::default();
            let event_loop = winit::event_loop::EventLoop::new();

            /*===== Panic this =====*/
            app.borrow_mut().open_window(winit_windows.create_window(
                &event_loop,
                app.borrow().id(),
                &info,
            ));
        });
    }
}

The last revision I stopped at.

Using Mutex in those fields of the App structure that will be used in the plugins. That way, I won't have to call borrow_mut() even if I need to change the value. It will be enough to call borrow()

impl Plugin for WinitWSIPlugin {
    fn build(&self, app: Rc<RefCell<App>>) {
        app.clone().borrow().set_runner(move || {
            let AppSystem::Windowed { info } = app.borrow().system();
            let mut winit_windows = WinitWindows::default();
            let event_loop = winit::event_loop::EventLoop::new();
            app.borrow().open_window(winit_windows.create_window(
                &event_loop,
                app.borrow().id(),
                &info,
            ));

            for component in app.borrow().components().borrow_mut().iter_mut() {
                component.init(app.clone());
            }

            let app = app.clone();
            event_loop.run(move |event, _, control_flow| {
                control_flow.set_wait();

                for component in app.borrow().components().borrow_mut().iter_mut() {
                    component.update(app.clone());
                }

                match event {
                    winit::event::Event::WindowEvent {
                        window_id: _,
                        event,
                    } => match event {
                        winit::event::WindowEvent::CloseRequested => control_flow.set_exit(),
                        _ => (),
                    },
                    _ => (),
                }
            });
        });
    }
}

You could get around having to share App (which I'm not sure will work anyways) by just passing it into the closure as a parameter:

struct App {
    runners: Vec<Box<dyn Fn(&mut App)>>,
}
impl App {
    fn set_runner(&mut self, f: impl Fn(&mut App) + 'static) {
        self.runners.push(Box::new(f));
    }
    fn run(&mut self) {
        // take the runners out of self to avoid borrow & mutable borrow at the same time
        let runners = std::mem::take(&mut self.runners);
        for runner in runners.iter() {
            // pass self into the runner so it can change app state and create windows etc
            runner(self);
        }
        // put the runners back.
        self.runners = runners;
    }
    fn open_window(&mut self) {}
}

trait Plugin {
    fn build(&self, app: &mut App);
}
struct WinitWSIPlugin;
impl Plugin for WinitWSIPlugin {
    fn build(&self, app: &mut App) {
        // the closure now takes a `&mut App` as parameter
        // the argument type is optional and just shown for demonstration here
        app.set_runner(move |app: &mut App| {
            app.open_window();
        });
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM