Передача значения в функцию вне аргументов - PullRequest
1 голос
/ 04 мая 2019

Я получил библиотеку (nannou), которая хочет вызвать функцию с подписью fn(&'r nannou::App, nannou::Frame) -> nannou::Frame.

Мне нужно передать какое-то дополнительное значение в эту функцию (мой буфер изображений).

Мое приложение выглядит так:

fn main {
    let buff = Buff::generate(..);
    nannou::view(view);
}

fn view(app: &App, frame: Frame) -> Frame {...}

Мне нужно передать buff в view.Я пытался использовать partal_application , но Rust жалуется, что expected fn pointer, found closure.

Как я могу это сделать?Один, неправильный и безобразный способ, который я знал - использование глобальной переменной.

Есть ли лучшие способы?Какая лучшая практика для этого в Rust?

Ответы [ 2 ]

2 голосов
/ 13 июня 2019

Я думаю, что проблема здесь в том, что мы используем функцию view(..) как обратный вызов внутри для рисования графики. Таким образом, минимальная установка будет выглядеть так:

fn main() {
    nannou::sketch(view);
}

fn view(app: &App, frame: Frame) -> Frame {
    // Draw stuff
}

Однако, если вы хотите передать данные, тогда нам нужно использовать Model, например:

fn main() {
    nannou::app(model).update(update).run();
}

struct Model {
    my_data: MyData,
}

fn model(app: &App) -> Model {
    app
        .new_window()
        .with_dimensions(720, 720)
        .view(view)
        .build()
        .unwrap();
    let my_data = MyData::new();
    Model { my_data }
}

fn update(_app: &App, _model: &mut Model, _update: Update) {}

fn view(app: &App, model: &Model, frame: Frame) -> Frame {
    // Draw stuff
}

Обратите внимание, что функция вида имеет другую подпись при такой настройке. Он включает Model, в который вы можете поместить свои собственные данные. Он неизменен, пока вы хотите обновить его в функции update(), но при необходимости вы можете обойтись с помощью RefCell.

Обычно я запускаю другие потоки из функции model(), а затем использую каналы в Model для обратной связи с циклом nannou, например:

fn model(app: &App) -> Model {
    let (talk_to_nannou, data_from_my_thread) = mpsc::channel();
    thread::spawn(|| {
        //All the stuff I want to do
        talk_to_nannou.send("Hey");
    });
    Model {
        data_from_my_thread,
    };
}

fn view(app: &App, model: &Model, frame: Frame) -> Frame {
    if let Some(msg) = model.data_from_my_thread.try_recv() {
        dbg!(msg);
    }
}

Вы могли бы думать об этом иначе, если бы вы добавляли его в существующее приложение, например:

fn main() {
    // My awesome app that has heaps of cool stuff
    thread::spawn(|| {
        nannou::app(model).update(update).run();
    });
    // Do more stuff in my cool app
}

struct Model {
    my_data: MyData,
}

fn model(app: &App) -> Model {
    app.new_window()
        .with_dimensions(720, 720)
        .view(view)
        .build()
        .unwrap();
    let my_data = MyData::new();
    Model { my_data }
}

fn update(_app: &App, _model: &mut Model, _update: Update) {}

fn view(app: &App, model: &Model, frame: Frame) -> Frame {
    // Draw stuff
}

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

Ознакомьтесь с примерами и Guide для получения дополнительной информации

1 голос
/ 04 мая 2019

Похоже, что API nannou очень ограничен.Если бы он использовал черты Fn* в типе обратного вызова, вы могли бы использовать замыкание и захватить ваш дополнительный аргумент.Поскольку API является указателем на функцию, вы вынуждены использовать глобальное состояние для передачи данных.

Вот еще один способ сделать это.Я предполагаю, что ваши данные и функции выглядят так:

#[derive(Debug)]
struct ExtraData {
    data: usize,
}

type MyViewFn = fn(app: &nannou::App, frame: nannou::Frame, extra: &mut ExtraData) -> nannou::Frame;

fn my_callback(app: &nannou::App, frame: nannou::Frame, extra: &mut ExtraData) -> nannou::Frame {
    println!("{:?}", extra);
    frame
}

fn main() {
    call_view_with(my_callback, ExtraData { data: 42 });
}

То есть call_view_with упаковывает view, чтобы получить дополнительный аргумент.Это заставляет его работать так:

// This function is unsafe and should not be called concurrently to avoid difficult bugs
fn call_view_with(callback: MyViewFn, extra: ExtraData) {
    // static mut needs to be initialized with a constant expression for some reason
    static mut static_extra: ExtraData = ExtraData::default();
    // Using mutable static requires unsafe
    unsafe {
        static_extra.data = extra.data;
    }
    static mut static_func_ptr: MyViewFn = default_callback;
    unsafe {
        static_func_ptr = callback;
    }
    // Rust allows nested function definitions. They can not capture dynamic local variables,
    // only const and static variables.
    fn view_fn(app: &nannou::App, frame: nannou::Frame) -> nannou::Frame {
        unsafe { return static_func_ptr(app, frame, &mut static_extra) }
    }
    nannou::view(view_fn);
}


impl ExtraData {
    const fn default() -> Self {
        ExtraData { data: 0 }
    }
}

fn default_callback(
    app: &nannou::App,
    frame: nannou::Frame,
    extra: &mut ExtraData,
) -> nannou::Frame {
    frame
}

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

...