/********************************************************** * * * LONELY? WANNA HAVE FUN? * * * * DO YOURSELF A FAVOUR - GET * * * * M O D E L S @ H O M E * * * ********************************************************** * Copyright 2000-2025 by Elmar Krieger * ********************************************************** * This is the client/server source code * * Details, news and the license for using this code can * * be found at www.yasara.org/models * ********************************************************** Please cite the following article when using this code: Models@Home: distributed computing in bioinformatics using a screensaver-based approach Krieger E, Vriend G (2002) Bioinformatics 18, 315-318 PMID11847079 The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The following additional programs/libraries are used: LINUX: SDL -> SDL library SDL_image -> Image loading SDL_net -> TCP/IP Network interface All three are available from www.libsdl.org Use gcc to compile. WINDOWS: tlist.exe -> create a list of running processes kill.exe -> kill a process SDL.dll -> SDL Library jpeg.dll -> JPEG image handling All these files are normally present in the cluster directory Source code is written for cross-compilation with mingw32 under Linux. HOW DOES THE CLUSTER WORK? ========================== An extensive description is available from www.yasara.org/models One computer with a nice large and empty harddrive officially becomes "the server" by running the CMBI cluster software with the -ser parameter: cluster -ser Optionally cluster -ser -new also initializes the job queue "cluster.job" Its IP address must be stored in the configuration file cluster.cnf, which is also present on all the clients. Clients can be activated directly using the -cli parameter: cluster -cli or without the screensaver, as a console-only application: cluster -cli -con You can also specify the number of CPUs to use and the maximum job priority to accept: cluster -cli -con -cpus 4 -pmax 3 1>stdout 2>stderr & On the server, in the cluster folder you can do touch kill_1234 to kill job 1234 (can take up to 30 minutes) touch exclude_010.000.000.001 to exclude client with IP 10.0.0.1 from all future jobs Clients send a message to the server in one of the following cases: 1) They are now available to accept a job (screen saver mode entered) 2) They must stop working (screen saver interrupted by user, job is not complete and thus lost) 3) They have finished a job and return the data. 4) They are working on a long job, but haven't finished yet. In this case they must send a "still alive" message every 30 minutes. The server waits for incoming messages from clients. The following messages can arrive (formats are described in the network function group) NET_CLIENTREADY NET_CLIENTSTOPPED NET_CLIENTFAILED NET_CLIENTDONE NET_CLIENTACTIVE NET_JOBDATA NET_CLIENTREADY =============== Server receives a "NET_CLIENTREADY" message -> client is ready to start working. This message also contains timestamps of all the files listed in cluster.cnf (which are part of the network update group). The server compares the timestamps with those of the files stored in the /update subdirectory and sends any newer versions back to the client. Server takes the following actions: 1) Check all the timestamps: - Is the filename present in subdirectory /update? - Yes: Is it newer than the version stored on the client? - Yes: Transmit new file to client 2) Check the cluster.job file to see if there are any jobs waiting to be done. Jobs are added there by other programs having access to this file (by NFS or running on the server). This is done be renaming cluster.job to cluster.jup (Job UPdate), adding the job and renaming the file back to cluster.job Libraries to do that are available in C and Python. 3) Submit the data files required to do the job 4) Submit corresponding line of job-file to client (used to return files) 5) Set job status to "A" and store submission time. 6) Check submission times every 5 minutes. Jobs that are active since more than 45 minutes are assumed to be lost (no still-alive message arrived) Job is set to waiting again and sent to a different client. NET_CLIENTSTOPPED ================= Server receives a "NET_CLIENTSTOPPED" message -> all threads on the client must stop working. Server takes the following actions: 1) Set the job and all other jobs with the same IP address waiting and submit to a different client NET_CLIENTFAILED ================ Server receives a "NET_CLIENTFAILED" message -> job on the client failed for unknown reasons. Server takes the following actions: 1) Set the job waiting and submit to a different client NET_CLIENTDONE ============== Server receives a "NET_CLIENTDONE" message -> client has finished job Server takes the following actions: 1) Delete job from list. 2) Create DONEFLAG file. NET_CLIENTACTIVE ================ Server receives a "NET_CLIENTACTIVE" message -> client is working on a long job Server takes the following actions: 1) Update timestamp (CHECK field of cluster.job) NET_JOBDATA: ============ Server receives a "NET_JOBDATA" message containing a result file -> file is stored in the working directory associated with the job. CLUSTER.JOB FORMAT: =================== cluster.job is a text file with the following format: JOB NUMB: An arbitrary job number S: Job status (W=WAITING,A=ACTIVE) P: Priority (0=low, 8=high, 9=long job,>12 hours,not for screen saver mode) CS: Number of CPU cores used by this job. Based on this setting, the client decides how many jobs it accepts until all its CPUs (thr_cpus) are busy. CLIENT IP: IP address of client that is currently working on the job. To support today's multicore machine, each client can accept several jobs to keep all CPUs busy, indicated after the colon ':'. CLIENT NAME: First 16 characters of client name if available TIME: Time of job-submission to client CHECK: Time of last still alive message received (seconds since 1970, hexadecimal) PROGRAM: 6 characters, program to be run on client: WHATIF,YASARA,etc. OPTIONS: Command line options used when running the specified program DIR: Working directory on supervisor client COPY: Files to copy to working client TEXT: Text files to copy to working client RETURN: Files to return from working client after job is done DONEFLAG: Filename that will be generated by the server as soon as job is done. JOB NUMB_S_P_CLIENT IP_______CLIENT NAME______TIME OF JOB SUBMISSION___CHECK____PROGRAM__OPTIONS |DIR|COPY|TEXT|RETURN|DONEFLAG JOB______S_P_CS_CLIENT IP___________CLIENT NAME______TIME OF JOB SUBMISSION___CHECK____PROGRAM__OPTIONS|DIR|COPY|TEXT|RETURN|DONEFLAG 00000000 W 0 01 WHATIF |/elmar/whatif|1crn.pdb|STARTUP.FIL="fulchk 1crn.pdb##y#"|pdbout.txt,*.eps|1crn.done 00000001 A 5 01 123.234.456.789:001 CMBIPC1 Tue May 21 13:46:22 1991 13fed734 YASARA -nss -con -mcr job.mcr|/elmar/yasara|1crn.pdb|job.mcr="LOADPDB 1crn#ADDHYD 1#SAVEPDB 1 1crnh.pdb#EXITYSR#"|*| The Client does the following things: ===================================== - Directory /usr/cluster/job is cleared - All requested files are copied there - cd /usr/cluster/job - Execute clu_runcommand HOW TO SELECT THE TYPE OF SCREEN SAVER ====================================== The file cluster.scr contains the following string: BLACK (This will display a blank screen) LOGON (This will display the Windows NT logon box, even under Linux) LOGONSLOW (This will display the Windows NT logon box, but with infrequent contacts to the server.) NORMAL (This will display the Models@Home screen saver) PRUDISH (This will display the screen saver without naked skin) If the file cluster.scr is not present, the normal screen saver will be displayed. MULTITHREADING ============== The client spawns multiple threads to handle more than one job at once. clu_client must call clu_lock before each operation that affects other threads, like clu_killchildproc or err_exit KNOWN ISSUES ============ The following known issues are due to Windows flaws. Workarounds are welcome. - 2007/03/06, the cluster hang in net_receivepacket->net_receive->recv for unknown reasons and had to be killed. First time in seven years..? - About once a week, randomly, the screensaver window gets minimized. All attempts to regularily maximize the window using system calls failed. This doesn't have any influence on the cluster, however a user coming back to his PC may wonder why there is no screensaver and just the normal login box (Windows NT) or where all these minimized window icons come from (Windows 2000). In both cases, the standard procedure of pressing Ctrl-Alt-Del to log in works as usual. - About once every month, Windows 2000 complains about running out of virtual memory. It will deny memory allocation requests from the cluster software and/or the program executed and thereby crash one or both of them. This message is independent of the swap file size and also not due to memory leaks of any kind. It's a flaw in Windows 2000, nothing comparable occurs in Windows NT+Linux. If only the application crashes before the required result files have been created, the Models@Home client will report the job interrupted and it will be sent to another client. If the Models@Home client itself crashes, the Models@Home server will notice at most 30 minutes later that no "still-alive-message" was received and send the job to another client. Note that in the mean time, the Models@Home client will have been restarted and requested a new job. So for a certain period of time, the same client will show up twice in cluster.job. -execl(p), execv(p) is broken in Windows XP. The 'system' function has to be used instead. Known problems: After a screensaver restart (new conf files etc.), it may fail to grab X11_Input: #0 0x005ea7a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2 #1 0x006c650d in ___newselect_nocancel () from /lib/tls/libc.so.6 #2 0xb7fbc9c6 in SDL_Delay () from /usr/lib/libSDL-1.2.so.0 #3 0xb7fa3839 in X11_GrabInputNoLock () from /usr/lib/libSDL-1.2.so.0 #4 0xb7fa398f in X11_GrabInput () from /usr/lib/libSDL-1.2.so.0 #5 0xb7f975b3 in SDL_WM_GrabInput () from /usr/lib/libSDL-1.2.so.0 #6 0xb7f97f1c in SDL_SetVideoMode () from /usr/lib/libSDL-1.2.so.0 #7 0x0804a7d7 in vga_setvideomode (resx=1024, resy=768, bpp=16) at cluster.c:841 #8 0x0804b749 in scr_initialize () at cluster.c:1475 #9 0x0804e7c0 in main (argc=3, argv=0xbffffa84) at cluster.c:3042 Requires a reboot... kdm doesn't call Xsetup etc. anymore, use preferred=gdm in /etc/X11/prefdm The hideous bug: http://stackoverflow.com/questions/17226876/recv-call-hangs-after-remote-host-terminates LIST OF MOST HIDEOUS BUGS: 2016-05: After yasara23 froze and was restarted, net_client still contained the sockets. These are not net_removecliented if a job is set waiting again. 2016-01: The hideous hang in recv when clients request a job is still present. Happened after 'mv cluster.job cluster.ele' and some editing, maybe the delay is the problem? Or maybe related to FD_SETSIZE? http://stackoverflow.com/questions/7976388/increasing-limit-of-fd-setsize-and-select (Bug in SDLNet_CheckSockets, SDLnetselect.c) 2016-01: If a client freezes (CPU overheating, kernel panic..), the sockets stay open, and the server keeps them in the list forever. 2015-12: After clients finished their job and closed the TCP connection to the server, the server did not get notified (the socket was never signalled as ready for reading, so the server never called recv and never received a 0 indicating a closed connection). The reason was that multiple client threads run at the same time, and if one of them executes another program via system/fork, this new process inherits all sockets, whose reference count is increased. So closing a socket decreases the reference count, but doesn't reach a count of 0, so the socket is never really closed. One workaround is the shutdown function, but the proper solution is to flag sockets to be closed-on-exec. This code has been added in SDL_net-1.2.7/SDLnetTCP.c, function SDLNet_TCP_Open: if (fcntl(sock->channel,F_SETFD,fcntl(sock->channel,F_GETFD)|FD_CLOEXEC)==-1)... http://stackoverflow.com/questions/6125068/what-does-the-fd-cloexec-fcntl-flag-do "Setting the flag atomically when the file is opened is pretty much essential to any threaded program that might be opening files while another thread might be executing external programs. Unfortunately it's only available for open and not accept, socket, pipe, etc... - R.. May 25 '11 at 14:19" */ /* PREPROCESSOR INSTRUCTIONS, DECLARATIONS ======================================= */ #define DEBUG 0 //#define REBOOT typedef short int16; typedef unsigned short uint16; typedef int int32; typedef unsigned int uint32; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LINUX #include #include #include #include #include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) #define dsc_makedir(A,B) mkdir(A,B) #define FILESLASH '/' #define MOD_RW 0666 #define MOD_RWX 0777 #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #else #include #include #include #define dsc_makedir(A,B) mkdir(A) #define FILESLASH '\\' #define MOD_RW (S_IREAD|S_IWRITE) #define MOD_RWX (S_IREAD|S_IWRITE|S_IEXEC) #endif #include "SDL.h" #include "SDL_net.h" #include "SDL_image.h" #include "SDL_thread.h" /* FOR COMPATIBILITY WITH THE YASARA SOURCE CODE */ #define mem_alloc malloc #define mem_realloc realloc #define mem_free free #define mem_copy memmove // Our mem_copy is assumed to work if dstNAMELENMAX) { *joined=0; return(1); } str_copy(joined,path); if (i) { /* PATH GIVEN, CHECK FOR SLASH */ if (joined[i-1]!='\\'&&joined[i-1]!='/') joined[i++]=FILESLASH; } str_copy(&joined[i],filename); str_strip(joined); /* ADAPT SLASHES FOR OPERATING SYSTEM */ i=0; while (joined[i]) { if (joined[i]=='\\'||joined[i]=='/') joined[i]=FILESLASH; i++; } return(0); } /* GET TIME OF LAST MODIFICATION ============================= */ time_t dsc_modtime(char *filename) { int i; struct stat file; i=stat(filename,&file); /* IF FILE DOESN'T EXIST, RETURN */ if (i==-1) return(0); else return(file.st_mtime); } /* GET SIZE OF FILE IN BYTES ========================= */ long dsc_filesize(const char *filename) { struct stat file; if (stat(filename,&file)==-1) return(0); return(file.st_size); } /* LOAD A FILE OF UNKNOWN LENGTH FROM DISC AND ALLOCATE MEMORY =========================================================== RETURNS THE NUMBER OF BYTES ALLOCATED */ int dsc_loadfilealloc(char **address,char *filename) { int i,size; FILE *fb; *address=NULL; if ((fb=fopen(filename,"rb"))!=NULL) { size=dsc_filesize(filename); *address=mem_alloc(size); i=fread(*address,1,size,fb); *address=mem_realloc(*address,i); fclose(fb); return(i); } return(0); } /* SAVE A FILE ON DISC =================== */ int dsc_savefile(char *filename,char *address,int length) { int i; FILE *fp; i=0; if ((fp=fopen(filename,"wb"))!=NULL) { i=fwrite(address,1,length,fp); fclose(fp); } return(i); } /* MATCH FILENAME WITH WILDCARD ============================ RETURNS: 0 - MATCH 1 - MISMATCH */ #define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ #define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ #define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ int dsc_fnmatch(const char *wildcard,const char *string,int flags) { int i,j,k,fraglen; if ((flags&FNM_PERIOD)&&*string=='.'&&*wildcard!='.') return(1); i=j=0; while (wildcard[i]&&string[j]) { /* FILESLASH MUST BE MATCHED EXACTLY IF FNM_PATHNAME IS SET */ if ((flags&FNM_PATHNAME)&&(string[j]=='/'||string[j]=='\\')) { if (wildcard[i]!=string[j]) return(1); } /* A MATCH ? */ if (wildcard[i]==string[j]||wildcard[i]=='?') { /* PERFECT MATCH OR '?' JOKER */ i++; j++; } else { /* MISMATCH */ k=i; while (wildcard[i]=='*') i++; if (i!=k) { /* '*' JOKER FOUND */ fraglen=0; while (wildcard[i+fraglen]&&wildcard[i+fraglen]!='?'&&wildcard[i+fraglen]!='*') fraglen++; if (!fraglen) return(0); while (string[j]&&str_cmpn(&string[j],&wildcard[i],fraglen)) j++; if (!string[j]) return(1); j+=fraglen; i+=fraglen; } else return(1); } } while (wildcard[i]=='*') i++; return(wildcard[i]|string[j]); } /* FIND NEXT DIRECTORY ENTRY MATCHING WILDCARD =========================================== */ struct FIND { char attribute; unsigned short time,date; unsigned long size; char name[NAMELENMAX+1]; }; /* Wildcard for directory access */ char dsc_wildcard[NAMELENMAX+1]; DIR *dsc_directory; /* Pointer to current directory */ struct FIND dsc_direntry; /* One directory entry */ struct FIND *dsc_findnext(void) { struct dirent *ep; while ((ep=readdir(dsc_directory))) { if (!dsc_fnmatch(dsc_wildcard,ep->d_name,FNM_PATHNAME|FNM_PERIOD)) { str_copy(dsc_direntry.name,ep->d_name); return(&dsc_direntry); } } return(NULL); } /* FIND FIRST DIRECTORY ENTRY MATCHING WILDCARD ============================================ IF WILDCARD CONTAINS A RELATIVE PATH, IT MUST START WITH ./ ATTRIBUTE: 0 - FILES 16 - SUBDIRECTORIES */ struct FIND *dsc_findfirst(const char *wildcard,int attribute) { int i; char ch; char dirname[NAMELENMAX+1]; /* SEPARATE FILENAME FROM DIRECTORY NAME */ i=str_len(wildcard); if (i>NAMELENMAX) return(NULL); while (i) { ch=wildcard[i-1]; if (ch==FILESLASH) break; i--; } /* EXTRACT DIRECTORY NAME, MUST NOT CONTAIN A TERMINAL SLASH (OTHERWISE opendir WON'T WORK UNDER WINDOWS) */ if (i) { mem_copy(dirname,(char*)wildcard,i); dirname[i-1]=0; } else { dirname[0]='.'; dirname[1]=0; } /* OPEN DIRECTORY */ str_copy(dsc_wildcard,&wildcard[i]); if (dsc_directory!=NULL) closedir(dsc_directory); dsc_directory=opendir(dirname); if (dsc_directory==NULL) return(NULL); return(dsc_findnext()); } /* REMOVE ALL FILES MATCHING A WILDCARD ==================================== */ void dsc_removeall(char *dir,char *wildcard) { char filename[NAMELENMAX+1]; struct FIND *dta; dsc_joinpath(filename,dir,wildcard); dta=dsc_findfirst(filename,0); while (dta!=NULL) { dsc_joinpath(filename,dir,dta->name); dsc_remove(filename); dta=dsc_findnext(); } } /* DETERMINE FREE DISC SPACE ========================= RETURNS THE NUMBER OF MEGABYTES STILL FREE */ #if WINDOWS typedef BOOL (WINAPI *P_GDFSE)(LPCTSTR, PULARGE_INTEGER,PULARGE_INTEGER,PULARGE_INTEGER); int dsc_freespace(void) { BOOL fResult; char *pszDrive; DWORD dwSectPerClust,dwBytesPerSect,dwFreeClusters,dwTotalClusters; P_GDFSE pGetDiskFreeSpaceEx = NULL; unsigned long long i64FreeBytesToCaller,i64TotalBytes,i64FreeBytes; pszDrive="c:\\"; pGetDiskFreeSpaceEx=(P_GDFSE)GetProcAddress(GetModuleHandle ("kernel32.dll"),"GetDiskFreeSpaceExA"); if (pGetDiskFreeSpaceEx) { fResult=pGetDiskFreeSpaceEx(pszDrive,(PULARGE_INTEGER)&i64FreeBytesToCaller, (PULARGE_INTEGER)&i64TotalBytes,(PULARGE_INTEGER)&i64FreeBytes); if (fResult) return(i64FreeBytesToCaller/(1024*1024)); } else { fResult=GetDiskFreeSpace(pszDrive,&dwSectPerClust,&dwBytesPerSect,&dwFreeClusters,&dwTotalClusters); if (fResult) { i64FreeBytes=(long long)dwFreeClusters*dwSectPerClust*dwBytesPerSect; return(i64FreeBytes/(1024*1024)); } } return(0); } #else int dsc_freespace(void) { char workdir[NAMELENMAX+1],syscommand[NAMELENMAX*2+1]; char *dfout,*dfpos; int freespace; getcwd(workdir,NAMELENMAX); str_print(syscommand,"df -m '%s' discfree.txt",workdir); if (!txt_loadfilealloc(&dfout,"discfree.txt")) return(0); dfpos=dfout; txt_setnextline(&dfpos); if (str_len(dfpos)<50) freespace=0; else freespace=str_atoi(&dfpos[40]); mem_free(dfout); return(freespace); } #endif /* ====================================================================== E R R O R H A N D L I N G F U N C T I O N G R O U P ====================================================================== */ char err_message[OUTPUTLENMAX+1]; char msg_logfilename[NAMELENMAX+1]; /* SHOW WARNING ============ */ void err_warn(void) { char filename[NAMELENMAX+1]; FILE *ft; /* PRINT FATAL ERROR MESSAGE */ printf(err_message); printf("\n"); /* ADD TO LOG FILE */ dsc_joinpath(filename,clusterdir,"cluster.err"); if ((ft=fopen(filename,"a"))!=NULL) { fprintf(ft,err_message); fprintf(ft,"\n"); fclose(ft); } } void err_warnmessage(char *message) { str_copys(err_message,message); err_warn(); } /* EXIT WITH ERROR =============== */ void err_exit(void) { err_warn(); exit(1); } void err_exitmessage(char *message) { str_copys(err_message,message); err_exit(); } /* ADD MESSAGE TO LOG FILE ======================= */ void __attribute__ ((format(printf,1,2))) msg_log(char *message,...) { char line[NAMELENMAX*2+1]; FILE *ft; va_list args; va_start(args,message); vsnprintf(line,str_sizeof(line)-8,message,args); va_end(args); str_cat(line,"\n"); printf(line); ft=fopen(msg_logfilename,"a"); if (ft!=NULL) { fprintf(ft,line); fclose(ft); dsc_chmod(msg_logfilename,MOD_RW); } } /* ====================================================================== L I S T F U N C T I O N G R O U P (L S T) ====================================================================== */ #define LST_ELEMENTS 0 #define LST_START 1 #define LST_STRSIZE 1 #define LST_STRSTART 8 /* START is counted in StartBytes/sizeof(list) */ #define LST_BLOCKSHIFTS 12 /* Lists are allocated in blocks to speed up */ #define LST_BLOCKSIZE (1<>LST_BLOCKSHIFTS; if (blocks>(listsize>>LST_BLOCKSHIFTS)) list=mem_realloc(list,(blocks+1)<txt&&chr_islinefeed(txtpos[-1])) txtpos--; *txtpos='\n'; return(dsc_savefile(filename,txt,txtpos-txt+1)); } /* JUMP TO NEXT LINE ================= */ void txt_setnextline(char **txtpos) { char *tp; tp=*txtpos; while (*tp&&*tp!='\n') tp++; if (*tp) tp++; *txtpos=tp; } /* JUMP TO PREVIOUS LINE ===================== */ void txt_prevline(char **txtpos,char *txtstart) { char *tp; tp=*txtpos; while (*tp!='\n'&&tp>txtstart) tp--; if (tp>txtstart) tp--; while (tp>txtstart&&tp[-1]!='\n') tp--; *txtpos=tp; } /* GET LENGTH OF LINE ================== */ int txt_linelen(char *tp) { int i; i=0; while (!chr_islinefeed(tp[i])&&tp[i]) i++; return(i); } /* ====================================================================== V G A F U N C T I O N G R O U P ====================================================================== */ char*vga_vram; /* Pointer to linear video ram (main page) */ int vga_drawinramflag=1; /* Draw only in normal ram, not directly in vram */ int vga_fullscrflag=1; /* Run in fullscreen mode (Linux,Windows) */ int vga_offsetx; /* X - Offset if screen>resx */ int vga_offsety; /* Y - Offset */ SDL_Surface *vga_surface; /* Pointer to info about main SDL surface */ /* QUIT NCURSES VIDEO MODE ======================= */ #if LINUX void finish(int sig) { endwin(); exit(sig); } #endif /* SET VIDEOMODE ============= RETURNS: 0 - OK 1 - ERROR */ int vga_setvideomode(int resx,int resy,int bpp) { int i,format; SDL_Rect **modes; SDL_Surface *background; SDL_Rect rect,cliprect; /* CHECK IF THERE IS ALREADY AN ALLOCATED SURFACE - FREE IT IF YES */ if (resx==80) { /* TEXT MODE REQUESTED */ #if LINUX signal(SIGINT,finish); /* arrange interrupts to terminate */ initscr(); /* initialize the curses library */ keypad(stdscr, TRUE); /* enable keyboard mapping */ nonl(); /* tell curses not to do NL->CR/NL on output */ cbreak(); /* take input chars one at a time, no wait for \n */ noecho(); #else fprintf(stderr,"No text mode available under WINDOWS.\n"); exit(1); #endif } else { /* GRAPHICS MODE - ALLOCATE SDL SURFACE */ format=0; //SDL_DOUBLEBUF|SDL_HWPALETTE; if (vga_fullscrflag) format|=SDL_FULLSCREEN; if (vga_drawinramflag) format|=SDL_SWSURFACE; else format|=SDL_HWSURFACE|SDL_DOUBLEBUF; #if LINUX /* FULLSCREEN DOESN'T WORK UNDER LINUX, WE ALLOCATE THE ENTIRE SCREEN */ modes=SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_HWSURFACE); if (modes!=(SDL_Rect**)0&&modes[0]->w>resx) { vga_surface=SDL_SetVideoMode(modes[0]->w,modes[0]->h,bpp,format); background=SDL_CreateRGBSurface(SDL_SWSURFACE,modes[0]->w,modes[0]->h,bpp, vga_surface->format->Rmask,vga_surface->format->Gmask, vga_surface->format->Bmask,vga_surface->format->Amask); SDL_ToDisplayFormat(&background); rect.x=rect.y=0; rect.w=modes[0]->w; rect.h=modes[0]->h; SDL_BlitSurface(background,&rect,vga_surface,&rect); SDL_FreeSurface(background); cliprect.x=(rect.w-resx)/2; cliprect.y=(rect.h-resy)/2; vga_offsetx=cliprect.x; vga_offsety=cliprect.y; cliprect.w=resx; cliprect.h=resy; SDL_SetClipRect(vga_surface,&cliprect); } #else /* IN WINDOWS, WE CHOOSE resx/resy UNLESS THE CURRENT SCREEN IS AT A HIGHER RESOLUTION */ /* DUE TO ANOTHER WINDOWS BUG, WE CANNOT LOWER THE RESOLUTION, BECAUSE THE DESKTOP MIGHT KEEP THE LOWER RESOLUTION AFTER THE SCREENSAVER EXITS */ if (GetSystemMetrics(SM_CXSCREEN)>resx||GetSystemMetrics(SM_CYSCREEN)>resy) { rect.x=rect.y=0; rect.w=GetSystemMetrics(SM_CXSCREEN); rect.h=GetSystemMetrics(SM_CYSCREEN); vga_surface=SDL_SetVideoMode(rect.w,rect.h,bpp,format); if (vga_surface!=NULL) { background=SDL_CreateRGBSurface(SDL_SWSURFACE,rect.w,rect.h,bpp, vga_surface->format->Rmask,vga_surface->format->Gmask, vga_surface->format->Bmask,vga_surface->format->Amask); SDL_ToDisplayFormat(&background); SDL_BlitSurface(background,&rect,vga_surface,&rect); SDL_FreeSurface(background); cliprect.x=(rect.w-resx)/2; cliprect.y=(rect.h-resy)/2; vga_offsetx=cliprect.x; vga_offsety=cliprect.y; cliprect.w=resx; cliprect.h=resy; SDL_SetClipRect(vga_surface,&cliprect); } } #endif else { vga_surface=SDL_SetVideoMode(resx,resy,bpp,format); vga_offsetx=vga_offsety=0; } if (vga_surface==NULL) { /* WRITE THE AVAILABLE VIDEO MODES TO ERROR LOG FOR DIAGNOSIS */ err_warnmessage("Video surface could not be allocated.\nOnly the following modes are available:"); modes=SDL_ListModes(NULL,format); i=0; while (1) { if (modes==(SDL_Rect**)0) break; if (modes==(SDL_Rect**)-1) err_warnmessage("All resolutions available"); if (!modes[i]) break; str_print(err_message,"VideoMode %dx%d available.",modes[i]->w,modes[i]->h); err_warn(); i++; } err_exit(); } if (!(vga_surface->flags&SDL_FULLSCREEN)&&vga_fullscrflag) { /* AFTER ABOUT TWO DAYS RUNNING THE SCREENSAVER, IT BECOMES SUDDENLY IMPOSSIBLE TO SET THE FULLSCREEN VIDEOMODE UNDER WINDOWS. */ err_warnmessage("Fullscreen mode could not be activated."); } vga_vram=vga_surface->pixels; SDL_Flip(vga_surface); } return(0); } /* DEBUGGER FOR LINUX ================== */ int debugid; /* Debugger Hex-Editor Address */ #if LINUX #define trm_print printw /* SET CURSOR-POSITION (TEXT MODE) =============================== */ void txt_locatetm(int x,int y) { move(y,x); } /* PRINT HEXADECIMAL NUMBER (TEXT MODE) ==================================== */ char txt_hexdigits[]="0123456789ABCDEF"; void txt_printhextm(int posx,int posy,int value,int digits) { int i; move(posy,posx); for (i=0;i>((digits-1-i)*4))&15]); } } int dbg_eax,dbg_ebx,dbg_ecx,dbg_edx,dbg_esi,dbg_edi,dbg_ebp,dbg_esp,dbg_flags; double dbg_st0,dbg_st1,dbg_st2,dbg_st3,dbg_st4,dbg_st5,dbg_st6,dbg_st7; double dbg_mm0,dbg_mm1,dbg_mm2,dbg_mm3,dbg_mm4,dbg_mm5,dbg_mm6,dbg_mm7; short dbg_cs,dbg_ds,dbg_es,dummy; int dbg_seg; void debug(void) { int a; unsigned char ch; char *charray; #if WINDOWS __asm__ __volatile__("fstl _dbg_st0;fincstp;fstl _dbg_st1;fincstp;fstl _dbg_st2;fincstp;fstl _dbg_st3;fincstp;fstl _dbg_st4;fincstp;fstl _dbg_st5;fincstp;fstl _dbg_st6;fincstp;fstl _dbg_st7;movq %%mm0,_dbg_mm0;movq %%mm1,_dbg_mm1;movq %%mm2,_dbg_mm2;movq %%mm3,_dbg_mm3;movq %%mm4,_dbg_mm4;movq %%mm5,_dbg_mm5;movq %%mm6,_dbg_mm6;movq %%mm7,_dbg_mm7;finit;movl %%eax,_dbg_eax;movl %%ebx,_dbg_ebx;movl %%ecx,_dbg_ecx;movl %%edx,_dbg_edx;movl %%esi,_dbg_esi;movl %%edi,_dbg_edi;movl %%ebp,_dbg_ebp;movl %%esp,_dbg_esp;mov %%cs,_dbg_cs;mov %%ds,_dbg_ds;mov %%es,_dbg_es;pushfl;pop %%eax;mov %%eax,_dbg_flags; pushal;push %%ds;pop %%es" : : : "memory" ); #else __asm__ __volatile__("fstl dbg_st0;fincstp;fstl dbg_st1;fincstp;fstl dbg_st2;fincstp;fstl dbg_st3;fincstp;fstl dbg_st4;fincstp;fstl dbg_st5;fincstp;fstl dbg_st6;fincstp;fstl dbg_st7;movq %%mm0,dbg_mm0;movq %%mm1,dbg_mm1;movq %%mm2,dbg_mm2;movq %%mm3,dbg_mm3;movq %%mm4,dbg_mm4;movq %%mm5,dbg_mm5;movq %%mm6,dbg_mm6;movq %%mm7,dbg_mm7;finit;movl %%eax,dbg_eax;movl %%ebx,dbg_ebx;movl %%ecx,dbg_ecx;movl %%edx,dbg_edx;movl %%esi,dbg_esi;movl %%edi,dbg_edi;movl %%ebp,dbg_ebp;movl %%esp,dbg_esp;mov %%cs,dbg_cs;mov %%ds,dbg_ds;mov %%es,dbg_es;pushfl;pop %%eax;mov %%eax,dbg_flags;pushal;push %%ds;pop %%es" : : : "memory" ); #endif vga_setvideomode(80,25,2); if (LINES<25||COLS<80) { trm_print("Terminal is not large enough to run debugger.\n"); trm_print("Resize window and try again. Current size is %dx%d characters.\n\n",COLS,LINES); ch=getch(); goto end; } trm_print("MODELS@HOME DEBUGGER\n\n"); trm_print("EAX=%08X EBX=%08X ECX=%08X EDX=%08X ESI=%08X EDI=%08X\n", dbg_eax,dbg_ebx,dbg_ecx,dbg_edx,dbg_esi,dbg_edi); trm_print("CS =%04X DS =%04X ES =%04X EBP=%08X ESP=%08X\n", dbg_cs,dbg_ds,dbg_es,dbg_ebp,dbg_esp); trm_print("ST(0)=%lf ST(1)=%lf ST(2)=%lf ST(3)=%lf\n", dbg_st0,dbg_st1,dbg_st2,dbg_st3); trm_print("ST(4)=%lf ST(5)=%lf ST(6)=%lf ST(7)=%lf\n\n", dbg_st4,dbg_st5,dbg_st6,dbg_st7); trm_print("Flags: CF=%d PF=%d AF=%d ZF=%d SF=%d IF=%d DF=%d OF=%d", (dbg_flags&1),(dbg_flags>>2&1),(dbg_flags>>4&1),(dbg_flags>>6&1), (dbg_flags>>7&1),(dbg_flags>>9&1),(dbg_flags>>10&1),(dbg_flags>>11&1)); for (a=0;a<16;a++) { txt_locatetm(0,8+a); trm_print("%08X",debugid+a*16); } for (a=0;a<256;a++) { txt_locatetm(a%16*3+12,8+a/16); charray=(char*)debugid; ch=charray[a]; trm_print("%02X",(unsigned int)ch); txt_locatetm(a%16+64,8+a/16); if (ch>31) trm_print("%c",ch); else trm_print("."); } while ((ch=getch())!=' ') { if (ch=='q'||ch=='Q') debugid-=128; if (ch=='a'||ch=='A') debugid+=128; if (ch=='w'||ch=='W') debugid-=1024; if (ch=='s'||ch=='S') debugid+=1024; if (ch=='e'||ch=='E') debugid-=16384; if (ch=='d'||ch=='D') debugid+=16384; if (ch=='r'||ch=='R') debugid-=65536; if (ch=='f'||ch=='F') debugid+=65536; for (a=0;a<16;a++) { txt_locatetm(0,8+a); trm_print("%08X",debugid+a*16); } for (a=0;a<256;a++) { txt_locatetm(a%16*3+12,8+a/16); charray=(char*)debugid; ch=charray[a]; trm_print("%02X",(unsigned int)ch); txt_locatetm(a%16+64,8+a/16); if (ch>31) trm_print("%c",ch); else trm_print("."); } } end: __asm__ __volatile__("popal" : : : "eax","ebx","ecx","edx","esi","edi","cc" ); finish(0); } void de(int errcode) { __asm__ __volatile__("mov 8(%%ebp),%%eax" : : : "memory" ); debug(); } void ds(void *address) { debugid=(int)address; debug(); } void di2(int i,int j) { __asm__ __volatile__("mov 8(%%ebp),%%eax\nmov 12(%%ebp),%%ebx" : : : "memory" ); debug(); } #elif defined WINDOWS void de(int errcode) { printf("Error %d occurred.\n",errcode); exit(errcode); } void ds(void *address) { de(*((int*)address)); } void di2(int i,int j) { printf("Errors %d+%d occurred.\n",i,j); exit(i); } #endif /* ====================================================================== R A N D O M N U M B E R F U N C T I O N G R O U P ====================================================================== */ /* This is the ``Mersenne Twister'' random number generator MT19937, which generates pseudorandom integers uniformly distributed in 0..(2^32 - 1) starting from any odd seed in 0..(2^32 - 1). This version is a recode by Shawn Cokus (Cokus@math.washington.edu) on March 8, 1998 of a version by Takuji Nishimura (who had suggestions from Topher Cooper and Marc Rieffel in July-August 1997). */ #define RND_N (624) // length of state vector #define RND_M (397) // a period parameter #define RND_K (0x9908B0DFU) // a magic constant #define rnd_hiBit(u) ((u) & 0x80000000U) // mask all but highest bit of u #define rnd_loBit(u) ((u) & 0x00000001U) // mask all but lowest bit of u #define rnd_loBits(u) ((u) & 0x7FFFFFFFU) // mask the highest bit of u #define rnd_mixBits(u, v) (rnd_hiBit(u)|rnd_loBits(v)) // move hi bit of u to hi bit of v static uint32 rnd_state[RND_N+1]; // state vector + 1 extra to not violate ANSI C static uint32 *rnd_next; // next random value is computed from here static int rnd_left = -1; // can *next++ this many times before reloading /* SEED RANDOM NUMBER GENERATOR ============================ */ void rnd_seed(uint32 seed) { register uint32 x = (seed | 1U) & 0xFFFFFFFFU, *s = rnd_state; register int j; rnd_left=0; *s++=x; for (j=RND_N;j>1;j--) { *s++=(x*=69069U)&0xFFFFFFFFU; } } /* RELOAD NUMBER GENERATOR ======================= */ uint32 rnd_reload(void) { register uint32 *p0=rnd_state, *p2=rnd_state+2, *pM=rnd_state+RND_M, s0, s1; register int j; if(rnd_left<-1) rnd_seed(4357U); rnd_left=RND_N-1; rnd_next=rnd_state+1; for(s0=rnd_state[0], s1=rnd_state[1], j=RND_N-RND_M+1; --j; s0=s1, s1=*p2++) *p0++ = *pM++ ^ (rnd_mixBits(s0, s1) >> 1) ^ (rnd_loBit(s1) ? RND_K : 0U); for(pM=rnd_state, j=RND_M; --j; s0=s1, s1=*p2++) *p0++ = *pM++ ^ (rnd_mixBits(s0, s1) >> 1) ^ (rnd_loBit(s1) ? RND_K : 0U); s1=rnd_state[0], *p0 = *pM ^ (rnd_mixBits(s0, s1) >> 1) ^ (rnd_loBit(s1) ? RND_K : 0U); s1 ^= (s1 >> 11); s1 ^= (s1 << 7) & 0x9D2C5680U; s1 ^= (s1 << 15) & 0xEFC60000U; return(s1 ^ (s1 >> 18)); } /* CREATE 32 BIT RANDOM NUMBER =========================== */ int rnd_int32(void) { uint32 y; if(--rnd_left < 0) return(rnd_reload()); y = *rnd_next++; y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; y ^= (y << 15) & 0xEFC60000U; return((int)(y ^ (y >> 18))); } /* RETURN RANDOM NUMBER OF INT SIZE (31 BIT) ========================================= */ int rnd_int31(void) { return(rnd_int32()&0x7fffffff); } /* RETURN RANDOM NUMBER OF SHORT SIZE (16 BIT) =========================================== */ int rnd_int16(void) { return(rnd_int32()&0x0ffff); } /* RETURN RANDOM NUMBER OF SHORT SIZE (15 BIT) =========================================== */ int rnd_int15(void) { return(rnd_int32()&0x7fff); } /* RETURN RANDOM NUMBER WITHIN RANGE [0..RANGE-1] ============================================== */ int rnd_range(int range) { return((int)(((double)rnd_int31()/2147483648.0)*range)); } /* ====================================================================== K E Y B O A R D F U N C T I O N G R O U P ====================================================================== */ /* CLEAR KEYBOARD BUFFER ===================== */ void key_clearbuffer(void) { SDL_Event event; /* EMPTY EVENT QUEUE */ while (SDL_PollEvent(&event)) {}; } /* TEST IF KEY OR MOUSE BOTTON HAS BEEN PRESSED ============================================ RETURNS: 0 - NO 1 - YES */ int key_input(void) { SDL_Event event; /* THIS IS A WORKAROUND FOR AN SDL BUG: WHEN LAUNCHED BEFORE LINUX LOGIN (FROM XDM,KDM OR GDM), SDL_PollEvent CAUSES SCREEN FLICKER BY CALLING SDL_PullEvents() UNLESS THE MOUSE IS MOVED. SDL_WarpMouse DOES THAT */ #if LINUX SDL_WarpMouse(0,vga_surface->h); #endif while (SDL_PollEvent(&event)) { switch(event.type) { case SDL_MOUSEBUTTONDOWN: { /* ANOTHER WORKAROUND: IF TWO BUTTONS ARE PRESSED SIMULTANEOUSLY, THIS CANNOT BE DETECTED IN THE HOSTILE ENVIRONMENT (BEFORE LINUX LOGIN) */ if (event.button.button==SDL_BUTTON_LEFT|| event.button.button==SDL_BUTTON_RIGHT|| event.button.button==SDL_BUTTON_MIDDLE) return(1); break; } case SDL_KEYDOWN: { return(1); break; } } } return(0); } /* ====================================================================== R E G I S T R Y F U N C T I O N G R O U P ====================================================================== */ #if WINDOWS /* READ A REGISTRY KEY =================== */ int reg_readkey(char *value,int valuelen,HKEY hkey,char *subkey,char *valuename) { int err; HKEY handle; err=RegOpenKeyEx(hkey,subkey,0,KEY_QUERY_VALUE,&handle); if (err!=ERROR_SUCCESS) return(ERR); err=RegQueryValueEx(handle,valuename,NULL,NULL,value,(LPDWORD)&valuelen); RegCloseKey(handle); if (err!=ERROR_SUCCESS) return(ERR); return(OK); } /* WRITE A REGISTRY KEY ==================== */ int reg_writekey(HKEY hkey,char *subkey,char *valuename,char *value) { int err; HKEY handle; err=RegOpenKeyEx(hkey,subkey,0,KEY_SET_VALUE,&handle); if (err!=ERROR_SUCCESS) return(err); err=RegSetValueEx(handle,valuename,0,REG_SZ,value,str_len(value)+1); RegCloseKey(handle); if (err!=ERROR_SUCCESS) return(err); return(OK); } #endif /* ====================================================================== S D L F U N C T I O N G R O U P ====================================================================== */ /* FLIP SURFACE HORIZONTALLY ========================= */ SDL_Surface *SDL_FlipSurfaceH(SDL_Surface *srcsurface) { int i,j,k,w,h,bpp; char *srcpixel,*dstpixel; SDL_Surface *dstsurface; /* COLLECT SOURCE SURFACE DATA */ w=srcsurface->w; h=srcsurface->h; bpp=srcsurface->format->BytesPerPixel; /* ALLOCATE DESTINATION SURFACE */ dstsurface=SDL_CreateRGBSurface(SDL_HWSURFACE,w,h,bpp<<3,srcsurface->format->Rmask, srcsurface->format->Gmask,srcsurface->format->Bmask,srcsurface->format->Amask); /* FLIP IMAGE */ SDL_LockSurface(srcsurface); SDL_LockSurface(dstsurface); srcpixel=srcsurface->pixels; dstpixel=dstsurface->pixels; for (i=0;ipitch; dstpixel+=dstsurface->pitch; } SDL_UnlockSurface(srcsurface); SDL_UnlockSurface(dstsurface); return(dstsurface); } /* SHRINK SURFACE ============== */ SDL_Surface *SDL_ShrinkSurface(SDL_Surface *srcsurface,float percent) { int i,j,k,l,wsrc,wdst,hsrc,hdst,bpp; char *srcpixel,*dstpixel; SDL_Surface *dstsurface; /* COLLECT SOURCE SURFACE DATA */ wsrc=srcsurface->w; hsrc=srcsurface->h; bpp=srcsurface->format->BytesPerPixel; /* CALCULATE DESTINATION SURFACE WIDTH AND HEIGHT */ wdst=wsrc*percent; hdst=hsrc*percent; /* ALLOCATE DESTINATION SURFACE */ dstsurface=SDL_CreateRGBSurface(SDL_SWSURFACE,wdst,hdst,bpp<<3,srcsurface->format->Rmask, srcsurface->format->Gmask,srcsurface->format->Bmask,srcsurface->format->Amask); /* SHRINK IMAGE */ SDL_LockSurface(srcsurface); SDL_LockSurface(dstsurface); srcpixel=srcsurface->pixels; dstpixel=dstsurface->pixels; for (i=0;ipixels+((i*hsrc)/hdst)*srcsurface->pitch; for (j=0;jpitch; dstpixel+=dstsurface->pitch; } SDL_UnlockSurface(srcsurface); SDL_UnlockSurface(dstsurface); return(dstsurface); } /* CONVERT SURFACE TO DISPLAY FORMAT ================================= */ void SDL_ToDisplayFormat(SDL_Surface **surfadd) { //Uint8 red,green,blue; //Uint32 flags,colorkey; SDL_Surface *surface; /* DUE TO A BUG IN SDL_CreateRGBSurface, SDL_SRCCOLORKEY MUST BE SET AFTERWARDS */ //flags=(*surfadd)->flags&(SDL_SRCALPHA|SDL_RLEACCELOK); //surface=SDL_ConvertSurface(*surfadd,vga_surface->format, flags); surface=SDL_DisplayFormat(*surfadd); /* if ((*surfadd)->flags&SDL_SRCCOLORKEY) { SDL_GetRGB((*surfadd)->format->colorkey,(*surfadd)->format,&red,&green,&blue); colorkey=SDL_MapRGB(surface->format,red,green,blue); SDL_SetColorKey(surface,SDL_SRCCOLORKEY,colorkey); } */ SDL_FreeSurface(*surfadd); *surfadd=surface; } /* ====================================================================== M U L T I T H R E A D I N G F U N C T I O N G R O U P ====================================================================== */ SDL_mutex *thr_mutex; /* Mutex to synchronize multiple client threads */ int thr_locked; /* INITIALIZE MULTI THREADING ========================== */ void thr_init(void) { /* CREATE MUTEX FOR THREAD SYNCHRONIZATION */ thr_mutex=SDL_CreateMutex(); } /* LOCK MUTEX BEFORE ENTERING CRITICAL SECTION =========================================== */ void thr_lock(void) { if (SDL_LockMutex(thr_mutex)) err_exitmessage("Failed to lock mutex for thread synchronization"); if (thr_locked) err_exitmessage("Mutex already locked"); thr_locked=1; } /* UNLOCK MUTEX AFTER LEAVING CRITICAL SECTION =========================================== */ void thr_unlock(void) { if (!thr_locked) err_exitmessage("Mutex not locked"); thr_locked=0; if (SDL_UnlockMutex(thr_mutex)) err_exitmessage("Failed to unlock mutex for thread synchronization"); } /* GET THE NUMBER OF AVAILABLE CPUS ================================ */ int thr_cpus(void) { int cpus; #if LINUX #define CPU_SMAX 1024 /* Maximum number of CPUs */ if (__CPU_SETSIZE!=CPU_SMAX) err_exitmessage("Number of CPU mismatch"); cpus=sysconf(_SC_NPROCESSORS_ONLN); cpus=max(1,cpus); #elif defined WINDOWS SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); cpus=sysinfo.dwNumberOfProcessors; #elif defined MACOS int mib[2]; size_t len; mib[0]=CTL_HW; mib[1]=HW_NCPU; len=sizeof(cpus); if (sysctl(mib,2,&cpus,&len,NULL,0)==-1) return(1); #endif return(cpus); } /* ====================================================================== S C R E E N S A V E R F U N C T I O N G R O U P ====================================================================== */ /* HOW SPRITES WORK: WITH DOUBLE BUFFERING: - scr_clearsprites REMOVES ALL SPRITES AT WORKING SCREEN (scr_spriteold) - scr_clearsprites COPIES LAST SPRITE COORDINATES TO scr_spriteold - scr_putsprites DISPLAYS SPRITES AND UPDATES scr_spritenew - scr_flip FLIPS THE SCREEN WITHOUT DOUBLE BUFFERING: - scr_clearsprites REMOVES ALL SPRITES AT WORKING SCREEN (scr_spriteold) - scr_putsprites DISPLAYS SPRITES AND UPDATES scr_spritenew - scr_flip UPDATES ALL RECTANGLES BASED ON scr_spriteold+new - scr_flip COPIES scr_spritenew TO scr_spriteold */ /* SCREEN SAVER PARAMETERS */ #define SCR_SIZEX 1024 #define SCR_SIZEY 768 #define SCR_BPP 16 #define SCR_WINTITLE "MODELS@HOME - The CMBI Modelservice" #define SCR_TYPEBLACK 0 #define SCR_TYPELOGON 1 #define SCR_TYPENORMAL 2 #define SCR_TYPEPRUDISH 3 #define SCR_TYPEMASK 3 #define SCR_TYPESLOW 4 /* THE VARIOUS SPRITES */ #define SCR_SPRITEINIMAPS 22/* Initial number of sprite maps loaded from disc */ #define SCR_SPRITEMAPS 32 /* Final number of sprite maps (incl.zoomed+flipped) */ #define SCR_SPRITES 18 /* Number of sprites displayed on screen */ #define SCR_SPRCMBI 0 /* CMBI logo */ #define SCR_SPRJUMPER1 1 /* Body builder jumping on MyoD */ #define SCR_SPRJUMPER2 3 #define SCR_SPRJUMPER3 5 #define SCR_SPRJUMPER4 7 #define SCR_SPRJUMPER5 9 #define SCR_SPRLONELY 11 /* Lonely? */ #define SCR_SPRFUN 12 /* Wanna have fun? */ #define SCR_SPRFAVOR 13 /* Do yourself a favour! Get.. */ #define SCR_SPRMODATHOME 14 /* MODELS@HOME */ #define SCR_SPRMODEL1 15 /* Girl carrying 1AON and 2BNH */ #define SCR_SPRMODEL2 18 #define SCR_SPRPOEM1 21 /* Where on this planet doesn't matter, */ #define SCR_SPRPOEM2 22 /* computers count - the more, the better! */ #define SCR_SPRPOEM3 23 /* From little kids on roller-skates */ #define SCR_SPRPOEM4 24 /* To presidents of sixty states, */ #define SCR_SPRPOEM5 25 /* and even our Pope in Rome, */ #define SCR_SPRPOEM6 26 /* they all have got.. MODELS@HOME */ #define SCR_SPRLINUXLOGIN 27/* Login under Linux */ #define SCR_SPRWINLOGIN 28 /* Login under Windows */ #define SCR_SPRYASARA 29 /* YASARA logo */ #define SCR_SPRWHATIF 30 /* WHATIF logo */ #define SCR_SPRNTLOGON 31 /* The Windows logon box if loaded */ /* THE FIRST SPRITE NUMBERS USED (DISPLAY PRIORITY) */ #define SCR_SPRFIRSTLOGO 10 #define SCR_SPRFIRSTJUMPER 5 #define SCR_SPRFIRSTMODEL 0 #define SCR_SPRFIRSTTITLE 11 #define SCR_SPRFIRSTLOGIN 17 /* NAME OF SPRITES ON DISC */ char scr_spritename[]="cluster.spa"; /* TRANSPARENT COLOUR (RED=GREEN=BLUE) */ Uint32 scr_spritetranscol[SCR_SPRITEINIMAPS*3]= {128,128,128, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 128,128,128, 128,128,128, 128,128,128, 255,000,255, 255,000,255, 255,000,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,000,255, 255,000,255, 255,000,255, 255,000,255 }; /* FOR EVERY INIMAP: BIT1=ZOOM FLAG,BIT2=HORIZONTAL FLIPPING FLAG */ #define SCR_SPRZOOMFLAG 1 #define SCR_SPRFLIPFLAG 2 int scr_spritezoomflip[SCR_SPRITEINIMAPS]={ 0,1,1,1,1,1,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0 }; /* THE SPRITEMAPS */ SDL_Surface *scr_sprite[SCR_SPRITEMAPS]; /* BACKUPS OF SPRITE COORDINATES TO CLEAR THEM */ struct { int x; int y; SDL_Surface *surf; } scr_spritenew[SCR_SPRITES]; struct { int x; int y; SDL_Surface *surf; } scr_spriteold[SCR_SPRITES]; /* NAMES OF BACKGROUND GRAPHICS */ #define SCR_BACKGROUNDS 36 SDL_Surface *scr_background; char scr_bgname[]="cluster.bga"; /* THE START AND END POINTS OF THE PATHS TAKEN BY THE MODELS, PLUS SHRINKING FACTOR DWORD: StartX (must be 0 or 1023) DWORD: StartY DWORD: EndX (if not 0 or 1023, model turns around) DWORD: EndY */ struct { int startx; int starty; int endx; int endy; float shrink; } scr_path[SCR_BACKGROUNDS+1]= { {0,800,1023,800,1.0}, {0,750,1023,750,1.0}, {1023,635,0,675,0.5}, {0,510,425,398,0.5}, {0,755,730,800,1.0}, {0,790,820,790,1.0}, {1023,764,280,764,1.0}, {1023,450,0,420,0.5}, {1023,250,50,440,0.5}, {1023,670,500,670,0.5}, {0,760,1023,610,1.0}, {1023,40,510,110,0.25}, {1023,580,370,530,0.5}, {0,767,1023,767,0.35}, {0,620,680,660,1.0}, {1023,575,530,430,0.5}, {1023,410,410,410,0.25},{0,755,1023,755,1.0}, {1023,580,625,505,0.125},{0,160,590,290,0.25}, {0,768,1023,768,1.0}, {0,380,350,380,0.25}, {0,770,1023,770,0.5}, {1023,760,140,760,1.0}, {0,760,1023,760,1.0}, {1023,760,140,760,1.0}, {0,780,1023,780,1.0}, {1023,220,400,610,0.5}, {0,820,1023,820,1.0}, {0,660,1023,570,1.0}, {0,380,1023,380,0.5}, {0,370,1023,370,0.18}, {1023,210,0,250,0.25}, {0,580,1023,510,0.5}, {0,250,600,190,0.25}, {1023,340,0,260,0.25}, {0,767,1023,767,0.25} }; /* THE VARIOUS SCREEN SAVER ACTIONS */ #define SCR_IDLE 1 /* Computer is idle - run expensive screen saver */ #define SCR_BUSY 0 /* Computer is busy, run standard screen saver */ #define SCR_ACTIONS 4 /* Number of different actions */ #define SCR_ACTLOGO 0 /* CMBI/WHATIF/YASARA logo crossing the screen */ #define SCR_ACTTITLE 1 /* MODELS@HOME advertisement */ #define SCR_ACTJUMPER 2 /* Jumping body builder */ #define SCR_ACTMODEL 3 /* Girl carrying GroEL */ #define SCR_ACTDONE 4 /* Action finished */ #define SCR_MAXACTDAYBUSY 4 /* Maximum number of actions on screen during daytime if busy */ #define SCR_MAXACTDAYIDLE 7 /* Maximum number of actions on screen during daytime if idle */ #define SCR_MAXACTNIGHT 1 /* Maximum number of actions on screen during night */ struct /* Various action parameters */ { int type; int counter; int maxcount; int sprite; int data; int x; int y; } scr_action[max(max(SCR_MAXACTDAYBUSY,SCR_MAXACTDAYIDLE),SCR_MAXACTNIGHT)]; /* Number of active actions per type */ int scr_actions[SCR_ACTIONS]; /* Maximum number of actions per type during daytime */ int scr_maxactday[SCR_ACTIONS]={1,1,5,5}; /* Maximum number of actions per type during night */ int scr_maxactnight[SCR_ACTIONS]={0,0,1,1}; int scr_type; /* Type of screensaver used (black,logon,normal,prude) */ /* FLIP SCREENS ============ */ void scr_flip(void) { int i,xmin,xmax,ymin,ymax; if (vga_surface->flags&SDL_HWSURFACE) { /* HARDWARE PAGE FLIPPING */ SDL_Flip(vga_surface); } else { /* NO HARDWARE PAGE FLIPPING, UPDATE SPRITES MANUALLY */ for (i=0;iw); ymax=max(ymax,scr_spriteold[i].y+scr_spriteold[i].surf->h); } if (scr_spritenew[i].surf!=NULL) { xmin=min(xmin,scr_spritenew[i].x); ymin=min(ymin,scr_spritenew[i].y); xmax=max(xmax,scr_spritenew[i].x+scr_spritenew[i].surf->w); ymax=max(ymax,scr_spritenew[i].y+scr_spritenew[i].surf->h); } /* MAKE SURE UPDATE RECTANGLE FITS ON SCREEN (NO EFFECT OTHERWISE) */ xmin=max(0,xmin); ymin=max(0,ymin); xmax=min(SCR_SIZEX,xmax); ymax=min(SCR_SIZEY,ymax); /* UPDATE RECTANGLE */ if (xminw; srcrect.h=scr_spritenew[i].surf->h; dstrect.x=scr_spritenew[i].x+vga_offsetx; dstrect.y=scr_spritenew[i].y+vga_offsety; /* DISPLAY SPRITE */ SDL_BlitSurface(scr_spritenew[i].surf,&srcrect,vga_surface,&dstrect); } } } /* CLEAR ALL SPRITES ================= */ void scr_clearsprites(void) { int i,x,y; SDL_Rect srcrect,dstrect; /* CLEAR SPRITES AT OLD POSITIONS (TWO FRAMES BEHIND) */ for (i=0;iw; srcrect.h=scr_spriteold[i].surf->h; dstrect.x=x+vga_offsetx; dstrect.y=y+vga_offsety; /* CLEAR SPRITE */ SDL_BlitSurface(scr_background,&srcrect,vga_surface,&dstrect); } if (vga_surface->flags&SDL_HWSURFACE) { /* HARDWARE PAGE FLIPPING */ scr_spriteold[i].x=scr_spritenew[i].x; scr_spriteold[i].y=scr_spritenew[i].y; scr_spriteold[i].surf=scr_spritenew[i].surf; scr_spritenew[i].surf=NULL; } } } /* INITIALIZE SCREEN SAVER ======================= */ void scr_initialize(void) { int i,j,k,map; char *type,*tmpmap; /* SET TITLE */ SDL_WM_SetCaption(SCR_WINTITLE,"MODELS@HOME"); rnd_seed(time(NULL)); /* SET VIDEOMODE */ vga_surface=NULL; vga_setvideomode(SCR_SIZEX,SCR_SIZEY,SCR_BPP); map=0; for (i=0;i5) { if (!strnicmp(type,"BLACK",5)) { scr_type=SCR_TYPEBLACK; //if (!strnicmp(&type[5],"SLOW",4)) scr_type|=SCR_TYPESLOW; } else if (!strnicmp(type,"LOGON",5)) { scr_type=SCR_TYPELOGON; //if (!strnicmp(&type[5],"SLOW",4)) scr_type|=SCR_TYPESLOW; #if WINDOWS scr_sprite[map]=SDL_LoadBMP("cluster.spx"); #else scr_sprite[map]=SDL_LoadBMP("cluster.spy"); #endif } else if (!strnicmp(type,"PRUDISH",7)) scr_type=SCR_TYPEPRUDISH; } mem_free(type); #if WINDOWS /* CURSOR MUST BE DISPLAYED UNDER LINUX TO EVADE FLICKERING BUG */ SDL_ShowCursor(0); #endif rnd_seed(10); } /* MAIN SCREEN SAVER - SHOW GRAPHICS AND WAIT FOR USER INPUT ========================================================= subprogready POINTS TO A FUNCTION THAT RETURNS TRUE WHEN THE CHILD PROCESS IS READY. IF IDLEFLAG&1, A COMPUTATIONALLY MORE EXPENSIVE SCREEN SAVER IS USED. IF IDLEFLAG&2, NO CHECK IF explorer.exe IS RUNNING IS DONE. RETURNS: 0 - ONE SCREEN SAVER SCENE COMPLETED 1 - USER KEY INPUT OR PROCESS INTERRUPTED 2 - CHILD PROCESS COMPLETE (subprogready RETURNED TRUE) */ int scr_saver(int (*subprogready)(int),int idleflag,int clu_progwinnames,char **clu_progwinname) { char ch,filename[NAMELENMAX+1]; int i,j,k; int bg,map,doneflag,x,actions,type,counter,w,h,startx,endx,starty,endy; int steps,jumps,dsx,dex,nightshiftflag,loginx,loginstep; float slope; uint32 transcol; SDL_Rect rect,rect2; time_t timestamp; #if WINDOWS HWND windowid; int clu_countinstances(char*); /* A MIGHTY MYSTERY WINDOWS/SDL BUG MAKES THE SCREENSAVER WINDOW DISAPPEAR AFTER ABOUT 2 WEEKS, NEVERTHELESS THE SCREENSAVER KEEPS RUNNING IN THE BACKGROUND. IF THE USER THEN LOGS IN, THE SCREENSAVER WILL KEEP RUNNING AND CONSUMING PROCESSOR TIME AND MEMORY. THEREFORE WE QUIT IF explorer.exe IS RUNNING (WHICH INDICATES THAT THE USER HAS LOGGED IN) */ if (!(idleflag&2)&&clu_countinstances("EXPLORER.EXE")) return(1); /* DUE TO THE BUG ABOVE, WE MUST RESET THE VIDEOMODE */ /* SKIPPED NOW */ //vga_surface=NULL; // IF VIDEOMODE CANNOT BE SET, WE EXIT THE SCREEN SAVER //vga_setvideomode(SCR_SIZEX,SCR_SIZEY,SCR_BPP); #endif /* GET TIME */ time(×tamp); //hours=localtime(×tamp)->tm_hour; /* FROM 19.00h TO 08.00h WE HAVE GOT A NIGHTSHIFT AND USE A COMPUTATIONALLY CHEAP MINI-SCREEN SAVER */ nightshiftflag=0;//(hours>=19||hours<8); /* LOAD RANDOM BACKGROUND */ type=scr_type&SCR_TYPEMASK; if (!nightshiftflag&&type!=SCR_TYPEBLACK&&type!=SCR_TYPELOGON) { /* DAY - NORMAL SCREEN SAVER */ bg=rnd_range(SCR_BACKGROUNDS); /* SPECIAL HACK - MAKE PALM BEACH APPEAR MORE OFTEN */ if (!(rnd_int31()&7)) bg=0; if (bg<26) ch='a'+bg; else ch='0'+bg-26; scr_bgname[str_len(scr_bgname)-1]=ch; dsc_joinpath(filename,clusterdir,scr_bgname); scr_background=IMG_Load(filename); /* FAILED TO LOAD IMAGE? */ if (scr_background==NULL) { str_print(err_message,"Background data file %s missing.",scr_bgname); thr_lock(); err_exit(); } } else { /* IT'S NIGHT - MINI SCREEN SAVER, OR JUST BLACK SCREEN/LOGON SCREEN */ bg=SCR_BACKGROUNDS; scr_background=SDL_CreateRGBSurface(SDL_SWSURFACE,SCR_SIZEX,SCR_SIZEY,SCR_BPP, vga_surface->format->Rmask,vga_surface->format->Gmask, vga_surface->format->Bmask,vga_surface->format->Amask); } /* CONVERT TRUE COLOR BACKGROUND TO CURRENT COLOR FORMAT */ SDL_ToDisplayFormat(&scr_background); /* CLEAR ALL SPRITES COORDINATES */ for (i=0;i1) break; } } else { /* TRUE Models@Home SCREEN SAVER */ /* SET COLOR KEYS AND CREATE ZOOMED AND FLIPPED SPRITES OF APPROPRIATE SIZE */ map=0; for (i=0;iformat,scr_spritetranscol[i*3], scr_spritetranscol[i*3+1],scr_spritetranscol[i*3+2]); SDL_SetColorKey(scr_sprite[map],SDL_SRCCOLORKEY,transcol); SDL_ToDisplayFormat(&scr_sprite[map++]); } else { /* SPRITE IS ZOOM- OR FLIPPABLE, DON'T ADAPT BACKUP COLORMAP */ map++; if (scr_spritezoomflip[i]&SCR_SPRZOOMFLAG) { /* SHRINK SPRITE */ scr_sprite[map]=SDL_ShrinkSurface(scr_sprite[map-1],scr_path[bg].shrink); transcol=SDL_MapRGB(scr_sprite[map]->format,scr_spritetranscol[i*3], scr_spritetranscol[i*3+1],scr_spritetranscol[i*3+2]); SDL_SetColorKey(scr_sprite[map],SDL_SRCCOLORKEY,transcol); SDL_ToDisplayFormat(&scr_sprite[map++]); } if (scr_spritezoomflip[i]&SCR_SPRFLIPFLAG) { /* FLIP SPRITE HORIZONTALLY */ scr_sprite[map]=SDL_FlipSurfaceH(scr_sprite[map-1]); transcol=SDL_MapRGB(scr_sprite[map]->format,scr_spritetranscol[i*3], scr_spritetranscol[i*3+1],scr_spritetranscol[i*3+2]); SDL_SetColorKey(scr_sprite[map],SDL_SRCCOLORKEY,transcol); SDL_ToDisplayFormat(&scr_sprite[map++]); } } } /* CLEAR EVENT BUFFER */ key_clearbuffer(); /* WAIT FOR ONE MINUTE */ /* INITIALIZE ACTIONS */ /* GAMBLE NUMBER OF ACTIONS */ if ((scr_type&SCR_TYPEMASK)==SCR_TYPEPRUDISH) { /* IT'S A PRUDISH WORLD */ scr_maxactday[SCR_ACTJUMPER]=0; scr_maxactday[SCR_ACTMODEL]=0; if (!nightshiftflag) actions=rnd_range(2)+1; else actions=1; } else { /* IT'S A FUN WORLD */ if (!nightshiftflag) { if (idleflag&SCR_IDLE) actions=rnd_range(SCR_MAXACTDAYIDLE)+1; else actions=rnd_range(SCR_MAXACTDAYBUSY)+1; } else actions=rnd_range(SCR_MAXACTNIGHT)+1; } mem_set(scr_actions,0,sizeof(scr_actions)); for (i=0;i>8)&1||(scr_type&SCR_TYPEMASK)==SCR_TYPEPRUDISH) { /* LONELY? WANNA HAVE FUN? */ scr_action[i].data=SCR_SPRLONELY-1; } else { /* THE POEM, AS "MODELS@HOME" RIMES WITH "THE POPE IN ROME", THIS PART IS ALSO CENSORED */ scr_action[i].data=SCR_SPRPOEM1-1; } break; } case SCR_ACTJUMPER: { /* LEUCINE ZIPPER JUMPER */ scr_action[i].sprite=SCR_SPRFIRSTJUMPER+scr_actions[type]; scr_action[i].counter-=25; break; } case SCR_ACTMODEL: { /* GroEL MODEL */ scr_action[i].sprite=SCR_SPRFIRSTMODEL+scr_actions[type]; break; } } /* INCREASE NUMBER OF ACTIONS OF THIS TYPE */ scr_actions[type]++; } /* PLACE THE LOGIN MESSAGE */ loginx=0; loginstep=1; /* MOVE THE SPRITES */ do { /* WAIT FOR 75 MILLISECONDS */ SDL_Delay(75); doneflag=1; /* CLEAR ALL SPRITES */ scr_clearsprites(); /* MOVE THE LOGIN MESSAGE AND TURN AROUND IF BORDER IS TOUCHED */ loginx=loginx+loginstep; #if LINUX if (loginx==0||loginx==SCR_SIZEX-scr_sprite[SCR_SPRLINUXLOGIN]->w) loginstep=-loginstep; scr_putsprite(SCR_SPRFIRSTLOGIN,SCR_SPRLINUXLOGIN,loginx,1); #else if (loginx==0||loginx==SCR_SIZEX-scr_sprite[SCR_SPRWINLOGIN]->w) loginstep=-loginstep; scr_putsprite(SCR_SPRFIRSTLOGIN,SCR_SPRWINLOGIN,loginx,1); #endif /* MOVE THE VARIOUS SPRITES */ for (i=0;i=0) { switch(scr_action[i].type) { case SCR_ACTLOGO: { /* MOVE LOGO */ x=scr_action[i].x+scr_action[i].data; scr_action[i].x=x; if (x>SCR_SIZEX) scr_action[i].type=SCR_ACTDONE; scr_putsprite(SCR_SPRFIRSTLOGO,scr_action[i].sprite,x,scr_action[i].y); break; } case SCR_ACTTITLE: { /* MODELS@HOME ADVERTISEMENT */ counter=scr_action[i].counter; if (scr_action[i].data>=SCR_SPRLONELY-1&& scr_action[i].data<=SCR_SPRMODATHOME) { /* TYPE 1 - LONELY, WANNA HAVE FUN? */ if (!(counter%100)) { /* NEW PART OF TEXT */ scr_action[i].data++; /* RANDOM Y-POSITION, ONLY MODELS@HOME LOGO IS CENTERED */ scr_action[i].y=rnd_range(SCR_SIZEY-scr_sprite[scr_action[i].data]->h); if (counter==300) scr_action[i].y=(SCR_SIZEY-573)/2; /* END REACHED? */ if (counter>=400) scr_action[i].type=SCR_ACTDONE; } /* SET HORIZONTAL POSITION */ counter%=100; j=scr_sprite[scr_action[i].data]->w; k=(SCR_SIZEX-j)/2; if (counter>=60) scr_action[i].x=-j+((90-counter)*(k+j))/30; else if (counter<=30) scr_action[i].x=k+((30-counter)*(SCR_SIZEX-k))/30; scr_putsprite(scr_action[i].sprite,scr_action[i].data,scr_action[i].x,scr_action[i].y); } else { /* TYPE 2 - THE POEM */ if (!(counter%100)) { /* NEW PART OF TEXT */ scr_action[i].data++; if (scr_action[i].data>SCR_SPRPOEM6) { scr_action[i].data=SCR_SPRMODATHOME; scr_action[i].y=(SCR_SIZEY-573)/2; } } if (counter>=560) dsx=((counter-560)*SCR_SIZEX)/40; else dsx=0; /* SPEED THINGS UP A BIT */ if (counter<500&&counter%100==30) scr_action[i].counter+=29; /* DISPLAY ALL POEM LINES */ counter%=100; for (j=0;j<=scr_action[i].data-SCR_SPRPOEM1;j++) { k=(SCR_SIZEX-scr_sprite[SCR_SPRPOEM1+j]->w)/2; if (j==scr_action[i].data-SCR_SPRPOEM1&&counter<=30) { /* LAST POEM LINE IS STILL MOVING */ k+=((30-counter)*(SCR_SIZEX-k))/30; } scr_putsprite(scr_action[i].sprite+j,SCR_SPRPOEM1+j,k+dsx, (SCR_SIZEY-504)/2+j*96); } } break; } case SCR_ACTJUMPER: { /* LEUCINE ZIPPER JUMPER */ /* GET SIZE OF POSSIBLY SHRINKED SPRITE */ w=scr_sprite[SCR_SPRJUMPER1+1]->w; h=scr_sprite[SCR_SPRJUMPER1+1]->h; /* DETERMINE START AND END X/Y-POSITON */ startx=scr_path[bg].startx; endx=scr_path[bg].endx; starty=scr_path[bg].starty; endy=scr_path[bg].endy; jumps=(abs(endx-startx)+1)*8.0/(scr_path[bg].shrink*SCR_SIZEX); if (scr_path[bg].startx==0) { dsx=-w/2; if (endx==SCR_SIZEX-1) dex=w/2; else dex=-w/2; } else { dsx=w/2; if (!endx) dex=-w/2; else dex=+w/2; } slope=(float)(endy-starty)/(float)(endx-startx); startx+=dsx; endx+=dex; starty+=dsx*slope; endy+=dex*slope; counter=scr_action[i].counter; if (counter==jumps*2*30) scr_action[i].type=SCR_ACTDONE; /* TURN AROUND */ if (counter>jumps*30) counter=jumps*2*30-counter; if (counter%30<4) { /* JUMPING UP */ scr_action[i].data=SCR_SPRJUMPER5+1-2*(counter%30); scr_action[i].x=startx+((counter/30)*(endx-startx))/jumps; scr_action[i].y=starty+((counter/30)*(endy-starty))/jumps; } else if (counter%30<26) { /* FLYING */ scr_action[i].data=SCR_SPRJUMPER1+1; scr_action[i].x=startx+(((counter/30)*22+counter%30-4)*(endx-startx))/(jumps*22); scr_action[i].y=starty+(((counter/30)*22+counter%30-4)*(endy-starty))/(jumps*22)- 250.0*scr_path[bg].shrink*sin((counter%30-4)*0.1428); } else { /* LANDING */ scr_action[i].data=SCR_SPRJUMPER1+1+2*((counter%30)-26); scr_action[i].x=startx+(((counter/30)+1)*(endx-startx))/jumps; scr_action[i].y=starty+(((counter/30)+1)*(endy-starty))/jumps; } /* MOVE FROM BOTTOM MIDDLE TO TOP LEFT POINT */ scr_action[i].x-=w/2; scr_action[i].y-=h; scr_putsprite(scr_action[i].sprite,scr_action[i].data,scr_action[i].x,scr_action[i].y); break; } case SCR_ACTMODEL: { /* GIRL CARRYING GroEL */ /* GET SIZE OF POSSIBLY SHRINKED SPRITE */ w=scr_sprite[SCR_SPRMODEL1+1]->w; h=scr_sprite[SCR_SPRMODEL1+1]->h; /* DETERMINE START AND END X/Y-POSITON */ startx=scr_path[bg].startx; endx=scr_path[bg].endx; starty=scr_path[bg].starty; endy=scr_path[bg].endy; steps=(abs(endx-startx)+1)*400/(scr_path[bg].shrink*SCR_SIZEX); if (scr_path[bg].startx==0) { dsx=-w/2; if (endx==SCR_SIZEX-1) dex=w/2; else dex=-w/2; } else { dsx=w/2; if (!endx) dex=-w/2; else dex=+w/2; } slope=(float)(endy-starty)/(float)(endx-startx); startx+=dsx; endx+=dex; starty+=dsx*slope; endy+=dex*slope; counter=scr_action[i].counter; if (counter==steps) scr_action[i].type=SCR_ACTDONE; scr_action[i].data=SCR_SPRMODEL1+1+3*((counter>>2)&1)+((startx>endx)^(counter>steps/2)); /* TURN AROUND */ if (counter>steps/2) counter=steps-counter; /* GET CURRENT POSITION AND MOVE FROM BOTTOM MIDDLE TO TOP LEFT POINT */ scr_action[i].x=startx+(counter*(endx-startx))/(steps/2)-w/2; scr_action[i].y=starty+(counter*(endy-starty))/(steps/2)-h; scr_putsprite(scr_action[i].sprite,scr_action[i].data,scr_action[i].x,scr_action[i].y); break; } } } /* INCREASE STATUS COUNTER */ scr_action[i].counter++; } /* DISPLAY CHANGES */ scr_showsprites(); scr_flip(); /* QUIT ON USER INPUT */ //SDL_WM_GrabInput(SDL_GRAB_ON); i=key_input(); if (i==1||sigintflag) doneflag=2; /* QUIT IF CHILD PROCESS TERMINATES */ if (subprogready&&subprogready(0)) doneflag=3; /* QUIT IF WINDOW WAS SWITCHED TO BACKGROUND */ #if WINDOWS /* SOME PROGRAM WINDOWS MIGHT APPEAR IN FRONT OF THE SCREEN SAVER HERE THEY ARE ALL MINIMIZED */ for (i=0;i>1,SCR_SIZEY>>1); return(doneflag-1); } /* ====================================================================== J O B F U N C T I O N G R O U P ====================================================================== */ /* A JOB LIST FILENAME MUST ALWAYS END WITH .job IT IS RENAMED TO *.jup (job update) BEFORE BEING ACCESSED (SO THAT NO OTHER PROGRAM CAN MODIFY THE FILE) */ #define JOB_HEADERLEN 87 /* Length of one job header in the cluster.job file, right until 'PROGRAM' */ #define JOB_NUMBER 0 /* Job number in column 0 */ #define JOB_NUMLEN 8 #define JOB_STATUS 9 /* Job status in column 9 */ #define JOB_PRIORITY 11 /* Job priority in column 11 */ #define JOB_CPUS 13 /* Job CPU requirement in column 13 */ #define JOB_IPADD 16 /* Job IP address in column 16 */ #define JOB_IPADDLEN 15 #define JOB_THREAD 32 /* Job thread on client */ #define JOB_THREADLEN 3 #define JOB_NAME 36 #define JOB_NAMELEN 16 #define JOB_TIME 53 #define JOB_TIMELEN 24 #define JOB_CHECK 78 #define JOB_CHECKLEN 8 #define JOB_PROGRAM 87 #define JOB_OPTIONS 96 volatile int job_loadlisterrno; /* CREATE A NEW JOB LIST ===================== RETURNS 0 - OK 1 - ERROR */ int job_initlist(char *listname) { char lockname[NAMELENMAX+1]; FILE *ft; /* REMOVE LOCKED JOB LIST IF PRESENT */ str_copy(lockname,listname); str_copy(&lockname[str_len(lockname)-3],"jup"); dsc_remove(lockname); /* CREATE EMPTY JOB LIST */ if (!(ft=fopen(listname,"w"))) return(1); fprintf(ft,"JOB______S_P_CS_CLIENT IP___________CLIENT NAME______TIME OF JOB SUBMISSION___" "CHECK____PROGRAM__OPTIONS|DIR|COPY|TEXT|RETURN|DONEFLAG\n"); fclose(ft); dsc_chmod(listname,MOD_RW); return(0); } /* LOAD JOB LIST ============= */ char *job_loadlist(char *listname) { char *joblist,lockname[NAMELENMAX+1]; str_copy(lockname,listname); str_copy(&lockname[str_len(lockname)-4],".jup"); if (dsc_ispresent(lockname)) printf("WARNING - File %s is already present.\n",lockname); /* WAIT TILL FILE IS ACCESSIBLE */ while (dsc_rename(lockname,listname)==-1) { /* 2016-04: HANG HERE FOR UNKNOWN REASONS, cluster.jup PRESENT WITH SIZE 0 */ job_loadlisterrno=errno; time_delay(1000); } /* READ JOB LIST */ txt_loadfilealloc(&joblist,lockname); return(joblist); } /* SAVE JOB LIST ============= RETURNS 0 - OK 1 - ERROR */ int job_savelist(char *listname,char *joblist) { char lockname[NAMELENMAX+1]; str_copy(lockname,listname); str_copy(&lockname[str_len(lockname)-4],".jup"); if (!txt_savefile(lockname,joblist)) return(1); if (dsc_rename(listname,lockname)==-1) return(1); mem_free(joblist); return(0); } /* SHARE JOB LIST WITH OTHERS ========================== RETURNS 0 - OK 1 - ERROR */ int job_sharelist(char *listname) { char lockname[NAMELENMAX+1]; str_copy(lockname,listname); str_copy(&lockname[str_len(lockname)-4],".jup"); if (dsc_rename(listname,lockname)==-1) return(1); return(0); } /* COUNT NUMBER OF WAITING JOBS ============================ */ int job_waiting(char *listname) { char *job,*joblist; int waiting; if (DEBUG) msg_log("Counting waiting jobs..."); /* LOAD JOB LIST */ joblist=job_loadlist(listname); job_sharelist(listname); job=joblist; txt_setnextline(&job); /* COUNT NUMBER OF JOBS WITH STATUS "W" */ waiting=0; while (*job) { if (job[JOB_STATUS]=='W') waiting++; txt_setnextline(&job); } /* FREE LIST AND RETURN */ mem_free(joblist); return(waiting); } /* GET LENGTH OF JOB LIST ====================== */ int job_listlen(char *joblist) { if (joblist==NULL) return(0); return(str_len(joblist)); } /* ADD A JOB TO LIST ================= */ void job_add(char **tmplistadd,int priority,int cpus,char *progopt) { char *tmplist; int listlen,progoptlen; tmplist=*tmplistadd; /* GET LENGTH OF JOBLIST AND PROGRAM OPTIONS */ listlen=job_listlen(tmplist); progoptlen=str_len(progopt); /* RESIZE TMPLIST */ tmplist=mem_realloc(tmplist,listlen+JOB_HEADERLEN+progoptlen+2); /* ADD NEW JOB DATA */ /* NUMBER OF JOB = NUMBER OF LAST JOB IN LIST +1 */ str_print(&tmplist[listlen],"???????? W %1d %02d " " ",priority,cpus); /* ADD PROGRAM AND OPTIONS */ mem_copy(&tmplist[listlen+JOB_HEADERLEN],progopt,progoptlen); /* ADD LIST END */ tmplist[listlen+JOB_HEADERLEN+progoptlen]='\n'; tmplist[listlen+JOB_HEADERLEN+progoptlen+1]=0; /* STORE NEW TMPLIST ADDRESS */ *tmplistadd=tmplist; } /* SUBMIT JOBS TO MAIN QUEUE ========================= RETURNS OK/ERR */ int job_submit(char *listname,char *tmplist) { int joblistlen,tmplistlen,jobnum; char *joblist,*joblistpos; joblist=job_loadlist(listname); joblistlen=job_listlen(joblist); /* GET NUMBER OF LAST JOB IN LIST */ joblistpos=&joblist[joblistlen]; txt_prevline(&joblistpos,joblist); if (joblistpos==joblist) jobnum=0; else jobnum=str_atoi(joblistpos)+1; /* INCREASE SIZE OF JOBLIST */ tmplistlen=job_listlen(tmplist); joblist=mem_realloc(joblist,joblistlen+tmplistlen+1); joblistpos=&joblist[joblistlen]; mem_copy(joblistpos,tmplist,tmplistlen+1); /* INSERT JOB NUMBERS */ while (*joblistpos) { if (jobnum>99999999) jobnum=0; str_print(joblistpos,"%08d",jobnum++); joblistpos[8]=' '; txt_setnextline(&joblistpos); } mem_free(tmplist); return(job_savelist(listname,joblist)); } /* FIND JOB IN LIST ================ RETURNS NULL IF NOT FOUND */ char *job_find(char *joblist,int jobnum) { char *job; job=joblist; txt_setnextline(&job); while (*job) { if (str_atoi(job)==jobnum) break; txt_setnextline(&job); } /* JOB FOUND? */ if (!*job) job=NULL; return(job); } /* EXTRACT IP ADDRESS FROM JOB DESCRIPTION ======================================= RETURNS 0 - OK 1 - ILLEGAL IP ADDRESS */ int job_getipadd(uint32 *ip,char *job) { int i,val; job+=JOB_IPADD; for (i=0;i<4;i++,job=str_chr(job,'.')+1) { val=str_atoi(job); if (val<0||val>255) return(1); ((unsigned char*)ip)[i]=val; } return(0); } /* ====================================================================== N E T W O R K F U N C T I O N G R O U P ====================================================================== */ /* PACKET FORMAT: NET_CLIENTREADY: DWORD: NET_CLIENTREADY DWORD: Size of packet in bytes DWORD: LongJob flag from cluster.cnf DWORD: Number of available CPUs DWORD: Number of client thread DWORD: Number of updateable files DWORD: First timestamp DWORD: Second timestamp ..... CHAR[]:First path and name of file from cluster.upd, terminated with 0 CHAR[]:Second path and name of file from cluster.upd, terminated with 0 NET_FILEUPDATE/NET_JOBDATA: DWORD: Type of packet (e.g. NET_FILEUPDATE,NET_JOBDATA) DWORD: Size of packet in bytes DWORD: File description (e.g. job number) DWORD: Timestamp of file (will be set after saving) CHAR[]: Filename without path CHAR: 0 CHAR[]: File content */ #define NET_CLIENTSMAX 100 #define NET_NOMESSAGE 0 #define NET_CLOSE 1 #define NET_ACCEPT 2 #define NET_CLIENTREADY 3 #define NET_CLIENTSTOPPED 4 #define NET_CLIENTDONE 5 #define NET_CLIENTACTIVE 6 #define NET_FILEUPDATE 7 #define NET_JOB 8 /* Send job description */ #define NET_JOBDATA 9 /* Send job data file */ #define NET_NOJOB 10 /* No job waiting */ #define NET_KILLJOB 11 /* Current job is to be killed */ #define NET_CLIENTFAILED 12 /* Client failed to complete job */ typedef int32 pckdata; #define NET_PCKTYPE 0 #define NET_PCKSIZE 1 #define NET_PCKDATA 2 #define NET_PCKHEADERSIZE NET_PCKDATA*sizeof(pckdata) #define NET_FILEINFO 2 #define NET_FILETIME 3 #define NET_FILENAME 4 TCPsocket net_servsock; SDLNet_SocketSet net_socketset; struct { TCPsocket sock; IPaddress peer; int thr; char name[256]; } net_client[NET_CLIENTSMAX]; /* ALLOCATE NETWORK PACKET ======================= PACKET FORMAT: (ALL DATA IN LITTLE ENDIAN FORMAT) DWORD: Type of packet DWORD: Size of packet DWORD: Data */ pckdata *net_allocpacket(int type,int size) { pckdata *packet; size+=NET_PCKHEADERSIZE; packet=mem_alloc(size); if (packet!=NULL) { /* MEMORY COULD BE ALLOCATED, INITIALIZE PACKET HEADER */ packet[NET_PCKTYPE]=type; packet[NET_PCKSIZE]=size; } return(packet); } /* SAVE A FILE STORED IN NETWORK PACKET ==================================== NOT THREAD-SAFE, MUST BE FLANKED WITH thr_lock/thr_unlock */ int net_savefile(char *filename,pckdata *packet,int user,int group) { int namelen,saved; char tmpfilename[NAMELENMAX+1]; struct utimbuf timeinfo; saved=0; namelen=str_len(filename); /* CREATE TEMPORARY FILENAME */ while (namelen&&filename[namelen-1]!='/'&&filename[namelen-1]!='\\'&& filename[namelen-1]!=':') namelen--; if (namelen+str_len(TMPFILENAME)name); //msg_log("net_senddir: Found match %s.",filename); if (!net_sendfile(sock,type,info,filename)) sent++; dta=dsc_findnext(); } return(sent); } /* RECEIVE DATA USING TCP ====================== RETURNS -1 ON ERROR, OTHERWISE SIZE OF RECEIVED DATA WE CANNOT USE SDLNet_TCP_Recv BECAUSE THIS RUNS INTO A WINDOWS BUG THAT ENDS RECEIPT OF LARGE FILES WITH WSAGetLastError()==10055 NAMED 'WSAENOBUFS'. SO WE CHOP IT UP INTO SMALLER PIECES */ int net_receive(TCPsocket sock,void *data,int requestedsize,int thr) { int size,readsize,failures; /* SERVER SOCKETS ARE FOR ACCEPTING CONNECTIONS ONLY */ readsize=failures=0; if (sock->sflag) return(NONE); do { errno=0; //if (thr!=NONE) msg_log("Thread %d: net_receive calling with size %d\n",thr,min(0x100000,requestedsize-readsize)); size=recv(sock->channel,(char*)data+readsize,min(0x100000,requestedsize-readsize),0); //if (thr!=NONE) msg_log("Thread %d: net_receive got %d, done %d of %d, errno %d EINTR %d.",thr,size,readsize,requestedsize,errno,EINTR); if (!size) return(0); if (size<0) { //msg_log("Got error %d, EINTR=%d, failures=%d.",errno,EINTR,failures); fflush(stdout); if (errno!=EINTR) return(NONE); /* IF ERROR WAS 'EINTR' WE TRY A FEW TIMES (SEEMS THAT THIS CAN HAPPEN TEMPORARILY??) */ SDL_Delay(50); if (++failures>20) return(NONE); } if (size>0) readsize+=size; } while (readsizeready=0; return(readsize); } /* RECEIVE NETWORK PACKET USING TCP ================================ RETURNS NULL IF NETWORK ERROR OCCURRED */ pckdata *net_receivepacket(TCPsocket sock,int thr) { int i,pos,size; pckdata *packet; pckdata header[2]; fflush(stdout); /* RECEIVE PACKET HEADER */ size=8; pos=0; //msg_log("net_receivepacket: Thread=%d",thr); while (size) { i=net_receive(sock,&((char*)header)[pos],size,thr); if (i<=0) return(NULL); pos+=i; size-=i; } /* ALLOCATE MEMORY TO STORE PACKET (SIZE TAKEN FROM HEADER) */ size=header[NET_PCKSIZE]; packet=mem_alloc(size); if (packet==NULL) { str_print(err_message,"Fatal network error: Not enough memory to receive packet (size=%d bytes)",size); /* CAN'T LOCK HERE, SINCE WE MAY ALREADY BE IN CRITICAL SECTION */ err_exit(); } /* COPY HEADER TO FINAL PACKET DATA */ packet[NET_PCKTYPE]=header[NET_PCKTYPE]; packet[NET_PCKSIZE]=header[NET_PCKSIZE]; if (DEBUG) msg_log("Thread %d: Received packet of size %d with type %d.",thr,size,packet[NET_PCKTYPE]); /* RECEIVE REST OF PACKET */ size-=NET_PCKHEADERSIZE; pos=0; while (size) { i=net_receive(sock,&((char*)packet)[NET_PCKHEADERSIZE+pos],size,thr); if (i<=0) { mem_free(packet); if (DEBUG) msg_log("Thread %d: net_receivepacket: Error %d (%s), remaining size %d",thr,i,SDLNet_GetError(),size); return(NULL); } pos+=i; size-=i; } return(packet); } /* RECEIVE NETWORK MESSAGE USING TCP ================================= */ int net_receivemessage(TCPsocket sock,int thr) { int message; pckdata *packet; /* RECEIVE PACKET */ packet=net_receivepacket(sock,thr); if (packet==NULL) return(0); /* EXTRACT TYPE */ message=packet[NET_PCKTYPE]; mem_free(packet); return(message); } /* INITIALIZE NETWORK ================== */ void net_init(void) { net_servsock=NULL; net_socketset=NULL; if (SDLNet_Init()<0) { str_print(err_message,"Couldn't initialize SDL net: %s\n",SDLNet_GetError()); err_exit(); } } /* CLOSE NETWORK CONNECTIONS AT EXIT ================================= */ void net_close(void) { if (net_servsock!=NULL) { SDLNet_TCP_Close(net_servsock); net_servsock = NULL; } if (net_socketset!=NULL) { SDLNet_FreeSocketSet(net_socketset); net_socketset = NULL; } SDLNet_Quit(); } /* DISCONNECT AND REMOVE CLIENT ============================ */ void net_removeclient(int cli) { if (!net_client[cli].sock) { str_print(err_message,"Cannot disconnect client %d without socket",cli); err_exit(); } SDLNet_TCP_DelSocket(net_socketset,net_client[cli].sock); SDLNet_TCP_Close(net_client[cli].sock); net_client[cli].sock=NULL; } /* PRINT IP ADDRESS TO STRING ========================== */ void net_sprintip(char *string,char *prefix,IPaddress *ip) { str_print(string,"%s%03d.%03d.%03d.%03d",prefix, ((unsigned char*)&ip->host)[0],((unsigned char*)&ip->host)[1], ((unsigned char*)&ip->host)[2],((unsigned char*)&ip->host)[3]); } /* ====================================================================== M A I N C L U S T E R P R O G R A M ====================================================================== */ #define CLU_USERID 38475 #define CLU_GROUPID 38475 #define CLU_JOBFILE "cluster.job" #define CLU_PROGRAMSMAX 32 /* Maximum number of programs that can be run on the clients (never more than 127, since number is stored in char) */ /* Time interval of still alive messages sent by clients in seconds */ #define CLU_ALIVECHECKTIME 600 #define CLU_CPUSMAX 64 /* Maximum number of client CPUs */ #if LINUX #define CLU_CONFIGFILE "cluster.cnf" #else #define CLU_CONFIGFILE "cluster.cnw" #endif /* ENTRIES WITHIN cluster.cnf DEFINING HOW EACH OF THE PROGRAMS IS RUN */ char *clu_runfield[CLU_PROGRAMSMAX]; char *clu_runcommand[CLU_PROGRAMSMAX]; char *clu_runpath[CLU_PROGRAMSMAX]; char *clu_progwinname[CLU_PROGRAMSMAX]; char *clu_updatelist; /* List of files that need to be kept up2date */ int clu_cpus; /* Number of CPUs */ int clu_cpusfree; /* Number of free CPUs that can still work on a job */ int clu_consoleflag; /* Flag if client is run in console mode */ int clu_scrsavlinflag; /* Flag if screen saver run after logging in (no check for explorer.exe needed) */ int clu_prioritymax; /* Maximum job priority accepted by client */ int clu_progwinnames; /* Number of program window names to minimize */ /* Exit status of subprogram */ volatile int clu_subprogstatus[CLU_CPUSMAX]; /* The client threads */ SDL_Thread *clu_child[CLU_CPUSMAX]; IPaddress clu_serverip; /* IP address of Models@Home server */ /* SEARCH FOR PROGRAM INDEX ======================== RETURNS -1 IF PROGRAM NOT FOUND IN clu_runfield */ int clu_progindex(char *progname) { int i; for (i=0;iNUL "); #else str_join(&command,"1>/dev/null 2>/dev/null "); #endif /* CREATE A CHILD PROCESS */ /* KEEPING THE ORIGINAL PATH WAS REPORTED TO SEGFAULT AFTER A WHILE DUE TO GROWING PATH NAMES. NOT VERIFIED SO FAR. oldpath=getenv("PATH"); newpath=mem_alloc(5+str_len(clu_runpath[program])+1+str_len(oldpath)+1); str_copy(newpath,"PATH="); str_cat(newpath,clu_runpath[program]); #if LINUX str_cat(newpath,":"); str_cat(newpath,oldpath); setenv("PATH",newpath+5,1); #else str_cat(newpath,";"); str_cat(newpath,oldpath); putenv(newpath); #endif */ /* RUN SUBPROGRAM */ if (DEBUG) msg_log("Thread %d: Running program %d using command line '%s'.",thread,program,command); system(command); if (DEBUG) msg_log("Thread %d: Program finished.",thread); mem_free(command); //mem_free(newpath); /* TELL MAIN PROCESS THAT WE ARE READY */ clu_subprogstatus[thread]=1; /* END THREAD */ return(0); } /* CHECK IF SUBPROGRAM HAS TERMINATED ================================== */ int clu_subprogready(int thr) { return(clu_subprogstatus[thr]); } /* KILL ALL CHILD PROCESSES ======================== clu_lock MUST HAVE BEEN CALLED BEFORE */ void clu_killchildproc(void) { char *tlist,*line; char killcommand[NAMELENMAX+1]; int pid; #if WINDOWS char *line2; int minspaces; #else int ppid; int *ppidlist; #endif /* I COULDN'T FIND A CLEAN WAY TO KILL A PROCESS AND ALL ITS CHILD PROCESSES, BOTH UNDER WINDOWS AND LINUX, THAT'S WHY A HACK IS USED. (IF YOU'VE GOT A SOLUTION, PLEASE CONTACT elmar@cmbi.kun.nl) IN WINDOWS, WE RUN tlist.exe TO GET A PROCESS-TREE, PARSE THE TEXT FILE AND KILL ALL PROCESSES ASSOCIATED WITH THE SCREEN SAVER. IN LINUX, WE RUN ps -ef TO GET A LIST OF PROCESSES, PARSE THE TEXT FILE AND KILL ALL PROCESSES BELONGING TO USER models */ dsc_remove("tlist.txt"); #if WINDOWS system("tlist.exe -t >tlist.txt"); #else getcwd(killcommand,NAMELENMAX); msg_log("killchildproc: dir=%s",killcommand); system("ps -ef >tlist.txt"); #endif /* LOAD PROCESS TREE */ if (!txt_loadfilealloc(&tlist,"tlist.txt")) err_exitmessage("TLIST.EXE failed in killchildproc"); line=tlist; #if WINDOWS while (*line) { /* COUNT LEADING SPACES */ minspaces=0; while (line[minspaces]==' ') minspaces++; line+=minspaces; if (!strnicmp(line,"cluster.exe",11)) { /* CLUSTER.EXE FOUND */ while(1) { txt_setnextline(&line); line2=line; while (*line2==' ') line2++; /* SMALLER INDENTATION LEVEL? */ if (!isalnum(*line2)||line2-line%s",tlistexe,tlisttxt); system(syscom); /* LOAD PROCESS TREE */ if (!txt_loadfilealloc(&tlist,tlisttxt)) { str_print(err_message,"%s failed in countinstances for %s",tlistexe,program); err_exit(); } line=tlist; instances=0; while (*line) { /* COUNT LEADING SPACES */ minspaces=0; while (line[minspaces]==' ') minspaces++; line+=minspaces; if (!strnicmp(line,program,str_len(program))) { /* PROGRAM FOUND */ instances++; } txt_setnextline(&line); } mem_free(tlist); return(instances); } #endif /* SEND STILL ALIVE MESSAGE ======================== RETURNS THE SERVER'S ANSWER, MAY BE NET_KILLJOB TO KILL JOB */ int clu_sendalivemessage(TCPsocket sock,int job) { int message; pckdata *packet; /* A LONG JOB, TIME TO SEND A "STILL ALIVE MESSAGE" TO SERVER */ packet=net_allocpacket(NET_CLIENTACTIVE,sizeof(pckdata)); packet[NET_PCKDATA]=job; net_sendpacket(sock,packet); if (DEBUG) msg_log("Still alive message sent to server."); /* WAIT FOR ACKNOWLEDGEMENT */ message=net_receivemessage(sock,NONE); return(message); } /* ADD SOCKET TO SET, SO THAT THEY CAN ALL BE CLOSED AT EXIT ========================================================= */ void clu_addsocket(TCPsocket sock) { thr_lock(); SDLNet_TCP_AddSocket(net_socketset,sock); thr_unlock(); } /* DELETE SOCKET FROM SET ====================== */ void clu_delsocket(TCPsocket sock) { thr_lock(); SDLNet_TCP_DelSocket(net_socketset,sock); thr_unlock(); } /* CLUSTER CLIENT ============== */ int clu_client(void *thradd) { char jobdir[NAMELENMAX+1],filename[NAMELENMAX+1],command[NAMELENMAX*2+1]; char *jobpos,*jobpos2,*jobposbak,*updatepos,*job; int i,retval,thr,newclusterflag,message,ch,wildcard; int joblen,program,allpresent,waitcycles,result,cpus,freespace; time_t timestamp,lastchecktime; pckdata *packet; TCPsocket sock; thr=*(int32*)thradd; sock=NULL; if (DEBUG) msg_log("Thread %d: Starting up...",thr); while (1) { /* MAIN CLIENT LOOP */ /* EACH waitcycle LASTS ABOUT 15 SECONDS */ waitcycles=1; /* CHECK THAT THERE IS ENOUGH FREE DISK SPACE, THIS FUNCTION IS NOT THREAD-SAFE */ thr_lock(); freespace=dsc_freespace(); thr_unlock(); if (freespace<200) { /* LESS THAN 200 MEGABYTES OF FREE DISC SPACE - THAT'S TOO DANGEROUS */ err_warnmessage("Less than 200MB of free disc space, not connected to server"); waitcycles=40; goto cliwait; } /* INITIALIZE TCP SOCKET TO COMMUNICATE WITH SERVER */ thr_lock();//Trying to avoid hang bug sock=SDLNet_TCP_Open(&clu_serverip); thr_unlock(); if (!sock) { err_warnmessage("Connection to server failed."); goto cliwait; } if (DEBUG) msg_log("Thread %d: Connected to server with socket %p.",thr,sock); clu_addsocket(sock); /* WAIT FOR SERVER TO ALLOW OR REJECT CONNECTION */ if (DEBUG) { time(×tamp); thr_lock();//localtime call msg_log("Thread %d: Added socket at %s.",thr,asctime(localtime(×tamp))); thr_unlock(); } if ((message=net_receivemessage(sock,thr))!=NET_ACCEPT)//here it hangs, after server sent a NoJobs message, or server didn't receive connection { /* SERVER DIDN'T ACCEPT CONNECTION - WAIT A BIT AND TRY AGAIN */ if (DEBUG) msg_log("Thread %d: Server refused connection with message %d - no jobs waiting or too many clients.",thr,message); goto cliwait; } /* SERVER ACCEPTED CONNECTION */ /* SEND TIMESTAMPS */ if (DEBUG) msg_log("Thread %d: Sending current timestamps message.",thr); /* ACQUIRE LOCK, SO THAT clu_cpusfree BELONGS TO US */ thr_lock(); /* CREATE NET_CLIENTREADY PACKET WITH TIMESTAMP OF ALL UPDATEABLE FILES */ packet=net_allocpacket(NET_CLIENTREADY,(lst_len(clu_updatelist)+4)*sizeof(pckdata)+lst_strsize(clu_updatelist)-LST_STRSTART); packet[NET_PCKDATA]=clu_prioritymax; packet[NET_PCKDATA+1]=clu_cpusfree; packet[NET_PCKDATA+2]=thr; packet[NET_PCKDATA+3]=lst_len(clu_updatelist); for (updatepos=lst_startstr(clu_updatelist),i=0;ilastchecktime+CLU_ALIVECHECKTIME) { /* A LONG JOB, TIME TO SEND A "STILL ALIVE MESSAGE" TO SERVER */ lastchecktime=timestamp; message=clu_sendalivemessage(sock,str_atoi(job)); if (message==NET_KILLJOB) goto clikill; } } } else { /* DO NOT USE GRAPHICAL SCREEN SAVER */ do { result=0; /* WAIT A BIT */ SDL_Delay(1000); /* CHECK FOR SIGINT/SIGTERM */ if (sigintflag) result=1; /* CHECK FOR TERMINATION OF CHILD PROCESS */ if (clu_subprogready(thr)) result=2; /* GET CURRENT TIME */ time(×tamp); if (timestamp>lastchecktime+CLU_ALIVECHECKTIME) { /* A LONG JOB, TIME TO SEND A "STILL ALIVE MESSAGE" TO SERVER */ lastchecktime=timestamp; message=clu_sendalivemessage(sock,str_atoi(job)); if (message==NET_KILLJOB) { msg_log("Sent alive message, but server decided to kill us"); goto clikill; } } } while (!result); } /* WAIT FOR THE THREAD TO FINISH OFFICIALLY, THIS ALSO FREES THE RESOURCES */ SDL_WaitThread(clu_child[thr],NULL); /* CPUS ARE AVAILABLE AGAIN */ thr_lock(); clu_cpusfree+=cpus; if (DEBUG) msg_log("Thread %d: Result returned from screensaver or waiting:%d",thr,result); /* WHAT HAPPENED? */ if (result==1) { /* INTERRUPTED BY USER */ /* SEND MESSAGE TO SERVER */ if (DEBUG) msg_log("Thread %d: Sending NET_CLIENTSTOPPED message to server...",thr); packet=net_allocpacket(NET_CLIENTSTOPPED,sizeof(pckdata)); packet[NET_PCKDATA]=str_atoi(job); net_sendpacket(sock,packet); /* USER WANTS TO QUIT SCREEN SAVER */ /* KILL ALL PROCESSES IN THE GROUP */ clu_killchildproc(); err_exitmessage("Interrupted by user"); } /* CHILD PROCESS TERMINATED WITH POSSIBLE RESULTS */ if (DEBUG) msg_log("Thread %d: Child process terminated.",thr); /* RETURN PROCESS RESULTS TO SERVER */ /* MOVE TO LIST OF FILES THAT ARE TO BE RETURNED */ jobpos=&job[JOB_OPTIONS]; for (i=0;i<4;i++) jobpos=str_chr(jobpos,'|')+1; /* SEND ALL THE RESULT FILES TO SERVER */ if (DEBUG) msg_log("Thread %d: Sending result files to server...",thr); /* SEE IF ALL RESULT FILES ARE PRESENT */ allpresent=1; jobposbak=jobpos; do { jobpos2=jobpos; wildcard=0; /* MOVE TO END OF FILENAME(WILDCARD) */ while ((ch=*jobpos2)!='|'&&*jobpos2!=',') { if (ch=='*'||ch=='?') wildcard=1; jobpos2++; } if (!wildcard) { /* ADD TERMINATING ZERO TO CREATE A STRING */ *jobpos2=0; /* SEE IF RESULT FILE IS PRESENT */ dsc_joinpath(filename,jobdir,jobpos); if (!dsc_ispresent(filename)) { allpresent=0; if (DEBUG) msg_log("Thread %d: Result file not found: %s",thr,filename); } /* NEXT FILENAME */ *jobpos2=ch; } jobpos=jobpos2+1; } while (ch!='|'); /* CHECK IF ALL RESULT FILES WERE CREATED. COULD BE THAT THE PROGRAM CRASHED, OR THAT WINDOWS 2000 HAD SWAP FILE PROBLEMS AND JUST TERMINATED IT */ if (!allpresent) { thr_unlock(); packet=net_allocpacket(NET_CLIENTFAILED,sizeof(pckdata)); packet[NET_PCKDATA]=str_atoi(job); net_sendpacket(sock,packet); //Was this a bug workaround? SDL_Delay(3000); mem_free(job); waitcycles=4; goto cliwait; } jobpos=jobposbak; do { jobpos2=jobpos; /* MOVE TO END OF FILENAME(WILDCARD) */ while ((ch=*jobpos2)!='|'&&*jobpos2!=',') jobpos2++; /* ADD TERMINATING ZERO TO CREATE A STRING */ *jobpos2=0; /* SEND ALL FILES WITHIN workdir MATCHING THE WILDCARD */ net_senddir(sock,NET_JOBDATA,str_atoi(job),jobdir,jobpos); /* NEXT FILENAME */ *jobpos2=ch; jobpos=jobpos2+1; } while (ch!='|'); /* LAST NON-THREAD SAFE FUNCTION (net_senddir) COMPLETED */ thr_unlock(); /* SEND JOB FINISHED MESSAGE TO SERVER */ if (DEBUG) msg_log("Thread %d: Sending NET_CLIENTDONE message to server...",thr); packet=net_allocpacket(NET_CLIENTDONE,sizeof(pckdata)); packet[NET_PCKDATA]=str_atoi(job); net_sendpacket(sock,packet); mem_free(job); /* msg_log("Thread %d: Shutting down, Closing socket %p (%d)",thr,sock,((int*)sock)[1]); clu_delsocket(sock); thr_lock();//Bug hunting if (shutdown(((int*)sock)[1],2)) msg_log("Shutdown failed with %d",errno); SDLNet_TCP_Close(sock); thr_unlock(); sock=NULL; */ goto cliagain; } default: { /* UNKNOWN PACKET RECEIVED */ if (DEBUG) msg_log("Thread %d: An unknown packet type was received.",thr); mem_free(packet); } } } /* CLIENT KILLED BY SERVER, WE CAN CURRENTLY NOT RECOVER, SINCE clu_killchildproc ALSO KILLS THE PROGRAMS LAUNCHED BY OTHER THREADS */ msg_log("Reached clikill in an unexpected way"); clikill: thr_lock(); clu_killchildproc(); err_exitmessage("All jobs on this client killed by server."); /* CONNECTION PROBLEM OR NO JOBS AVAILABLE, WAIT A BIT */ cliwait: if (!clu_consoleflag&&!thr) { /* USE GRAPHICAL SCREEN SAVER */ for (i=0;i1) { str_print(err_message,"Number of instances found upon startup: %d",instances); err_exit(); } #endif } /* START NEW LOG */ dsc_remove(msg_logfilename); /* INITIALIZE SDL */ /* SDL_INIT_TIMER seems to be related to core dumps */ //if (SDL_InitSubSystem(SDL_INIT_NOPARACHUTE|SDL_INIT_TIMER|(SDL_INIT_VIDEO*(clu_consoleflag^1)))<0) if (SDL_InitSubSystem(SDL_INIT_NOPARACHUTE|(SDL_INIT_VIDEO*(clu_consoleflag^1)))<0) { str_print(err_message, "Couldn't initialize SDL: %s",SDL_GetError()); err_exit(); } /* THIS IS ONLY NEEDED BY SDL UNDER LINUX (TO CREATE CORE DUMPS) */ signal(SIGSEGV,SIG_DFL); #if LINUX /* THIS IS NEEDED FOR LINUX, BECAUSE ACCESS TO A BROKEN TCP CONNECTION GENERATES A SIGPIPE, WHICH WOULD KILL THE PROGRAM */ signal(SIGPIPE,SIG_IGN); #endif /* INIT NETWORK AND MULTI THREADING */ net_init(); thr_init(); /* CLEAN UP AT EXIT */ atexit(clu_quit); /* TEST SCREEN SAVER IF REQUESTED (-tst) */ if (scrsavtstflag) { scr_initialize(); #if WINDOWS /* MODIFY THE REGISTRY */ /* LET THE SCREENSAVER APPEAR QUICKLY */ reg_writekey(HKEY_USERS,".DEFAULT\\Control Panel\\Desktop","ScreenSaveActive","1"); reg_writekey(HKEY_USERS,".DEFAULT\\Control Panel\\Desktop","ScreenSaveTimeOut","60"); /* CHOOSE logon2.scr AS THE LOGON SCREENSAVER */ /* logon.scr CANNOT BE REPLACED ANYMORE SINCE WINDOWS XP USES FILE PROTECTION */ reg_writekey(HKEY_USERS,".DEFAULT\\Control Panel\\Desktop","SCRNSAVE.EXE","logon2.scr"); #endif while (1) { result=scr_saver(NULL,2|SCR_BUSY,0,NULL); if (result) break; } exit(0); } if (DEBUG) { /* BACKUP CURRENT LOGFILE */ dsc_remove("cluster.log.last"); dsc_rename("cluster.log.last","cluster.log"); msg_log("Starting up..."); msg_log("Initializing network..."); } /* LOAD CONFIGURATION FILE cluster.cnf */ if (!txt_loadfilealloc(&config,CLU_CONFIGFILE)) err_exitmessage("Couldn't load configuration file."); /* PARSE CONFIGURATION FILE */ if (DEBUG) msg_log("Parsing configuration file..."); confpos=config; clu_updatelist=lst_str(); clu_serverip.host=INADDR_NONE; port=1234; subnetmask=0xffffff00; programs=0; clu_progwinnames=0; mem_set(clu_runcommand,0,sizeof(char*)*CLU_PROGRAMSMAX); /* SET STANDARD PATH FOR ALL PROGRAMS */ for (i=0;i=0&&powermode<=5) { char powermodestr[4]; str_print(powermodestr,"%d",powermode); reg_writekey(HKEY_USERS,".DEFAULT\\Control Panel\\PowerCfg","CurrentPowerPolicy",powermodestr); } #endif /* INIT SCREEN SAVER */ if (DEBUG) msg_log("Initializing SDL and screensaver..."); if (!clu_consoleflag) scr_initialize(); /* INITIALIZE THE TCP SOCKET SET */ if (DEBUG) msg_log("Initializing TCP socket..."); net_socketset=SDLNet_AllocSocketSet(clu_cpus); if (net_socketset==NULL) { str_print(err_message, "Couldn't create socket set: %s",SDLNet_GetError()); err_exit(); } /* SPAWN ONE CLIENT PER CPU (CORE) */ if (DEBUG) msg_log("Spawning %d clients...",clu_cpus); for (i=0;ilastchecktime+5*60) { /* 5 MINUTES HAVE PASSED SINCE LAST CHECK FOR LOST JOBS */ msg_log("Checking for lost jobs, last check at %s",asctime(localtime(&lastchecktime))); lastchecktime=timestamp; /* LOAD LIST OF JOBS */ joblist=job_loadlist(CLU_JOBFILE); job=joblist; /* SKIP HEADER */ txt_setnextline(&job); /* LOOP THROUGH ALL JOBS IN LIST */ while (*job) { if (job[JOB_STATUS]=='A') { /* ACTIVE JOB FOUND, GET LAST CHECK OK TIME */ sscanf(&job[JOB_CHECK],"%8x",&i); if (i+CLU_ALIVECHECKTIME*2<(int)timestamp) { /* CLIENT HASN'T SENT AN ALIVE MESSAGE IN TIME, ASSUME JOB IS LOST AND SET WAITING AGAIN */ msg_log("No 'still alive message' for job %d received in time. Set waiting again.",str_atoi(job)); job[JOB_STATUS]='W'; /* DISCONNECT CLIENT */ job_getipadd(&clientip.host,job); for (i=0;ihost^host)&subnetmask)) { /* MAXIMUM NUMBER OF CLIENTS CONNECTED OR NO JOBS WAITING */ net_sendmessage(newsock,NET_CLOSE); SDLNet_TCP_Close(newsock); if (j==NET_CLIENTSMAX) msg_log("Connection refused - all clients present."); else if (!ip) msg_log("Connection refused - client IP could not be determined."); else if (jobs) msg_log("Connection refused - client IP does not match subnet mask."); } else { /* ADD NEW CLIENT TO LIST */ net_client[j].sock=newsock; net_client[j].peer=*SDLNet_TCP_GetPeerAddress(newsock); net_client[j].thr=NONE; clientname=(char*)SDLNet_ResolveIP(&net_client[j].peer); if (clientname==NULL) { /* DNS LOOKUP FAILED */ clientname="DNS server down"; } str_copyn(net_client[j].name,clientname,255); SDLNet_TCP_AddSocket(net_socketset,net_client[j].sock); /* SEND ACCEPT MESSAGE */ if (net_sendmessage(newsock,NET_ACCEPT)) { /* FAILED, REMOVE CLIENT */ err_warnmessage("Couldn't send NET_ACCEPT message."); msg_log("ERROR - Couldn't send NET_ACCEPT message."); net_removeclient(j); } else msg_log("New client %s connected on socket %d (IP=%x).",net_client[j].name,j,SDL_SwapBE32(net_client[j].peer.host)); } } } /* CHECK FOR TRANSMISSION FROM EXISTING CLIENTS (DELAY/FINISHED) */ for (i=0;itimestamp) { msg_log("Sending %s to client...",filename); if (net_sendfile(net_client[i].sock,NET_FILEUPDATE,0,filename)) { str_print(err_message,"WARNING! File %s could not be sent to client, cancelling job.",filename); err_warn(); net_sendmessage(net_client[i].sock,NET_NOJOB); break; } } } mem_free(packet); /* SKIP REST IF CLIENT IS SKIPPED */ if (j %c ? And Status %c =='W'? And CPUs %d >= %d?\n",str_atoi(job),job[JOB_PRIORITY]-'0',accprioritymax,job[JOB_PRIORITY],prioritymax,job[JOB_STATUS],cpus,str_atoi(job+JOB_CPUS)); if (job[JOB_PRIORITY]-'0'<=accprioritymax&&job[JOB_PRIORITY]>prioritymax&&job[JOB_STATUS]=='W'&& cpus>=str_atoi(job+JOB_CPUS)) { nextjob=job; prioritymax=job[JOB_PRIORITY]; } } /* DO WE HAVE A JOB FOR THE CLIENT? */ if (!nextjob) { /* SORRY, NO JOBS IN THE PIPELINE, DISPLAY JUST SCREEN SAVER */ msg_log("Currently no jobs available."); net_sendmessage(net_client[i].sock,NET_NOJOB); } else { /* A JOB SUITED FOR THE CLIENT WAS FOUND */ job=nextjob; /* GET JOB NUMBER */ jobnum=str_atoi(job); /* SET JOB STATUS TO ACTIVE */ job[JOB_STATUS]='A'; /* ADD CLIENT IP ADDRESS (str_print ADDS A ZERO, RESTORE ' ') */ net_sprintip(&job[JOB_IPADD],"",&net_client[i].peer); str_print(&job[JOB_IPADD+JOB_IPADDLEN],":%03d",net_client[i].thr); job[JOB_THREAD+JOB_THREADLEN]=' '; /* ADD CLIENT NAME */ mem_set(&job[JOB_NAME],' ',JOB_NAMELEN); mem_copy(&job[JOB_NAME],net_client[i].name, min(JOB_NAMELEN,str_len(net_client[i].name))); /* ADD TIME OF FIRST SUBMISSION */ time(×tamp); str_print(&job[JOB_TIME],"%s",asctime(localtime(×tamp))); if (job[JOB_TIME+JOB_TIMELEN]!='\n') err_exitmessage("Conversion of time to string failed. Job list corrupted."); job[JOB_TIME+JOB_TIMELEN]=' '; /* ADD THIS TIME ALSO AS TIME OF LAST CHECK */ str_print(&job[JOB_CHECK],"%8x",(int)timestamp); job[JOB_CHECK+JOB_CHECKLEN]=' '; /* SKIP PROGRAM OPTIONS AND MOVE TO WORKING DIRECTORY */ jobpos=&job[JOB_OPTIONS]; jobpos=str_chr(jobpos,'|')+1; /* EXTRACT WORKING DIRECTORY AND MOVE TO COPY SECTION */ j=0; while (*jobpos!='|'&&j