new render_threads module, make node_main use it, compiles, not yet tested
authorTero Marttila <terom@fixme.fi>
Thu, 26 Jun 2008 01:32:56 +0300
changeset 23 31307efd7e78
parent 22 4627760fc0d1
child 24 8307d28329ae
new render_threads module, make node_main use it, compiles, not yet tested

committer: Tero Marttila <terom@fixme.fi>
Makefile
common.c
common.h
node_main.c
remote_node.c
render.c
render.h
render_local.c
render_net.h
render_remote.c
render_remote.h
render_slices.c
render_thread.c
render_thread.h
render_thread_struct.h
render_threads.c
--- a/Makefile	Tue Jun 17 19:08:05 2008 +0300
+++ b/Makefile	Thu Jun 26 01:32:56 2008 +0300
@@ -6,25 +6,26 @@
 all: web_main file_main node_main
 
 common.o: common.c common.h
-http.o: http.c http.h common.o
-remote_node.o: remote_node.c remote_node.h common.o
-remote_pool.o: remote_pool.c remote_pool.h common.o
-render.o: render.c render.h common.o
-render_remote.o: render_remote.c render_remote.h common.o
-render_png.o: render_png.c render_png.h common.o
-render_raw.o: render_raw.c render_raw.h common.o
-render_local.o: render_local.c render_local.h common.o
-render_multi.o: render_multi.c render_multi.h render_remote.o common.o
-render_mandelbrot.o: render_mandelbrot.c render_mandelbrot.h common.o
-render_slices.o: render_slices.c render_slices.h common.o
-render_threads.o: render_threads.c render_threads.h common.o
+http.o: http.c http.h common.h
+remote_node.o: remote_node.c remote_node.h common.h
+remote_pool.o: remote_pool.c remote_pool.h common.h
+render.o: render.c render.h common.h
+render_remote.o: render_remote.c render_remote.h common.h
+render_png.o: render_png.c render_png.h common.h
+render_raw.o: render_raw.c render_raw.h common.h
+render_local.o: render_local.c render_local.h common.h render_png.h render_raw.h render_mandelbrot.h
+render_multi.o: render_multi.c render_multi.h render_remote.o common.h
+render_mandelbrot.o: render_mandelbrot.c render_mandelbrot.h common.h
+render_slices.o: render_slices.c render_slices.h common.h
+render_threads.o: render_threads.c render_threads.h common.h
+render_thread.o: render_thread.c render_thread.h common.h render_local.h
 
 file_main.o: file_main.c
-node_main.o: node_main.c
+node_main.o: node_main.c render_net.h
 web_main.o: web_main.c
 
 file_main: file_main.o common.o render.o render_raw.o render_png.o render_local.o render_mandelbrot.o
-node_main: node_main.o common.o render.o render_raw.o render_png.o render_local.o render_slices.o render_threads.o render_mandelbrot.o
+node_main: node_main.o common.o render.o render_thread.o render_local.o render_png.o render_raw.o render_mandelbrot.o
 web_main: web_main.o common.o http.o render.o render_png.o remote_node.o remote_pool.o render_remote.o render_multi.o render_slices.o
 
 clean :
--- a/common.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/common.c	Thu Jun 26 01:32:56 2008 +0300
@@ -7,31 +7,33 @@
 
 #include "common.h"
 
