Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ Extensive portions of Ae and Pr SDKs are wrapped:
| ✅ Command | ✅ AngleParam | ✅ Supplier | |
| ✅ Comp | 🔳 ANSI | ✅ Surface | |
| ✅ Composite | ✅ Background Frame | | |
| 🔳 Compute | 🔳 Batch Sampling | | |
| Compute | 🔳 Batch Sampling | | |
| ✅ Dynamic Stream | ✅ Cache On Load | | |
| ✅ Effect | ✅ Channel | | |
| 🔳 File Import Manager | ✅ Color Settings | | |
| ✅ Footage | ✅ Color Callbacks | | |
| 🔳 Hash | ✅ Color Callbacks 16 | | |
| Hash | ✅ Color Callbacks 16 | | |
| ✅ IO In | ✅ Color Callbacks Float | | |
| 🔳 IO Out | ✅ ColorParam | | |
| ✅ Item | ✅ Effect Custom UI | | |
Expand Down
3,563 changes: 1,997 additions & 1,566 deletions after-effects-sys/bindings_macos.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions after-effects-sys/wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include "AE_IO_FileExt.h"
#include "AE_Macros.h"
#include "AE_PluginData.h"
#include "AE_HashSuite.h"
#include "AE_ComputeCacheSuite.h"
#include "FIEL_Public.h"
#include "Mach-O_prefix.h"
#include "PF_Masks.h"
Expand All @@ -38,6 +40,7 @@
#include "PrSDKPixelFormat.h"
#include "SuiteHelper.h"


// Headers/SP subfolder
//#include "SPAccess.h"
//#include "SPAdapts.h"
Expand Down
3 changes: 2 additions & 1 deletion after-effects/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ artisan-2-api = ["after-effects-sys/artisan-2-api"]
default = []

[dependencies]
after-effects-sys = "0.3" #{path = "../after-effects-sys"}
after-effects-sys = "0.3"
#after-effects-sys = { path = "../after-effects-sys" }
bincode = { version = "2.0", features = ["serde"] }
bitflags = "2.9"
cstr-literal = "0.1"
Expand Down
6 changes: 6 additions & 0 deletions after-effects/src/aegp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub mod suites {
DynamicStreamSuite as DynamicStream };
pub(crate) mod utility; pub use utility ::UtilitySuite as Utility;
pub(crate) mod world; pub use world ::WorldSuite as World;
pub(crate) mod compute_cache; pub use compute_cache ::ComputeCacheSuite as ComputeCache;
pub(crate) mod hash; pub use hash ::HashSuite as Hash;
}

pub type PluginId = ae_sys::AEGP_PluginID;
Expand Down Expand Up @@ -77,6 +79,9 @@ pub use suites::comp::{
CompFlags,
CompHandle,
};
pub use suites::compute_cache:: {
ComputeClassId
};
pub use suites::effect::{
Effect,
EffectFlags,
Expand Down Expand Up @@ -167,3 +172,4 @@ pub use suites::world::{
WorldHandle,
WorldType,
};
pub use suites::hash::Guid;
235 changes: 235 additions & 0 deletions after-effects/src/aegp/suites/compute_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use std::{ffi::CString, marker::PhantomData, str::FromStr};

use after_effects_sys::{
AEGP_CCCheckoutReceiptP, AEGP_CCComputeKeyP, AEGP_CCComputeOptionsRefconP,
AEGP_CCComputeValueRefconP, AEGP_ComputeCacheCallbacks,
};

use crate::{aegp::Guid, *};

#[inline(always)]
fn conjure<F>() -> F {
const { assert!(std::mem::size_of::<F>() == 0) }
unsafe { std::mem::zeroed() }
}

define_suite!(
ComputeCacheSuite,
AEGP_ComputeCacheSuite1,
kAEGPComputeCacheSuite,
kAEGPComputeCacheSuiteVersion1
);

pub struct ComputeCacheReceipt<V> {
receipt_ptr: AEGP_CCCheckoutReceiptP,
_phantom_data_v: PhantomData<V>,
}

/// A type safe class ID - this is essentiall a &'str which in most cases will be static.
/// because the underlying API relies on void* we track the option and value types on the id
/// to avoid casting to the wrong type.
///
/// See the simulation example for an example of usage.
pub struct ComputeClassId<'a, O, V> {
_phantom_data_options: PhantomData<O>,
_phantom_data_value: PhantomData<V>,
pub id: &'a str,
}

impl<'a, O, V> ComputeClassId<'a, O, V> {
pub const fn new(id: &'a str) -> Self {
Self {
_phantom_data_options: PhantomData,
_phantom_data_value: PhantomData,
id,
}
}

pub fn id(&self) -> &'a str { self.id }
}

impl<V> AsPtr<AEGP_CCCheckoutReceiptP> for ComputeCacheReceipt<V> {
#[inline]
fn as_ptr(&self) -> ae_sys::AEGP_CCCheckoutReceiptP { self.receipt_ptr }
}

