Replace CADisplayLink
with CVDisplayLink
(#7583)
Release Notes: - Fixed a bug that caused Zed to render at 60fps even on ProMotion displays. - Fixed a bug that could saturate the main thread event loop in certain circumstances. --------- Co-authored-by: Thorsten <thorsten@zed.dev> Co-authored-by: Nathan <nathan@zed.dev> Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
9e538e7916
commit
67b96b2b40
11 changed files with 218 additions and 278 deletions
|
@ -27,6 +27,7 @@ fn generate_dispatch_bindings() {
|
||||||
let bindings = bindgen::Builder::default()
|
let bindings = bindgen::Builder::default()
|
||||||
.header("src/platform/mac/dispatch.h")
|
.header("src/platform/mac/dispatch.h")
|
||||||
.allowlist_var("_dispatch_main_q")
|
.allowlist_var("_dispatch_main_q")
|
||||||
|
.allowlist_var("_dispatch_source_type_data_add")
|
||||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
||||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
|
.allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
|
||||||
.allowlist_var("DISPATCH_TIME_NOW")
|
.allowlist_var("DISPATCH_TIME_NOW")
|
||||||
|
@ -34,6 +35,13 @@ fn generate_dispatch_bindings() {
|
||||||
.allowlist_function("dispatch_async_f")
|
.allowlist_function("dispatch_async_f")
|
||||||
.allowlist_function("dispatch_after_f")
|
.allowlist_function("dispatch_after_f")
|
||||||
.allowlist_function("dispatch_time")
|
.allowlist_function("dispatch_time")
|
||||||
|
.allowlist_function("dispatch_source_merge_data")
|
||||||
|
.allowlist_function("dispatch_source_create")
|
||||||
|
.allowlist_function("dispatch_source_set_event_handler_f")
|
||||||
|
.allowlist_function("dispatch_resume")
|
||||||
|
.allowlist_function("dispatch_suspend")
|
||||||
|
.allowlist_function("dispatch_source_cancel")
|
||||||
|
.allowlist_function("dispatch_set_context")
|
||||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||||
.layout_tests(false)
|
.layout_tests(false)
|
||||||
.generate()
|
.generate()
|
||||||
|
|
|
@ -18,8 +18,8 @@ use crate::WindowAppearance;
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||||
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||||
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
||||||
Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
||||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
|
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
|
||||||
TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
|
TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
|
@ -193,7 +193,6 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
|
||||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||||
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
|
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
|
||||||
|
@ -213,8 +212,6 @@ pub struct AppContext {
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
pub(crate) actions: Rc<ActionRegistry>,
|
pub(crate) actions: Rc<ActionRegistry>,
|
||||||
pub(crate) active_drag: Option<AnyDrag>,
|
pub(crate) active_drag: Option<AnyDrag>,
|
||||||
pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
|
|
||||||
pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
|
|
||||||
pub(crate) background_executor: BackgroundExecutor,
|
pub(crate) background_executor: BackgroundExecutor,
|
||||||
pub(crate) foreground_executor: ForegroundExecutor,
|
pub(crate) foreground_executor: ForegroundExecutor,
|
||||||
pub(crate) svg_renderer: SvgRenderer,
|
pub(crate) svg_renderer: SvgRenderer,
|
||||||
|
@ -275,8 +272,6 @@ impl AppContext {
|
||||||
flushing_effects: false,
|
flushing_effects: false,
|
||||||
pending_updates: 0,
|
pending_updates: 0,
|
||||||
active_drag: None,
|
active_drag: None,
|
||||||
next_frame_callbacks: FxHashMap::default(),
|
|
||||||
frame_consumers: FxHashMap::default(),
|
|
||||||
background_executor: executor,
|
background_executor: executor,
|
||||||
foreground_executor,
|
foreground_executor,
|
||||||
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
||||||
|
|
|
@ -81,14 +81,6 @@ pub(crate) trait Platform: 'static {
|
||||||
/// Returns the appearance of the application's windows.
|
/// Returns the appearance of the application's windows.
|
||||||
fn window_appearance(&self) -> WindowAppearance;
|
fn window_appearance(&self) -> WindowAppearance;
|
||||||
|
|
||||||
fn set_display_link_output_callback(
|
|
||||||
&self,
|
|
||||||
display_id: DisplayId,
|
|
||||||
callback: Box<dyn FnMut() + Send>,
|
|
||||||
);
|
|
||||||
fn start_display_link(&self, display_id: DisplayId);
|
|
||||||
fn stop_display_link(&self, display_id: DisplayId);
|
|
||||||
|
|
||||||
fn open_url(&self, url: &str);
|
fn open_url(&self, url: &str);
|
||||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||||
fn prompt_for_paths(
|
fn prompt_for_paths(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! an origin at the bottom left of the main display.
|
//! an origin at the bottom left of the main display.
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod display;
|
mod display;
|
||||||
mod display_linker;
|
mod display_link;
|
||||||
mod events;
|
mod events;
|
||||||
mod metal_atlas;
|
mod metal_atlas;
|
||||||
mod metal_renderer;
|
mod metal_renderer;
|
||||||
|
@ -23,7 +23,7 @@ use std::ops::Range;
|
||||||
|
|
||||||
pub(crate) use dispatcher::*;
|
pub(crate) use dispatcher::*;
|
||||||
pub(crate) use display::*;
|
pub(crate) use display::*;
|
||||||
pub(crate) use display_linker::*;
|
pub(crate) use display_link::*;
|
||||||
pub(crate) use metal_atlas::*;
|
pub(crate) use metal_atlas::*;
|
||||||
pub(crate) use platform::*;
|
pub(crate) use platform::*;
|
||||||
pub(crate) use text_system::*;
|
pub(crate) use text_system::*;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
|
#include <dispatch/source.h>
|
||||||
|
|
|
@ -1,93 +1,96 @@
|
||||||
use std::{
|
use crate::{
|
||||||
ffi::c_void,
|
dispatch_get_main_queue,
|
||||||
mem,
|
dispatch_sys::{
|
||||||
sync::{Arc, Weak},
|
_dispatch_source_type_data_add, dispatch_resume, dispatch_set_context,
|
||||||
|
dispatch_source_cancel, dispatch_source_create, dispatch_source_merge_data,
|
||||||
|
dispatch_source_set_event_handler_f, dispatch_source_t, dispatch_suspend,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use core_graphics::display::CGDirectDisplayID;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
use crate::DisplayId;
|
pub struct DisplayLink {
|
||||||
use collections::HashMap;
|
display_link: sys::DisplayLink,
|
||||||
use parking_lot::Mutex;
|
frame_requests: dispatch_source_t,
|
||||||
|
|
||||||
pub(crate) struct MacDisplayLinker {
|
|
||||||
links: HashMap<DisplayId, MacDisplayLink>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MacDisplayLink {
|
impl DisplayLink {
|
||||||
system_link: sys::DisplayLink,
|
pub fn new(
|
||||||
_output_callback: Arc<OutputCallback>,
|
display_id: CGDirectDisplayID,
|
||||||
}
|
data: *mut c_void,
|
||||||
|
callback: unsafe extern "C" fn(*mut c_void),
|
||||||
impl MacDisplayLinker {
|
) -> Result<DisplayLink> {
|
||||||
pub fn new() -> Self {
|
unsafe extern "C" fn display_link_callback(
|
||||||
MacDisplayLinker {
|
_display_link_out: *mut sys::CVDisplayLink,
|
||||||
links: Default::default(),
|
_current_time: *const sys::CVTimeStamp,
|
||||||
|
_output_time: *const sys::CVTimeStamp,
|
||||||
|
_flags_in: i64,
|
||||||
|
_flags_out: *mut i64,
|
||||||
|
frame_requests: *mut c_void,
|
||||||
|
) -> i32 {
|
||||||
|
let frame_requests = frame_requests as dispatch_source_t;
|
||||||
|
dispatch_source_merge_data(frame_requests, 1);
|
||||||
|
0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
|
unsafe {
|
||||||
|
let frame_requests = dispatch_source_create(
|
||||||
impl MacDisplayLinker {
|
&_dispatch_source_type_data_add,
|
||||||
pub fn set_output_callback(
|
0,
|
||||||
&mut self,
|
0,
|
||||||
display_id: DisplayId,
|
dispatch_get_main_queue(),
|
||||||
output_callback: Box<dyn FnMut() + Send>,
|
|
||||||
) {
|
|
||||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
|
||||||
let callback = Arc::new(Mutex::new(output_callback));
|
|
||||||
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
|
|
||||||
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
|
|
||||||
|
|
||||||
self.links.insert(
|
|
||||||
display_id,
|
|
||||||
MacDisplayLink {
|
|
||||||
_output_callback: callback,
|
|
||||||
system_link,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} else {
|
dispatch_set_context(
|
||||||
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
|
crate::dispatch_sys::dispatch_object_t {
|
||||||
|
_ds: frame_requests,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
dispatch_source_set_event_handler_f(frame_requests, Some(callback));
|
||||||
|
|
||||||
|
let display_link = sys::DisplayLink::new(
|
||||||
|
display_id,
|
||||||
|
display_link_callback,
|
||||||
|
frame_requests as *mut c_void,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
display_link,
|
||||||
|
frame_requests,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, display_id: DisplayId) {
|
pub fn start(&mut self) -> Result<()> {
|
||||||
if let Some(link) = self.links.get_mut(&display_id) {
|
unsafe {
|
||||||
unsafe {
|
dispatch_resume(crate::dispatch_sys::dispatch_object_t {
|
||||||
link.system_link.start();
|
_ds: self.frame_requests,
|
||||||
}
|
});
|
||||||
} else {
|
self.display_link.start()?;
|
||||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, display_id: DisplayId) {
|
pub fn stop(&mut self) -> Result<()> {
|
||||||
if let Some(link) = self.links.get_mut(&display_id) {
|
unsafe {
|
||||||
unsafe {
|
dispatch_suspend(crate::dispatch_sys::dispatch_object_t {
|
||||||
link.system_link.stop();
|
_ds: self.frame_requests,
|
||||||
}
|
});
|
||||||
} else {
|
self.display_link.stop()?;
|
||||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn trampoline(
|
impl Drop for DisplayLink {
|
||||||
_display_link_out: *mut sys::CVDisplayLink,
|
fn drop(&mut self) {
|
||||||
current_time: *const sys::CVTimeStamp,
|
self.stop().log_err();
|
||||||
output_time: *const sys::CVTimeStamp,
|
unsafe {
|
||||||
_flags_in: i64,
|
dispatch_source_cancel(self.frame_requests);
|
||||||
_flags_out: *mut i64,
|
|
||||||
user_data: *mut c_void,
|
|
||||||
) -> i32 {
|
|
||||||
if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
|
||||||
let output_callback: Weak<OutputCallback> =
|
|
||||||
Weak::from_raw(user_data as *mut OutputCallback);
|
|
||||||
if let Some(output_callback) = output_callback.upgrade() {
|
|
||||||
(output_callback.lock())()
|
|
||||||
}
|
}
|
||||||
mem::forget(output_callback);
|
|
||||||
}
|
}
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod sys {
|
mod sys {
|
||||||
|
@ -96,10 +99,12 @@ mod sys {
|
||||||
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
|
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
|
||||||
#![allow(dead_code, non_upper_case_globals)]
|
#![allow(dead_code, non_upper_case_globals)]
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use core_graphics::display::CGDirectDisplayID;
|
||||||
use foreign_types::{foreign_type, ForeignType};
|
use foreign_types::{foreign_type, ForeignType};
|
||||||
use std::{
|
use std::{
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
fmt::{Debug, Formatter, Result},
|
fmt::{self, Debug, Formatter},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -114,7 +119,7 @@ mod sys {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for DisplayLink {
|
impl Debug for DisplayLink {
|
||||||
fn fmt(&self, formatter: &mut Formatter) -> Result {
|
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||||
formatter
|
formatter
|
||||||
.debug_tuple("DisplayLink")
|
.debug_tuple("DisplayLink")
|
||||||
.field(&self.as_ptr())
|
.field(&self.as_ptr())
|
||||||
|
@ -201,19 +206,15 @@ mod sys {
|
||||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||||
display_link_out: *mut *mut CVDisplayLink,
|
display_link_out: *mut *mut CVDisplayLink,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||||
|
display_link: &mut DisplayLinkRef,
|
||||||
display_id: u32,
|
display_id: u32,
|
||||||
display_link_out: *mut *mut CVDisplayLink,
|
|
||||||
) -> i32;
|
) -> i32;
|
||||||
pub fn CVDisplayLinkSetOutputCallback(
|
pub fn CVDisplayLinkSetOutputCallback(
|
||||||
display_link: &mut DisplayLinkRef,
|
display_link: &mut DisplayLinkRef,
|
||||||
callback: CVDisplayLinkOutputCallback,
|
callback: CVDisplayLinkOutputCallback,
|
||||||
user_info: *mut c_void,
|
user_info: *mut c_void,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
|
||||||
display_link: &mut DisplayLinkRef,
|
|
||||||
display_id: u32,
|
|
||||||
) -> i32;
|
|
||||||
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
||||||
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
||||||
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
||||||
|
@ -221,52 +222,46 @@ mod sys {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayLink {
|
impl DisplayLink {
|
||||||
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
|
|
||||||
pub unsafe fn new() -> Option<Self> {
|
|
||||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
|
||||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
|
||||||
if code == 0 {
|
|
||||||
Some(DisplayLink::from_ptr(display_link))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
||||||
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
|
pub unsafe fn new(
|
||||||
|
display_id: CGDirectDisplayID,
|
||||||
|
callback: CVDisplayLinkOutputCallback,
|
||||||
|
user_info: *mut c_void,
|
||||||
|
) -> Result<Self> {
|
||||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||||
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
|
|
||||||
if code == 0 {
|
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||||
Some(DisplayLink::from_ptr(display_link))
|
anyhow::ensure!(code == 0, "could not create display link, code: {}", code);
|
||||||
} else {
|
|
||||||
None
|
let mut display_link = DisplayLink::from_ptr(display_link);
|
||||||
}
|
|
||||||
|
let code = CVDisplayLinkSetOutputCallback(&mut display_link, callback, user_info);
|
||||||
|
anyhow::ensure!(code == 0, "could not set output callback, code: {}", code);
|
||||||
|
|
||||||
|
let code = CVDisplayLinkSetCurrentCGDisplay(&mut display_link, display_id);
|
||||||
|
anyhow::ensure!(
|
||||||
|
code == 0,
|
||||||
|
"could not assign display to display link, code: {}",
|
||||||
|
code
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(display_link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayLinkRef {
|
impl DisplayLinkRef {
|
||||||
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
|
|
||||||
pub unsafe fn set_output_callback(
|
|
||||||
&mut self,
|
|
||||||
callback: CVDisplayLinkOutputCallback,
|
|
||||||
user_info: *mut c_void,
|
|
||||||
) {
|
|
||||||
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
|
|
||||||
pub unsafe fn set_current_display(&mut self, display_id: u32) {
|
|
||||||
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
||||||
pub unsafe fn start(&mut self) {
|
pub unsafe fn start(&mut self) -> Result<()> {
|
||||||
assert_eq!(CVDisplayLinkStart(self), 0);
|
let code = CVDisplayLinkStart(self);
|
||||||
|
anyhow::ensure!(code == 0, "could not start display link, code: {}", code);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
|
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
|
||||||
pub unsafe fn stop(&mut self) {
|
pub unsafe fn stop(&mut self) -> Result<()> {
|
||||||
assert_eq!(CVDisplayLinkStop(self), 0);
|
let code = CVDisplayLinkStop(self);
|
||||||
|
anyhow::ensure!(code == 0, "could not stop display link, code: {}", code);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,6 +32,7 @@ const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary dec
|
||||||
pub(crate) struct MetalRenderer {
|
pub(crate) struct MetalRenderer {
|
||||||
device: metal::Device,
|
device: metal::Device,
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
|
presents_with_transaction: bool,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||||
|
@ -60,8 +61,8 @@ impl MetalRenderer {
|
||||||
let layer = metal::MetalLayer::new();
|
let layer = metal::MetalLayer::new();
|
||||||
layer.set_device(&device);
|
layer.set_device(&device);
|
||||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||||
layer.set_presents_with_transaction(true);
|
|
||||||
layer.set_opaque(true);
|
layer.set_opaque(true);
|
||||||
|
layer.set_maximum_drawable_count(3);
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
||||||
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
||||||
|
@ -174,6 +175,7 @@ impl MetalRenderer {
|
||||||
Self {
|
Self {
|
||||||
device,
|
device,
|
||||||
layer,
|
layer,
|
||||||
|
presents_with_transaction: false,
|
||||||
command_queue,
|
command_queue,
|
||||||
paths_rasterization_pipeline_state,
|
paths_rasterization_pipeline_state,
|
||||||
path_sprites_pipeline_state,
|
path_sprites_pipeline_state,
|
||||||
|
@ -198,6 +200,12 @@ impl MetalRenderer {
|
||||||
&self.sprite_atlas
|
&self.sprite_atlas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
|
||||||
|
self.presents_with_transaction = presents_with_transaction;
|
||||||
|
self.layer
|
||||||
|
.set_presents_with_transaction(presents_with_transaction);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn draw(&mut self, scene: &Scene) {
|
||||||
let layer = self.layer.clone();
|
let layer = self.layer.clone();
|
||||||
let viewport_size = layer.drawable_size();
|
let viewport_size = layer.drawable_size();
|
||||||
|
@ -347,11 +355,17 @@ impl MetalRenderer {
|
||||||
});
|
});
|
||||||
let block = block.copy();
|
let block = block.copy();
|
||||||
command_buffer.add_completed_handler(&block);
|
command_buffer.add_completed_handler(&block);
|
||||||
command_buffer.commit();
|
|
||||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||||
|
|
||||||
command_buffer.wait_until_scheduled();
|
if self.presents_with_transaction {
|
||||||
drawable.present();
|
command_buffer.commit();
|
||||||
|
command_buffer.wait_until_scheduled();
|
||||||
|
drawable.present();
|
||||||
|
} else {
|
||||||
|
command_buffer.present_drawable(drawable);
|
||||||
|
command_buffer.commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rasterize_paths(
|
fn rasterize_paths(
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::{events::key_to_native, BoolExt};
|
use super::{events::key_to_native, BoolExt};
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacTextSystem, MacWindow, Menu,
|
||||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
|
||||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance,
|
PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, WindowOptions,
|
||||||
WindowOptions,
|
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
|
@ -146,7 +145,6 @@ pub(crate) struct MacPlatformState {
|
||||||
background_executor: BackgroundExecutor,
|
background_executor: BackgroundExecutor,
|
||||||
foreground_executor: ForegroundExecutor,
|
foreground_executor: ForegroundExecutor,
|
||||||
text_system: Arc<MacTextSystem>,
|
text_system: Arc<MacTextSystem>,
|
||||||
display_linker: MacDisplayLinker,
|
|
||||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||||
pasteboard: id,
|
pasteboard: id,
|
||||||
text_hash_pasteboard_type: id,
|
text_hash_pasteboard_type: id,
|
||||||
|
@ -177,7 +175,6 @@ impl MacPlatform {
|
||||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||||
text_system: Arc::new(MacTextSystem::new()),
|
text_system: Arc::new(MacTextSystem::new()),
|
||||||
display_linker: MacDisplayLinker::new(),
|
|
||||||
instance_buffer_pool: Arc::default(),
|
instance_buffer_pool: Arc::default(),
|
||||||
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
||||||
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
|
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
|
||||||
|
@ -514,25 +511,6 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_display_link_output_callback(
|
|
||||||
&self,
|
|
||||||
display_id: DisplayId,
|
|
||||||
callback: Box<dyn FnMut() + Send>,
|
|
||||||
) {
|
|
||||||
self.0
|
|
||||||
.lock()
|
|
||||||
.display_linker
|
|
||||||
.set_output_callback(display_id, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_display_link(&self, display_id: DisplayId) {
|
|
||||||
self.0.lock().display_linker.start(display_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_display_link(&self, display_id: DisplayId) {
|
|
||||||
self.0.lock().display_linker.stop(display_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_url(&self, url: &str) {
|
fn open_url(&self, url: &str) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let url = NSURL::alloc(nil)
|
let url = NSURL::alloc(nil)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||||
use crate::{
|
use crate::{
|
||||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||||
Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
|
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
||||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
|
||||||
|
WindowOptions,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -16,11 +17,11 @@ use cocoa::{
|
||||||
},
|
},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{
|
foundation::{
|
||||||
NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration,
|
NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
|
||||||
NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
NSSize, NSString, NSUInteger,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGRect;
|
use core_graphics::display::{CGDirectDisplayID, CGRect};
|
||||||
use ctor::ctor;
|
use ctor::ctor;
|
||||||
use foreign_types::ForeignTypeRef;
|
use foreign_types::ForeignTypeRef;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
|
@ -50,6 +51,7 @@ use std::{
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
const WINDOW_STATE_IVAR: &str = "windowState";
|
const WINDOW_STATE_IVAR: &str = "windowState";
|
||||||
|
|
||||||
|
@ -168,7 +170,6 @@ unsafe fn build_classes() {
|
||||||
sel!(displayLayer:),
|
sel!(displayLayer:),
|
||||||
display_layer as extern "C" fn(&Object, Sel, id),
|
display_layer as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
decl.add_method(sel!(step:), step as extern "C" fn(&Object, Sel, id));
|
|
||||||
|
|
||||||
decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
|
decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
|
@ -330,7 +331,7 @@ struct MacWindowState {
|
||||||
executor: ForegroundExecutor,
|
executor: ForegroundExecutor,
|
||||||
native_window: id,
|
native_window: id,
|
||||||
native_view: NonNull<Object>,
|
native_view: NonNull<Object>,
|
||||||
display_link: id,
|
display_link: Option<DisplayLink>,
|
||||||
renderer: MetalRenderer,
|
renderer: MetalRenderer,
|
||||||
kind: WindowKind,
|
kind: WindowKind,
|
||||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||||
|
@ -402,6 +403,21 @@ impl MacWindowState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_display_link(&mut self) {
|
||||||
|
self.stop_display_link();
|
||||||
|
let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
|
||||||
|
if let Some(mut display_link) =
|
||||||
|
DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
|
||||||
|
{
|
||||||
|
display_link.start().log_err();
|
||||||
|
self.display_link = Some(display_link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_display_link(&mut self) {
|
||||||
|
self.display_link = None;
|
||||||
|
}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
let style_mask = self.native_window.styleMask();
|
let style_mask = self.native_window.styleMask();
|
||||||
|
@ -506,11 +522,8 @@ impl MacWindow {
|
||||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||||
let device_description = NSScreen::deviceDescription(screen);
|
let display_id = display_id_for_screen(screen);
|
||||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
if display_id == display.id().0 {
|
||||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
|
||||||
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
|
||||||
if screen_number as u32 == display.id().0 {
|
|
||||||
target_screen = screen;
|
target_screen = screen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -539,7 +552,7 @@ impl MacWindow {
|
||||||
executor,
|
executor,
|
||||||
native_window,
|
native_window,
|
||||||
native_view: NonNull::new_unchecked(native_view),
|
native_view: NonNull::new_unchecked(native_view),
|
||||||
display_link: nil,
|
display_link: None,
|
||||||
renderer: MetalRenderer::new(instance_buffer_pool),
|
renderer: MetalRenderer::new(instance_buffer_pool),
|
||||||
kind: options.kind,
|
kind: options.kind,
|
||||||
request_frame_callback: None,
|
request_frame_callback: None,
|
||||||
|
@ -695,19 +708,11 @@ impl MacWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn start_display_link(native_screen: id, native_view: id) -> id {
|
|
||||||
let display_link: id =
|
|
||||||
msg_send![native_screen, displayLinkWithTarget: native_view selector: sel!(step:)];
|
|
||||||
let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop];
|
|
||||||
let _: () = msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode];
|
|
||||||
display_link
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for MacWindow {
|
impl Drop for MacWindow {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let mut this = self.0.lock();
|
let mut this = self.0.lock();
|
||||||
let window = this.native_window;
|
let window = this.native_window;
|
||||||
this.display_link = nil;
|
this.display_link.take();
|
||||||
this.executor
|
this.executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -1336,17 +1341,16 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||||
|
|
||||||
extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
let mut lock = window_state.lock();
|
let lock = &mut *window_state.lock();
|
||||||
unsafe {
|
unsafe {
|
||||||
if lock
|
if lock
|
||||||
.native_window
|
.native_window
|
||||||
.occlusionState()
|
.occlusionState()
|
||||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
||||||
{
|
{
|
||||||
lock.display_link =
|
lock.start_display_link();
|
||||||
start_display_link(lock.native_window.screen(), lock.native_view.as_ptr());
|
|
||||||
} else {
|
} else {
|
||||||
lock.display_link = nil;
|
lock.stop_display_link();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1387,14 +1391,7 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
|
||||||
extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
|
extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
let mut lock = window_state.as_ref().lock();
|
let mut lock = window_state.as_ref().lock();
|
||||||
unsafe {
|
lock.start_display_link();
|
||||||
let screen = lock.native_window.screen();
|
|
||||||
if screen == nil {
|
|
||||||
lock.display_link = nil;
|
|
||||||
} else {
|
|
||||||
lock.display_link = start_display_link(screen, lock.native_view.as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
||||||
|
@ -1540,26 +1537,27 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
let mut lock = window_state.lock();
|
let mut lock = window_state.lock();
|
||||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||||
|
lock.renderer.set_presents_with_transaction(true);
|
||||||
|
lock.stop_display_link();
|
||||||
drop(lock);
|
drop(lock);
|
||||||
callback();
|
callback();
|
||||||
window_state.lock().request_frame_callback = Some(callback);
|
|
||||||
|
let mut lock = window_state.lock();
|
||||||
|
lock.request_frame_callback = Some(callback);
|
||||||
|
lock.renderer.set_presents_with_transaction(false);
|
||||||
|
lock.start_display_link();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn step(this: &Object, _: Sel, display_link: id) {
|
unsafe extern "C" fn step(view: *mut c_void) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let view = view as id;
|
||||||
|
let window_state = unsafe { get_window_state(&*view) };
|
||||||
let mut lock = window_state.lock();
|
let mut lock = window_state.lock();
|
||||||
|
|
||||||
if lock.display_link == display_link {
|
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
drop(lock);
|
||||||
drop(lock);
|
callback();
|
||||||
callback();
|
window_state.lock().request_frame_callback = Some(callback);
|
||||||
window_state.lock().request_frame_callback = Some(callback);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![display_link, invalidate];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1882,3 +1880,11 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
|
||||||
|
let device_description = NSScreen::deviceDescription(screen);
|
||||||
|
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||||
|
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||||
|
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||||
|
screen_number as CGDirectDisplayID
|
||||||
|
}
|
||||||
|
|
|
@ -186,18 +186,6 @@ impl Platform for TestPlatform {
|
||||||
WindowAppearance::Light
|
WindowAppearance::Light
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_display_link_output_callback(
|
|
||||||
&self,
|
|
||||||
_display_id: DisplayId,
|
|
||||||
mut callback: Box<dyn FnMut() + Send>,
|
|
||||||
) {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_display_link(&self, _display_id: DisplayId) {}
|
|
||||||
|
|
||||||
fn stop_display_link(&self, _display_id: DisplayId) {}
|
|
||||||
|
|
||||||
fn open_url(&self, url: &str) {
|
fn open_url(&self, url: &str) {
|
||||||
*self.opened_url.borrow_mut() = Some(url.to_string())
|
*self.opened_url.borrow_mut() = Some(url.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,7 @@ use crate::{
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::FxHashSet;
|
use collections::FxHashSet;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures::{
|
use futures::channel::oneshot;
|
||||||
channel::{mpsc, oneshot},
|
|
||||||
StreamExt,
|
|
||||||
};
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -23,7 +20,6 @@ use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::hash_map::Entry,
|
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
future::Future,
|
future::Future,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
@ -243,6 +239,8 @@ impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
|
||||||
/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
|
/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
|
||||||
pub struct DismissEvent;
|
pub struct DismissEvent;
|
||||||
|
|
||||||
|
type FrameCallback = Box<dyn FnOnce(&mut WindowContext)>;
|
||||||
|
|
||||||
// Holds the state for a specific window.
|
// Holds the state for a specific window.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
@ -259,6 +257,7 @@ pub struct Window {
|
||||||
pub(crate) element_id_stack: GlobalElementId,
|
pub(crate) element_id_stack: GlobalElementId,
|
||||||
pub(crate) rendered_frame: Frame,
|
pub(crate) rendered_frame: Frame,
|
||||||
pub(crate) next_frame: Frame,
|
pub(crate) next_frame: Frame,
|
||||||
|
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||||
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||||
|
@ -338,14 +337,27 @@ impl Window {
|
||||||
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
|
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
|
||||||
let dirty = Rc::new(Cell::new(true));
|
let dirty = Rc::new(Cell::new(true));
|
||||||
let active = Rc::new(Cell::new(false));
|
let active = Rc::new(Cell::new(false));
|
||||||
|
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
|
||||||
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
||||||
|
|
||||||
platform_window.on_request_frame(Box::new({
|
platform_window.on_request_frame(Box::new({
|
||||||
let mut cx = cx.to_async();
|
let mut cx = cx.to_async();
|
||||||
let dirty = dirty.clone();
|
let dirty = dirty.clone();
|
||||||
let active = active.clone();
|
let active = active.clone();
|
||||||
|
let next_frame_callbacks = next_frame_callbacks.clone();
|
||||||
let last_input_timestamp = last_input_timestamp.clone();
|
let last_input_timestamp = last_input_timestamp.clone();
|
||||||
move || {
|
move || {
|
||||||
|
let next_frame_callbacks = next_frame_callbacks.take();
|
||||||
|
if !next_frame_callbacks.is_empty() {
|
||||||
|
handle
|
||||||
|
.update(&mut cx, |_, cx| {
|
||||||
|
for callback in next_frame_callbacks {
|
||||||
|
callback(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
if dirty.get() {
|
if dirty.get() {
|
||||||
measure("frame duration", || {
|
measure("frame duration", || {
|
||||||
handle
|
handle
|
||||||
|
@ -428,6 +440,7 @@ impl Window {
|
||||||
element_id_stack: GlobalElementId::default(),
|
element_id_stack: GlobalElementId::default(),
|
||||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
|
next_frame_callbacks,
|
||||||
dirty_views: FxHashSet::default(),
|
dirty_views: FxHashSet::default(),
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
focus_listeners: SubscriberSet::new(),
|
focus_listeners: SubscriberSet::new(),
|
||||||
|
@ -670,57 +683,7 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
/// Schedule the given closure to be run directly after the current frame is rendered.
|
/// Schedule the given closure to be run directly after the current frame is rendered.
|
||||||
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
|
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
|
||||||
let handle = self.window.handle;
|
RefCell::borrow_mut(&self.window.next_frame_callbacks).push(Box::new(callback));
|
||||||
let display_id = self.window.display_id;
|
|
||||||
|
|
||||||
let mut frame_consumers = std::mem::take(&mut self.app.frame_consumers);
|
|
||||||
if let Entry::Vacant(e) = frame_consumers.entry(display_id) {
|
|
||||||
let (tx, mut rx) = mpsc::unbounded::<()>();
|
|
||||||
self.platform.set_display_link_output_callback(
|
|
||||||
display_id,
|
|
||||||
Box::new(move || _ = tx.unbounded_send(())),
|
|
||||||
);
|
|
||||||
|
|
||||||
let consumer_task = self.app.spawn(|cx| async move {
|
|
||||||
while rx.next().await.is_some() {
|
|
||||||
cx.update(|cx| {
|
|
||||||
for callback in cx
|
|
||||||
.next_frame_callbacks
|
|
||||||
.get_mut(&display_id)
|
|
||||||
.unwrap()
|
|
||||||
.drain(..)
|
|
||||||
.collect::<SmallVec<[_; 32]>>()
|
|
||||||
{
|
|
||||||
callback(cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
// Flush effects, then stop the display link if no new next_frame_callbacks have been added.
|
|
||||||
|
|
||||||
cx.update(|cx| {
|
|
||||||
if cx.next_frame_callbacks.is_empty() {
|
|
||||||
cx.platform.stop_display_link(display_id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
e.insert(consumer_task);
|
|
||||||
}
|
|
||||||
debug_assert!(self.app.frame_consumers.is_empty());
|
|
||||||
self.app.frame_consumers = frame_consumers;
|
|
||||||
|
|
||||||
if self.next_frame_callbacks.is_empty() {
|
|
||||||
self.platform.start_display_link(display_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.next_frame_callbacks
|
|
||||||
.entry(display_id)
|
|
||||||
.or_default()
|
|
||||||
.push(Box::new(move |cx: &mut AppContext| {
|
|
||||||
cx.update_window(handle, |_root_view, cx| callback(cx)).ok();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn the future returned by the given closure on the application thread pool.
|
/// Spawn the future returned by the given closure on the application thread pool.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue