4343//!
4444//! it will build `target/aarch64-apple-ios/release/libgifski.a` (ignore the warning about cdylib).
4545
46- use crate :: { Collector , NoProgress , ProgressCallback , ProgressReporter , Repeat , Settings , Writer } ;
46+ use crate :: progress:: ProgressCallback ;
47+ use crate :: { CCallbacks , Collector , ErrorCallback , ProgressReporter , Repeat , Settings , Writer } ;
4748use imgref:: { Img , ImgVec } ;
4849use rgb:: { RGB8 , RGBA8 } ;
4950use std:: fs;
50- use std:: ffi:: { CStr , CString } ;
51+ use std:: ffi:: CStr ;
5152use std:: fs:: File ;
5253use std:: io;
5354use std:: io:: Write ;
@@ -57,7 +58,7 @@ use std::path::{Path, PathBuf};
5758use std:: ptr;
5859use std:: slice;
5960use std:: thread;
60- use std:: sync:: { Arc , Mutex } ;
61+ use std:: sync:: { Arc , Mutex , MutexGuard } ;
6162mod c_api_error;
6263use self :: c_api_error:: GifskiError ;
6364use std:: panic:: catch_unwind;
@@ -96,8 +97,8 @@ pub struct GifskiHandle {
9697pub struct GifskiHandleInternal {
9798 writer : Mutex < Option < Writer > > ,
9899 collector : Mutex < Option < Collector > > ,
99- progress : Mutex < Option < ProgressCallback > > ,
100- error_callback : Mutex < Option < Box < dyn Fn ( String ) + ' static + Sync + Send > > > ,
100+ /// Progress and error callbacks
101+ callbacks : Mutex < CCallbacks > ,
101102 /// Bool set to true when the thread has been set up,
102103 /// prevents re-setting of the thread after `finish()`
103104 write_thread : Mutex < ( bool , Option < thread:: JoinHandle < GifskiError > > ) > ,
@@ -126,8 +127,10 @@ pub unsafe extern "C" fn gifski_new(settings: *const GifskiSettings) -> *const G
126127 writer : Mutex :: new ( Some ( writer) ) ,
127128 write_thread : Mutex :: new ( ( false , None ) ) ,
128129 collector : Mutex :: new ( Some ( collector) ) ,
129- progress : Mutex :: new ( None ) ,
130- error_callback : Mutex :: new ( None ) ,
130+ callbacks : Mutex :: new ( CCallbacks {
131+ progress : None ,
132+ error : None ,
133+ } ) ,
131134 } ) )
132135 . cast :: < GifskiHandle > ( )
133136 } else {
@@ -378,18 +381,12 @@ pub unsafe extern "C" fn gifski_add_frame_rgb(handle: *const GifskiHandle, frame
378381/// This function must be called before `gifski_set_file_output()` to take effect.
379382#[ no_mangle]
380383pub unsafe extern "C" fn gifski_set_progress_callback ( handle : * const GifskiHandle , cb : unsafe extern "C" fn ( * mut c_void ) -> c_int , user_data : * mut c_void ) -> GifskiError {
381- let Some ( g) = borrow ( handle) else { return GifskiError :: NULL_ARG } ;
382-
383- if g. write_thread . lock ( ) . map_or ( true , |t| t. 0 ) {
384- g. print_error ( "tried to set progress callback after writing has already started" . into ( ) ) ;
385- return GifskiError :: INVALID_STATE ;
386- }
387- match g. progress . lock ( ) {
388- Ok ( mut progress) => {
389- * progress = Some ( ProgressCallback :: new ( cb, user_data) ) ;
384+ match set_callbacks ( borrow ( handle) ) {
385+ Ok ( mut callbacks) => {
386+ callbacks. progress = Some ( ProgressCallback :: new ( cb, user_data) ) ;
390387 GifskiError :: OK
391388 } ,
392- Err ( _ ) => GifskiError :: THREAD_LOST ,
389+ Err ( e ) => e ,
393390 }
394391}
395392
@@ -408,27 +405,28 @@ pub unsafe extern "C" fn gifski_set_progress_callback(handle: *const GifskiHandl
408405/// This function must be called before `gifski_set_file_output()` to take effect.
409406#[ no_mangle]
410407pub unsafe extern "C" fn gifski_set_error_message_callback ( handle : * const GifskiHandle , cb : unsafe extern "C" fn ( * const c_char , * mut c_void ) , user_data : * mut c_void ) -> GifskiError {
411- let Some ( g) = borrow ( handle) else { return GifskiError :: NULL_ARG } ;
412-
413- let user_data = SendableUserData ( user_data) ;
414- match g. error_callback . lock ( ) {
415- Ok ( mut error_callback) => {
416- * error_callback = Some ( Box :: new ( move |mut s : String | {
417- s. reserve_exact ( 1 ) ;
418- s. push ( '\0' ) ;
419- let cstring = CString :: from_vec_with_nul ( s. into_bytes ( ) ) . unwrap_or_default ( ) ;
420- unsafe { cb ( cstring. as_ptr ( ) , user_data. clone ( ) . 0 ) } // the clone is a no-op, only to force closure to own it
421- } ) ) ;
408+ match set_callbacks ( borrow ( handle) ) {
409+ Ok ( mut callbacks) => {
410+ callbacks. error = Some ( ErrorCallback {
411+ callback : cb,
412+ user_data,
413+ } ) ;
422414 GifskiError :: OK
423415 } ,
424- Err ( _ ) => GifskiError :: THREAD_LOST ,
416+ Err ( e ) => e ,
425417 }
426418}
427419
428- #[ derive( Clone ) ]
429- struct SendableUserData ( * mut c_void ) ;
430- unsafe impl Send for SendableUserData { }
431- unsafe impl Sync for SendableUserData { }
420+ fn set_callbacks ( g : Option < & GifskiHandleInternal > ) -> Result < MutexGuard < ' _ , CCallbacks > , GifskiError > {
421+ let g = g. ok_or ( GifskiError :: NULL_ARG ) ?;
422+
423+ if g. write_thread . lock ( ) . map_or ( true , |t| t. 0 ) {
424+ g. print_error ( "tried to set a callback after writing has already started" . into ( ) ) ;
425+ return Err ( GifskiError :: INVALID_STATE ) ;
426+ }
427+
428+ g. callbacks . lock ( ) . map_err ( |_| GifskiError :: THREAD_LOST )
429+ }
432430
433431/// Start writing to the `destination`. This has to be called before any frames are added.
434432///
@@ -523,11 +521,10 @@ fn gifski_write_thread_start<W: 'static + Write + Send>(g: &GifskiHandleInterna
523521 return Err ( GifskiError :: INVALID_STATE ) ;
524522 }
525523 let writer = g. writer . lock ( ) . map_err ( |_| GifskiError :: THREAD_LOST ) ?. take ( ) ;
526- let mut user_progress = g. progress . lock ( ) . map_err ( |_| GifskiError :: THREAD_LOST ) ?. take ( ) ;
524+ let mut user_progress = g. callbacks . lock ( ) . map_err ( |_| GifskiError :: THREAD_LOST ) ?. clone ( ) ;
527525 let handle = thread:: Builder :: new ( ) . name ( "c-write" . into ( ) ) . spawn ( move || {
528526 if let Some ( writer) = writer {
529- let progress = user_progress. as_mut ( ) . map ( |m| m as & mut dyn ProgressReporter ) ;
530- match writer. write ( file, progress. unwrap_or ( & mut NoProgress { } ) ) . into ( ) {
527+ match writer. write ( file, & mut user_progress) . into ( ) {
531528 res @ ( GifskiError :: OK | GifskiError :: ALREADY_EXISTS ) => res,
532529 err => {
533530 if let Some ( path) = path {
@@ -596,16 +593,18 @@ pub unsafe extern "C" fn gifski_finish(g: *const GifskiHandle) -> GifskiError {
596593}
597594
598595impl GifskiHandleInternal {
596+ #[ cold]
599597 fn print_error ( & self , mut err : String ) {
600- if let Ok ( Some ( cb ) ) = self . error_callback . lock ( ) . as_deref ( ) {
601- cb ( err) ;
598+ if let Ok ( reporter ) = self . callbacks . lock ( ) . as_deref_mut ( ) {
599+ reporter . error ( err)
602600 } else {
603601 err. reserve_exact ( 1 ) ;
604602 err. push ( '\n' ) ;
605603 let _ = std:: io:: stderr ( ) . write_all ( err. as_bytes ( ) ) ;
606604 }
607605 }
608606
607+ #[ cold]
609608 fn print_panic ( & self , e : Box < dyn std:: any:: Any + Send > ) {
610609 let msg = e. downcast_ref :: < String > ( ) . map ( |s| s. as_str ( ) )
611610 . or_else ( || e. downcast_ref :: < & str > ( ) . copied ( ) ) . unwrap_or ( "unknown panic" ) ;
@@ -643,7 +642,7 @@ fn c_cb() {
643642 assert_eq ! ( GifskiError :: OK , gifski_set_write_callback( g, Some ( cb) , ptr:: addr_of_mut!( write_called) . cast( ) ) ) ;
644643 assert_eq ! ( GifskiError :: INVALID_STATE , gifski_set_progress_callback( g, pcb, ptr:: addr_of_mut!( progress_called) . cast( ) ) ) ;
645644 assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 0 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 3. ) ) ;
646- assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 0 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 10. ) ) ;
645+ assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 1 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 10. ) ) ;
647646 assert_eq ! ( GifskiError :: OK , gifski_finish( g) ) ;
648647 }
649648 assert ! ( write_called) ;
@@ -673,7 +672,7 @@ fn progress_abort() {
673672 assert_eq ! ( GifskiError :: OK , gifski_set_progress_callback( g, pcb, ptr:: null_mut( ) ) ) ;
674673 assert_eq ! ( GifskiError :: OK , gifski_set_write_callback( g, Some ( cb) , ptr:: null_mut( ) ) ) ;
675674 assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 0 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 3. ) ) ;
676- assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 0 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 10. ) ) ;
675+ assert_eq ! ( GifskiError :: OK , gifski_add_frame_rgb( g, 1 , 1 , 3 , 1 , & RGB :: new( 0 , 0 , 0 ) , 10. ) ) ;
677676 assert_eq ! ( GifskiError :: ABORTED , gifski_finish( g) ) ;
678677 }
679678}
@@ -740,7 +739,7 @@ fn test_error_callback() {
740739 assert_eq ! ( GifskiError :: OK , gifski_set_write_callback( g, Some ( cb) , 1 as _) ) ;
741740 assert_eq ! ( GifskiError :: INVALID_STATE , gifski_set_write_callback( g, Some ( cb) , 1 as _) ) ;
742741 assert_eq ! ( GifskiError :: INVALID_STATE , gifski_finish( g) ) ;
743- assert_eq ! ( "gifski_set_file_output/gifski_set_write_callback has been called already" , callback_msg. unwrap ( ) ) ;
742+ assert_eq ! ( "gifski_set_file_output/gifski_set_write_callback has been called already" , callback_msg. expect ( "set msg" ) ) ;
744743 }
745744}
746745
0 commit comments