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

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

Fix first arg to select() in geovis

File size: 30.8 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 <osgEarthFeatures/FeatureModelSource>
22#include <osgEarthSymbology/Color>
23#include <osgEarthSymbology/Style>
24#include <osgEarthSymbology/StyleSheet>
25#include <osgEarthSymbology/LineSymbol>
26#include <osgEarthSymbology/RenderSymbol>
27
28#include <osgEarthDrivers/gdal/GDALOptions>
29#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
30#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
31
32#include "Trace.h"
33#include "CmdProc.h"
34#include "ReadBuffer.h"
35#include "Types.h"
36#include "RendererCmd.h"
37#include "RenderServer.h"
38#include "Renderer.h"
39#include "PPMWriter.h"
40#include "TGAWriter.h"
41#include "ResponseQueue.h"
42#ifdef USE_READ_THREAD
43#include "CommandQueue.h"
44#endif
45
46using namespace GeoVis;
47
48static int lastCmdStatus;
49
50#ifndef USE_THREADS
51static ssize_t
52SocketWrite(const void *bytes, size_t len)
53{
54    size_t ofs = 0;
55    ssize_t bytesWritten;
56    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
57        ofs += bytesWritten;
58        if (ofs == len)
59            break;
60    }
61    if (bytesWritten < 0) {
62        ERROR("write: %s", strerror(errno));
63    }
64    return bytesWritten;
65}
66#endif
67
68static bool
69SocketRead(char *bytes, size_t len)
70{
71    ReadBuffer::BufferStatus status;
72    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
73    TRACE("followingData status: %d", status);
74    return (status == ReadBuffer::OK);
75}
76
77ssize_t
78GeoVis::queueResponse(const void *bytes, size_t len,
79                      Response::AllocationType allocType,
80                      Response::ResponseType type)
81{
82#ifdef USE_THREADS
83    Response *response = new Response(type);
84    response->setMessage((unsigned char *)bytes, len, allocType);
85    g_outQueue->enqueue(response);
86    return (ssize_t)len;
87#else
88    return SocketWrite(bytes, len);
89#endif
90}
91
92static int
93ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
94{
95    int result;
96#ifdef WANT_TRACE
97    char *str = Tcl_DStringValue(dsPtr);
98    std::string cmd(str);
99    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
100    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
101#endif
102    lastCmdStatus = TCL_OK;
103    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
104                        Tcl_DStringLength(dsPtr),
105                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
106    Tcl_DStringSetLength(dsPtr, 0);
107    if (lastCmdStatus == TCL_BREAK) {
108        return TCL_BREAK;
109    }
110    lastCmdStatus = result;
111    if (result != TCL_OK) {
112        TRACE("Error: %d", result);
113    }
114    return result;
115}
116
117static int
118GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
119{
120    int value;
121
122    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
123        return TCL_ERROR;
124    }
125    *boolPtr = (bool)value;
126    return TCL_OK;
127}
128
129static int
130GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
131{
132    double value;
133
134    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
135        return TCL_ERROR;
136    }
137    *valuePtr = (float)value;
138    return TCL_OK;
139}
140
141static int
142CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
143               Tcl_Obj *const *objv)
144{
145    double quat[4];
146
147    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
148        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
149        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
150        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
151        return TCL_ERROR;
152    }
153
154    g_renderer->setCameraOrientation(quat);
155    return TCL_OK;
156}
157
158static int
159CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
160            Tcl_Obj *const *objv)
161{
162    double x, y;
163
164    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
165        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
166        return TCL_ERROR;
167    }
168
169    g_renderer->panCamera(x, y);
170    return TCL_OK;
171}
172
173static int
174CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
175              Tcl_Obj *const *objv)
176{
177    if (objc == 3) {
178        const char *string = Tcl_GetString(objv[2]);
179        char c = string[0];
180        if ((c != 'a') || (strcmp(string, "all") != 0)) {
181            Tcl_AppendResult(interp, "bad camera reset option \"", string,
182                         "\": should be all", (char*)NULL);
183            return TCL_ERROR;
184        }
185        g_renderer->resetCamera(true);
186    } else {
187        g_renderer->resetCamera(false);
188    }
189    return TCL_OK;
190}
191
192static int
193CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
194               Tcl_Obj *const *objv)
195{
196    double x, y;
197
198    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
199        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
200        return TCL_ERROR;
201    }
202
203    g_renderer->rotateCamera(x, y);
204    return TCL_OK;
205}
206
207static int
208CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
209              Tcl_Obj *const *objv)
210{
211    bool state;
212
213    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
214        return TCL_ERROR;
215    }
216
217    g_renderer->setThrowingEnabled(state);
218    return TCL_OK;
219}
220
221static int
222CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
223            Tcl_Obj *const *objv)
224{
225    double z;
226
227    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
228        return TCL_ERROR;
229    }
230
231    g_renderer->zoomCamera(z);
232    return TCL_OK;
233}
234
235static CmdSpec cameraOps[] = {
236    {"orient", 1, CameraOrientOp, 6, 6, "qw qx qy qz"},
237    {"pan",    1, CameraPanOp, 4, 4, "panX panY"},
238    {"reset",  2, CameraResetOp, 2, 3, "?all?"},
239    {"rotate", 2, CameraRotateOp, 4, 4, "azimuth elevation"},
240    {"throw",  1, CameraThrowOp, 3, 3, "bool"},
241    {"zoom",   1, CameraZoomOp, 3, 3, "zoomAmount"}
242};
243static int nCameraOps = NumCmdSpecs(cameraOps);
244
245static int
246CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
247          Tcl_Obj *const *objv)
248{
249    Tcl_ObjCmdProc *proc;
250
251    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
252                        CMDSPEC_ARG1, objc, objv, 0);
253    if (proc == NULL) {
254        return TCL_ERROR;
255    }
256    return (*proc) (clientData, interp, objc, objv);
257}
258
259static int
260ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
261              Tcl_Obj *const *objv)
262{
263    Tcl_DString ds;
264    Tcl_Obj *objPtr, *listObjPtr, **items;
265    int numItems;
266    char buf[BUFSIZ];
267    const char *string;
268    int length;
269    int result;
270    static bool first = true;
271
272    /* Use the initial client key value pairs as the parts for a generating
273     * a unique file name. */
274    int fd = GeoVis::getStatsFile(interp, objv[1]);
275    if (fd < 0) {
276        Tcl_AppendResult(interp, "can't open stats file: ",
277                         Tcl_PosixError(interp), (char *)NULL);
278        return TCL_ERROR;
279    }
280    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
281    Tcl_IncrRefCount(listObjPtr);
282    if (first) {
283        first = false;
284        objPtr = Tcl_NewStringObj("render_start", 12);
285        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
286        /* server */
287        objPtr = Tcl_NewStringObj("server", 6);
288        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
289        objPtr = Tcl_NewStringObj("geovis", 6);
290        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
291        /* pid */
292        objPtr = Tcl_NewStringObj("pid", 3);
293        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
294        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
295        /* machine */
296        objPtr = Tcl_NewStringObj("machine", 7);
297        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
298        gethostname(buf, BUFSIZ-1);
299        buf[BUFSIZ-1] = '\0';
300        objPtr = Tcl_NewStringObj(buf, -1);
301        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
302    } else {
303        objPtr = Tcl_NewStringObj("render_info", 11);
304        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
305    }
306    /* date */
307    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
308    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
309    buf[strlen(buf) - 1] = '\0';
310    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
311    /* date_secs */
312    Tcl_ListObjAppendElement(interp, listObjPtr,
313                             Tcl_NewStringObj("date_secs", 9));
314    Tcl_ListObjAppendElement(interp, listObjPtr,
315                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
316    /* Client arguments. */
317    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
318        return TCL_ERROR;
319    }
320    for (int i = 0; i < numItems; i++) {
321        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
322    }
323    Tcl_DStringInit(&ds);
324    string = Tcl_GetStringFromObj(listObjPtr, &length);
325    Tcl_DStringAppend(&ds, string, length);
326    Tcl_DStringAppend(&ds, "\n", 1);
327    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
328                                      Tcl_DStringLength(&ds));
329    Tcl_DStringFree(&ds);
330    Tcl_DecrRefCount(listObjPtr);
331    return result;
332}
333
334static int
335ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
336              Tcl_Obj *const *objv)
337{
338    lastCmdStatus = TCL_BREAK;
339    return TCL_OK;
340}
341
342static int
343KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
344           Tcl_Obj *const *objv)
345{
346    int key;
347    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
348        return TCL_ERROR;
349    }
350
351    g_renderer->keyPress(key);
352    return TCL_OK;
353}
354
355static int
356KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
357               Tcl_Obj *const *objv)
358{
359    int key;
360    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
361        return TCL_ERROR;
362    }
363
364    g_renderer->keyRelease(key);
365    return TCL_OK;
366}
367
368static CmdSpec keyOps[] = {
369    {"press",    1, KeyPressOp,       3, 3, "key"},
370    {"release",  1, KeyReleaseOp,     3, 3, "key"},
371};
372static int nKeyOps = NumCmdSpecs(keyOps);
373
374static int
375KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
376         Tcl_Obj *const *objv)
377{
378    Tcl_ObjCmdProc *proc;
379
380    proc = GetOpFromObj(interp, nKeyOps, keyOps,
381                        CMDSPEC_ARG1, objc, objv, 0);
382    if (proc == NULL) {
383        return TCL_ERROR;
384    }
385    return (*proc) (clientData, interp, objc, objv);
386}
387
388static int
389MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
390              Tcl_Obj *const *objv)
391{
392    char *type = Tcl_GetString(objv[3]);
393    if (type[0] == 'i' && strcmp(type, "image") == 0) {
394        osgEarth::Drivers::GDALOptions opts;
395        char *url =  Tcl_GetString(objv[4]);
396        char *name = Tcl_GetString(objv[5]);
397
398        opts.url() = url;
399
400        g_renderer->addImageLayer(name, opts);
401    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
402        osgEarth::Drivers::GDALOptions opts;
403        char *url =  Tcl_GetString(objv[4]);
404        char *name = Tcl_GetString(objv[5]);
405
406        opts.url() = url;
407
408        g_renderer->addElevationLayer(name, opts);
409    } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
410        osgEarth::Drivers::OGRFeatureOptions opts;
411        char *url =  Tcl_GetString(objv[4]);
412        char *name = Tcl_GetString(objv[5]);
413        opts.url() = url;
414
415        osgEarth::Symbology::Style style;
416        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
417        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
418        ls->stroke()->width() = 2.0f;
419
420        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
421        rs->depthOffset()->enabled() = true;
422        rs->depthOffset()->minBias() = 1000;
423
424        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
425        geomOpts.featureOptions() = opts;
426        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
427        geomOpts.styles()->addStyle(style);
428        geomOpts.enableLighting() = false;
429        g_renderer->addModelLayer(name, geomOpts);
430    } else {
431        Tcl_AppendResult(interp, "unknown map layer type \"", type,
432                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
433        return TCL_ERROR;
434    }
435    return TCL_OK;
436}
437
438static int
439MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
440                 Tcl_Obj *const *objv)
441{
442    if (objc > 3) {
443        char *name = Tcl_GetString(objv[3]);
444        g_renderer->removeImageLayer(name);
445        g_renderer->removeElevationLayer(name);
446        g_renderer->removeModelLayer(name);
447    } else {
448        g_renderer->clearMap();
449    }
450
451    return TCL_OK;
452}
453
454static int
455MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
456               Tcl_Obj *const *objv)
457{
458    int pos;
459    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
460        return TCL_ERROR;
461    }
462    char *name = Tcl_GetString(objv[4]);
463    if (pos < 0) {
464        Tcl_AppendResult(interp, "bad layer pos ", pos,
465                         ": must be positive", (char*)NULL);
466        return TCL_ERROR;
467    }
468    g_renderer->moveImageLayer(name, (unsigned int)pos);
469    g_renderer->moveElevationLayer(name, (unsigned int)pos);
470    g_renderer->moveModelLayer(name, (unsigned int)pos);
471
472    return TCL_OK;
473}
474
475static int
476MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
477                  Tcl_Obj *const *objv)
478{
479    double opacity;
480    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
481        return TCL_ERROR;
482    }
483    char *name = Tcl_GetString(objv[4]);
484    if (opacity < 0.0 || opacity > 1.0) {
485        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
486                         ": must be [0,1]", (char*)NULL);
487        return TCL_ERROR;
488    }
489    g_renderer->setImageLayerOpacity(name, opacity);
490    g_renderer->setModelLayerOpacity(name, opacity);
491
492    return TCL_OK;
493}
494
495static int
496MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
497                  Tcl_Obj *const *objv)
498{
499    bool visible;
500    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
501        return TCL_ERROR;
502    }
503    char *name = Tcl_GetString(objv[4]);
504
505    g_renderer->setImageLayerVisibility(name, visible);
506    g_renderer->setElevationLayerVisibility(name, visible);
507    g_renderer->setModelLayerVisibility(name, visible);
508
509    return TCL_OK;
510}
511
512static CmdSpec mapLayerOps[] = {
513    {"add",     1, MapLayerAddOp,       6, 6, "type url name"},
514    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
515    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
516    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
517    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
518};
519static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
520
521static int
522MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
523           Tcl_Obj *const *objv)
524{
525    Tcl_ObjCmdProc *proc;
526
527    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
528                        CMDSPEC_ARG2, objc, objv, 0);
529    if (proc == NULL) {
530        return TCL_ERROR;
531    }
532    return (*proc) (clientData, interp, objc, objv);
533}
534
535static int
536MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
537          Tcl_Obj *const *objv)
538{
539    char *opt = Tcl_GetString(objv[2]);
540    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
541        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
542    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
543        std::ostringstream path;
544        path << "server:" << Tcl_GetString(objv[3]);
545        g_renderer->loadEarthFile(path.str().c_str());
546    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
547        opt = Tcl_GetString(objv[3]);
548        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
549            return TCL_ERROR;
550        }
551        int len;
552        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
553            return TCL_ERROR;
554        }
555        // Read Earth file from socket
556        char *buf = (char *)malloc((size_t)len);
557        SocketRead(buf, (size_t)len);
558        std::ostringstream path;
559        path << "/tmp/tmp" << getpid() << ".earth";
560        FILE *tmpFile = fopen(path.str().c_str(), "w");
561        fwrite(buf, len, 1, tmpFile);
562        fclose(tmpFile);
563        g_renderer->loadEarthFile(path.str().c_str());
564        unlink(path.str().c_str());
565        free(buf);
566    } else {
567        return TCL_ERROR;
568    }
569    return TCL_OK;
570}
571
572static int
573MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
574           Tcl_Obj *const *objv)
575{
576    char *typeStr = Tcl_GetString(objv[2]);
577    osgEarth::MapOptions::CoordinateSystemType type;
578    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
579        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
580    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
581        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
582    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
583        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
584    } else {
585        Tcl_AppendResult(interp, "bad map type \"", typeStr,
586                         "\": must be geocentric, geocentric_cube or projected", (char*)NULL);
587        return TCL_ERROR;
588    }
589
590    char *profile = NULL;
591    if (objc > 3) {
592        profile = Tcl_GetString(objv[3]);
593    }
594
595    g_renderer->resetMap(type, profile);
596
597    return TCL_OK;
598}
599
600static CmdSpec mapOps[] = {
601    {"layer",    2, MapLayerOp,       3, 6, "op ?params...?"},
602    {"load",     2, MapLoadOp,        4, 5, "options"},
603    {"reset",    1, MapResetOp,       3, 4, "type ?profile?"},
604};
605static int nMapOps = NumCmdSpecs(mapOps);
606
607static int
608MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
609       Tcl_Obj *const *objv)
610{
611    Tcl_ObjCmdProc *proc;
612
613    proc = GetOpFromObj(interp, nMapOps, mapOps,
614                        CMDSPEC_ARG1, objc, objv, 0);
615    if (proc == NULL) {
616        return TCL_ERROR;
617    }
618    return (*proc) (clientData, interp, objc, objv);
619}
620
621static int
622MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
623             Tcl_Obj *const *objv)
624{
625    int button;
626    double x, y;
627
628    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
629        return TCL_ERROR;
630    }
631    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
632        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
633        return TCL_ERROR;
634    }
635
636    g_renderer->mouseClick(button, x, y);
637    return TCL_OK;
638}
639
640static int
641MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
642                   Tcl_Obj *const *objv)
643{
644    int button;
645    double x, y;
646
647    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
648        return TCL_ERROR;
649    }
650    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
651        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
652        return TCL_ERROR;
653    }
654
655    g_renderer->mouseDoubleClick(button, x, y);
656    return TCL_OK;
657}
658
659static int
660MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
661            Tcl_Obj *const *objv)
662{
663    int button;
664    double x, y;
665
666    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
667        return TCL_ERROR;
668    }
669    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
670        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
671        return TCL_ERROR;
672    }
673
674    g_renderer->mouseDrag(button, x, y);
675    return TCL_OK;
676}
677
678static int
679MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
680              Tcl_Obj *const *objv)
681{
682    double x, y;
683
684    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
685        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
686        return TCL_ERROR;
687    }
688
689    g_renderer->mouseMotion(x, y);
690    return TCL_OK;
691}
692
693static int
694MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
695               Tcl_Obj *const *objv)
696{
697    int button;
698    double x, y;
699
700    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
701        return TCL_ERROR;
702    }
703    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
704        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
705        return TCL_ERROR;
706    }
707
708    g_renderer->mouseRelease(button, x, y);
709    return TCL_OK;
710}
711
712static int
713MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
714              Tcl_Obj *const *objv)
715{
716    int direction;
717
718    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
719        return TCL_ERROR;
720    }
721
722    g_renderer->mouseScroll(direction);
723    return TCL_OK;
724}
725
726static CmdSpec mouseOps[] = {
727    {"click",    1, MouseClickOp,       5, 5, "button x y"},
728    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
729    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
730    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
731    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
732    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
733};
734static int nMouseOps = NumCmdSpecs(mouseOps);
735
736static int
737MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
738         Tcl_Obj *const *objv)
739{
740    Tcl_ObjCmdProc *proc;
741
742    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
743                        CMDSPEC_ARG1, objc, objv, 0);
744    if (proc == NULL) {
745        return TCL_ERROR;
746    }
747    return (*proc) (clientData, interp, objc, objv);
748}
749
750static int
751RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
752                 Tcl_Obj *const *objv)
753{
754    g_renderer->eventuallyRender();
755    return TCL_OK;
756}
757
758static CmdSpec rendererOps[] = {
759    {"render",     1, RendererRenderOp, 2, 2, ""},
760};
761static int nRendererOps = NumCmdSpecs(rendererOps);
762
763static int
764RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
765            Tcl_Obj *const *objv)
766{
767    Tcl_ObjCmdProc *proc;
768
769    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
770                        CMDSPEC_ARG1, objc, objv, 0);
771    if (proc == NULL) {
772        return TCL_ERROR;
773    }
774    return (*proc) (clientData, interp, objc, objv);
775}
776
777static int
778ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
779                Tcl_Obj *const *objv)
780{
781    float color[3];
782
783    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
784        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
785        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
786        return TCL_ERROR;
787    }
788
789    g_renderer->setBackgroundColor(color);
790    return TCL_OK;
791}
792
793static int
794ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
795             Tcl_Obj *const *objv)
796{
797    int width, height;
798
799    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
800        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
801        return TCL_ERROR;
802    }
803
804    g_renderer->setWindowSize(width, height);
805    return TCL_OK;
806}
807
808static CmdSpec screenOps[] = {
809    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
810    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
811};
812static int nScreenOps = NumCmdSpecs(screenOps);
813
814static int
815ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
816          Tcl_Obj *const *objv)
817{
818    Tcl_ObjCmdProc *proc;
819
820    proc = GetOpFromObj(interp, nScreenOps, screenOps,
821                        CMDSPEC_ARG1, objc, objv, 0);
822    if (proc == NULL) {
823        return TCL_ERROR;
824    }
825    return (*proc) (clientData, interp, objc, objv);
826}
827
828#ifdef USE_READ_THREAD
829int
830GeoVis::queueCommands(Tcl_Interp *interp,
831                      ClientData clientData,
832                      ReadBuffer *inBufPtr)
833{
834    Tcl_DString commandString;
835    Tcl_DStringInit(&commandString);
836    fd_set readFds;
837
838    FD_ZERO(&readFds);
839    FD_SET(inBufPtr->file(), &readFds);
840    while (inBufPtr->isLineAvailable() ||
841           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
842        size_t numBytes;
843        unsigned char *buffer;
844
845        /* A short read is treated as an error here because we assume that we
846         * will always get commands line by line. */
847        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
848            /* Terminate the server if we can't communicate with the client
849             * anymore. */
850            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
851                TRACE("Exiting server on EOF from client");
852                return -1;
853            } else {
854                ERROR("Exiting server, failed to read from client: %s",
855                      strerror(errno));
856                return -1;
857            }
858        }
859        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
860        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
861            // Add to queue
862            Command *command = new Command(Command::COMMAND);
863            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
864                                Tcl_DStringLength(&commandString), Command::VOLATILE);
865            g_inQueue->enqueue(command);
866            Tcl_DStringSetLength(&commandString, 0);
867        }
868        FD_SET(inBufPtr->file(), &readFds);
869    }
870
871    return 1;
872}
873#endif
874
875/**
876 * \brief Execute commands from client in Tcl interpreter
877 *
878 * In this threaded model, the select call is for event compression.  We
879 * want to execute render server commands as long as they keep coming. 
880 * This lets us execute a stream of many commands but render once.  This
881 * benefits camera movements, screen resizing, and opacity changes
882 * (using a slider on the client).  The down side is you don't render
883 * until there's a lull in the command stream.  If the client needs an
884 * image, it can issue an "imgflush" command.  That breaks us out of the
885 * read loop.
886 */
887int
888GeoVis::processCommands(Tcl_Interp *interp,
889                        ClientData clientData,
890                        ReadBuffer *inBufPtr,
891                        int fdOut,
892                        long timeout)
893{
894    int ret = 1;
895    int status = TCL_OK;
896
897    Tcl_DString command;
898    Tcl_DStringInit(&command);
899    fd_set readFds;
900    struct timeval tv, *tvPtr;
901
902    FD_ZERO(&readFds);
903    FD_SET(inBufPtr->file(), &readFds);
904    tvPtr = NULL;                       /* Wait for the first read. This is so
905                                         * that we don't spin when no data is
906                                         * available. */
907    if (timeout >= 0L) {
908        tv.tv_sec = 0L;
909        tv.tv_usec = timeout;
910        tvPtr = &tv;
911    } else {
912        TRACE("Blocking on select()");
913    }
914    while (inBufPtr->isLineAvailable() ||
915           (select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr) > 0)) {
916        size_t numBytes;
917        unsigned char *buffer;
918
919        /* A short read is treated as an error here because we assume that we
920         * will always get commands line by line. */
921        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
922            /* Terminate the server if we can't communicate with the client
923             * anymore. */
924            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
925                TRACE("Exiting server on EOF from client");
926                return -1;
927            } else {
928                ERROR("Exiting server, failed to read from client: %s",
929                      strerror(errno));
930                return -1;
931            }
932        }
933        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
934        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
935            struct timeval start, finish;
936            gettimeofday(&start, NULL);
937            status = ExecuteCommand(interp, &command);
938            gettimeofday(&finish, NULL);
939            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
940            g_stats.nCommands++;
941            if (status == TCL_BREAK) {
942                return 2;               /* This was caused by a "imgflush"
943                                         * command. Break out of the read loop
944                                         * and allow a new image to be
945                                         * rendered. */
946            } else { //if (status != TCL_OK) {
947                ret = 0;
948                if (handleError(interp, clientData, status, fdOut) < 0) {
949                    return -1;
950                }
951            }
952            if (status == TCL_OK) {
953                ret = 3;
954            }
955        }
956
957        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
958                                         * if no data is available. */
959        FD_SET(inBufPtr->file(), &readFds);
960        tvPtr = &tv;
961    }
962
963    return ret;
964}
965
966/**
967 * \brief Send error message to client socket
968 */
969int
970GeoVis::handleError(Tcl_Interp *interp,
971                    ClientData clientData,
972                    int status, int fdOut)
973{
974    const char *string;
975    int nBytes;
976
977    if (status != TCL_OK) {
978        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
979        nBytes = strlen(string);
980        if (nBytes > 0) {
981            TRACE("status=%d errorInfo=(%s)", status, string);
982
983            std::ostringstream oss;
984            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
985            nBytes = oss.str().length();
986
987            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
988                return -1;
989            }
990        }
991    }
992
993    string = getUserMessages();
994    nBytes = strlen(string);
995    if (nBytes > 0) {
996        TRACE("userError=(%s)", string);
997
998        std::ostringstream oss;
999        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1000        nBytes = oss.str().length();
1001
1002        if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1003            return -1;
1004        }
1005
1006        clearUserMessages();
1007    }
1008
1009    return 0;
1010}
1011
1012/**
1013 * \brief Create Tcl interpreter and add commands
1014 *
1015 * \return The initialized Tcl interpreter
1016 */
1017void
1018GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
1019{
1020    Tcl_MakeSafe(interp);
1021    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
1022    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
1023    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
1024    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
1025    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
1026    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
1027    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
1028    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
1029}
1030
1031/**
1032 * \brief Delete Tcl commands and interpreter
1033 */
1034void GeoVis::exitTcl(Tcl_Interp *interp)
1035{
1036    Tcl_DeleteCommand(interp, "camera");
1037    Tcl_DeleteCommand(interp, "clientinfo");
1038    Tcl_DeleteCommand(interp, "imgflush");
1039    Tcl_DeleteCommand(interp, "key");
1040    Tcl_DeleteCommand(interp, "map");
1041    Tcl_DeleteCommand(interp, "mouse");
1042    Tcl_DeleteCommand(interp, "renderer");
1043    Tcl_DeleteCommand(interp, "screen");
1044
1045    Tcl_DeleteInterp(interp);
1046}
Note: See TracBrowser for help on using the repository browser.