impl ComputeCacheSuite {
/// Acquire this suite from the host. Returns error if the suite is not available.
/// Suite is released on drop.
pub fn new() -> Result<Self, Error> { crate::Suite::new() }

/// The callback functions must be statically known function items (not closures
/// with captured state). This is enforced at compile time.
pub fn register_class<O, V, GenKey, Compute, ApproxSize, Delete>(
&self,
compute_class_id: &ComputeClassId<O, V>,
_generate_key: GenKey,
_compute: Compute,
_approx_size: ApproxSize,
_delete: Delete,
) -> Result<(), Error>
where
GenKey: Fn(&O) -> Result<Guid, Error>,
Compute: Fn(&O) -> Result<V, Error>,
ApproxSize: Fn(&V) -> usize,
Delete: Fn(V),
{
const { assert!(std::mem::size_of::<GenKey>() == 0) }
const { assert!(std::mem::size_of::<Compute>() == 0) }
const { assert!(std::mem::size_of::<ApproxSize>() == 0) }
const { assert!(std::mem::size_of::<Delete>() == 0) }

unsafe extern "C" fn generate_key_trampoline<O, GenKey>(
options_p: AEGP_CCComputeOptionsRefconP,
out_key_p: AEGP_CCComputeKeyP,
) -> ae_sys::A_Err
where
GenKey: Fn(&O) -> Result<Guid, Error>,
{
let opts = unsafe { &*(options_p as *const O) };
match conjure::<GenKey>()(opts) {
Ok(guid) => {
unsafe { *out_key_p = guid.0 };
ae_sys::A_Err_NONE as _
}
Err(e) => e.into(),
}
}

unsafe extern "C" fn compute_trampoline<O, V, Compute>(
options_p: AEGP_CCComputeOptionsRefconP,
out_value_pp: *mut AEGP_CCComputeValueRefconP,
) -> ae_sys::A_Err
where
Compute: Fn(&O) -> Result<V, Error>,
{
let opts = unsafe { &*(options_p as *const O) };
match conjure::<Compute>()(opts) {
Ok(value) => {
unsafe { *out_value_pp = Box::into_raw(Box::new(value)) as _ };
ae_sys::A_Err_NONE as _
}
Err(e) => e.into(),
}
}

unsafe extern "C" fn approx_size_trampoline<V, ApproxSize>(
value_p: AEGP_CCComputeValueRefconP,
) -> usize
where
ApproxSize: Fn(&V) -> usize,
{
conjure::<ApproxSize>()(unsafe { &*(value_p as *const V) })
}

unsafe extern "C" fn delete_trampoline<V, Delete>(value_p: AEGP_CCComputeValueRefconP)
where
Delete: Fn(V),
{
conjure::<Delete>()(unsafe { *Box::from_raw(value_p as *mut V) })
}

let c_str = CString::from_str(compute_class_id.id()).map_err(|_| Error::InvalidParms)?;

let callbacks = AEGP_ComputeCacheCallbacks {
generate_key: Some(generate_key_trampoline::<O, GenKey>),
compute: Some(compute_trampoline::<O, V, Compute>),
approx_size_value: Some(approx_size_trampoline::<V, ApproxSize>),
delete_compute_value: Some(delete_trampoline::<V, Delete>),
};

call_suite_fn!(
self,
AEGP_ClassRegister,
c_str.as_ptr(),
&callbacks as *const _
)
}

/// Checks if a cache value has already been computed without triggering computation.
/// Returns the receipt if available, otherwise returns `None`.
///
/// Useful for polling patterns where another thread handles computation.
pub fn checkout_cached<O, V>(
&self,
compute_class_id: &ComputeClassId<O, V>,
options: &mut O,
) -> Result<Option<ComputeCacheReceipt<V>>, Error> {
let c_str = CString::from_str(compute_class_id.id()).map_err(|_| Error::InvalidParms)?;
let result = call_suite_fn_single!(self, AEGP_CheckoutCached -> AEGP_CCCheckoutReceiptP, c_str.as_ptr(), options as *mut O as *mut _);

match result {
Ok(ptr) => Ok(Some(ComputeCacheReceipt {
receipt_ptr: ptr as *mut _,
_phantom_data_v: PhantomData,
})),
Err(Error::NotInComputeCache) => Ok(None),
Err(e) => Err(e),
}
}

/// Unregisters a previously registered cache type using its globally unique identifier.
/// All cached values will be purged at this time through calls to `delete_compute_value`.
///
/// Typically invoked during `PF_Cmd_GLOBAL_SETDOWN`.
pub fn unregister_class<O, V>(
&self,
compute_class_id: &ComputeClassId<O, V>,
) -> Result<(), Error> {
let c_str = CString::from_str(compute_class_id.id()).map_err(|_| Error::InvalidParms)?;
call_suite_fn!(self, AEGP_ClassUnregister, c_str.as_ptr())
}

/// The primary checkout function that computes or retrieves a receipt.
///
/// The `wait_for_other_thread` parameter determines behavior: when `true`,
/// it always computes or waits for completion; when `false`, it returns
/// [`Error::NotInComputeCache`] if another thread is already computing.
pub fn compute_if_needed_and_checkout<O, V>(
&self,
compute_class_id: ComputeClassId<O, V>,
options: &mut O,
wait_for_other_thread: bool,
) -> Result<ComputeCacheReceipt<V>, Error> {
let c_str = CString::from_str(compute_class_id.id()).map_err(|_| Error::InvalidParms)?;
let receipt_ptr = call_suite_fn_single!(
self,
AEGP_ComputeIfNeededAndCheckout -> AEGP_CCCheckoutReceiptP,
c_str.as_ptr(),
options as *mut O as *mut _,
wait_for_other_thread
)?;
Ok(ComputeCacheReceipt {
receipt_ptr,
_phantom_data_v: PhantomData,
})
}

/// Signals completion of cache value usage before returning to the host.
pub fn check_in_compute_receipt(
&self,
receipt: impl AsPtr<AEGP_CCCheckoutReceiptP>,
) -> Result<(), Error> {
call_suite_fn!(self, AEGP_CheckinComputeReceipt, receipt.as_ptr())
}

/// Retrieves the computed cache value using a receipt from either
/// [`compute_if_needed_and_checkout`](Self::compute_if_needed_and_checkout) or
/// [`checkout_cached`](Self::checkout_cached).
///
/// The returned reference is valid until the receipt is checked in.
///
/// Warn: This will transmute the underlying pointer to type V, if these do not
/// line up it will induce UB.
pub fn receipt_compute_value<'a, V>(
&self,
receipt: &ComputeCacheReceipt<V>,
) -> Result<&'a V, Error> {
let value_ptr = call_suite_fn_single!(
self,
AEGP_GetReceiptComputeValue -> ae_sys::AEGP_CCComputeValueRefconP,
receipt.as_ptr()
)?;
unsafe { (value_ptr as *const V).as_ref() }.ok_or(Error::Generic)
}
}
67 changes: 67 additions & 0 deletions after-effects/src/aegp/suites/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::hash::Hash;

