mtl-rs 0.2.0

Rust bindings for Apple's Metal API
use std::path::Path;

use objc2::{
    Message,
    encode::{Encode, Encoding, RefEncode},
    extern_class, extern_conformance, extern_methods, extern_protocol, msg_send,
    rc::{Allocated, Retained},
};
use objc2_foundation::{CopyingHelper, NSCopying, NSData, NSError, NSObject, NSObjectProtocol};

use crate::{MetalError, util::file_url};

/// Configuration options for pipeline dataset serializer objects.
///
/// Use these options to enable different functionality in instances of ``MTL4PipelineDataSetSerializer``.
///
/// You can combine these values via a logical `OR` and set it to ``MTL4PipelineDataSetSerializerDescriptor/configuration``
/// to specify desired level of serialization support for instances of ``MTL4PipelineDataSetSerializer``.
///
/// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4pipelinedatasetserializerconfiguration?language=objc)
// NS_OPTIONS
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MTL4PipelineDataSetSerializerConfiguration(pub usize);
bitflags::bitflags! {
    impl MTL4PipelineDataSetSerializerConfiguration: usize {
/// Enables serializing pipeline scripts.
///
/// Set this mask to use ``MTL4PipelineDataSetSerializer.serializeAsPipelinesScriptWithError``.
///
/// This for the default behavior.
        #[doc(alias = "MTL4PipelineDataSetSerializerConfigurationCaptureDescriptors")]
        const CaptureDescriptors = 1<<0;
/// Enables serializing pipeline binary functions.
///
/// Set this mask to use ``MTL4PipelineDataSetSerializer.serializeAsArchiveAndFlush(toURL:error:)``.
        #[doc(alias = "MTL4PipelineDataSetSerializerConfigurationCaptureBinaries")]
        const CaptureBinaries = 1<<1;
    }
}

unsafe impl Encode for MTL4PipelineDataSetSerializerConfiguration {
    const ENCODING: Encoding = usize::ENCODING;
}

unsafe impl RefEncode for MTL4PipelineDataSetSerializerConfiguration {
    const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}

extern_class!(
    /// Groups together properties to create a pipeline data set serializer.
    ///
    /// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4pipelinedatasetserializerdescriptor?language=objc)
    #[unsafe(super(NSObject))]
    #[derive(Debug, PartialEq, Eq, Hash)]
    pub struct MTL4PipelineDataSetSerializerDescriptor;
);

extern_conformance!(
    unsafe impl NSCopying for MTL4PipelineDataSetSerializerDescriptor {}
);

unsafe impl CopyingHelper for MTL4PipelineDataSetSerializerDescriptor {
    type Result = Self;
}

extern_conformance!(
    unsafe impl NSObjectProtocol for MTL4PipelineDataSetSerializerDescriptor {}
);

impl MTL4PipelineDataSetSerializerDescriptor {
    extern_methods!(
        /// Specifies the configuration of the serialization process.
        ///
        /// The configuration of the serialization process determines the mechanisms you use to serialize pipeline data sets.
        ///
        /// When this configuration contains ``MTL4PipelineDataSetSerializerConfigurationCaptureDescriptors``, use
        /// ``serializeAsPipelinesScriptWithError:`` to serialize pipeline scripts.
        ///
        /// If this option contains ``MTL4PipelineDataSetSerializerConfigurationCaptureBinaries``, the serializer can additionally
        /// serialize to a binary archive by calling ``serializeAsArchiveAndFlushToURL:error::``.
        #[unsafe(method(configuration))]
        #[unsafe(method_family = none)]
        pub fn configuration(&self) -> MTL4PipelineDataSetSerializerConfiguration;

        /// Setter for [`configuration`][Self::configuration].
        #[unsafe(method(setConfiguration:))]
        #[unsafe(method_family = none)]
        pub fn set_configuration(
            &self,
            configuration: MTL4PipelineDataSetSerializerConfiguration,
        );
    );
}

/// Methods declared on superclass `NSObject`.
impl MTL4PipelineDataSetSerializerDescriptor {
    extern_methods!(
        #[unsafe(method(init))]
        #[unsafe(method_family = init)]
        pub fn init(this: Allocated<Self>) -> Retained<Self>;

        #[unsafe(method(new))]
        #[unsafe(method_family = new)]
        pub fn new() -> Retained<Self>;
    );
}

extern_protocol!(
    /// A fast-addition container for collecting data during pipeline state creation.
    ///
    /// Pipeline data serializer instances allow you to create binary archives and serialize pipeline scripts to use with
    /// the offline Metal binary generator (`metal-tt`)
    /// <doc
    /// :compiling-binary-archives-from-a-custom-configuration-script.md>.
    ///
    /// You capture and retain all relevant data for all pipelines a compiler instance creates by providing an instance of
    /// this object to its ``MTL4CompilerDescriptor``.
    ///
    /// After capturing data, you can serialize it to a binary archive to persist its contents offline by calling
    /// ``serializeAsArchiveAndFlushToURL:error:``. You can also serialize a pipeline script suitable for the offline binary
    /// generator (`metal-tt`) by calling ``serializeAsPipelinesScriptWithError:``
    ///
    /// - Note: The objects ``MTL4PipelineDataSetSerializer`` contains are opaque and can't accelerate compilation for
    /// compilers they are not attached to. Additionally, your program can't read data out of data set serializer instances.
    ///
    /// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4pipelinedatasetserializer?language=objc)
    pub unsafe trait MTL4PipelineDataSetSerializer: NSObjectProtocol {}
);

pub trait MTL4PipelineDataSetSerializerExt: MTL4PipelineDataSetSerializer + Message {
    /// Serializes a pipeline data set to an archive at a filesystem path.
    fn serialize_as_archive_and_flush_to_path(
        &self,
        path: &Path,
    ) -> Result<(), MetalError>
    where
        Self: Sized,
    {
        let url = file_url(path, "serializeAsArchiveAndFlushToURL:error:")?;
        unsafe { msg_send![self, serializeAsArchiveAndFlushToURL: &*url, error: _] }.map_err(MetalError::from_nserror)
    }

    /// Serializes a serializer data set to a pipeline script as bytes.
    fn serialize_as_pipelines_script(&self) -> Result<Box<[u8]>, MetalError>
    where
        Self: Sized,
    {
        let data: Result<Retained<NSData>, Retained<NSError>> =
            unsafe { msg_send![self, serializeAsPipelinesScriptWithError: _] };
        data.map(|data| data.to_vec().into_boxed_slice()).map_err(MetalError::from_nserror)
    }
}

impl<T: MTL4PipelineDataSetSerializer + Message> MTL4PipelineDataSetSerializerExt for T {}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use objc2::runtime::ProtocolObject;

    use super::{MTL4PipelineDataSetSerializer, MTL4PipelineDataSetSerializerExt};
    use crate::MetalError;

    #[test]
    fn serialization_errors_have_rust_native_signatures() {
        let _: fn(&ProtocolObject<dyn MTL4PipelineDataSetSerializer>, &Path) -> Result<(), MetalError> =
            <ProtocolObject<dyn MTL4PipelineDataSetSerializer> as MTL4PipelineDataSetSerializerExt>::serialize_as_archive_and_flush_to_path;
        let _: fn(&ProtocolObject<dyn MTL4PipelineDataSetSerializer>) -> Result<Box<[u8]>, MetalError> =
            <ProtocolObject<dyn MTL4PipelineDataSetSerializer> as MTL4PipelineDataSetSerializerExt>::serialize_as_pipelines_script;
    }
}