Skip to content

Commit 72302e9

Browse files
authored
macOS: use CFRunLoopTimer, improve view release logic (#84)
* macOS: fix property_no fn * Use CFRunLoopTimer instead if NSTimer This means the timer doesn't keep a reference to the view, which should make it easer to check retain_count in release. * macOS: take pointer instead of Arc in WindowState::setup_timer * Save retain count increase from build fn, use in release fn * macOS: in window setup, run build fn before doing parenting * macOS: clean up parenting * macOS: wrap WindowState in Box instead of Arc to improve clarity * macOS: use better names for ivar consts, move them to view.rs * Remove no longer used crate static_assertions * macOS: in view release fn, delete class when retain_count == 1 * macOS: set window state ivar to null after dropping * macOS: store retain count after build in WindowState * macOS: rename BASEVIEW_WINDOW_STATE_IVAR to BASEVIEW_STATE_IVAR
1 parent 868bd05 commit 72302e9

3 files changed

Lines changed: 120 additions & 116 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ license = "MIT OR Apache-2.0"
1616
[dependencies]
1717
keyboard-types = { version = "0.5.0", default-features = false }
1818
raw-window-handle = "0.3.3"
19-
static_assertions = "1.1.0"
2019

2120
[target.'cfg(target_os="linux")'.dependencies]
2221
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
@@ -29,6 +28,7 @@ winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "
2928

3029
[target.'cfg(target_os="macos")'.dependencies]
3130
cocoa = "0.24.0"
31+
core-foundation = "0.9.1"
3232
objc = "0.2.7"
3333
uuid = { version = "0.8", features = ["v4"] }
3434

src/macos/view.rs

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::ffi::c_void;
2-
use std::sync::Arc;
32

43
use cocoa::appkit::{NSEvent, NSView};
54
use cocoa::base::{id, nil, BOOL, YES, NO};
@@ -17,9 +16,11 @@ use uuid::Uuid;
1716
use crate::{Event, MouseButton, MouseEvent, Point, WindowOpenOptions};
1817
use crate::MouseEvent::{ButtonPressed, ButtonReleased};
1918

20-
use super::window::{
21-
WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME
22-
};
19+
use super::window::WindowState;
20+
21+
22+
/// Name of the field used to store the `WindowState` pointer.
23+
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
2324

2425

2526
pub(super) unsafe fn create_view(
@@ -66,14 +67,9 @@ unsafe fn create_view_class() -> &'static Class {
6667
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL
6768
);
6869

69-
class.add_method(
70-
sel!(triggerOnFrame:),
71-
trigger_on_frame as extern "C" fn(&Object, Sel, id)
72-
);
73-
7470
class.add_method(
7571
sel!(release),
76-
release as extern "C" fn(&Object, Sel)
72+
release as extern "C" fn(&mut Object, Sel)
7773
);
7874
class.add_method(
7975
sel!(viewWillMoveToWindow:),
@@ -151,8 +147,7 @@ unsafe fn create_view_class() -> &'static Class {
151147
flags_changed as extern "C" fn(&Object, Sel, id),
152148
);
153149

154-
class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME);
155-
class.add_ivar::<*mut c_void>(FRAME_TIMER_IVAR_NAME);
150+
class.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR);
156151

157152
class.register()
158153
}
@@ -170,7 +165,7 @@ extern "C" fn property_no(
170165
_this: &Object,
171166
_sel: Sel,
172167
) -> BOOL {
173-
YES
168+
NO
174169
}
175170

176171

@@ -183,20 +178,7 @@ extern "C" fn accepts_first_mouse(
183178
}
184179

185180

