source: geovis/trunk/RenderServer.cpp @ 4628

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

Add some Makefile flags for sleep throttling settings

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