我有一个图书馆(nannou),wants to call a function的签名是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
。我尝试使用partial_application,但是Rust抱怨expected fn pointer, found closure
。
我该怎么做?我知道的一种错误,丑陋的方式-使用全局变量。
还有更好的方法吗?在Rust中,最佳做法是什么?
答案 0 :(得分: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需要运行其内部循环来完成所有工作,但是很高兴能在另一个线程上工作。
答案 1 :(得分:1)
Traceback (most recent call last):
File "/data/user/0/ru.iiec.pydroid3/files/temp_iiec_codefile.py", line 19, in <module>
buttonlist.append(Button(width=5, relief=SUNKEN, bg="Black", command=change_color(button_counter)))
File "/data/user/0/ru.iiec.pydroid3/files/temp_iiec_codefile.py", line 11, in change_color
buttonlist[button_number].configure(bg=color)
IndexError: list index out of range
的API似乎非常严格。如果它在回调类型中使用了nannou
特征,则可以使用闭包并捕获额外的参数。由于API是函数指针,因此您不得不使用全局状态来传递数据。
这是另一种方式。我假设您的数据和功能如下所示:
Fn*
也就是说,#[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
}
一样危险。我想其他功能至少不能以这种方式修改数据,但是您仍然必须小心避免并发错误。