/* * 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 #include #include #include #include #if !defined(__linux__) && !defined(ANDROID) #include "win32_pipe.h" #endif #include "raylib.h" #include "rayui.h" #include "bg.h" #define UNLOADTIMEOUTSECONDS 10 static int mypipe(int fds[2]); static int mypiperead(int fd, char *buf, int count); static int mypipewrite(int fd, char *buf, int count); void bg_staticinit(void) { #if !defined(__linux__) && !defined(ANDROID) win32pipe_init(); #endif } void bg_staticfini(void) { #if !defined(__linux__) && !defined(ANDROID) win32pipe_fini(); #endif } 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="Error init pipes (please check program is not blocked in firewall)")==NULL || (bg->pipe[0]=bg->pipe[1]=-1)!=-1 || mypipe(bg->pipe)!=0 || (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) { char dummy=1; #if 1 fprintf(stderr,"bg_free: notifying thread to exit\n"); #endif mypipewrite(bg->pipe[WR],&dummy,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; } if(bg->pipe[0]!=-1) close(bg->pipe[0]),bg->pipe[0]=-1; if(bg->pipe[1]!=-1) close(bg->pipe[1]),bg->pipe[1]=-1; if(bg->bgload!=NULL) { bgload_t *bgload; for(i=0,bgload=bg->bgload;isizebgload;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;isizebgload;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;isizebgload;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 i; bgload_t *bgload; char dummy; if(bg==NULL) return(-1); for(i=0,bgload=bg->bgload;isizebgload;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 dummy=0; mypipewrite(bg->pipe[WR],&dummy,1); return(0); /* already on list */ } } for(i=0,bgload=bg->bgload;isizebgload;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 dummy=0; bgload->lended_to_thread=1; mypipewrite(bg->pipe[WR],&dummy,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;isizebgload;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))) { #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; char dummy; bg=(bg_t *)parambg; int i; bgload_t *bgload,*candidate; while(1) { mypiperead(bg->pipe[RD],&dummy,1); #if 1 fprintf(stderr,"Thread received byte: %i\n",(int)((unsigned char)dummy)); #endif if(dummy!=0) break; /* was told to exit */ for(candidate=NULL,i=0,bgload=bg->bgload;isizebgload;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; } } pthread_exit(NULL); return(NULL); } #if !defined(__linux__) && !defined(ANDROID) static int mypipe(int fds[2]) { return(win32pipe_pipe(fds)); } static int mypiperead(int fd, char *buf, int count) { return(win32pipe_read(fd,buf,count)); } static int mypipewrite(int fd, char *buf, int count) { return(win32pipe_write(fd,buf,count)); } #else static int mypipe(int fds[2]) { return(pipe(fds)); } static int mypiperead(int fd, char *buf, int count) { return(read(fd,buf,count)); } static int mypipewrite(int fd, char *buf, int count) { return(write(fd,buf,count)); } #endif