#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "life.h" #ifdef __GNUC__ #define inline __inline__ #endif #define CELLBATCH 1024 #define BELOW 25 #define MARGIN 4 enum { IMAGE, SHM, POINTS, QUIET, OTHER }; static Display *display; static int screen; static Window window; static int minx, miny, oldy; static unsigned width, height, border, depth; static int back, fore; static GC deadgc, livegc; static XShmSegmentInfo imageshm; static XImage *image; static size_t imagesize; static life_bmp *imagedata; static XPoint deadpoints[CELLBATCH], livepoints[CELLBATCH]; static int deadcount, livecount; static inline void life_draw_bmp_image(life_ctx *ctx, life_pos x, life_pos y, life_bmp old, life_bmp new) { /* we know y is within miny and maxy */ if(x < ctx->minx || x >= ctx->maxx) return; imagedata[(x - ctx->minx) / WORDSIZE + (y - ctx->miny) * width / WORDSIZE] = new; } static void flushpoints(void *ctx) { XDrawPoints(display, window, deadgc, deadpoints, deadcount, CoordModeOrigin); XDrawPoints(display, window, livegc, livepoints, livecount, CoordModeOrigin); deadcount = livecount = 0; } static inline void life_draw_bmp_points(life_ctx *ctx, life_pos x, life_pos y, life_bmp old, life_bmp new) { bool flush; int i; flush = false; if(old) { for(i = 0; i < WORDSIZE; i++) if(old & 1 << i) { deadpoints[deadcount].x = x - ctx->minx + i; deadpoints[deadcount].y = y - oldy; deadcount++; } if(deadcount + WORDSIZE > CELLBATCH) flush = true; } if(new) { for(i = 0; i < WORDSIZE; i++) if(new & 1 << i) { livepoints[livecount].x = x - ctx->minx + i; livepoints[livecount].y = y - ctx->miny; livecount++; } if(livecount + WORDSIZE > CELLBATCH) flush = true; } if(flush) flushpoints(ctx); } void life_draw_bmp(life_ctx *ctx, life_pos x, life_pos y, life_bmp old, life_bmp new) { switch(*(int*)ctx->aux) { case(QUIET): return; case(SHM): case(IMAGE): life_draw_bmp_image(ctx, x, y, old, new); return; case(POINTS): life_draw_bmp_points(ctx, x, y, old, new); return; } } void life_draw_flush(life_ctx *ctx) { switch(*(int*)ctx->aux) { case(QUIET): return; case(SHM): XShmPutImage(display, window, livegc, image, 0, 0, minx, miny, image->width, image->height, True); XSync(display, False); return; case(IMAGE): XPutImage(display, window, livegc, image, 0, 0, minx, miny, image->width, image->height); return; case(POINTS): flushpoints(ctx); XFlush(display); return; } } void life_draw_clear(life_ctx *ctx) { XSetWindowBackground(display, window, back); XClearWindow(display, window); } /* * Ensure that life_draw_bmp() is inlined. */ #define life_draw_bmp(a,b,c,d,e) life_draw_bmp_image(a,b,c,d,e) #define life(a,b,c) life_image(a,b,c) #define life_info life_info_image #include "life.c" #undef life_draw_bmp #undef life_info #undef life #define life_draw_bmp(a,b,c,d,e) life_draw_bmp_points(a,b,c,d,e) #define life(a,b,c) life_points(a,b,c) #define life_info life_info_points #include "life.c" #undef life_draw_bmp #undef life_info #undef life #define life_draw_bmp(a,b,c,d,e) #define life(a,b,c) life_quiet(a,b,c) #define life_info life_info_quiet #include "life.c" #undef life_draw_bmp #undef life_info #undef life life_cells * life(life_cells *this, life_cells *new, void *ctx) { switch(*(int*)((life_ctx*)ctx)->aux % OTHER) { case(QUIET): return(life_quiet(this,new,ctx)); case(SHM): case(IMAGE): return(life_image(this,new,ctx)); case(POINTS): return(life_points(this,new,ctx)); } } /* for linkage */ void life_draw_dead(life_ctx *ctx, life_pos x, life_pos y) { } void life_draw_live(life_ctx *ctx, life_pos x, life_pos y) { } static void usage(void) { fprintf(stderr, "usage: lifesmoke [-bpq] [-n numgen] [-s slowness] [-w width]\n" " -b move puffer off the bottom of the screen\n" " -n number of generations to compute\n" " -p pause before starting\n" " -q no display\n" " -s slowness (ms)\n" " -w width of the line puffer\n"); exit(1); } int main(int argc, char *argv[]) { life_ctx ctx; XGCValues xgcv; Window root; int opt, mode, other; unsigned numgen, puffersize; bool below, debug, pause, slow; char buf[8]; struct timespec tv; signal(SIGPIPE, SIG_IGN); mode = SHM; other = 1; below = debug = pause = slow = false; numgen = 0; puffersize = 0; window = 0; while((opt = getopt(argc, argv, "bdn:o:pPqSs:W:w:")) != -1) switch (opt) { case('b'): below = true; break; case('d'): pause = debug = true; break; case('n'): numgen = atoi(optarg); break; case('o'): other = atoi(optarg); break; case('P'): mode = POINTS; break; case('p'): pause = true; break; case('q'): mode = QUIET; break; case('S'): mode = IMAGE; break; case('s'): slow = true; tv.tv_sec = 0; tv.tv_nsec = atoi(optarg) * 1000000; break; case('W'): window = atoi(optarg); break; case('w'): puffersize = atoi(optarg); break; default: usage(); } argc -= optind; argv += optind; if(argc != 0) usage(); display = XOpenDisplay(NULL); if(display == NULL) err(1, "Couldn't open display"); screen = DefaultScreen(display); if (window == 0) window = RootWindow(display, screen); back = BlackPixel(display, screen); fore = WhitePixel(display, screen); xgcv.background = back; xgcv.foreground = back; deadgc = XCreateGC(display, window, GCForeground | GCBackground, &xgcv); xgcv.background = back; xgcv.foreground = fore; livegc = XCreateGC(display, window, GCForeground | GCBackground, &xgcv); XGetGeometry(display, window, &root, &minx, &miny, &width, &height, &border, &depth); width = width / WORDSIZE * WORDSIZE; if(below) height += BELOW; if(puffersize == 0) puffersize = (width - 64) / 96; /* XXX: endianness */ imagesize = sizeof(life_bmp) * width * height / WORDSIZE; if(mode == SHM && XShmQueryExtension(display)) { imageshm.shmid = shmget(IPC_PRIVATE, imagesize, IPC_CREAT|0777); if(imageshm.shmid == -1) err(1, "shmget"); imageshm.shmaddr = shmat(imageshm.shmid, NULL, 0); if(imageshm.shmaddr == (void*)-1) err(1, "shmat"); imageshm.readOnly=True; imagedata = (void*)imageshm.shmaddr; memset(imagedata, 0, imagesize); image = XShmCreateImage(display, NULL, 1, XYBitmap, (void*)imagedata, &imageshm, width, height); if(image == NULL) errx(1, "XShmCreateImage failed"); XShmAttach(display, &imageshm); /* Delete the segment as soon as the server has attached, which is before XSync returns. The segment remains while there are still attached processes, so it will go away automatically after we exit and the server detaches. */ XSync(display, False); shmctl(imageshm.shmid, IPC_RMID, NULL); warnx("SHM segment %i", imageshm.shmid); } else if(mode == SHM || mode == IMAGE) { mode = IMAGE; imageshm.shmid = -1; imagedata = malloc(imagesize); if(imagedata == NULL) err(1, "malloc"); memset(imagedata, 0, imagesize); image = XCreateImage(display, NULL, 1, XYBitmap, 0, (void *)imagedata, width, height, 8, 0); if(image == NULL) errx(1, "XCreateImage failed"); } life_init(&ctx, &mode, CENTRE_X - minx - width / 2, CENTRE_Y - miny - height / 2, CENTRE_X - minx + width / 2, CENTRE_Y - miny + height / 2); ctx.cells = ctx.new; ctx.new = ctx.end = life_form_linepuffer(ctx.cells, 0, CENTRE_Y + height * 2, puffersize); ctx.maxy = life_maxy(&ctx) + MARGIN; ctx.miny = oldy = ctx.maxy - height; life_draw(&ctx); if(debug) life_debug(&ctx); if(pause) fgets(buf, sizeof(buf), stdin); while(numgen == 0 || numgen > ctx.generation) { life_cells *p; if(slow) nanosleep(&tv, NULL); if(ctx.generation % other) { mode += OTHER; life_step(&ctx); mode -= OTHER; } else life_step(&ctx); oldy = ctx.miny; ctx.maxy = life_maxy(&ctx) + MARGIN; ctx.miny = ctx.maxy - height; p = ctx.cells; while(p->pos > 0 || p->pos < ctx.miny + MARGIN) p++; ctx.cells = p; } printf("generation %lu workdone %lu\n", ctx.generation, ctx.workdone); XCloseDisplay(display); return(0); }