Registering timers within nested function #88
-
I have the following python code using reapy that I'm trying to emulate (reduced for clarity): def on_event(previous_project, previous_project_timestamp):
"""
Continuously defer the script until the user changes project tabs
or saves the project by checking the timestamp of the project file.
If the session is a new and empty project without a name or path,
reset the tab so that we can catch the next event without a panic.
"""
# Check for tab change
if not previous_project.is_current_project:
current_project = reapy.Project()
if not current_project.name and current_project.path:
return reapy.defer(lambda: reset_tab())
rpp_filepath = get_rpp_filepath(current_project)
current_project_timestamp = get_file_timestamp(rpp_filepath)
session_data = get_session_data(current_project, rpp_filepath, current_project_timestamp, ReaperEventTrigger.OnTab)
send_session_data_to_daemon(session_data)
previous_project = current_project
previous_project_timestamp = current_project_timestamp
# Check for file save (modified timestamp)
else:
rpp_filepath = get_rpp_filepath(previous_project)
previous_project_current_timestamp = get_file_timestamp(rpp_filepath)
if previous_project_current_timestamp != previous_project_timestamp:
session_data = get_session_data(previous_project, rpp_filepath, previous_project_current_timestamp, ReaperEventTrigger.OnSave)
send_session_data_to_daemon(session_data)
previous_project_timestamp = previous_project_current_timestamp
return reapy.defer(lambda: on_event(previous_project, previous_project_timestamp))
def reset_tab():
"""Reset the current tab until another tab is opened, or new session is saved."""
project = reapy.Project()
if project.name and project.path:
rpp_filepath = get_rpp_filepath(project)
timestamp = get_file_timestamp(rpp_filepath)
trigger = ReaperEventTrigger.OnSave if not project.tracks else ReaperEventTrigger.OnTab
session_data = get_session_data(project, timestamp, trigger)
send_session_data_to_daemon(session_data)
on_event(project, timestamp)
else:
reapy.defer(lambda: reset_tab())
def main():
"""Wait to kick off the process until a session is opened, or new session is saved."""
project = reapy.Project()
if project.name and project.path:
rpp_filepath = get_rpp_filepath(project)
timestamp = get_file_timestamp(rpp_filepath)
trigger = ReaperEventTrigger.OnSave if not project.tracks else ReaperEventTrigger.OnStart
session_data = get_session_data(project, rpp_filepath, timestamp, trigger)
send_session_data_to_daemon(session_data)
on_event(project, timestamp)
else:
reapy.defer(lambda: main())
main() So far with my rust plugin I have the following, but I realized that I need a mutable session in order to register (defer) another function. How exactly would I go about doing that? I'm not very familiar with using #[reaper_extension_plugin]
pub fn plugin_main(context: PluginContext) -> Result<(), Box<dyn std::error::Error>> {
let mut session = ReaperSession::load(context);
session.plugin_register_add_timer(on_start)?; // This is the start of the actual plugin logic that runs on a loop.
// TODO: Don't ignore if this fails
let _ = REAPER_SESSION.set(Fragile::new(session)); // Avoid session going out of scope and being dropped.
Ok(())
}
/// The entrypoint to the plugin itself, after the extension has been bootstrapped.
extern "C" fn on_start() {
let session = reaper_session();
let reaper = session.reaper();
let Some(project) = Project::load(reaper) else {
return; // wait for a project to be opened
};
if !project.name().is_empty() && project.file().path().exists() {
// TODO: Refactor this logic, probably flawed but worked in python...
let trigger = if project.is_empty() {
event::EventTrigger::OnSave
} else {
event::EventTrigger::OnStart
};
if let Some(Err(err)) = get_session_data(project, trigger)
.map(|session_data| send_session_data_to_daemon(&session_data))
{
if err.kind() == std::io::ErrorKind::ConnectionRefused {
// TODO: Any time the connection is refused we should kill the plugin, but not REAPER.
// In the future maybe we can make a feature where the user can restart the plugin
// from a GUI element instead of having to restart REAPER.
reaper.show_message_box(
format!(
"Error: failed to send data to daemon: {err:?}
Please check that the daemon is running and restart cloud-reaper."
),
"cloud-reaper: error",
reaper_medium::MessageBoxType::Okay,
);
// this kills the REAPER thread, see the above...
std::process::exit(1)
}
}
}
}
extern "C" fn on_event(previous_project: Project) {
let reaper = reaper_session().reaper();
if !previous_project.is_current_project() {
let project = Project::load(reaper);
if project.as_ref().is_some_and(|current_project| {
!current_project.name().is_empty() && current_project.file().path().exists()
}) {
let Some(project) = project else {
return;
};
if let Some(Err(err)) = get_session_data(project.clone(), EventTrigger::OnTab)
.map(|session_data| send_session_data_to_daemon(&session_data))
{
if err.kind() == std::io::ErrorKind::ConnectionRefused {
// TODO: Any time the connection is refused we should kill the plugin, but not REAPER.
// In the future maybe we can make a feature where the user can restart the plugin
// from a GUI element instead of having to restart REAPER.
reaper.show_message_box(
format!(
"Error: failed to send data to daemon: {err:?}
Please check that the daemon is running and restart cloud-reaper."
),
"cloud-reaper: error",
reaper_medium::MessageBoxType::Okay,
);
// this kills the REAPER thread, see the above...
std::process::exit(1)
}
}
} else {
// todo
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 2 replies
-
The only other approach I could think of is to use global variables within the plugin_main function instead, and then conditional statements for which timer to register but that seems pretty clunky |
Beta Was this translation helpful? Give feedback.
-
I managed to be able to get a mutable handle to the reaper_session variable using |
Beta Was this translation helpful? Give feedback.
-
Just reporting back here, I was able to get a fully functioning extension plugin with this method! I'll showcase it later in the show and tell section (: |
Beta Was this translation helpful? Give feedback.
-
Yes, accessing global state is the normal way for timers, also on the C++ side. Because timers don't support passing any user data. But internal control surfaces do because they are represented as real classes in the C++ SDK for REAPER, not just flat functions. The Example: use reaper_medium::ControlSurface;
#[derive(Debug)]
struct MyControlSurface {
count: u64
}
impl ControlSurface for MyControlSurface {
fn run(&mut self) {
println!("Main loop cycle {}", self.count);
self.count += 1;
}
}
session.plugin_register_add_csurf_inst(Box::new(MyControlSurface { count: 0 })); For Playtime and ReaLearn, I use this way mainly because I also need to get notified about various events occurring in REAPER, which control surfaces support (they have other methods than just |
Beta Was this translation helpful? Give feedback.
I managed to be able to get a mutable handle to the reaper_session variable using
once_cell::sync::OnceCell<std::sync::Mutex<ReaperSession>>
then registering and unregistering timers that way, but I"m not sure if that's how it's intended to be used.