after-effects icon indicating copy to clipboard operation
after-effects copied to clipboard

High level interface

Open AdrianEddy opened this issue 7 months ago • 25 comments

I'm designing a high level plugin interface for the bindings, and my general idea is to have something like:

struct PortablePlugin { }
impl AdobePlugin for PortablePlugin {
    fn can_load(_host_name: &str, _host_version: &str) -> bool { true }
    fn new() -> Self { Self {} }

    fn handle_command(&mut self, cmd: Command) {
        match cmd {
            Command::About { mut out_data, .. } => {
                out_data.set_return_msg("Test plugin."));
            }
            Command::Render { in_data, mut params, output, .. } => {
                // ...
            }
            Command::GpuDeviceSetup { in_data, mut out_data, extra/*: GPUDeviceSetupExtra*/ } => { ... }
            // ...
        }
    }
}

register_plugin!(PortablePlugin);

basically the idea is to only expose relevant and valid data in each command, including all extras properly casted. I have that implemented so far, but I have a few concerns I wanted to discuss:

Struct instances

  1. Should we create and maintain global instance for user? What I imagine is that PortablePlugin struct would be stored in global_data and would be a single object alive for the entire time the project is open (and any plugin instance is added to the layer)
  2. Should we create and maintain sequence instance for user? That would be a separate struct, which is unique per every layer where the plugin is applied. This would be stored in sequence_data and I imagine I would require the user to make this struct serde::Serialize and serde::Deserialize and automatically handle flattening the data with bincode. Also that struct should be Send + Sync I believe, because AE can call the commands from any thread at any time? Is that correct?

I believe these two points are very useful for almost all plugins and at the same time quite time-consuming to implement properly by the user. Pros:

  • User can focus on just his actual plugin code without worrying about complicated details of Handles and flattening
  • Much easier to get started with writing plugins

Cons:

  • A bit of memory overhead, since the instances would be always created. But since we're talking about video processing plugins here, any overhead like that is nothing compared to actual pixels processing so it should be fine. We could maybe make this optional with a register_plugin macro parameter or something

Parameters

  1. What do you think to create something like ParameterRegistry, which would be a manager for user params, with the enum-based getter/setter provided by user, and all parameters would be defined and retrieved using this enum. Something like:
enum MyParams {
    MixChannels
};
fn handle_command(&mut self, cmd: Command) {
    match cmd {
        Command::ParamsSetup { mut params } => {
            params.add_param(MyParams::MixChannels, 
                "Mix channels", 
                ae::FloatSliderDef::new()
                    .set_value(10.0)
                    .precision(1)
                    .display_flags(ae::ValueDisplayFlag::PERCENT)
            );
        }
        Command::Render { mut params, .. } => {
            let slider = params.get::<ae::Param::FloatSlider>(MyParams::MixChannels);
            // slider.value()
        }
    }
}
register_plugin!(PortablePlugin, MyParams);

That struct would maintain params array, indexes and counts (and would be stored in global instance data), so users won't have to worry about it.

I'll have more things to discuss once I get further, but let's start with these.

Let me know your thoughts, thanks!

CC @virtualritz @mobile-bungalow

AdrianEddy avatar Dec 24 '23 03:12 AdrianEddy