helgoboss / reaper-rs

Rust bindings for the REAPER C++ API
MIT License
80 stars 8 forks source link

alphabet medium-level covering #67

Open Levitanus opened 1 year ago

Levitanus commented 1 year ago

I'll move alphabetically through functions which can be straightforwardly wrapped. So, basically, this PR can be merged in master time to time without big concerns.

Levitanus commented 1 year ago

With this commit I'm wondering if there is more idiomatic way of unwrapping misc types, when underlying API wants 0, -1 or else.

For example:

let cmd: i32;
match command {
    None => cmd = 0,
    Some(id) => cmd = id.get() as i32,
}

I would like to write something like: command.unwrap_or(0) in low level function call body.

Levitanus commented 1 year ago

I've realized, that I not really understand the concept of usage scope. I mean, what should be in the main thread, and what — not. And should be this checked or not (are there any costs for this).

helgoboss commented 1 year ago

I've realized, that I not really understand the concept of usage scope. I mean, what should be in the main thread, and what — not. And should be this checked or not (are there any costs for this).

In general, you need to know that most functions of the REAPER API are main-thread only. If you call them in any other thread, it's undefined behavior ... and that should be avoided at all costs because undefined behavior often means crash. There are a few functions which can be called from any thread and others that must be called from a real-time thread.

The medium-level API attempts to provide safe functions (that "vow" not to exhibit undefined behavior), but of course only if that's possible without deviating from the original REAPER API function cut. That's often not possible. Fortunately, when it comes to avoiding undefined behavior due to usage from the wrong thread, it is possible 👍. That's why you find the self.require_main_thread() calls all over the place. It's an overhead, but it's super tiny and extremely worth it. I used wrong threading many years ago when I started writing Playtime because it worked on my machine. Later, I realized I got it all wrong and had to completely redesign parts of my architecture ... not good. A panic as built into reaper-rs would have saved me that effort (a panic is not nice but it's better than a crash or other undefined behavior). And I'm not the only one who fell into that trap: https://github.com/helgoboss/realearn/issues/705 ... in fact, I have the feeling that most extension writers are not aware of the threading restrictions in the beginning.

That's why I consider the panic generated by self.require_main_thread() as absolutely essential and you should use that as well whenever you build a main-thread only function. UsageScope is a modest attempt to fail at compilation time when attempting to call a medium-level function from the wrong thread. It also helps with auto-completion and is a kind of "documentation in code". However, it can't be more than that, it's quite limited. You can easily circumvent it, e.g. by consciously passing an instance of Reaper<MainThreadScope> to the audio thread and using it there. And in the high-level APIs where I use different structs and statics, this kind of type safety doesn't work at all. So it's less important than require_main_thread().

The real challenge for you as medium-level API contributor :) is to figure out if a function is main-thread only or not. Because there are exceptions. And they are not documented by Cockos :( Usually, I always assume main-thread only and later make the usage scope more broad if I realize that was wrong. It's safer and better in terms of staying backward compatible. If I need it in the real-time thread, I usually write Justin an email and ask him if it's okay to use it from there.

Levitanus commented 1 year ago

Wow. It became much clearer! Thank you.

But, where do we really going out of the MainThread? Do I understand corretly, that all API listeners (ActionHook, CSurf) are still in the main thread? And shit can happen, mostly in three cases: