module fortplot_fast_io !! Fast I/O operations for Windows CI performance optimization !! !! This module provides optimized file I/O operations that can switch between !! memory-backed and disk-based operations based on the execution environment. !! Addresses Issue #188: Slow test execution on Windows CI. use iso_fortran_env, only: int8, int32, int64, real64 use fortplot_windows_performance, only: should_use_memory_backend, & get_performance_config, & performance_config_t use fortplot_memory_backend, only: memory_backend_t, get_memory_backend use fortplot_logging, only: log_debug, log_info implicit none private public :: fast_savefig public :: fast_file_exists public :: fast_file_size public :: enable_fast_io public :: disable_fast_io public :: get_fast_io_stats ! Module state logical, save :: fast_io_enabled = .false. integer, save :: memory_saves = 0 integer, save :: disk_saves = 0 real(real64), save :: total_memory_time = 0.0_real64 real(real64), save :: total_disk_time = 0.0_real64 contains subroutine fast_savefig(fig, filename, use_memory_override) !! Fast savefig that can use memory backend when appropriate use fortplot_figure_core, only: figure_t ! render_figure is a type-bound procedure, not needed in import use fortplot_utils, only: get_backend_from_filename class(figure_t), intent(inout) :: fig character(len=*), intent(in) :: filename logical, intent(in), optional :: use_memory_override type(memory_backend_t), pointer :: mem_backend type(performance_config_t) :: perf_config logical :: use_memory real(real64) :: start_time, end_time character(len=20) :: backend_type integer(int8), dimension(:), allocatable :: buffer_data integer :: buffer_size ! Determine whether to use memory backend if (present(use_memory_override)) then use_memory = use_memory_override else use_memory = should_use_memory_backend() .and. fast_io_enabled end if call cpu_time(start_time) ! For now, always use disk-based savefig since render_figure is private ! Memory backend optimization needs to be reworked after refactoring call savefig_disk(fig, filename) disk_saves = disk_saves + 1 call cpu_time(end_time) if (use_memory) then total_memory_time = total_memory_time + (end_time - start_time) else total_disk_time = total_disk_time + (end_time - start_time) end if end subroutine fast_savefig subroutine savefig_disk(fig, filename) !! Standard disk-based savefig (wrapper for original) ! savefig is a type-bound procedure, not needed in import use fortplot_figure_core, only: figure_t class(figure_t), intent(inout) :: fig character(len=*), intent(in) :: filename call fig%savefig(filename) end subroutine savefig_disk subroutine figure_to_buffer(fig, backend_type, buffer_data, buffer_size) !! Convert rendered figure to byte buffer use fortplot_figure_core, only: figure_t class(figure_t), intent(inout) :: fig character(len=*), intent(in) :: backend_type integer(int8), dimension(:), allocatable, intent(out) :: buffer_data integer, intent(out) :: buffer_size ! This is a simplified implementation ! In production, this would extract the rendered data from the backend ! For now, we'll create a minimal buffer as placeholder buffer_size = 0 select case(trim(backend_type)) case('png') call extract_png_buffer(fig, buffer_data, buffer_size) case('pdf') call extract_pdf_buffer(fig, buffer_data, buffer_size) case('ascii') call extract_ascii_buffer(fig, buffer_data, buffer_size) case default buffer_size = 0 end select end subroutine figure_to_buffer subroutine extract_png_buffer(fig, buffer_data, buffer_size) !! Extract PNG data from figure backend use fortplot_figure_core, only: figure_t class(figure_t), intent(inout) :: fig integer(int8), dimension(:), allocatable, intent(out) :: buffer_data integer, intent(out) :: buffer_size ! For PNG backend, we would extract the bitmap data ! This requires accessing the backend's internal bitmap ! For now, create a minimal valid PNG header as placeholder buffer_size = 8 ! Minimal PNG signature allocate(buffer_data(buffer_size)) ! PNG signature buffer_data(1:8) = [int(z'89', int8), int(z'50', int8), int(z'4E', int8), int(z'47', int8), & int(z'0D', int8), int(z'0A', int8), int(z'1A', int8), int(z'0A', int8)] end subroutine extract_png_buffer subroutine extract_pdf_buffer(fig, buffer_data, buffer_size) !! Extract PDF data from figure backend use fortplot_figure_core, only: figure_t class(figure_t), intent(inout) :: fig integer(int8), dimension(:), allocatable, intent(out) :: buffer_data integer, intent(out) :: buffer_size ! For PDF backend, we would extract the PDF document ! For now, create a minimal PDF header as placeholder buffer_size = 9 ! Minimal PDF header allocate(buffer_data(buffer_size)) ! PDF signature "%PDF-1.4\n" buffer_data(1:9) = [int(iachar('%'), int8), int(iachar('P'), int8), & int(iachar('D'), int8), int(iachar('F'), int8), & int(iachar('-'), int8), int(iachar('1'), int8), & int(iachar('.'), int8), int(iachar('4'), int8), & int(10, int8)] end subroutine extract_pdf_buffer subroutine extract_ascii_buffer(fig, buffer_data, buffer_size) !! Extract ASCII data from figure backend use fortplot_figure_core, only: figure_t class(figure_t), intent(inout) :: fig integer(int8), dimension(:), allocatable, intent(out) :: buffer_data integer, intent(out) :: buffer_size character(len=10) :: test_ascii = "ASCII_TEST" integer :: i buffer_size = len_trim(test_ascii) allocate(buffer_data(buffer_size)) do i = 1, buffer_size buffer_data(i) = int(iachar(test_ascii(i:i)), int8) end do end subroutine extract_ascii_buffer function fast_file_exists(filename) result(exists) !! Fast file existence check (may use memory backend) character(len=*), intent(in) :: filename logical :: exists type(memory_backend_t), pointer :: mem_backend if (should_use_memory_backend() .and. fast_io_enabled) then mem_backend => get_memory_backend() exists = mem_backend%exists(filename) ! If not in memory, check disk if (.not. exists) then inquire(file=filename, exist=exists) end if else inquire(file=filename, exist=exists) end if end function fast_file_exists function fast_file_size(filename) result(size) !! Fast file size check (may use memory backend) character(len=*), intent(in) :: filename integer(int64) :: size type(memory_backend_t), pointer :: mem_backend integer :: iostat if (should_use_memory_backend() .and. fast_io_enabled) then mem_backend => get_memory_backend() size = mem_backend%get_size(filename) ! If not in memory, check disk if (size < 0) then inquire(file=filename, size=size, iostat=iostat) if (iostat /= 0) size = -1 end if else inquire(file=filename, size=size, iostat=iostat) if (iostat /= 0) size = -1 end if end function fast_file_size subroutine enable_fast_io() !! Enable fast I/O operations use fortplot_windows_performance, only: setup_windows_performance call setup_windows_performance() fast_io_enabled = .true. call log_info("Fast I/O enabled for Windows CI performance") end subroutine enable_fast_io subroutine disable_fast_io() !! Disable fast I/O operations fast_io_enabled = .false. call log_info("Fast I/O disabled") end subroutine disable_fast_io subroutine get_fast_io_stats(memory_count, disk_count, memory_time, disk_time) !! Get fast I/O statistics integer, intent(out), optional :: memory_count integer, intent(out), optional :: disk_count real(real64), intent(out), optional :: memory_time real(real64), intent(out), optional :: disk_time if (present(memory_count)) memory_count = memory_saves if (present(disk_count)) disk_count = disk_saves if (present(memory_time)) memory_time = total_memory_time if (present(disk_time)) disk_time = total_disk_time end subroutine get_fast_io_stats end module fortplot_fast_io