source: trunk/packages/vizservers/geovis/RendererCmd.cpp @ 3998

Last change on this file since 3998 was 3998, checked in by ldelgass, 10 years ago

Add prelimilary skeleton for geovis map rendering server. Not functional, not
integrated into configure, etc.

File size: 15.6 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdlib>
9#include <cstdio>
10#include <cstring>
11#include <cfloat>
12#include <cerrno>
13#include <string>
14#include <sstream>
15#include <vector>
16#include <unistd.h>
17#include <sys/select.h>
18#include <sys/uio.h>
19#include <tcl.h>
20
21#include "Trace.h"
22#include "CmdProc.h"
23#include "ReadBuffer.h"
24#include "Types.h"
25#include "RendererCmd.h"
26#include "RenderServer.h"
27#include "Renderer.h"
28#include "PPMWriter.h"
29#include "TGAWriter.h"
30#ifdef USE_THREADS
31#include "ResponseQueue.h"
32#endif
33
34using namespace GeoVis;
35
36static int lastCmdStatus;
37
38ssize_t
39GeoVis::queueResponse(const void *bytes, size_t len,
40                      Response::AllocationType allocType,
41                      Response::ResponseType type)
42{
43#ifdef USE_THREADS
44    Response *response = new Response(type);
45    response->setMessage((unsigned char *)bytes, len, allocType);
46    g_queue->enqueue(response);
47    return (ssize_t)len;
48#else
49    return SocketWrite(bytes, len);
50#endif
51}
52
53static ssize_t
54SocketWrite(const void *bytes, size_t len)
55{
56    size_t ofs = 0;
57    ssize_t bytesWritten;
58    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
59        ofs += bytesWritten;
60        if (ofs == len)
61            break;
62    }
63    if (bytesWritten < 0) {
64        ERROR("write: %s", strerror(errno));
65    }
66    return bytesWritten;
67}
68
69static bool
70SocketRead(char *bytes, size_t len)
71{
72    ReadBuffer::BufferStatus status;
73    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
74    TRACE("followingData status: %d", status);
75    return (status == ReadBuffer::OK);
76}
77
78static int
79ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
80{
81    int result;
82#ifdef WANT_TRACE
83    char *str = Tcl_DStringValue(dsPtr);
84    std::string cmd(str);
85    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
86    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
87#endif
88    lastCmdStatus = TCL_OK;
89    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
90                        Tcl_DStringLength(dsPtr),
91                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
92    Tcl_DStringSetLength(dsPtr, 0);
93    if (lastCmdStatus == TCL_BREAK) {
94        return TCL_BREAK;
95    }
96    lastCmdStatus = result;
97    if (result != TCL_OK) {
98        TRACE("Error: %d", result);
99    }
100    return result;
101}
102
103static int
104GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
105{
106    int value;
107
108    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
109        return TCL_ERROR;
110    }
111    *boolPtr = (bool)value;
112    return TCL_OK;
113}
114
115static int
116GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
117{
118    double value;
119
120    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
121        return TCL_ERROR;
122    }
123    *valuePtr = (float)value;
124    return TCL_OK;
125}
126
127static int
128CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
129               Tcl_Obj *const *objv)
130{
131    double quat[4];
132
133    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
134        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
135        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
136        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
137        return TCL_ERROR;
138    }
139
140    g_renderer->setCameraOrientation(quat);
141    return TCL_OK;
142}
143
144static int
145CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
146            Tcl_Obj *const *objv)
147{
148    double x, y;
149
150    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
151        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
152        return TCL_ERROR;
153    }
154
155    g_renderer->panCamera(x, y);
156    return TCL_OK;
157}
158
159static int
160CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
161              Tcl_Obj *const *objv)
162{
163    if (objc == 3) {
164        const char *string = Tcl_GetString(objv[2]);
165        char c = string[0];
166        if ((c != 'a') || (strcmp(string, "all") != 0)) {
167            Tcl_AppendResult(interp, "bad camera reset option \"", string,
168                         "\": should be all", (char*)NULL);
169            return TCL_ERROR;
170        }
171        g_renderer->resetCamera(true);
172    } else {
173        g_renderer->resetCamera(false);
174    }
175    return TCL_OK;
176}
177
178static int
179CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
180            Tcl_Obj *const *objv)
181{
182    double z;
183
184    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
185        return TCL_ERROR;
186    }
187
188    g_renderer->zoomCamera(z);
189    return TCL_OK;
190}
191
192static Rappture::CmdSpec cameraOps[] = {
193    {"orient", 3, CameraOrientOp, 6, 6, "qw qx qy qz"},
194    {"pan",    1, CameraPanOp, 4, 4, "panX panY"},
195    {"reset",  2, CameraResetOp, 2, 3, "?all?"},
196    {"zoom",   1, CameraZoomOp, 3, 3, "zoomAmount"}
197};
198static int nCameraOps = NumCmdSpecs(cameraOps);
199
200static int
201CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
202          Tcl_Obj *const *objv)
203{
204    Tcl_ObjCmdProc *proc;
205
206    proc = Rappture::GetOpFromObj(interp, nCameraOps, cameraOps,
207                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
208    if (proc == NULL) {
209        return TCL_ERROR;
210    }
211    return (*proc) (clientData, interp, objc, objv);
212}
213
214static int
215ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
216              Tcl_Obj *const *objv)
217{
218    Tcl_DString ds;
219    Tcl_Obj *objPtr, *listObjPtr, **items;
220    int numItems;
221    char buf[BUFSIZ];
222    const char *string;
223    int length;
224    int result;
225    static bool first = true;
226
227    /* Use the initial client key value pairs as the parts for a generating
228     * a unique file name. */
229    int fd = GeoVis::getStatsFile(interp, objv[1]);
230    if (fd < 0) {
231        Tcl_AppendResult(interp, "can't open stats file: ",
232                         Tcl_PosixError(interp), (char *)NULL);
233        return TCL_ERROR;
234    }
235    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
236    Tcl_IncrRefCount(listObjPtr);
237    if (first) {
238        first = false;
239        objPtr = Tcl_NewStringObj("render_start", 12);
240        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
241        /* server */
242        objPtr = Tcl_NewStringObj("server", 6);
243        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
244        objPtr = Tcl_NewStringObj("geovis", 6);
245        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
246        /* pid */
247        objPtr = Tcl_NewStringObj("pid", 3);
248        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
249        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
250        /* machine */
251        objPtr = Tcl_NewStringObj("machine", 7);
252        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
253        gethostname(buf, BUFSIZ-1);
254        buf[BUFSIZ-1] = '\0';
255        objPtr = Tcl_NewStringObj(buf, -1);
256        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
257    } else {
258        objPtr = Tcl_NewStringObj("render_info", 11);
259        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
260    }
261    /* date */
262    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
263    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
264    buf[strlen(buf) - 1] = '\0';
265    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
266    /* date_secs */
267    Tcl_ListObjAppendElement(interp, listObjPtr,
268                             Tcl_NewStringObj("date_secs", 9));
269    Tcl_ListObjAppendElement(interp, listObjPtr,
270                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
271    /* Client arguments. */
272    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
273        return TCL_ERROR;
274    }
275    for (int i = 0; i < numItems; i++) {
276        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
277    }
278    Tcl_DStringInit(&ds);
279    string = Tcl_GetStringFromObj(listObjPtr, &length);
280    Tcl_DStringAppend(&ds, string, length);
281    Tcl_DStringAppend(&ds, "\n", 1);
282    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
283                                      Tcl_DStringLength(&ds));
284    Tcl_DStringFree(&ds);
285    Tcl_DecrRefCount(listObjPtr);
286    return result;
287}
288
289static int
290ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
291              Tcl_Obj *const *objv)
292{
293    lastCmdStatus = TCL_BREAK;
294    return TCL_OK;
295}
296
297static int
298RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
299                 Tcl_Obj *const *objv)
300{
301    g_renderer->eventuallyRender();
302    return TCL_OK;
303}
304
305static Rappture::CmdSpec rendererOps[] = {
306    {"render",     1, RendererRenderOp, 2, 2, ""}
307};
308static int nRendererOps = NumCmdSpecs(rendererOps);
309
310static int
311RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
312            Tcl_Obj *const *objv)
313{
314    Tcl_ObjCmdProc *proc;
315
316    proc = Rappture::GetOpFromObj(interp, nRendererOps, rendererOps,
317                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
318    if (proc == NULL) {
319        return TCL_ERROR;
320    }
321    return (*proc) (clientData, interp, objc, objv);
322}
323
324static int
325ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
326                Tcl_Obj *const *objv)
327{
328    float color[3];
329
330    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
331        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
332        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
333        return TCL_ERROR;
334    }
335
336    g_renderer->setBackgroundColor(color);
337    return TCL_OK;
338}
339
340static int
341ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
342             Tcl_Obj *const *objv)
343{
344    int width, height;
345
346    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
347        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
348        return TCL_ERROR;
349    }
350
351    g_renderer->setWindowSize(width, height);
352    return TCL_OK;
353}
354
355static Rappture::CmdSpec screenOps[] = {
356    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
357    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
358};
359static int nScreenOps = NumCmdSpecs(screenOps);
360
361static int
362ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
363          Tcl_Obj *const *objv)
364{
365    Tcl_ObjCmdProc *proc;
366
367    proc = Rappture::GetOpFromObj(interp, nScreenOps, screenOps,
368                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
369    if (proc == NULL) {
370        return TCL_ERROR;
371    }
372    return (*proc) (clientData, interp, objc, objv);
373}
374
375/**
376 * \brief Execute commands from client in Tcl interpreter
377 *
378 * In this threaded model, the select call is for event compression.  We
379 * want to execute render server commands as long as they keep coming. 
380 * This lets us execute a stream of many commands but render once.  This
381 * benefits camera movements, screen resizing, and opacity changes
382 * (using a slider on the client).  The down side is you don't render
383 * until there's a lull in the command stream.  If the client needs an
384 * image, it can issue an "imgflush" command.  That breaks us out of the
385 * read loop.
386 */
387int
388GeoVis::processCommands(Tcl_Interp *interp,
389                        ClientData clientData,
390                        ReadBuffer *inBufPtr,
391                        int fdOut)
392{
393    int ret = 1;
394    int status = TCL_OK;
395
396    Tcl_DString command;
397    Tcl_DStringInit(&command);
398    fd_set readFds;
399    struct timeval tv, *tvPtr;
400
401    FD_ZERO(&readFds);
402    FD_SET(inBufPtr->file(), &readFds);
403    tvPtr = NULL;                       /* Wait for the first read. This is so
404                                         * that we don't spin when no data is
405                                         * available. */
406    while (inBufPtr->isLineAvailable() ||
407           (select(1, &readFds, NULL, NULL, tvPtr) > 0)) {
408        size_t numBytes;
409        unsigned char *buffer;
410
411        /* A short read is treated as an error here because we assume that we
412         * will always get commands line by line. */
413        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
414            /* Terminate the server if we can't communicate with the client
415             * anymore. */
416            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
417                TRACE("Exiting server on EOF from client");
418                return -1;
419            } else {
420                ERROR("Exiting server, failed to read from client: %s",
421                      strerror(errno));
422                return -1;
423            }
424        }
425        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
426        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
427            struct timeval start, finish;
428            gettimeofday(&start, NULL);
429            status = ExecuteCommand(interp, &command);
430            gettimeofday(&finish, NULL);
431            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
432            g_stats.nCommands++;
433            if (status == TCL_BREAK) {
434                return 1;               /* This was caused by a "imgflush"
435                                         * command. Break out of the read loop
436                                         * and allow a new image to be
437                                         * rendered. */
438            } else { //if (status != TCL_OK) {
439                ret = 0;
440                if (handleError(interp, clientData, status, fdOut) < 0) {
441                    return -1;
442                }
443            }
444        }
445
446        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
447                                         * if no data is available. */
448        FD_SET(inBufPtr->file(), &readFds);
449        tvPtr = &tv;
450    }
451
452    return ret;
453}
454
455/**
456 * \brief Send error message to client socket
457 */
458int
459GeoVis::handleError(Tcl_Interp *interp,
460                    ClientData clientData,
461                    int status, int fdOut)
462{
463    const char *string;
464    int nBytes;
465
466    if (status != TCL_OK) {
467        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
468        nBytes = strlen(string);
469        if (nBytes > 0) {
470            TRACE("status=%d errorInfo=(%s)", status, string);
471
472            std::ostringstream oss;
473            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
474            nBytes = oss.str().length();
475
476            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
477                return -1;
478            }
479        }
480    }
481
482    string = getUserMessages();
483    nBytes = strlen(string);
484    if (nBytes > 0) {
485        TRACE("userError=(%s)", string);
486
487        std::ostringstream oss;
488        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
489        nBytes = oss.str().length();
490
491        if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
492            return -1;
493        }
494
495        clearUserMessages();
496    }
497
498    return 0;
499}
500
501/**
502 * \brief Create Tcl interpreter and add commands
503 *
504 * \return The initialized Tcl interpreter
505 */
506void
507GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
508{
509    Tcl_MakeSafe(interp);
510    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
511    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
512    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
513    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
514    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
515}
516
517/**
518 * \brief Delete Tcl commands and interpreter
519 */
520void GeoVis::exitTcl(Tcl_Interp *interp)
521{
522    Tcl_DeleteCommand(interp, "camera");
523    Tcl_DeleteCommand(interp, "clientinfo");
524    Tcl_DeleteCommand(interp, "imgflush");
525    Tcl_DeleteCommand(interp, "renderer");
526    Tcl_DeleteCommand(interp, "screen");
527
528    Tcl_DeleteInterp(interp);
529}
Note: See TracBrowser for help on using the repository browser.