1

我有一个库(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 {...}

我需要传递buffview. 我尝试使用partial_application,但 Rust 抱怨说expected fn pointer, found closure

我怎样才能做到这一点?一种,我知道的错误和丑陋的方式 - 使用全局变量。

有没有更好的方法?Rust 的最佳实践是什么?

4

2 回答 2

2

似乎nannou's API 的限制非常严格。如果它使用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_withview行以获取额外的参数。使它起作用的原因是:

// 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全局要危险得多。我想其他函数至少不能以这种方式修改数据,但您仍然必须小心避免并发错误。

于 2019-05-04T14:55:45.873 回答
2

我认为这里的问题是我们在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东西塞进一个模块中,但这取决于你想如何安排事情。唯一的事情是,nannou 需要运行其内部循环来完成所有工作,但很高兴能在另一个线程上。

查看示例指南以获取更多信息

于 2019-06-13T00:39:26.253 回答