use std::ffi::{c_void, CStr};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use doom_fish_utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
use serde::Deserialize;
use crate::error::VisionKitError;
use crate::ffi;
use crate::image_analysis::ImageAnalysis;
use crate::image_analyzer::{ImageAnalyzerConfiguration, ImageOrientation};
use crate::live_text_interaction::LiveTextInteraction;
use crate::private::{json_cstring, path_to_cstring};
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct AnalysisSubjectBounds {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
unsafe fn cstring_result_to_string(ptr: *const c_void) -> String {
CStr::from_ptr(ptr.cast::<i8>())
.to_str()
.map_or_else(|_| String::new(), str::to_owned)
}
extern "C" fn analyze_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<ImageAnalysis>::complete_err(ctx, msg) };
} else if !result.is_null() {
let analysis = ImageAnalysis::from_token(result.cast_mut());
unsafe { AsyncCompletion::complete_ok(ctx, analysis) };
} else {
unsafe {
AsyncCompletion::<ImageAnalysis>::complete_err(ctx, "Unknown error".into());
};
}
}
#[must_use = "futures do nothing unless polled"]
pub struct AnalyzeImageFuture {
inner: AsyncCompletionFuture<ImageAnalysis>,
}
impl std::fmt::Debug for AnalyzeImageFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnalyzeImageFuture").finish_non_exhaustive()
}
}
impl Future for AnalyzeImageFuture {
type Output = Result<ImageAnalysis, VisionKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(VisionKitError::Unknown))
}
}
pub struct AsyncImageAnalyzer {
token: *mut c_void,
}
unsafe impl Send for AsyncImageAnalyzer {}
unsafe impl Sync for AsyncImageAnalyzer {}
impl Drop for AsyncImageAnalyzer {
fn drop(&mut self) {
if !self.token.is_null() {
unsafe { ffi::image_analyzer::vk_image_analyzer_release(self.token) };
}
}
}
impl AsyncImageAnalyzer {
pub fn new() -> Result<Self, VisionKitError> {
let token = unsafe { ffi::image_analyzer::vk_image_analyzer_new() };
if token.is_null() {
return Err(VisionKitError::UnavailableOnThisMacOS(
"ImageAnalyzer requires macOS 13+".to_owned(),
));
}
Ok(Self { token })
}
#[must_use]
pub fn is_supported() -> bool {
unsafe { ffi::image_analyzer::vk_image_analyzer_is_supported() != 0 }
}
pub fn analyze_image_at_path<P: AsRef<std::path::Path>>(
&self,
path: P,
orientation: ImageOrientation,
configuration: &ImageAnalyzerConfiguration,
) -> Result<AnalyzeImageFuture, VisionKitError> {
let path_cs = path_to_cstring(path.as_ref())?;
let cfg_cs = json_cstring(configuration)?;
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::image_analyzer::vk_image_analyzer_analyze_image_async(
self.token,
path_cs.as_ptr(),
orientation.raw_value(),
cfg_cs.as_ptr(),
analyze_cb,
ctx,
);
}
Ok(AnalyzeImageFuture { inner: future })
}
}
extern "C" fn subjects_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<String>::complete_err(ctx, msg) };
} else if !result.is_null() {
let json = unsafe { cstring_result_to_string(result) };
unsafe { AsyncCompletion::complete_ok(ctx, json) };
} else {
unsafe { AsyncCompletion::<String>::complete_err(ctx, "Unknown error".into()) };
}
}
extern "C" fn subject_at_cb(result: *const c_void, error: *const i8, ctx: *mut c_void) {
if !error.is_null() {
let msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<String>::complete_err(ctx, msg) };
} else if !result.is_null() {
let json = unsafe { cstring_result_to_string(result) };
unsafe { AsyncCompletion::complete_ok(ctx, json) };
} else {
unsafe { AsyncCompletion::<String>::complete_err(ctx, "Unknown error".into()) };
}
}
#[must_use = "futures do nothing unless polled"]
pub struct SubjectsFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for SubjectsFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubjectsFuture").finish_non_exhaustive()
}
}
impl Future for SubjectsFuture {
type Output = Result<Vec<AnalysisSubjectBounds>, VisionKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
r.map_err(VisionKitError::Unknown).and_then(|json| {
serde_json::from_str::<Vec<AnalysisSubjectBounds>>(&json).map_err(|e| {
VisionKitError::Unknown(format!(
"failed to decode subjects JSON from Swift bridge: {e}"
))
})
})
})
}
}
#[must_use = "futures do nothing unless polled"]
pub struct SubjectAtFuture {
inner: AsyncCompletionFuture<String>,
}
impl std::fmt::Debug for SubjectAtFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubjectAtFuture").finish_non_exhaustive()
}
}
impl Future for SubjectAtFuture {
type Output = Result<Option<AnalysisSubjectBounds>, VisionKitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map(|r| {
r.map_err(VisionKitError::Unknown).and_then(|json| {
serde_json::from_str::<Option<AnalysisSubjectBounds>>(&json).map_err(|e| {
VisionKitError::Unknown(format!(
"failed to decode subject-at JSON from Swift bridge: {e}"
))
})
})
})
}
}
pub struct AsyncOverlaySubjects {
token: *mut c_void,
}
unsafe impl Send for AsyncOverlaySubjects {}
impl AsyncOverlaySubjects {
#[must_use]
pub fn new(interaction: &LiveTextInteraction) -> Self {
Self {
token: interaction.raw_token(),
}
}
#[must_use = "returns a future that must be awaited"]
pub fn subjects(&self) -> SubjectsFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::live_text_interaction::vk_live_text_overlay_subjects_async(
self.token,
subjects_cb,
ctx,
);
}
SubjectsFuture { inner: future }
}
#[must_use = "returns a future that must be awaited"]
pub fn subject_at(&self, x: f64, y: f64) -> SubjectAtFuture {
let (future, ctx) = AsyncCompletion::create();
unsafe {
ffi::live_text_interaction::vk_live_text_overlay_subject_at_async(
self.token,
x,
y,
subject_at_cb,
ctx,
);
}
SubjectAtFuture { inner: future }
}
}
struct NoopWake;
impl Wake for NoopWake {
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
pub fn block_on<F: Future>(future: F) -> F::Output {
let waker = Waker::from(Arc::new(NoopWake));
let cx = &mut Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
loop {
match future.as_mut().poll(cx) {
Poll::Ready(val) => return val,
Poll::Pending => {
unsafe { ffi::image_analyzer::vk_pump_main_run_loop(10) };
}
}
}
}