-static void _generic_err_vargs (const char *func, int perr, const char *fmt, va_list va) {
+static void _generic_err_vargs (int use_stderr, const char *func, int perr, const char *fmt, va_list va) {
+    FILE *stream = use_stderr ? stderr : stdin;
+
     if (func)
-        fprintf(stderr, "%s: ", func);
+        fprintf(stream, "%s: ", func);
     
-    vfprintf(stderr, fmt, va);
+    vfprintf(stream, fmt, va);
     
     if (perr)
-        fprintf(stderr, ": %s\n", strerror(errno));
+        fprintf(stream, ": %s\n", strerror(errno));
 
-    fprintf(stderr, "\n");
+    fprintf(stream, "\n");
 }
 
-void _generic_err (const char *func, int perr, const char *fmt, ...) {
+void _generic_err (int use_stderr, const char *func, int perr, const char *fmt, ...) {
     va_list va;
 
     va_start(va, fmt);
-    _generic_err_vargs(func, perr, fmt, va);
+    _generic_err_vargs(use_stderr, func, perr, fmt, va);
     va_end(va);
 }
 
-void _generic_err_exit (const char *func, int perr, const char *fmt, ...) {
+void _generic_err_exit (int use_stderr, const char *func, int perr, const char *fmt, ...) {
     va_list va;
 
     va_start(va, fmt);
-    _generic_err_vargs(func, perr, fmt, va);
+    _generic_err_vargs(use_stderr, func, perr, fmt, va);
     va_end(va);
       
     exit(EXIT_FAILURE);
--- a/common.h	Tue Jun 17 19:08:05 2008 +0300
+++ b/common.h	Thu Jun 26 01:32:56 2008 +0300
@@ -3,15 +3,14 @@
  * error handling
  */
 
-void _generic_err ( /*int level, */ const char *func, int perr, const char *fmt, ...)
-        __attribute__ ((format (printf, 3, 4)));
+void _generic_err ( /*int level, */ int use_stderr, const char *func, int perr, const char *fmt, ...)
+        __attribute__ ((format (printf, 4, 5)));
 
 // needs to be defined as its own function for the noreturn attribute
-void _generic_err_exit ( /* int level, */ const char *func, int perr, const char *fmt, ...)
-        __attribute__ ((format (printf, 3, 4)))
+void _generic_err_exit ( /* int level, */ int used_stderr, const char *func, int perr, const char *fmt, ...)
+        __attribute__ ((format (printf, 4, 5)))
         __attribute__ ((noreturn));
 
-/*
 enum _debug_level {
     DEBUG_FATAL,
     DEBUG_ERROR,
@@ -20,18 +19,19 @@
     DEBUG_DEBUG,
 };
 
+// not currently used
 extern enum _debug_level _cur_debug_level;
-*/
 
 // various kinds of ways to handle an error, 2**3 of them, *g*
-#define error(...)                  _generic_err(       NULL, 0, __VA_ARGS__)
-#define err_exit(...)               _generic_err_exit(  NULL, 0, __VA_ARGS__)
-#define perr(...)                   _generic_err(       NULL, 1, __VA_ARGS__)
-#define perr_exit(...)              _generic_err_exit(  NULL, 1, __VA_ARGS__)
-#define err_func(func, ...)         _generic_err(       func, 0, __VA_ARGS__)
-#define err_func_exit(func, ...)    _generic_err_exit(  func, 0, __VA_ARGS__)
-#define perr_func(func, ...)        _generic_err(       func, 1, __VA_ARGS__)
-#define perr_func_exit(func, ...)   _generic_err_exit(  func, 1, __VA_ARGS__)
+#define info(...)                   _generic_err(       0,  NULL,   0,  __VA_ARGS__ )
+#define error(...)                  _generic_err(       1,  NULL,   0,  __VA_ARGS__ )
+#define err_exit(...)               _generic_err_exit(  1,  NULL,   0,  __VA_ARGS__ )
+#define perr(...)                   _generic_err(       1,  NULL,   1,  __VA_ARGS__ )
+#define perr_exit(...)              _generic_err_exit(  1,  NULL,   1,  __VA_ARGS__ )
+#define err_func(func, ...)         _generic_err(       1,  func,   0,  __VA_ARGS__ )
+#define err_func_exit(func, ...)    _generic_err_exit(  1,  func,   0,  __VA_ARGS__ )
+#define perr_func(func, ...)        _generic_err(       1,  func,   1,  __VA_ARGS__ )
+#define perr_func_exit(func, ...)   _generic_err_exit(  1,  func,   1,  __VA_ARGS__ )
 
 // error(func + colon + msg, ...) + goto error
 #define ERROR(...) do { err_func(__func__, __VA_ARGS__); goto error; } while (0)
@@ -47,6 +47,17 @@
 #define DEBUG(...) if (0) { }
 #endif
 
+// default is to enable INFO
+#ifndef INFO_ENABLED
+#define INFO_ENABLED 1
+#endif
+
+#if INFO_ENABLED
+#define INFO(...) info(__VA_ARGS__)
+#else
+#define INFO(...) if (0) { }
+#endif
+
 /*
  * Parse a host:port string.
  *
--- a/node_main.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/node_main.c	Thu Jun 26 01:32:56 2008 +0300
@@ -7,16 +7,19 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <assert.h>
+
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/event_compat.h>
+#include <event2/bufferevent.h>
+
 #include "common.h"
 #include "render.h"
-#include "render_remote.h"  // for RENDER_PORT_NAME
-#include "render_local.h"
-#include "render_threads.h"
-
-void sigpipe_handler (int signal) {
-    /* ignore */
-    fprintf(stderr, "SIGPIPE\n");
-}
+#include "render_struct.h"
+#include "render_net.h"
+#include "render_thread.h"
+#include "render_thread_struct.h"
 
 void sigpipe_ignore () {
     struct sigaction sigpipe_action;
@@ -28,123 +31,211 @@
         perr_exit("sigaction");
 }
 
-int my_fread(FILE *fh, void *ptr, size_t size) {
-    int ret = fread(ptr, size, 1, fh);
+/*
+ * State needed to handle a client
+ */
+struct client_info {
+    // the client socket
+    evutil_socket_t socket;
     
-    if (ret == 0) {
-        error("EOF");
-        return 0;
+    // the read-a-command buffer
+    struct bufferevent *bufev;
 
-    } else if (ret != 1) {
-        perror("fread");
-        return 0;
+    // the write-a-mandelbrot stream
+    FILE *out_stream;
+
+    // the render_thread op
+    // thread_info.is_active is useful
+    struct render_thread thread_info;
+};
+
+static void client_free (struct client_info *ctx) {
+    // free the read-a-command buffer
+    if (ctx->bufev)
+        bufferevent_free(ctx->bufev);
+    
+    // cancel the render thread if needed
+    if (ctx->thread_info.is_active)
+        render_thread_cancel(&ctx->thread_info);
+    
+    // deinit it in any case
+    render_thread_deinit(&ctx->thread_info);
+
+    // close the write-a-mandelbrot stream, or just the socket
+    if (ctx->out_stream) {
+        if (fclose(ctx->out_stream))
+            PWARNING("fclose");
+
+    } else if (ctx->socket != -1) {
+        if (close(ctx->socket))
+            PWARNING("close");
+
     }
 
-    return 1;
-}
-
-int read_byte (FILE *fh, u_int8_t *byte) {
-    return my_fread(fh, byte, sizeof(*byte));
-}
-
-int read_int (FILE *fh, u_int32_t *i) {
-    if (!my_fread(fh, i, sizeof(*i)))
-        return 0;
-
-    *i = ntohl(*i);
-
-    return 1;
-}
-
-int read_double (FILE *fh, double *dbl) {
-    if (!my_fread(fh, dbl, sizeof(*dbl)))
-        return 0;
-
-    return 1;
+    // free the client info
+    free(ctx);
 }
 
-void handle_client (int sock) {
-    double duration;
-    struct render *ctx = NULL;
-    FILE *fh;
-    u_int8_t mode;
-    u_int32_t img_w, img_h;
-    double x1, y1, x2, y2;
+static void handle_render_done (struct render_thread *thread_info, void *arg) {
+    struct client_info *ctx = arg;
 
-    struct render_threads *threads_info = NULL;
-    
-    // open it as a FILE*
-    if (!(fh = fdopen(sock, "r+")))
+    // just free it, it takes care of closing it as well
+    client_free(ctx);
+}
+
+static int handle_render_cmd (struct client_info *ctx, struct render_cmd *cmd) {
+    // the render ctx...
+    struct render render_info;
+
+    // open it as a normal FILE*
+    if (!(ctx->out_stream = fdopen(ctx->socket, "w")))
         ERROR("fdopen");
-    
-    // read the parameters
+
+#if INFO_ENABLED
+    INFO("client [%p]: render [%ux%u] (%f, %f) -> (%f, %f)", ctx, cmd->img_w, cmd->img_h, cmd->x1, cmd->y1, cmd->x2, cmd->y2);
+#endif    
+
+    // set up the render_info
     if (
-            !read_byte(fh, &mode)
-         || !read_int(fh, &img_w)
-         || !read_int(fh, &img_h)
-         || !read_double(fh, &x1)
-         || !read_double(fh, &y1)
-         || !read_double(fh, &x2)
-         || !read_double(fh, &y2)
+            render_init(&render_info)
+         || render_set_mode(&render_info, cmd->mode)
+         || render_set_size(&render_info, cmd->img_w, cmd->img_h)
+         || render_region_raw(&render_info, cmd->x1, cmd->y1, cmd->x2, cmd->y2)
+         || render_io_stream(&render_info, ctx->out_stream)
     )
-        ERROR("read_{byte,int,double}");
-
-    printf("RENDER: [%ux%u] (%f, %f) -> (%f, %f): ... ", img_w, img_h, x1, y1, x2, y2);
-    fflush(stdout);
-
-    // set up the render_ctx
-    if (!(ctx = render_alloc()))
-        ERROR("render_alloc");
-
-    if (render_set_mode(ctx, mode))
-        ERROR("render_set_mode");
+        ERROR("render_*");
+    
+    // start the render thread
+    if (render_thread_init(&ctx->thread_info, &render_info, &handle_render_done, ctx))
+        ERROR("render_thread_init");
 
-    if (render_set_size(ctx, img_w, img_h))
-        ERROR("render_set_size");
-
-    if (render_region_raw(ctx, x1, y1, x2, y2))
-        ERROR("render_region_raw");
+    // ok, wait for it to complete
+    return 0;
 
-    if (render_io_stream(ctx, fh))
-        ERROR("render_io_stream");
+error:
+    // FAAAIL
+    return -1;
+}
+
+static void handle_read (struct bufferevent *bev, void *arg) {
+    struct client_info *ctx = arg;
+    struct render_cmd cmd;
+
+    // meh, just read it in
+    size_t len;
     
+    // we set a watermark, so this should hold true
+    assert(len = bufferevent_read(bev, &cmd, sizeof(cmd)) == sizeof(cmd));
 
-    // render threaded \o/
-    if (!(threads_info = render_threads_alloc(ctx)))
+    // fix the byte order
+    cmd.img_w = ntohl(cmd.img_w);
+    cmd.img_h = ntohl(cmd.img_h);
+
+    // handle it
+    if (handle_render_cmd(ctx, &cmd))
         goto error;
     
-    if (render_threads_wait(threads_info))
-        goto error;
+    // ok
+    return;
+
+error:
+    client_free(ctx);
+}
+
+static void handle_error (struct bufferevent *bev, short what, void *arg) {
+    struct client_info *ctx = arg;
     
-    printf("done!\n");
-/*
-    // render!
-    if (render_local(ctx, &duration))
-        ERROR("render_local");
+    // read-EOF
+    if ((what & (EVBUFFER_READ | EVBUFFER_EOF)) && ctx->thread_info.is_active) {
+        // this is fine, expected, and doesn't matter
+        return;
+    }
+    
+    PWARNING("eventbuffer: %s %s",
+        (what & EVBUFFER_READ) ? "read" : ((what & EVBUFFER_WRITE) ? "write" : "???"),
+        (what & EVBUFFER_EOF) ? "eof" : ((what & EVBUFFER_ERROR) ? "error" : ((what & EVBUFFER_TIMEOUT) ? "timeout" : "???"))
+    );
 
-    printf("time=%fs\n", duration);
-*/
+    client_free(ctx);
+}
+
+static void handle_accept (evutil_socket_t fd, short event, void *arg) {
+    struct client_info *ctx = NULL;
+
+    evutil_socket_t socket = -1;
+    struct sockaddr_storage addr;
+    socklen_t addr_len;
     
-    // fall through to just clean up normally
+    // arg is NULL and unused
+    (void) arg;
+    
+    // accept the connection
+    addr_len = sizeof(struct sockaddr_storage);
+
+    if ((socket = accept(fd, (struct sockaddr *) &addr, &addr_len)) == -1)
+        PERROR("accept");
+    
+#if INFO_ENABLED
+    assert(INET_ADDRSTRLEN < INET6_ADDRSTRLEN);
+
+    char addr_buf[INET6_ADDRSTRLEN];
+    const char *addr_str;
+    short nport;
+
+    if (addr.ss_family == AF_UNIX)
+        addr_str = "local";
+    else if (addr.ss_family == AF_INET || addr.ss_family == AF_INET6) {
+        const void *src;
+        
+        if (addr.ss_family == AF_INET) {
+            src = &(((struct sockaddr_in *) &addr)->sin_addr);
+            nport = ((struct sockaddr_in *) &addr)->sin_port;
+        } else {
+            src = &(((struct sockaddr_in6 *) &addr)->sin6_addr);
+            nport = ((struct sockaddr_in6 *) &addr)->sin6_port;
+        }
+
+        if (!(inet_ntop(addr.ss_family, src, addr_buf, sizeof(addr_buf))))
+            PERROR("inet_ntop");
+
+        addr_str = addr_buf;
+    }
+
+    INFO("ACCEPT: %s:%hu\n", addr_str, ntohs(nport));
+
+#endif
+
+    // alloc a new client_info
+    if (!(ctx = calloc(1, sizeof(*ctx))))
+        ERROR("calloc");
+    
+    // store the socket
+    ctx->socket = socket;
+
+    // then a bufferevent so that we can read in the command
+    if (!(ctx->bufev = bufferevent_new(ctx->socket, &handle_read, NULL, &handle_error, ctx)))
+        ERROR("bufferevent_new");
+    
+    // and enable it for read only
+    if (bufferevent_enable(ctx->bufev, EV_READ))
+        ERROR("bufferevent_enable");
+    
+    // set the watermark for receiving the render_cmd
+    bufferevent_setwatermark(ctx->bufev, EV_READ, sizeof(struct render_cmd), 0);
+    
+    // now we just wait for the cmd...
 
 error:
     if (ctx)
-        render_free(ctx);
-
-    if (threads_info)
-        render_threads_free(threads_info);
-
-    // close the FILE* and socket
-    fclose(fh);
-    
-    return;
+        client_free(ctx);
+    else if (socket != -1)
+        close(socket);
 }
 
-
 int main (int argc, char** argv) {
-    int ssock, sock;
+    struct event_base *ev_base;
+    int ssock;
     struct sockaddr_in addr;
-    socklen_t addr_len;
     
     // parse arguments
     int opt;
@@ -171,6 +262,10 @@
 
     if (!(port = atoi(port_name)))
         ERROR("invalid port: %s", port_name);
+    
+    // init libevent
+    if (!(ev_base = event_init()))
+        FATAL("event_init");
 
     // create the socket
     if ((ssock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
@@ -186,26 +281,32 @@
     if (listen(ssock, 1) == -1)
         PERROR("listen");
     
+    // create the listen event
+    struct event listen_ev;
+
+    event_set(&listen_ev, ssock, EV_READ, &handle_accept, NULL);
+
+    if (event_add(&listen_ev, NULL))
+        PERROR("event_add");
+    
     // ignore sigpipe
     sigpipe_ignore();
     
-    // main accept loop
+    // run the libevent mainloop
     printf("RUN: %s:%hu\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
-
-    while (1) {
-        addr_len = sizeof(struct sockaddr_in);
+ 
+    if (event_base_dispatch(ev_base))
+        WARNING("event_dispatch");
 
-        // accept a new client
-        if ((sock = accept(ssock, (struct sockaddr *) &addr, &addr_len)) == -1)
-            PERROR("accept");
-        
-        printf("ACCEPT: %s:%hu\n", inet_ntoa(addr.sin_addr), addr.sin_port);
-        
-        // handle their resquest
-        handle_client(sock);
-    }
+    printf("SHUTDOWN\n");
+    
+    event_base_free(ev_base);
+
+    // succesful exit
+    return EXIT_SUCCESS;
 
 error:
-    return 1;
+    // failure
+    return EXIT_FAILURE;
 }
 
--- a/remote_node.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/remote_node.c	Thu Jun 26 01:32:56 2008 +0300
@@ -5,7 +5,7 @@
 #include <assert.h>
 
 #include "common.h"
-#include "render_remote.h"
+#include "render_net.h"
 #include "remote_node.h"
 
 int remote_node_init (struct remote_node *node_info, const char *hostname, const char *portname) {
--- a/render.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/render.c	Thu Jun 26 01:32:56 2008 +0300
@@ -4,10 +4,10 @@
 #include "render_struct.h"
 #include "render.h"
 
-int render_init (struct render *ctx, int mode) {
+int render_init (struct render *ctx) {
     memset(ctx, 0, sizeof(*ctx));
-    
-    return render_set_mode(ctx, mode);
+
+    return 0;
 }
 
 struct render *render_alloc () {
--- a/render.h	Tue Jun 17 19:08:05 2008 +0300
+++ b/render.h	Thu Jun 26 01:32:56 2008 +0300
@@ -36,7 +36,7 @@
 /*
  * Clear out the value of the given render context
  */
-int render_init (struct render *ctx, int mode);
+int render_init (struct render *ctx);
 
 /*
  * What kind of image to render, PNG or RAW?
--- a/render_local.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/render_local.c	Thu Jun 26 01:32:56 2008 +0300
@@ -15,15 +15,12 @@
     unsigned char *rowbuf = NULL;
     struct render_png png_ctx;
     struct render_raw raw_ctx;
+    int must_deinit = 0;
     clock_t t1, t2;
     
     if (duration)
         *duration = -1;
 
-    // ugh
-    memset(&png_ctx, 0, sizeof(png_ctx));
-    memset(&raw_ctx, 0, sizeof(raw_ctx));
-
     // alloc the memory buffer
     if (!(rowbuf = malloc(render->img_w)))
         ERROR("malloc");
@@ -32,9 +29,13 @@
     switch (render->mode) {
         case RENDER_PNG :
             // the render_png stuff
+            memset(&png_ctx, 0, sizeof(png_ctx));
+
             if (render_png_init(&png_ctx, render))
                 ERROR("render_png_init");
-                
+            
+            must_deinit = RENDER_PNG;
+
             //  set render_* to use the render_png
             if (render_local_mem(render, &rowbuf, (int(*)(void *arg, unsigned char *)) &render_png_row, &png_ctx))
                 ERROR("render_local_mem");
@@ -43,8 +44,12 @@
 
         case RENDER_RAW :
             // the render_raw stuff
+            memset(&raw_ctx, 0, sizeof(raw_ctx));
+
             if (render_raw_init(&raw_ctx, render))
                 ERROR("render_raw_init");
+            
+            must_deinit = RENDER_RAW;
 
             //  set render_* to use the render_raw
             if (render_local_mem(render, &rowbuf, (int(*)(void *arg, unsigned char *)) &render_raw_row, &raw_ctx))
@@ -73,9 +78,12 @@
 
 error:
     free(rowbuf);
+    
+    if (must_deinit == RENDER_PNG)
+        render_png_deinit(&png_ctx);
 
-    render_png_deinit(&png_ctx);
-    render_raw_deinit(&raw_ctx);
+    else if (must_deinit == RENDER_RAW)
+        render_raw_deinit(&raw_ctx);
 
     return -1;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_net.h	Thu Jun 26 01:32:56 2008 +0300
@@ -0,0 +1,21 @@
+#include <sys/types.h>
+
+#define RENDER_PORT_NAME "6159"
+
+#pragma pack(push)
+#pragma pack(1)
+
+struct render_cmd {
+    u_int8_t    mode;
+
+    u_int32_t   img_w;
+    u_int32_t   img_h;
+
+    double      x1;
+    double      y1;
+    double      x2;
+    double      y2;
+};
+
+#pragma pack(pop)
+
--- a/render_remote.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/render_remote.c	Thu Jun 26 01:32:56 2008 +0300
@@ -9,8 +9,9 @@
 #include <event2/event_struct.h>
 #include <event2/bufferevent.h>
 
-#include "render_struct.h"    // for render_cmd_build
+#include "render_struct.h"
 #include "render_remote.h"
+#include "render_net.h"
 #include "common.h"
 
 struct render_remote {
@@ -21,22 +22,8 @@
     struct event ev_connect, ev_data;
     struct bufferevent *bev_data;
 
-    #pragma pack(push)
-    #pragma pack(1)
-
-    struct {
-        u_int8_t    mode;
-
-        u_int32_t   img_w;
-        u_int32_t   img_h;
-
-        double      x1;
-        double      y1;
-        double      x2;
-        double      y2;
-    } render_cmd;
-
-    #pragma pack(pop)
+    // the command
+    struct render_cmd render_cmd;
     
     // have we sent the command yet?
     int cmd_sent;
--- a/render_remote.h	Tue Jun 17 19:08:05 2008 +0300
+++ b/render_remote.h	Thu Jun 26 01:32:56 2008 +0300
@@ -11,9 +11,6 @@
  * Execute a render operation on a remote render_node
  */
 
-
-#define RENDER_PORT_NAME "6159"
-
 struct render_remote;
 
 /*
--- a/render_slices.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/render_slices.c	Thu Jun 26 01:32:56 2008 +0300
@@ -45,8 +45,11 @@
     
     // should probably use memcpy instead...
     if (
-            render_init         (&ctx->slices[0].render_info,   RENDER_RAW)
-         || render_init         (&ctx->slices[1].render_info,   RENDER_RAW)
+            render_init         (&ctx->slices[0].render_info)
+         || render_init         (&ctx->slices[1].render_info)
+
+         || render_set_mode     (&ctx->slices[0].render_info,   RENDER_RAW)
+         || render_set_mode     (&ctx->slices[1].render_info,   RENDER_RAW)
 
          || render_set_size     (&ctx->slices[0].render_info,   render->img_w,  render->img_h)
          || render_set_size     (&ctx->slices[1].render_info,   render->img_w,  render->img_h)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_thread.c	Thu Jun 26 01:32:56 2008 +0300
@@ -0,0 +1,200 @@
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+#include <event2/event.h>
+
+#include "common.h"
+#include "render_thread.h"
+#include "render_thread_struct.h"
+#include "render_local.h"
+
+static void *_render_thread_func (void *arg) {
+    struct render_thread *ctx = arg;
+    
+    // measure how long it takes
+    double duration;
+    
+    // render it...
+    if (render_local(&ctx->render_info, &duration))
+        goto error;
+
+#ifdef DEBUG_INFO
+    u_int32_t img_w, img_h;
+
+    render_get_size(&ctx->render_info, &img_w, &img_h);
+
+    // report the duration
+    INFO("rendered [%ux%u] in %f seconds", img_w, img_h, duration);
+#endif
+
+    // notify completion, writeall()
+    ssize_t ret;
+    char *buf = (void *) &ctx;
+    size_t len = sizeof(ctx);
+
+    do {
+        ret = write(ctx->notify_fd, buf, len);
+
+        if (ret > 0) {
+            buf += ret;
+            len -= ret;
+        }
+    } while ((ret == -1 && errno == EINTR) || len > 0);
+    
+    if (ret == -1)
+        PERROR("write");
+    
+    // done...
+    return NULL;
+
+error:
+    return ctx;
+}
+
+static void _render_thread_done (evutil_socket_t fd, short what, void *arg) {
+    struct render_thread *ctx = arg;
+    void *thread_return;
+
+    // make a lazy effort to read the contents of the pipe
+    struct render_thread *ctx2;
+    char *buf = (void *) &ctx2;
+    size_t len = sizeof(ctx);
+
+    ssize_t ret;
+    
+    if ((ret = read(fd, buf, len)) == -1)
+        PWARNING("read");
+
+    else if (ret != len)
+        WARNING("short read");
+
+    else if (ctx2 != ctx)
+        FATAL("wrong ctx: %p <> %p", ctx, ctx2);
+    
+    // close the pipe
+    if (close(fd))
+        PWARNING("close(pipe-read)");
+
+    if (close(ctx->notify_fd))
+        PWARNING("close(pipe-write)");
+
+    // join the thread and check the return value
+    if (pthread_join(ctx->thread_id, &thread_return))
+        PWARNING("pthread_join");
+    else if (thread_return == PTHREAD_CANCELED)
+        PWARNING("PTHREAD_CANCELED");
+    else if (thread_return != NULL)
+        PWARNING("thread_return != NULL");
+
+    // mark it as done
+    ctx->is_active = 0;
+
+    // call our callback
+    ctx->cb_func(ctx, ctx->cb_arg);
+}
+
+int render_thread_init (struct render_thread *ctx, struct render *render_info, render_thread_done_cb cb_func, void *cb_arg) {
+    // we need to copy over the render info, as it will probably be invalidated before the thread finishes
+    memcpy(&ctx->render_info, render_info, sizeof(ctx->render_info));
+    
+    // the cb stuff
+    ctx->cb_func = cb_func;
+    ctx->cb_arg = cb_arg;
+
+    // the notify pipe
+    int pipefds[2];
+    
+    if (pipe(pipefds))
+        PERROR("pipe");
+    
+    // the write end...
+    ctx->notify_fd = pipefds[1];
+
+    // the read end...
+    event_set(&ctx->ev, pipefds[0], EV_READ, &_render_thread_done, ctx);
+
+    if (event_add(&ctx->ev, NULL))
+        PERROR("event_add");
+
+    // spawn the render thread
+    if (pthread_create(&ctx->thread_id, NULL, &_render_thread_func, ctx))
+        PERROR("pthread_create(manager_func)");
+    
+    // mark it as active
+    ctx->is_active = 1;
+    
+    return 0;
+
+error:
+    render_thread_deinit(ctx);
+    return -1;
+}
+
+void render_thread_cancel (struct render_thread *ctx) {
+    assert(ctx->is_active);
+  
+    // we don't care about joining the thread, detach
+    // XXX if already detached, it won't get canceled here
+    if (pthread_detach(ctx->thread_id))
+        PWARNING("pthread_detach");
+
+    // cancel it
+    else if (pthread_cancel(ctx->thread_id))
+        PWARNING("pthread_cancel");
+    
+    // XXX: should we actually join it before continuing?
+
+    // slam the pipe shut in front of its face
+    if (close(event_get_fd(&ctx->ev)))
+        PWARNING("close");
+
+    if (close(ctx->notify_fd))
+        PWARNING("close");
+
+    // pipe's closed, and don't call _render_thread_func
+    event_del(&ctx->ev);
+    
+    // we are now ready for deinit
+    ctx->is_active = 0;
+}
+
+void render_thread_deinit (struct render_thread *ctx) {
+    assert(!ctx->is_active);
+
+    // nothing to do
+}
+
+struct render_thread *render_thread_alloc (struct render *render_info, render_thread_done_cb cb_func, void *cb_arg) {
+    struct render_thread *ctx = NULL;
+
+    if (!(ctx = calloc(1, sizeof(*ctx))))
+        ERROR("calloc");
+    
+    // flag it for free()ing
+    ctx->owned_by_me = 1;
+    
+    // init with silent fall-through
+    if (render_thread_init(ctx, render_info, cb_func, cb_arg))
+        goto error;
+    
+    // success
+    return ctx;
+
+error:
+    // XXX: do other modules do this?
+    free(ctx);
+
+    return NULL;
+}
+
+void render_thread_free (struct render_thread *ctx) {
+    render_thread_deinit(ctx);
+
+    if (ctx->owned_by_me)
+        free(ctx);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_thread.h	Thu Jun 26 01:32:56 2008 +0300
@@ -0,0 +1,19 @@
+#ifndef RENDER_THREAD_H
+#define RENDER_THREAD_H
+
+#include "render.h"
+
+/*
+ * Compared to render_threads, this just uses a single thread to render the image and the PNG, which is better for
+ * rendering many small images
+ */
+
+struct render_thread;
+
+typedef void (*render_thread_done_cb)(struct render_thread *ctx, void *arg);
+
+struct render_thread *render_thread_alloc (struct render *render, render_thread_done_cb cb_func, void *cb_arg);
+
+void render_thread_free(struct render_thread *ctx);
+
+#endif /* RENDER_THREAD_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_thread_struct.h	Thu Jun 26 01:32:56 2008 +0300
@@ -0,0 +1,39 @@
+#ifndef RENDER_THREAD_STRUCT_H
+#define RENDER_THREAD_STRUCT_H
+
+#include <pthread.h>
+#include <event2/event_struct.h>
+
+#include "render_thread.h"
+#include "render_struct.h"
+
+struct render_thread {
+    int owned_by_me;
+
+    // the thread identifier
+    pthread_t thread_id;
+
+    // the render op, this includes the socket/stream
+    struct render render_info;
+    
+    // completion callback
+    render_thread_done_cb cb_func;
+    void *cb_arg;
+
+    // what fd to use for completion notification
+    int notify_fd;
+
+    // the notification event
+    struct event ev;
+
+    // already canceled/done?
+    int is_active;
+};
+
+int render_thread_init (struct render_thread *ctx, struct render *render_info, render_thread_done_cb cb_func, void *cb_arg);
+
+void render_thread_cancel (struct render_thread *ctx);
+
+void render_thread_deinit (struct render_thread *ctx);
+
+#endif /* RENDER_THREAD_STRUCT_H */
--- a/render_threads.c	Tue Jun 17 19:08:05 2008 +0300
+++ b/render_threads.c	Thu Jun 26 01:32:56 2008 +0300
@@ -3,7 +3,7 @@
 
 #include <pthread.h>
 
-#define DEBUG_ENABLED
+//#define DEBUG_ENABLED
 
 #include "common.h"
 #include "render_threads.h"