186-
extern "C" fn trigger_on_frame(
187-
this: &Object,
188-
_sel: Sel,
189-
_event: id
190-
){
191-
let state: &mut WindowState = unsafe {
192-
WindowState::from_field(this)
193-
};
194-
195-
state.trigger_frame();
196-
}
197-
198-
199-
extern "C" fn release(this: &Object, _sel: Sel) {
181+
extern "C" fn release(this: &mut Object, _sel: Sel) {
200182
unsafe {
201183
let superclass = msg_send![this, superclass];
202184

@@ -206,19 +188,26 @@ extern "C" fn release(this: &Object, _sel: Sel) {
206188
unsafe {
207189
let retain_count: usize = msg_send![this, retainCount];
208190

209-
if retain_count == 1 {
210-
// Invalidate frame timer
211-
let frame_timer_ptr: *mut c_void = *this.get_ivar(
212-
FRAME_TIMER_IVAR_NAME
213-
);
214-
let _: () = msg_send![frame_timer_ptr as id, invalidate];
215-
216-
// Drop WindowState
217-
let state_ptr: *mut c_void = *this.get_ivar(
218-
WINDOW_STATE_IVAR_NAME
219-
);
220-
Arc::from_raw(state_ptr as *mut WindowState);
191+
let state_ptr: *mut c_void = *this.get_ivar(BASEVIEW_STATE_IVAR);
192+
193+
if !state_ptr.is_null(){
194+
let retain_count_after_build = WindowState::from_field(this)
195+
.retain_count_after_build;
221196

197+
if retain_count <= retain_count_after_build {
198+
WindowState::from_field(this).remove_timer();
199+
200+
this.set_ivar(
201+
BASEVIEW_STATE_IVAR,
202+
::std::ptr::null() as *const c_void
203+
);
204+
205+
// Drop WindowState
206+
Box::from_raw(state_ptr as *mut WindowState);
207+
}
208+
}
209+
210+
if retain_count == 1 {
222211
// Delete class
223212
let class = msg_send![this, class];
224213
::objc::runtime::objc_disposeClassPair(class);

src/macos/window.rs

Lines changed: 91 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::ffi::c_void;
2-
use std::sync::Arc;
32

43
use cocoa::appkit::{
54
NSApp, NSApplication, NSApplicationActivationPolicyRegular,
65
NSBackingStoreBuffered, NSWindow, NSWindowStyleMask,
76
};
8-
use cocoa::base::{id, nil, NO, YES};
7+
use cocoa::base::{id, nil, NO};
98
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
9+
use core_foundation::runloop::{
10+
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer,
11+
kCFRunLoopDefaultMode,
12+
};
1013
use keyboard_types::KeyboardEvent;
1114

1215
use objc::{msg_send, runtime::Object, sel, sel_impl};
@@ -18,17 +21,10 @@ use crate::{
1821
WindowScalePolicy, WindowInfo
1922
};
2023

21-
use super::view::create_view;
24+
use super::view::{create_view, BASEVIEW_STATE_IVAR};
2225
use super::keyboard::KeyboardState;
2326

2427

25-
/// Name of the field used to store the `WindowState` pointer in the custom
26-
/// view class.
27-
pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME";
28-
29-
pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER";
30-
31-
3228
pub struct AppRunner;
3329

3430
impl AppRunner {
@@ -61,38 +57,35 @@ impl Window {
6157
{
6258
let _pool = unsafe { NSAutoreleasePool::new(nil) };
6359

64-
let (mut window, opt_app_runner) = match options.parent {
65-
Parent::WithParent(parent) => {
66-
if let RawWindowHandle::MacOS(handle) = parent {
67-
let ns_view = handle.ns_view as *mut objc::runtime::Object;
68-
69-
unsafe {
70-
let subview = create_view(&options);
60+
let mut window = unsafe {
61+
Window {
62+
ns_window: None,
63+
ns_view: create_view(&options),
64+
}
65+
};
7166

72-
let _: id = msg_send![ns_view, addSubview: subview];
67+
let window_handler = Box::new(build(&mut crate::Window(&mut window)));
7368

74-
let window = Window {
75-
ns_window: None,
76-
ns_view: subview,
77-
};
69+
let retain_count_after_build: usize = unsafe {
70+
msg_send![window.ns_view, retainCount]
71+
};
7872

79-
(window, None)
80-
}
81-
} else {
82-
panic!("Not a macOS window");
73+
let opt_app_runner = match options.parent {
74+
Parent::WithParent(RawWindowHandle::MacOS(handle)) => {
75+
unsafe {
76+
let () = msg_send![
77+
handle.ns_view as *mut Object,
78+
addSubview: window.ns_view
79+
];
8380
}
81+
82+
None
83+
},
84+
Parent::WithParent(_) => {
85+
panic!("Not a macOS window");
8486
},
8587
Parent::AsIfParented => {
86-
let ns_view = unsafe {
87-
create_view(&options)
88-
};
89-
90-
let window = Window {
91-
ns_window: None,
92-
ns_view,
93-
};
94-
95-
(window, None)
88+
None
9689
},
9790
Parent::None => {
9891
// It seems prudent to run NSApp() here before doing other
@@ -143,58 +136,30 @@ impl Window {
143136

144137
ns_window.makeKeyAndOrderFront_(nil);
145138

146-
let subview = create_view(&options);
139+
ns_window.setContentView_(window.ns_view);
147140

148-
ns_window.setContentView_(subview);
141+
window.ns_window = Some(ns_window);
149142

150-
let window = Window {
151-
ns_window: Some(ns_window),
152-
ns_view: subview,
153-
};
154-
155-
(window, Some(crate::AppRunner(AppRunner)))
143+
Some(crate::AppRunner(AppRunner))
156144
}
157145
},
158146
};
159147

160-
let window_handler = Box::new(build(&mut crate::Window(&mut window)));
161-
162-
let window_state_arc = Arc::new(WindowState {
148+
let window_state_ptr = Box::into_raw(Box::new(WindowState {
163149
window,
164150
window_handler,
165151
keyboard_state: KeyboardState::new(),
166-
});
167-
168-
let window_state_pointer = Arc::into_raw(
169-
window_state_arc.clone()
170-
) as *mut c_void;
152+
frame_timer: None,
153+
retain_count_after_build,
154+
}));
171155

172156
unsafe {
173-
(*window_state_arc.window.ns_view).set_ivar(
174-
WINDOW_STATE_IVAR_NAME,
175-
window_state_pointer
157+
(*(*window_state_ptr).window.ns_view).set_ivar(
158+
BASEVIEW_STATE_IVAR,
159+
window_state_ptr as *mut c_void
176160
);
177-
}
178161

179-
// Setup frame timer once window state is stored
180-
unsafe {
181-
let timer_interval = 0.015;
182-
let selector = sel!(triggerOnFrame:);
183-
184-
let timer: id = msg_send![
185-
::objc::class!(NSTimer),
186-
scheduledTimerWithTimeInterval:timer_interval
187-
target:window_state_arc.window.ns_view
188-
selector:selector
189-
userInfo:nil
190-
repeats:YES
191-
];
192-
193-
// Store pointer to timer for invalidation when view is released
194-
(*window_state_arc.window.ns_view).set_ivar(
195-
FRAME_TIMER_IVAR_NAME,
196-
timer as *mut c_void,
197-
)
162+
WindowState::setup_timer(window_state_ptr);
198163
}
199164

200165
opt_app_runner
@@ -206,6 +171,8 @@ pub(super) struct WindowState {
206171
window: Window,
207172
window_handler: Box<dyn WindowHandler>,
208173
keyboard_state: KeyboardState,
174+
frame_timer: Option<CFRunLoopTimer>,
175+
pub retain_count_after_build: usize,
209176
}
210177

211178

@@ -216,7 +183,7 @@ impl WindowState {
216183
/// WindowState. Apparently, macOS blocks for the duration of an event,
217184
/// callback, meaning that this shouldn't be a problem in practice.
218185
pub(super) unsafe fn from_field(obj: &Object) -> &mut Self {
219-
let state_ptr: *mut c_void = *obj.get_ivar(WINDOW_STATE_IVAR_NAME);
186+
let state_ptr: *mut c_void = *obj.get_ivar(BASEVIEW_STATE_IVAR);
220187

221188
&mut *(state_ptr as *mut Self)
222189
}
@@ -238,6 +205,54 @@ impl WindowState {
238205
) -> Option<KeyboardEvent> {
239206
self.keyboard_state.process_native_event(event)
240207
}
208+
209+
/// Don't call until WindowState pointer is stored in view
210+
unsafe fn setup_timer(window_state_ptr: *mut WindowState){
211+
extern "C" fn timer_callback(
212+
_: *mut __CFRunLoopTimer,
213+
window_state_ptr: *mut c_void,
214+
){
215+
unsafe {
216+
let window_state = &mut *(
217+
window_state_ptr as *mut WindowState
218+
);
219+
220+
window_state.trigger_frame();
221+
}
222+
}
223+
224+
let mut timer_context = CFRunLoopTimerContext {
225+
version: 0,
226+
info: window_state_ptr as *mut c_void,
227+
retain: None,
228+
release: None,
229+
copyDescription: None,
230+
};
231+
232+
let timer = CFRunLoopTimer::new(
233+
0.0,
234+
0.015,
235+
0,
236+
0,
237+
timer_callback,
238+
&mut timer_context,
239+
);
240+
241+
CFRunLoop::get_current()
242+
.add_timer(&timer, kCFRunLoopDefaultMode);
243+
244+
let window_state = &mut *(window_state_ptr);
245+
246+
window_state.frame_timer = Some(timer);
247+
}
248+
249+
/// Call when freeing view
250+
pub(super) unsafe fn remove_timer(&mut self){
251+
if let Some(frame_timer) = self.frame_timer.take(){
252+
CFRunLoop::get_current()
253+
.remove_timer(&frame_timer, kCFRunLoopDefaultMode);
254+
}
255+
}
241256
}
242257

243258

0 commit comments

Comments
 (0)