Skip to content

Commit

Permalink
Redo how instance variables work in declare_class!
Browse files Browse the repository at this point in the history
At the same time, add support for `msg_send_id![super(...)]`
  • Loading branch information
madsmtm committed Nov 27, 2023
1 parent b19d7cc commit 7b361f6
Show file tree
Hide file tree
Showing 66 changed files with 3,275 additions and 3,876 deletions.
52 changes: 23 additions & 29 deletions crates/icrate/examples/browser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![deny(unsafe_op_in_unsafe_fn)]
use core::{cell::OnceCell, ptr::NonNull};
use core::cell::OnceCell;

#[allow(deprecated)]
use icrate::{
Expand All @@ -19,26 +19,23 @@ use icrate::{
WebKit::{WKNavigation, WKNavigationDelegate, WKWebView},
};
use objc2::{
declare::{Ivar, IvarDrop},
declare_class, msg_send, msg_send_id,
declare_class, msg_send_id,
mutability::MainThreadOnly,
rc::Id,
runtime::{AnyObject, ProtocolObject, Sel},
sel, ClassType,
sel, ClassType, DeclaredClass,
};

type IdCell<T> = Box<OnceCell<Id<T>>>;

macro_rules! idcell {
($name:ident => $this:expr) => {
$this.$name.set($name).expect(&format!(
$this.ivars().$name.set($name).expect(&format!(
"ivar should not already be initialized: `{}`",
stringify!($name)
));
};
($name:ident <= $this:expr) => {
#[rustfmt::skip]
let Some($name) = $this.$name.get() else {
let Some($name) = $this.ivars().$name.get() else {
unreachable!(
"ivar should be initialized: `{}`",
stringify!($name)
Expand All @@ -47,31 +44,28 @@ macro_rules! idcell {
};
}

#[derive(Default)]
struct Ivars {
nav_url: OnceCell<Id<NSTextField>>,
web_view: OnceCell<Id<WKWebView>>,
window: OnceCell<Id<NSWindow>>,
}

declare_class!(
struct Delegate {
nav_url: IvarDrop<IdCell<NSTextField>, "_nav_url">,
web_view: IvarDrop<IdCell<WKWebView>, "_web_view">,
window: IvarDrop<IdCell<NSWindow>, "_window">,
}
mod ivars;
struct Delegate;

// SAFETY:
// - The superclass NSObject does not have any subclassing requirements.
// - Main thread only mutability is correct, since this is an application delegate.
// - `Delegate` does not implement `Drop`.
unsafe impl ClassType for Delegate {
type Super = NSObject;
type Mutability = MainThreadOnly;
const NAME: &'static str = "Delegate";
}

unsafe impl Delegate {
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
Ivar::write(&mut this.nav_url, IdCell::default());
Ivar::write(&mut this.web_view, IdCell::default());
Ivar::write(&mut this.window, IdCell::default());
NonNull::from(this)
})
}
impl DeclaredClass for Delegate {
type Ivars = Ivars;
}

unsafe impl NSObjectProtocol for Delegate {}
Expand Down Expand Up @@ -298,7 +292,9 @@ declare_class!(

impl Delegate {
pub fn new(mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![mtm.alloc(), init] }
let this = mtm.alloc();
let this = this.set_ivars(Ivars::default());
unsafe { msg_send_id![super(this), init] }
}
}

Expand All @@ -307,10 +303,8 @@ fn main() {
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicyRegular);

// initialize the delegate
let delegate = Delegate::new(mtm);

// configure the application delegate
let delegate = Delegate::new(mtm);
let object = ProtocolObject::from_ref(&*delegate);
app.setDelegate(Some(object));

Expand Down
70 changes: 30 additions & 40 deletions crates/icrate/examples/delegate.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,38 @@
#![deny(unsafe_op_in_unsafe_fn)]
use std::ptr::NonNull;

use icrate::AppKit::{NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate};
use icrate::Foundation::{
ns_string, MainThreadMarker, NSCopying, NSNotification, NSObject, NSObjectProtocol, NSString,
};
use objc2::declare::{Ivar, IvarBool, IvarDrop, IvarEncode};
use objc2::rc::Id;
use objc2::runtime::ProtocolObject;
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};

#[derive(Debug)]
#[allow(unused)]
struct Ivars {
ivar: u8,
another_ivar: bool,
box_ivar: Box<i32>,
maybe_box_ivar: Option<Box<i32>>,
id_ivar: Id<NSString>,
maybe_id_ivar: Option<Id<NSString>>,
}

declare_class!(
#[derive(Debug)]
struct AppDelegate {
ivar: IvarEncode<u8, "_ivar">,
another_ivar: IvarBool<"_another_ivar">,
box_ivar: IvarDrop<Box<i32>, "_box_ivar">,
maybe_box_ivar: IvarDrop<Option<Box<i32>>, "_maybe_box_ivar">,
id_ivar: IvarDrop<Id<NSString>, "_id_ivar">,
maybe_id_ivar: IvarDrop<Option<Id<NSString>>, "_maybe_id_ivar">,
}

mod ivars;
struct AppDelegate;

// SAFETY:
// - The superclass NSObject does not have any subclassing requirements.
// - Main thread only mutability is correct, since this is an application delegate.
// - `AppDelegate` does not implement `Drop`.
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "MyAppDelegate";
}

unsafe impl AppDelegate {
#[method(initWith:another:)]
unsafe fn init_with(
this: *mut Self,
ivar: u8,
another_ivar: bool,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(this), init] };

this.map(|this| {
Ivar::write(&mut this.ivar, ivar);
Ivar::write(&mut this.another_ivar, another_ivar);
Ivar::write(&mut this.maybe_box_ivar, None);
Ivar::write(&mut this.maybe_id_ivar, Some(ns_string!("def").copy()));
Ivar::write(&mut this.box_ivar, Box::new(2));
Ivar::write(&mut this.id_ivar, NSString::from_str("abc"));
NonNull::from(this)
})
}
impl DeclaredClass for AppDelegate {
type Ivars = Ivars;
}

unsafe impl NSObjectProtocol for AppDelegate {}
Expand All @@ -69,7 +54,16 @@ declare_class!(

impl AppDelegate {
pub fn new(ivar: u8, another_ivar: bool, mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![mtm.alloc(), initWith: ivar, another: another_ivar] }
let this = mtm.alloc();
let this = this.set_ivars(Ivars {
ivar,
another_ivar,
box_ivar: Box::new(2),
maybe_box_ivar: None,
id_ivar: NSString::from_str("abc"),
maybe_id_ivar: Some(ns_string!("def").copy()),
});
unsafe { msg_send_id![super(this), init] }
}
}

Expand All @@ -79,12 +73,8 @@ fn main() {
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicyRegular);

// initialize the delegate
let delegate = AppDelegate::new(42, true, mtm);

println!("{delegate:?}");

// configure the application delegate
let delegate = AppDelegate::new(42, true, mtm);
let object = ProtocolObject::from_ref(&*delegate);
app.setDelegate(Some(object));

Expand Down
67 changes: 29 additions & 38 deletions crates/icrate/examples/metal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ use icrate::{
MetalKit::{MTKView, MTKViewDelegate},
};
use objc2::{
declare::{Ivar, IvarDrop},
declare_class, msg_send, msg_send_id,
mutability::MainThreadOnly,
rc::Id,
runtime::ProtocolObject,
ClassType,
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Id, runtime::ProtocolObject,
ClassType, DeclaredClass,
};

#[rustfmt::skip]
Expand Down Expand Up @@ -101,18 +97,16 @@ pub struct Color {
pub b: f32,
}

type IdCell<T> = Box<OnceCell<Id<T>>>;

macro_rules! idcell {
($name:ident => $this:expr) => {
$this.$name.set($name).expect(&format!(
$this.ivars().$name.set($name).expect(&format!(
"ivar should not already be initialized: `{}`",
stringify!($name)
));
};
($name:ident <= $this:expr) => {
#[rustfmt::skip]
let Some($name) = $this.$name.get() else {
let Some($name) = $this.ivars().$name.get() else {
unreachable!(
"ivar should be initialized: `{}`",
stringify!($name)
Expand All @@ -121,38 +115,30 @@ macro_rules! idcell {
};
}

// declare the desired instance variables
struct Ivars {
start_date: Id<NSDate>,
command_queue: OnceCell<Id<ProtocolObject<dyn MTLCommandQueue>>>,
pipeline_state: OnceCell<Id<ProtocolObject<dyn MTLRenderPipelineState>>>,
window: OnceCell<Id<NSWindow>>,
}

// declare the Objective-C class machinery
declare_class!(
// declare the delegate class with our instance variables
#[rustfmt::skip] // FIXME: rustfmt breaks the macro parsing apparently
struct Delegate {
start_date: IvarDrop<Id<NSDate>, "_start_date">,
command_queue: IvarDrop<IdCell<ProtocolObject<dyn MTLCommandQueue>>, "_command_queue">,
pipeline_state: IvarDrop<IdCell<ProtocolObject<dyn MTLRenderPipelineState>>, "_pipeline_state">,
window: IvarDrop<IdCell<NSWindow>, "_window">,
}
mod ivars;
struct Delegate;

// declare the class type
// SAFETY:
// - The superclass NSObject does not have any subclassing requirements.
// - Main thread only mutability is correct, since this is an application delegate.
// - `Delegate` does not implement `Drop`.
unsafe impl ClassType for Delegate {
type Super = NSObject;
type Mutability = MainThreadOnly;
const NAME: &'static str = "Delegate";
}

// define the Delegate methods (e.g., initializer)
unsafe impl Delegate {
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
Ivar::write(&mut this.start_date, unsafe { NSDate::now() });
Ivar::write(&mut this.command_queue, IdCell::default());
Ivar::write(&mut this.pipeline_state, IdCell::default());
Ivar::write(&mut this.window, IdCell::default());
NonNull::from(this)
})
}
impl DeclaredClass for Delegate {
type Ivars = Ivars;
}

unsafe impl NSObjectProtocol for Delegate {}
Expand Down Expand Up @@ -277,7 +263,7 @@ declare_class!(

// compute the scene properties
let scene_properties_data = &SceneProperties {
time: unsafe { self.start_date.timeIntervalSinceNow() } as f32,
time: unsafe { self.ivars().start_date.timeIntervalSinceNow() } as f32,
};
// write the scene properties to the vertex shader argument buffer at index 0
let scene_properties_bytes = NonNull::from(scene_properties_data);
Expand Down Expand Up @@ -360,7 +346,14 @@ declare_class!(

impl Delegate {
pub fn new(mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![mtm.alloc(), init] }
let this = mtm.alloc();
let this = this.set_ivars(Ivars {
start_date: unsafe { NSDate::now() },
command_queue: OnceCell::default(),
pipeline_state: OnceCell::default(),
window: OnceCell::default(),
});
unsafe { msg_send_id![super(this), init] }
}
}

Expand All @@ -370,10 +363,8 @@ fn main() {
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicyRegular);

// initialize the delegate
let delegate = Delegate::new(mtm);

// configure the application delegate
let delegate = Delegate::new(mtm);
let object = ProtocolObject::from_ref(&*delegate);
app.setDelegate(Some(object));

Expand Down
4 changes: 2 additions & 2 deletions crates/icrate/tests/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ fn test_retains_stored() {

drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.drop += 1;
expected.assert_current();
}

Expand Down Expand Up @@ -187,7 +187,7 @@ fn test_iter_minimal_retains() {

assert_eq!(iter.count(), 0);
expected.release += 1;
expected.dealloc += 1;
expected.drop += 1;
expected.assert_current();
}

Expand Down
4 changes: 3 additions & 1 deletion crates/icrate/tests/auto_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use icrate::Foundation::*;
use objc2::mutability::{Immutable, Mutable};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{declare_class, ClassType};
use objc2::{declare_class, ClassType, DeclaredClass};

// We expect most Foundation types to be UnwindSafe and RefUnwindSafe,
// since they follow Rust's usual mutability rules (&T = immutable).
Expand Down Expand Up @@ -40,6 +40,8 @@ macro_rules! helper {
type Mutability = $mutability;
const NAME: &'static str = concat!(stringify!($name), "Test");
}

impl DeclaredClass for $name {}
);
};
}
Expand Down
2 changes: 1 addition & 1 deletion crates/icrate/tests/mutable_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn test_remove() {

array.removeAllObjects();
expected.release += 2;
expected.dealloc += 2;
expected.drop += 2;
expected.assert_current();
assert_eq!(array.len(), 0);
}
Expand Down
Loading

0 comments on commit 7b361f6

Please sign in to comment.