source: geovis/trunk/RenderServer.cpp @ 4956

Last change on this file since 4956 was 4956, checked in by ldelgass, 9 years ago

Delete cache dir from main() to allow renderer ref-counted objects to be
deleted and threads to exit first.

File size: 11.9 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2013  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdio>
9#include <cstring>
10#include <cstdlib>
11#include <cerrno>
12#include <unistd.h>
13#include <signal.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <fcntl.h>
17#include <sys/time.h>
18#include <sys/times.h>
19
20#include <string>
21#include <sstream>
22
23#include <tcl.h>
24
25#include "Trace.h"
26#include "FileUtil.h"
27#include "ReadBuffer.h"
28#include "RenderServer.h"
29#include "RendererCmd.h"
30#include "Renderer.h"
31#include "Stats.h"
32#include "PPMWriter.h"
33#include "TGAWriter.h"
34#ifdef USE_THREADS
35#include <pthread.h>
36#include "ResponseQueue.h"
37#ifdef USE_READ_THREAD
38#include "CommandQueue.h"
39#endif
40#endif
41
42using namespace GeoVis;
43
44Stats GeoVis::g_stats;
45
46int GeoVis::g_statsFile = -1; ///< Stats output file descriptor.
47int GeoVis::g_fdIn = STDIN_FILENO; ///< Input file descriptor
48int GeoVis::g_fdOut = STDOUT_FILENO; ///< Output file descriptor
49FILE *GeoVis::g_fOut = NULL; ///< Output file handle
50FILE *GeoVis::g_fLog = NULL; ///< Trace logging file handle
51Renderer *GeoVis::g_renderer = NULL; ///< Main render worker
52ReadBuffer *GeoVis::g_inBufPtr = NULL; ///< Socket read buffer
53#ifdef USE_THREADS
54ResponseQueue *GeoVis::g_outQueue = NULL;
55#ifdef USE_READER_THREAD
56CommandQueue *GeoVis::g_inQueue = NULL;
57#endif
58#endif
59
60static int
61queueViewpoint()
62{
63    osgEarth::Viewpoint view = g_renderer->getViewpoint();
64
65    std::ostringstream oss;
66    size_t len = 0;
67    oss << "nv>camera get "
68        << view.x() << " "
69        << view.y() << " "
70        << view.z() << " "
71        << view.getHeading() << " "
72        << view.getPitch() << " "
73        << view.getRange()
74        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getHorizInitString()) << "}"
75        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getVertInitString()) << "}"
76        << "\n";
77    std::string ostr = oss.str();
78    len = ostr.size();
79#ifdef USE_THREADS
80    queueResponse(ostr.c_str(), len, Response::VOLATILE);
81#else
82    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
83
84    if (bytesWritten < 0) {
85        return TCL_ERROR;
86    }
87#endif /*USE_THREADS*/
88    return TCL_OK;
89}
90
91#ifdef USE_THREADS
92static void
93queueFrame(ResponseQueue *queue, const unsigned char *imgData)
94{
95#ifdef DEBUG_WRITE_FRAME_FILE
96
97#ifdef RENDER_TARGA
98    writeTGAFile("/tmp/frame.tga",
99                 imgData,
100                 g_renderer->getWindowWidth(),
101                 g_renderer->getWindowHeight(),
102                 TARGA_BYTES_PER_PIXEL);
103#else
104    writeTGAFile("/tmp/frame.tga",
105                 imgData,
106                 g_renderer->getWindowWidth(),
107                 g_renderer->getWindowHeight(),
108                 TARGA_BYTES_PER_PIXEL,
109                 true);
110#endif  /*RENDER_TARGA*/
111
112#else
113 
114#ifdef RENDER_TARGA
115    queueTGA(queue, "nv>image -type image -bytes",
116             imgData,
117             g_renderer->getWindowWidth(),
118             g_renderer->getWindowHeight(),
119             TARGA_BYTES_PER_PIXEL);
120#else
121    char cmd[256];
122    sprintf(cmd, "nv>image -type image -token %lu -bytes", g_stats.nCommands);
123    queuePPM(queue, cmd,
124             imgData,
125             g_renderer->getWindowWidth(),
126             g_renderer->getWindowHeight());
127#endif  /*RENDER_TARGA*/
128#endif  /*DEBUG_WRITE_FRAME_FILE*/
129}
130
131#else
132
133static void
134writeFrame(int fd, const unsigned char *imgData)
135{
136#ifdef DEBUG_WRITE_FRAME_FILE
137
138#ifdef RENDER_TARGA
139    writeTGAFile("/tmp/frame.tga",
140                 imgData,
141                 g_renderer->getWindowWidth(),
142                 g_renderer->getWindowHeight(),
143                 TARGA_BYTES_PER_PIXEL);
144#else
145    writeTGAFile("/tmp/frame.tga",
146                 imgData,
147                 g_renderer->getWindowWidth(),
148                 g_renderer->getWindowHeight(),
149                 TARGA_BYTES_PER_PIXEL,
150                 true);
151#endif  /*RENDER_TARGA*/
152
153#else
154
155#ifdef RENDER_TARGA
156    writeTGA(fd, "nv>image -type image -bytes",
157             imgData,
158             g_renderer->getWindowWidth(),
159             g_renderer->getWindowHeight(),
160             TARGA_BYTES_PER_PIXEL);
161#else
162    writePPM(fd, "nv>image -type image -bytes",
163             imgData,
164             g_renderer->getWindowWidth(),
165             g_renderer->getWindowHeight());
166#endif  /*RENDER_TARGA*/
167#endif  /*DEBUG_WRITE_FRAME_FILE*/
168}
169#endif /*USE_THREADS*/
170
171static int
172sendAck()
173{
174    std::ostringstream oss;
175    oss << "nv>ok -token " << g_stats.nCommands << "\n";
176    std::string ostr = oss.str();
177    int nBytes = ostr.length();
178
179    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
180#ifdef USE_THREADS
181    queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::OK);
182#else
183    if (write(g_fdOut, ostr.c_str(), nBytes) < 0) {
184        ERROR("write failed: %s", strerror(errno));
185        return -1;
186    }
187#endif
188    return 0;
189}
190
191static void
192initService()
193{
194    g_fOut = fdopen(g_fdOut, "w");
195    // If running without socket, use stdout for debugging
196    if (g_fOut == NULL && g_fdOut != STDOUT_FILENO) {
197        g_fdOut = STDOUT_FILENO;
198        g_fOut = fdopen(g_fdOut, "w");
199    }
200
201    const char *user = getenv("USER");
202    char *logName = NULL;
203    int logNameLen = 0;
204
205    if (user == NULL) {
206        logNameLen = 19+1;
207        logName = (char *)calloc(logNameLen, sizeof(char));
208        strncpy(logName, "/tmp/geovis_log.txt", logNameLen);
209    } else {
210        logNameLen = 16+strlen(user)+4+1;
211        logName = (char *)calloc(logNameLen, sizeof(char));
212        strncpy(logName, "/tmp/geovis_log_", logNameLen);
213        strncat(logName, user, strlen(user));
214        strncat(logName, ".txt", 4);
215    }
216
217    // open log and map stderr to log file
218    g_fLog = fopen(logName, "w");
219    dup2(fileno(g_fLog), STDERR_FILENO);
220    // If we are writing to socket, map stdout to log
221    if (g_fdOut != STDOUT_FILENO) {
222        dup2(fileno(g_fLog), STDOUT_FILENO);
223    }
224
225    fflush(stdout);
226
227    // clean up malloc'd memory
228    if (logName != NULL) {
229        free(logName);
230    }
231}
232
233static void
234exitService()
235{
236    TRACE("Enter");
237
238    serverStats(g_stats, 0);
239
240    // close log file
241    if (g_fLog != NULL) {
242        fclose(g_fLog);
243        g_fLog = NULL;
244    }
245}
246
247#ifdef USE_THREADS
248
249#ifdef USE_READ_THREAD
250static void *
251readerThread(void *clientData)
252{
253    Tcl_Interp *interp = (Tcl_Interp *)clientData;
254
255    TRACE("Starting reader thread");
256
257    queueCommands(interp, NULL, g_inBufPtr);
258
259    return NULL;
260}
261#endif
262
263static void *
264writerThread(void *clientData)
265{
266    ResponseQueue *queue = (ResponseQueue *)clientData;
267
268    TRACE("Starting writer thread");
269    for (;;) {
270        Response *response = queue->dequeue();
271        if (response == NULL)
272            continue;
273        if (fwrite(response->message(), sizeof(unsigned char), response->length(),
274                   g_fOut) != response->length()) {
275            ERROR("short write while trying to write %ld bytes",
276                  response->length());
277        }
278        fflush(g_fOut);
279        TRACE("Wrote response of type %d", response->type());
280        delete response;
281        if (feof(g_fOut))
282            break;
283    }   
284    return NULL;
285}
286
287#endif  /*USE_THREADS*/
288
289int
290main(int argc, char *argv[])
291{
292    // Ignore SIGPIPE.  **Is this needed? **
293    signal(SIGPIPE, SIG_IGN);
294
295    std::string resourcePath;
296    while (1) {
297        int c = getopt(argc, argv, "p:i:o:");
298        if (c == -1) {
299            break;
300        }
301        switch (c) {
302        case 'p':
303            resourcePath = optarg;
304            break;
305        case 'i': {
306            int fd = atoi(optarg);
307            if (fd >=0 && fd < 5) {
308                g_fdIn = fd;
309            }
310        }
311            break;
312        case 'o': {
313            int fd = atoi(optarg);
314            if (fd >=0 && fd < 5) {
315                g_fdOut = fd;
316            }
317        }
318            break;
319        case '?':
320            break;
321        default:
322            return 1;
323        }
324    }
325
326    initService();
327    initLog();
328
329    memset(&g_stats, 0, sizeof(g_stats));
330    gettimeofday(&g_stats.start, NULL);
331
332    TRACE("Starting GeoVis Server");
333
334    // Sanity check: log descriptor can't be used for client IO
335    if (fileno(g_fLog) == g_fdIn) {
336        ERROR("Invalid input file descriptor");
337        return 1;
338    }
339    if (fileno(g_fLog) == g_fdOut) {
340        ERROR("Invalid output file descriptor");
341        return 1;
342    }
343    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
344
345    /* This synchronizes the client with the server, so that the client
346     * doesn't start writing commands before the server is ready. It could
347     * also be used to supply information about the server (version, memory
348     * size, etc). */
349    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
350    fflush(g_fOut);
351
352    g_renderer = new Renderer();
353    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
354
355    if (resourcePath.empty()) {
356        resourcePath = "/usr/share/osgearth/data";
357    }
358    g_renderer->setResourcePath(resourcePath);
359
360    Tcl_Interp *interp = Tcl_CreateInterp();
361
362#ifdef USE_THREADS
363    g_outQueue = new ResponseQueue();
364
365    pthread_t writerThreadId;
366    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
367        ERROR("Can't create writer thread: %s", strerror(errno));
368    }
369#endif
370    initTcl(interp, NULL);
371
372    osg::ref_ptr<osg::Image> imgData;
373
374    // Start main server loop
375    for (;;) {
376        long timeout = g_renderer->getTimeout();
377#ifdef SLEEP_AFTER_QUEUE_FRAME
378        g_renderer->markFrameStart();
379#endif
380        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, timeout);
381        if (cmdStatus < 0)
382            break;
383
384        if (g_renderer->render()) {
385            TRACE("Rendered new frame");
386            imgData = g_renderer->getRenderedFrame();
387            if (imgData == NULL) {
388                ERROR("Empty image");
389            } else {
390                TRACE("Image: %d x %d", imgData->s(), imgData->t());
391
392                if (imgData->s() == g_renderer->getWindowWidth() &&
393                    imgData->t() == g_renderer->getWindowHeight()) {
394                    queueViewpoint();
395#ifdef USE_THREADS
396                    queueFrame(g_outQueue, imgData->data());
397#else
398                    writeFrame(g_fdOut, imgData->data());
399#endif
400                }
401                g_stats.nFrames++;
402                g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
403            }
404#ifdef SLEEP_AFTER_QUEUE_FRAME
405            g_renderer->markFrameEnd();
406#endif
407        } else {
408            //TRACE("No render required");
409            if (cmdStatus > 1) {
410                sendAck();
411            } else {
412                TRACE("No render required and status = %d", cmdStatus);
413            }
414        }
415
416#if 0
417        double x, y, z;
418        if (g_renderer->getMousePoint(&x, &y, &z)) {
419            // send coords to client
420            size_t length;
421            char mesg[256];
422
423            length = snprintf(mesg, sizeof(mesg),
424                              "nv>map coords %g %g %g\n", x, y, z);
425
426            queueResponse(mesg, length, Response::VOLATILE);
427        }
428#endif
429
430        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
431            break;
432    }
433#ifdef USE_THREADS
434    // Writer thread is probably blocked on sem_wait, so cancel instead
435    // of joining
436    if (pthread_cancel(writerThreadId) < 0) {
437        ERROR("Can't cancel writer thread: %s", strerror(errno));
438    } else {
439        TRACE("Cancelled writer thread");
440    }
441
442    TRACE("Deleting ResponseQueue");
443    delete g_outQueue;
444    g_outQueue = NULL;
445#endif
446
447    TRACE("Stopping Tcl interpreter");
448    exitTcl(interp);
449    interp = NULL;
450
451    TRACE("Deleting ReadBuffer");
452    delete g_inBufPtr;
453    g_inBufPtr = NULL;
454
455    std::string cacheDir = g_renderer->getCacheDirectory();
456    TRACE("Deleting renderer");
457    delete g_renderer;
458    g_renderer = NULL;
459    // Clean up cache directory after renderer's threads have finished
460    removeDirectory(cacheDir.c_str());
461
462    TRACE("Exiting GeoVis Server");
463
464    closeLog();
465    exitService();
466
467    return 0;
468}
Note: See TracBrowser for help on using the repository browser.