Skip to content

Commit

Permalink
Add Helgobox extension and Helgobox runtime API mechanism draft
Browse files Browse the repository at this point in the history
  • Loading branch information
helgoboss committed Nov 28, 2023
1 parent a8e1ac9 commit 97ce7e4
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CONTRIBUTING.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ See link:ARCHITECTURE.adoc[here].
|`/csi` |Code for interfacing with the ControlSurfaceIntegrator (CSI) project
|`/dialogs` |The single source of truth for ReaLearn's GUI dialogs
|`/doc` |Documentation
|`/extension` |Helgobox REAPER extension (provides some additional convenience around the actual ReaLearn plug-in)
|`/helgoboss-license-processor` |Contains code for license processing (currently a private submodule)
|`/macros` |Various Rust macros for usage in this project only
|`/main` |Main crate (`realearn`)
|`/main` |Main crate: The actual ReaLearn instrument plug-in (`realearn`)
|`/playtime-api` |Playtime data structures for describing e.g. clip engine presets
|`/playtime-clip-engine` |Playtime Clip Engine for playing/recording clips (currently a private submodule)
|`/playtime-clip-engine-placeholder` |A placeholder crate for the Playtime Clip Engine. Users who don't have access to
Expand Down
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"allocator",
"api",
"dialogs",
"extension",
"macros",
"playtime-clip-engine",
"playtime-api",
Expand Down Expand Up @@ -96,6 +97,7 @@ thiserror = "1.0.45"
enum_dispatch = "0.3.6"
tinyvec = "1.6.0"
erased-serde = "0.3.31"
fragile = "2.0.0"

[profile.release]
# This is important for having line numbers in bug reports.
Expand Down
1 change: 1 addition & 0 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ default = []
playtime = ["dep:playtime-api"]

[dependencies]
reaper-low.workspace = true
serde.workspace = true
semver.workspace = true
schemars.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod persistence;

pub mod runtime;

mod root;
pub use root::*;
57 changes: 57 additions & 0 deletions api/src/runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use reaper_low::raw::ReaProject;
use reaper_low::PluginContext;
use std::ffi::{c_char, c_long, c_void, CStr};
use std::mem::transmute;

macro_rules! api {
($func_name:ident ($( $param_name:ident: $param_type:ty ),*) -> $ret_type:ty) => {
pub struct HelgoboxApiPointers {
$func_name: Option<fn($( $param_name: $param_type ),*) -> $ret_type>,
}

impl HelgoboxApiPointers {
pub fn load(plugin_context: &PluginContext) -> Self {
unsafe {
Self {
$func_name: transmute(plugin_context.GetFunc(
concat!(stringify!($func_name), "\0").as_ptr() as *const c_char,
)),
}
}
}
}

pub struct HelgoboxApiSession {
pointers: HelgoboxApiPointers,
}

impl HelgoboxApiSession {
pub fn new(pointers: HelgoboxApiPointers) -> Self {
Self {
pointers
}
}

pub fn $func_name(&self, $( $param_name: $param_type ),*) -> $ret_type {
self.pointers.$func_name.unwrap()($( $param_name ),*)
}
}

pub trait HelgoboxApi {
extern "C" fn $func_name($( $param_name: $param_type ),*) -> $ret_type;
}

pub fn register_helgobox_api<T: HelgoboxApi>(mut register_api_fn: impl FnMut(&CStr, *mut c_void)) {
unsafe {
register_api_fn(
CStr::from_ptr(concat!(stringify!($func_name), "\0").as_ptr() as *const c_char),
T::$func_name as *mut c_void,
);
}
}
};
}

api![
HB_FindFirstInstanceInProject(project: *const ReaProject) -> c_long
];
17 changes: 17 additions & 0 deletions extension/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "helgobox-extension"
version = "0.1.0"
edition = "2021"

[dependencies]
reaper-medium = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" }
reaper-low = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" }
reaper-macros = { git = "https://github.com/helgoboss/reaper-rs.git", branch = "master" }
anyhow.workspace = true
base.workspace = true
realearn-api.workspace = true
fragile.workspace = true

