Detailed changes
@@ -8,8 +8,8 @@ pub use model_context::*;
use refineable::Refineable;
use crate::{
- current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId,
- MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
+ current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor,
+ LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
@@ -51,15 +51,15 @@ impl App {
http_client: Arc<dyn HttpClient>,
) -> Self {
let executor = platform.executor();
- let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new();
let unit_entity = entities.insert(entities.reserve(), ());
Self(Arc::new_cyclic(|this| {
Mutex::new(AppContext {
this: this.clone(),
+ display_linker: Arc::new(DisplayLinker::new(platform.display_linker())),
+ text_system: Arc::new(TextSystem::new(platform.text_system())),
platform: MainThreadOnly::new(platform, executor.clone()),
executor,
- text_system,
svg_renderer: SvgRenderer::new(asset_source),
image_cache: ImageCache::new(http_client),
pending_updates: 0,
@@ -97,6 +97,7 @@ pub struct AppContext {
text_system: Arc<TextSystem>,
pending_updates: usize,
pub(crate) executor: Executor,
+ pub(crate) display_linker: Arc<DisplayLinker>,
pub(crate) svg_renderer: SvgRenderer,
pub(crate) image_cache: ImageCache,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
@@ -359,9 +360,15 @@ impl MainThread<AppContext> {
let id = cx.windows.insert(None);
let handle = WindowHandle::new(id);
let mut window = Window::new(handle.into(), options, cx);
+ let display_id = window.display_id;
let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
window.root_view.replace(root_view.into_any());
cx.windows.get_mut(id).unwrap().replace(window);
+
+ cx.display_linker.on_next_frame(display_id, |_, _| {
+ dbg!("next frame");
+ });
+
handle
})
}
@@ -0,0 +1,55 @@
+use crate::{DisplayId, PlatformDisplayLinker, VideoTimestamp};
+use collections::HashMap;
+use parking_lot::Mutex;
+use std::sync::Arc;
+
+type FrameCallback = Box<dyn FnOnce(&VideoTimestamp, &VideoTimestamp) + Send>;
+
+pub struct DisplayLinker {
+ platform_linker: Arc<dyn PlatformDisplayLinker>,
+ next_frame_callbacks: Arc<Mutex<HashMap<DisplayId, Vec<FrameCallback>>>>,
+}
+
+impl DisplayLinker {
+ pub(crate) fn new(platform_linker: Arc<dyn PlatformDisplayLinker>) -> Self {
+ Self {
+ platform_linker,
+ next_frame_callbacks: Default::default(),
+ }
+ }
+
+ pub(crate) fn on_next_frame(
+ &self,
+ display_id: DisplayId,
+ callback: impl FnOnce(&VideoTimestamp, &VideoTimestamp) + Send + 'static,
+ ) {
+ let next_frame_callbacks = self.next_frame_callbacks.clone();
+ let callback = Box::new(callback);
+ match self.next_frame_callbacks.lock().entry(display_id) {
+ collections::hash_map::Entry::Occupied(mut entry) => {
+ if entry.get().is_empty() {
+ self.platform_linker.start(display_id)
+ }
+ entry.get_mut().push(callback)
+ }
+ collections::hash_map::Entry::Vacant(entry) => {
+ // let platform_linker = self.platform_linker.clone();
+ self.platform_linker.set_output_callback(
+ display_id,
+ Box::new(move |current_time, output_time| {
+ for callback in next_frame_callbacks
+ .lock()
+ .get_mut(&display_id)
+ .unwrap()
+ .drain(..)
+ {
+ callback(current_time, output_time);
+ }
+ // platform_linker.stop(display_id);
+ }),
+ );
+ entry.insert(vec![callback]);
+ }
+ }
+ }
+}
@@ -1,6 +1,7 @@
mod app;
mod assets;
mod color;
+mod display_linker;
mod element;
mod elements;
mod executor;
@@ -22,6 +23,7 @@ pub use anyhow::Result;
pub use app::*;
pub use assets::*;
pub use color::*;
+pub use display_linker::*;
pub use element::*;
pub use elements::*;
pub use executor::*;
@@ -42,6 +42,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
pub trait Platform: 'static {
fn executor(&self) -> Executor;
+ fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
@@ -99,7 +100,6 @@ pub trait PlatformDisplay: Debug {
fn id(&self) -> DisplayId;
fn as_any(&self) -> &dyn Any;
fn bounds(&self) -> Bounds<GlobalPixels>;
- fn link(&self) -> Box<dyn PlatformDisplayLink>;
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
@@ -156,10 +156,14 @@ pub trait PlatformDispatcher: Send + Sync {
fn dispatch_on_main_thread(&self, task: Runnable);
}
-pub trait PlatformDisplayLink {
- // fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>);
- fn start(&mut self);
- fn stop(&mut self);
+pub trait PlatformDisplayLinker: Send + Sync {
+ fn set_output_callback(
+ &self,
+ display_id: DisplayId,
+ callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+ );
+ fn start(&self, display_id: DisplayId);
+ fn stop(&self, display_id: DisplayId);
}
pub trait PlatformTextSystem: Send + Sync {
@@ -2,7 +2,7 @@
///! an origin at the bottom left of the main display.
mod dispatcher;
mod display;
-// mod display_link;
+mod display_linker;
mod events;
mod metal_atlas;
mod metal_renderer;
@@ -33,7 +33,7 @@ use std::{
pub use dispatcher::*;
pub use display::*;
-// pub use display_link::*;
+pub use display_linker::*;
pub use metal_atlas::*;
pub use platform::*;
pub use text_system::*;
@@ -98,9 +98,4 @@ impl PlatformDisplay for MacDisplay {
display_bounds_from_native(native_bounds)
}
}
-
- fn link(&self) -> Box<dyn crate::PlatformDisplayLink> {
- unimplemented!()
- // Box::new(unsafe { MacDisplayLink::new(self.0) })
- }
}
@@ -1,41 +1,77 @@
-use crate::PlatformDisplayLink;
use std::ffi::c_void;
+use crate::{DisplayId, PlatformDisplayLinker};
+use collections::HashMap;
+use parking_lot::Mutex;
pub use sys::CVTimeStamp as VideoTimestamp;
-pub struct MacDisplayLink {
- sys_link: sys::DisplayLink,
- output_callback: Option<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>,
+pub struct MacDisplayLinker {
+ links: Mutex<HashMap<DisplayId, MacDisplayLink>>,
}
-impl MacDisplayLink {
- pub unsafe fn new(display_id: u32) -> Self {
- Self {
- sys_link: sys::DisplayLink::on_display(display_id).unwrap(),
- output_callback: None,
+struct MacDisplayLink {
+ output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+ system_link: sys::DisplayLink,
+}
+
+unsafe impl Send for MacDisplayLink {}
+
+impl MacDisplayLinker {
+ pub fn new() -> Self {
+ MacDisplayLinker {
+ links: Default::default(),
}
}
}
-impl PlatformDisplayLink for MacDisplayLink {
- fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>) {
- unsafe {
- self.sys_link.set_output_callback(
- trampoline,
- self.output_callback.as_mut().unwrap()
- as *mut dyn FnMut(&VideoTimestamp, &VideoTimestamp)
- as *mut c_void,
+impl PlatformDisplayLinker for MacDisplayLinker {
+ fn set_output_callback(
+ &self,
+ display_id: DisplayId,
+ mut output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+ ) {
+ if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
+ unsafe {
+ system_link.set_output_callback(
+ trampoline,
+ output_callback.as_mut() as *mut dyn FnMut(_, _) as *mut c_void,
+ )
+ }
+
+ let previous = self.links.lock().insert(
+ display_id,
+ MacDisplayLink {
+ output_callback,
+ system_link,
+ },
);
+ assert!(
+ previous.is_none(),
+ "You can currently only set an output callback once per display."
+ )
+ } else {
+ return log::warn!("DisplayLink could not be obtained for {:?}", display_id);
}
- self.output_callback = Some(callback);
}
- fn start(&mut self) {
- unsafe { self.sys_link.start() }
+ fn start(&self, display_id: DisplayId) {
+ if let Some(link) = self.links.lock().get_mut(&display_id) {
+ unsafe {
+ link.system_link.start();
+ }
+ } else {
+ log::warn!("No DisplayLink callback registered for {:?}", display_id)
+ }
}
- fn stop(&mut self) {
- unsafe { self.sys_link.stop() }
+ fn stop(&self, display_id: DisplayId) {
+ if let Some(link) = self.links.lock().get_mut(&display_id) {
+ unsafe {
+ link.system_link.stop();
+ }
+ } else {
+ log::warn!("No DisplayLink callback registered for {:?}", display_id)
+ }
}
}
@@ -48,11 +84,8 @@ unsafe extern "C" fn trampoline(
context: *mut c_void,
) -> i32 {
let output_callback = &mut (*(context as *mut MacDisplayLink)).output_callback;
- if let Some(callback) = output_callback {
- if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
- // convert sys::CVTimeStamp to VideoTimestamp
- callback(¤t_time, &output_time);
- }
+ if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
+ output_callback(¤t_time, &output_time);
}
0
}
@@ -61,9 +94,8 @@ mod sys {
//! Derived from display-link crate under the fololwing license:
//! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
- #![allow(dead_code)]
+ #![allow(dead_code, non_upper_case_globals)]
- pub use cocoa::quartzcore::CVTimeStamp;
use foreign_types::{foreign_type, ForeignType};
use std::{
ffi::c_void,
@@ -90,6 +122,64 @@ mod sys {
}
}
+ #[repr(C)]
+ #[derive(Clone, Copy)]
+ pub struct CVTimeStamp {
+ pub version: u32,
+ pub video_time_scale: i32,
+ pub video_time: i64,
+ pub host_time: u64,
+ pub rate_scalar: f64,
+ pub video_refresh_period: i64,
+ pub smpte_time: CVSMPTETime,
+ pub flags: u64,
+ pub reserved: u64,
+ }
+
+ pub type CVTimeStampFlags = u64;
+
+ pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
+ pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
+ pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
+ pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
+ pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
+ pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
+ pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
+ pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
+ kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
+ pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
+ kCVTimeStampTopField | kCVTimeStampBottomField;
+
+ #[repr(C)]
+ #[derive(Clone, Copy)]
+ pub struct CVSMPTETime {
+ pub subframes: i16,
+ pub subframe_divisor: i16,
+ pub counter: u32,
+ pub time_type: u32,
+ pub flags: u32,
+ pub hours: i16,
+ pub minutes: i16,
+ pub seconds: i16,
+ pub frames: i16,
+ }
+
+ pub type CVSMPTETimeType = u32;
+
+ pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
+ pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
+ pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
+ pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
+ pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
+ pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
+ pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
+ pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
+
+ pub type CVSMPTETimeFlags = u32;
+
+ pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
+ pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
+
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
display_link_out: *mut CVDisplayLink,
// A pointer to the current timestamp. This represents the timestamp when the callback is called.
@@ -1,8 +1,9 @@
use super::BoolExt;
use crate::{
AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
- MacDisplay, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay,
- PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
+ MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
+ PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result,
+ SemanticVersion, WindowOptions,
};
use anyhow::anyhow;
use block::ConcreteBlock;
@@ -347,6 +348,10 @@ impl Platform for MacPlatform {
self.0.lock().executor.clone()
}
+ fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker> {
+ Arc::new(MacDisplayLinker::new())
+ }
+
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.0.lock().text_system.clone()
}
@@ -15,6 +15,10 @@ impl Platform for TestPlatform {
unimplemented!()
}
+ fn display_linker(&self) -> std::sync::Arc<dyn crate::PlatformDisplayLinker> {
+ unimplemented!()
+ }
+
fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
unimplemented!()
}
@@ -1,10 +1,11 @@
use crate::{
image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
- BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId,
- GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly,
- MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
- RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style,
- TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+ BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId,
+ FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread,
+ MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
+ PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
+ SharedString, Size, Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions,
+ SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use smallvec::SmallVec;
@@ -16,6 +17,7 @@ pub struct AnyWindow {}
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+ pub(crate) display_id: DisplayId, // todo!("make private again?")
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
content_size: Size<Pixels>,
@@ -35,6 +37,7 @@ impl Window {
cx: &mut MainThread<AppContext>,
) -> Self {
let platform_window = cx.platform().open_window(handle, options);
+ let display_id = platform_window.display().id();
let sprite_atlas = platform_window.sprite_atlas();
let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size();
@@ -46,6 +49,12 @@ impl Window {
cx.update_window(handle, |cx| {
cx.window.scene = Scene::new(scale_factor);
cx.window.content_size = content_size;
+ cx.window.display_id = cx
+ .window
+ .platform_window
+ .borrow_on_main_thread()
+ .display()
+ .id();
cx.window.dirty = true;
})
.log_err();
@@ -57,6 +66,7 @@ impl Window {
Window {
handle,
platform_window,
+ display_id,
sprite_atlas,
rem_size: px(16.),
content_size,