use crate::*;

define_suite!(
HashSuite,
AEGP_HashSuite1,
kAEGPHashSuite,
kAEGPHashSuiteVersion1
);

/// A GUID used as a hash key for the compute cache.
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct Guid(pub ae_sys::AEGP_GUID);

impl Guid {
pub fn new() -> Self { Self(ae_sys::AEGP_GUID { bytes: [0; 4] }) }

pub fn as_mut_ptr(&mut self) -> *mut ae_sys::AEGP_GUID { &mut self.0 }

pub fn as_raw(&self) -> ae_sys::AEGP_GUID { self.0 }
}

impl Default for Guid {
fn default() -> Self { Self::new() }
}

impl From<ae_sys::AEGP_GUID> for Guid {
fn from(guid: ae_sys::AEGP_GUID) -> Self { Self(guid) }
}

impl From<Guid> for ae_sys::AEGP_GUID {
fn from(guid: Guid) -> Self { guid.0 }
}

impl HashSuite {
/// Acquire this suite from the host. Returns error if the suite is not available.
/// Suite is released on drop.
pub fn new() -> Result<Self, Error> { crate::Suite::new() }

/// Call this to begin creating the hash which will be returned in hashP
/// that can be used for returning from generate_key.
pub fn create_hash_from_ptr(&self, data: &[u8]) -> Result<Guid, Error> {
let mut hash = Guid::new();
call_suite_fn!(
self,
AEGP_CreateHashFromPtr,
data.len() as ae_sys::A_u_longlong,
data.as_ptr() as *const _,
hash.as_mut_ptr()
)?;
Ok(hash)
}

/// Call this for each effect parameter, layer checkout hash or other data
/// that would be used in calculating a cache entry.
pub fn hash_mix_in_ptr(&self, data: &[u8], hash: &mut Guid) -> Result<(), Error> {
call_suite_fn!(
self,
AEGP_HashMixInPtr,
data.len() as ae_sys::A_u_longlong,
data.as_ptr() as *const _,
hash.as_mut_ptr()
)
}
}
3 changes: 2 additions & 1 deletion after-effects/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ define_enum! {
InvalidParms = ae_sys::suiteError_InvalidParms,

Unknown10007 = UNKNOWN_ERR_10007,

NotInComputeCache = ae_sys::A_Err_NOT_IN_CACHE_OR_COMPUTE_PENDING,
None = ae_sys::PF_Err_NONE,
}
}
Expand Down Expand Up @@ -195,6 +195,7 @@ impl From<Error> for &'static str {
Error::InvalidParms => "InvalidParms",
Error::Reserved11 => "Reserved11",
Error::Unknown10007 => "Unknown10007",
Error::NotInComputeCache => "Value not found in compute cache.",
}
}
}
Expand Down
Loading