[lib]
name = "reaper_helgobox"
crate-type = ["cdylib"]
67 changes: 67 additions & 0 deletions extension/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use anyhow::Result;
use fragile::Fragile;
use realearn_api::runtime::{HelgoboxApiPointers, HelgoboxApiSession};
use reaper_low::PluginContext;
use reaper_macros::reaper_extension_plugin;
use reaper_medium::{reaper_str, CommandId, HookCommand, OwnedGaccelRegister, ReaperSession};
use std::error::Error;
use std::ptr::null;
use std::sync::OnceLock;

static EXTENSION: OnceLock<HelgoboxExtension> = OnceLock::new();

fn extension() -> &'static HelgoboxExtension {
EXTENSION
.get()
.expect("Helgobox extension not yet initialized")
}

#[reaper_extension_plugin]
fn plugin_main(context: PluginContext) -> std::result::Result<(), Box<dyn Error>> {
let _ = EXTENSION.set(HelgoboxExtension::load(context)?);
Ok(())
}

struct HelgoboxExtension {
my_command_id: CommandId,
reaper_session: Fragile<ReaperSession>,
}

impl HelgoboxExtension {
pub fn load(context: PluginContext) -> Result<Self> {
let mut session = ReaperSession::load(context);
let my_command_id =
session.plugin_register_add_command_id(reaper_str!("HB_SHOW_HIDE_PLAYTIME"))?;
session.plugin_register_add_hook_command::<MyHookCommand>()?;
session.plugin_register_add_gaccel(OwnedGaccelRegister::without_key_binding(
my_command_id,
"Show/hide Playtime",
))?;
let extension = Self {
my_command_id,
reaper_session: Fragile::new(session),
};
Ok(extension)
}
}

struct MyHookCommand;

impl HookCommand for MyHookCommand {
fn call(command_id: CommandId, _flag: i32) -> bool {
if command_id != extension().my_command_id {
return false;
}
let plugin_context = extension()
.reaper_session
.get()
.reaper()
.low()
.plugin_context();
let pointers = HelgoboxApiPointers::load(&plugin_context);
let session = HelgoboxApiSession::new(pointers);
let res = session.HB_FindFirstInstanceInProject(null());
println!("Executing my command: {res}!");
true
}
}
35 changes: 35 additions & 0 deletions main/src/infrastructure/plugin/api_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use realearn_api::runtime::{register_helgobox_api, HelgoboxApi};
use reaper_high::Reaper;
use reaper_low::raw::ReaProject;
use reaper_medium::{ReaperStr, RegistrationObject};
use std::borrow::Cow;
use std::ffi::c_long;

struct HelgoboxApiImpl;

impl HelgoboxApi for HelgoboxApiImpl {
extern "C" fn HB_FindFirstInstanceInProject(project: *const ReaProject) -> c_long {
42
}
}

pub fn register_api() {
let mut session = Reaper::get().medium_session();
register_helgobox_api::<HelgoboxApiImpl>(|name, ptr| unsafe {
session.plugin_register_add(RegistrationObject::Api(
Cow::Borrowed(ReaperStr::from_ptr(name.as_ptr())),
ptr,
));
});
}

// Finds the first Helgobox instance in the given project.
//
// If the given project is `null`, it will look in the current project.
//
// Returns the instance ID or -1 if none exists.

// Shows or hides the app for the given Helgobox instance and makes sure that the app displays
// Playtime.
//
// If necessary, this will also start the app and create a clip matrix for the given instance.
2 changes: 2 additions & 0 deletions main/src/infrastructure/plugin/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use base::{
use enum_iterator::IntoEnumIterator;

use crate::base::allocator::{RealearnAllocatorIntegration, RealearnDeallocator, GLOBAL_ALLOCATOR};
use crate::infrastructure::plugin::api_impl::register_api;
use crate::infrastructure::plugin::debug_util::resolve_symbols_from_clipboard;
use crate::infrastructure::plugin::tracing_util::TracingHook;
use crate::infrastructure::server::services::RealearnServices;
Expand Down Expand Up @@ -186,6 +187,7 @@ impl App {
support_email_address: "[email protected]".to_string(),
},
);
register_api();
let config = AppConfig::load().unwrap_or_else(|e| {
debug!(App::logger(), "{}", e);
Default::default()
Expand Down
1 change: 1 addition & 0 deletions main/src/infrastructure/plugin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod api_impl;
mod debug_util;
mod realearn_editor;
mod tracing_util;
Expand Down

0 comments on commit 97ce7e4

Please sign in to comment.