/* * bg.c * * Wrapper over pthread with the addition of some interthread comms. * * History: * 20250904 Creation from imgmover prototype. * * Author: Dario Rodriguez dario@darionomono.com * (c) Dario Rodriguez 2025 * This program is licensed under the terms of GNU GPL v2.1+ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <sys/time.h> #if !defined(__linux__) && !defined(ANDROID) #include "win32_pipe.h" #endif #include "raylib.h" #include "rayui.h" #include "bg.h" #define UNLOADTIMEOUTSECONDS 10 #define THREADNUMBER 8 static int mysleep(struct timeval *tv); void bg_staticinit(void) { /* not needed now, was for win32pipe_init() */ } void bg_staticfini(void) { /* not needed now, was for win32pipe_fini() */ } bg_t * bg_init(int sizebgload) { bg_t *bg; char *errstr; bg=NULL; if((errstr="Insuf. mem. for bg")==NULL || (bg=calloc(1,sizeof(bg_t)))==NULL || (errstr="Insuf mem bgload")==NULL || (bg->bgload=calloc(sizebgload,sizeof(bgload_t)))==NULL || (bg->sizebgload=sizebgload)!=sizebgload || (errstr="pthread attr init error")==NULL || pthread_attr_init(&(bg->tattr))!=0 || (errstr="pthread create error")==NULL || pthread_create(&(bg->thread),&(bg->tattr),bg_thread,(void *)bg)!=0 || (bg->flag_threadstarted=1)!=1 ) { global_messagebox("%s",errstr); bg_free(bg); return(NULL); } return(bg); } void bg_free(bg_t *bg) { int i; if(bg==NULL) return; /* nothing to do */ if(bg->flag_threadstarted) { bg->do_exit=1; #if 1 fprintf(stderr,"bg_free: joining thread\n"); #endif pthread_join(bg->thread,NULL); #if 1 fprintf(stderr,"bg_free: thread joined OK\n"); #endif bg->flag_threadstarted=0; bg->do_exit=0; } if(bg->bgload!=NULL) { bgload_t *bgload; for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->has_data) { UnloadImage(bgload->image); bgload->has_data=0; } } free(bg->bgload),bg->bgload=NULL,bg->sizebgload=0; } return; } int bg_resetmarks(bg_t *bg) { int i; bgload_t *bgload; if(bg==NULL) return(-1); for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) bgload->has_mark=0; return(0); } bgload_t * bg_get(bg_t *bg, char *path) { int i; bgload_t *bgload; if(bg==NULL) return(NULL); for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->thread_finished && bgload->has_data && strcmp(path,bgload->path)==0) { bgload->has_mark=1; #if 1 fprintf(stderr,"bg_get: \"%s\"\n",bgload->path); #endif return(bgload); } } return(NULL); } int bg_add(bg_t *bg, char *path, int flag_pinned) { int i; bgload_t *bgload; if(bg==NULL) return(-1); if(path!=NULL && flag_pinned) { memset(bg->pinnedpath,0,sizeof(bg->pinnedpath)); strncpy(bg->pinnedpath,path,sizeof(bg->pinnedpath)-1); } for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->lended_to_thread && strcmp(path,bgload->path)==0) { bgload->is_todo=1; bgload->has_mark=1; gettimeofday(&(bgload->tv_lastadded),NULL); // Breaks premise of "don't touch until thread finished", but this is rather innocuous return(0); /* already on list */ } } for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->lended_to_thread==0) { memset(bgload,0,sizeof(bgload_t)); strncpy(bgload->path,path,sizeof(bgload->path)); bgload->path[sizeof(bgload->path)-1]='\0'; bgload->is_todo=1; bgload->has_mark=1; gettimeofday(&(bgload->tv_lastadded),NULL); // Breaks premise of "don't touch until thread finished", but this is rather innocuous bgload->lended_to_thread=1; return(0); /* added to list */ } } return(-1); /* couldn't add */ } int bg_freeunmarked(bg_t *bg) { int i; bgload_t *bgload; time_t now; if(bg==NULL) return(-1); now=time(NULL); for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->lended_to_thread && bgload->thread_finished && bgload->has_mark==0) { if(bgload->has_data && (bgload->lastused<(now-UNLOADTIMEOUTSECONDS) || bgload->lastused>(now+UNLOADTIMEOUTSECONDS)) && strcmp(bgload->path,bg->pinnedpath)!=0 ) { #if 1 fprintf(stderr,"bg: Unloading: \"%s\"\n",bgload->path); #endif UnloadImage(bgload->image); bgload->has_data=0; } #if 1 else { fprintf(stderr,"bg: Cancelling: \"%s\"\n",bgload->path); } #endif memset(bgload,0,sizeof(bgload_t)); } else if(bgload->lended_to_thread && bgload->thread_finished && bgload->has_mark!=0 && bgload->has_data) { bgload->lastused=now; } } return(0); } void * bg_thread(void *parambg) { bg_t *bg; bg=(bg_t *)parambg; int i; bgload_t *bgload,*candidate; while(1) { if(bg->do_exit!=0) break; /* was told to exit */ for(candidate=NULL,i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) { if(bgload->lended_to_thread==0) continue; if(bgload->is_todo==0) { bgload->thread_finished=1; continue; } if(bgload->has_data==0 && bgload->has_failedload==0) { if(candidate==NULL || bgload->tv_lastadded.tv_sec>candidate->tv_lastadded.tv_sec || (bgload->tv_lastadded.tv_sec==candidate->tv_lastadded.tv_sec && bgload->tv_lastadded.tv_usec>candidate->tv_lastadded.tv_usec) ) { candidate=bgload; } } } if(candidate!=NULL) { bgload=candidate; bgload->image=global_loadimage(bgload->path); if(IsImageValid(bgload->image)) bgload->has_data=1; else bgload->has_failedload=1; bgload->lastused=time(NULL); bgload->thread_finished=1; } else { struct timeval tv; tv.tv_sec=0,tv.tv_usec=50*1000; /* sleep 50ms if nothing to do */ mysleep(&tv); } #if 1 fprintf(stderr,"."),fflush(stderr); #endif } pthread_exit(NULL); return(NULL); } #if !defined(__linux__) && !defined(ANDROID) static int mysleep(struct timeval *tv) { win32pipe_sleep(tv->tv_sec*1000+tv->tv_usec/1000); return(0); } #else static int mysleep(struct timeval *tv) { struct timespec ts; if(tv==NULL) return(-1); ts.tv_sec=tv->tv_sec,ts.tv_nsec=tv->tv_usec*1000; nanosleep(&ts,NULL); return(0); } #endif