Where am I

24.4. Modifying the Scene Graph

The critical issue for worker threads is how to safely modify the scene graph while other threads are either modifying the scene graph or traversing the scene graph. Similar to a database, we distinguish between scene graph readers and scene graph writers. A reader uses information contained in node fields, but does not modify the scene graph. Most action classes, for example SoGLRenderAction SoGLRenderAction SoGLRenderAction , are readers. A writer modifies the scene graph, either by changing field values or by adding and removing nodes. Any number of readers can access the scene graph at the same time, but only one writer can access the scene graph at any time (and no readers are allowed while the writer is modifying the scene graph).

Open Inventor currently implements a cooperative model of reader/writer control, using the SoDB SoDB SoDB methods readlock() / readunlock() and writelock() / writeunlock(). All action classes automatically request a read lock, by calling SoDB::readlock() , before beginning to traverse the scene graph. Any number of threads can be executing actions and be traversing the scene graph at the same time. Any thread that wants to modify the scene graph should request a write lock, by calling SoDB::writelock(), before making any change. SoDB::writelock() will not return until all readers have released their read locks (by calling SoDB::readunlock() ). Successfully acquiring the write lock ensures that no other threads are currently traversing (or modifying) the scene graph. After making its changes, the thread should release the write lock by calling SoDB::writeunlock() . Example 24.1, “ Request/Release exclusive access to scene graph” show an example of use of these exclusions methods.

Example 24.1.  Request/Release exclusive access to scene graph

// Request exclusive access to scene graph
  SoDB::writelock();

// Modify text node
  pTextNode->string.setValue(str);

// Release exclusive access as soon as possible
  SoDB::writeunlock();



A simple example named MT_Modify1 is provided in $OIVHOME/src/Inventor/MultiThread/examples. This example is based on the standard Inventor Mentor example Example 15.1, “ Using the Real-Time Global Field. In that example a digital clock is implemented by connecting the RealTime global field to an SoText3 SoText3 SoText3 string field. In the new example a digital clock is implemented by creating a separate thread which wakes up once every second and updates the string field of an SoText3 SoText3 SoText3 node. Some important points regarding Example 24.2, “ MT_Modify1.cxx”.

  1. Open Inventor must be initialized with multithread support. Call SoXt::threadInit() , not SoXt::init() .

  2. Each additional thread that will use Open Inventor objects must do per-thread initialization (for thread local storage).

    Call SoDB::threadInit() in each addition thread.

  3. This example uses Open Inventor’s portable SbThread class to create the thread, but this is only for convenience.

  4. If more than one thread may be accessing the scene graph (for example, the viewer in the main thread may be rendering), in order to safely modify the scene graph, the second thread must gain exclusive access by calling SoDB::writelock() .

Example 24.2.  MT_Modify1.cxx

#include <Inventor/SoDB.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoText3.h>

// Use Open Inventor's portable thread classes
#include <Inventor/threads/SbThread.h>

// Forward decls
  void *threadRout(void *_userData);
  void updateText(SoText3 *pTextNode);

////////////////////////////////////////////////////////////////////////
  int
  main(int argc, char **argv)
  {
  // Initialize Open Inventor
  // *** Be sure to initialize with multithread support! ***
  Widget myWindow = SoXt::threadInit(argv[0]);
    if (myWindow == NULL)
    {
    exit(1);
    }

  // Create scene graph ------------------------------------------------
  SoSeparator *root = new SoSeparator;
    root->ref();

    SoMaterial *myMaterial = new SoMaterial;
    myMaterial->diffuseColor.setValue(1.0, 0.0, 0.0);
    root->addChild(myMaterial);

    SoText3 *myText = new SoText3;
    root->addChild(myText);
    myText->string.setValue(SbTime::getTimeOfDay().formatDate());

  // Create viewer -----------------------------------------------------
    SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
    myViewer->setSceneGraph(root);
    myViewer->setTitle("Date & Time");
    myViewer->show();

  // Create thread to modify scene graph
  //
  // threadRout is the function the thread will execute
  // myText is passed to threadRout as userData
    SbThread *pModifyThread = SbThread::create(threadRout, (void*)myText);

    SoXt::show(myWindow);
    SoXt::mainLoop();

    return 0;
  }

////////////////////////////////////////////////////////////////////////
// Modify the text string

void
  updateText(SoText3 *pTextNode)
  {
  // Get date/time string
  SbString str = SbTime::getTimeOfDay().formatDate();

  // Request exclusive access to scene graph
    SoDB::writelock();

  // Modify text node
    pTextNode->string.setValue(str);

  // Release exclusive access as soon as possible
    SoDB::writeunlock();
  }

////////////////////////////////////////////////////////////////////////
// Thread action function

void *
  threadRout(void *userData)
  {
  // Every thread that makes Open Inventor calls must call SoDB::threadInit
  // first (and possibly other init methods, eg. SoInteraction::init).
  // This is required to setup thread local storage.
  SoDB::threadInit();

  // Get pointer to text node
    SoText3 *pTextNode = (SoText3 *)userData;

  // Wakeup interval
    const int seconds = 1;

#ifndef WIN32
  // On some Unix systems, Open Inventor's automatic redraw on scene
  // graph change does not work reliably when the scene graph is
  // changed in a non-UI thread (no event loop). This is an issue
  // with Xt timers that will be corrected in the next release.
  // For now, enabling the RealTimeSensor will ensure redraws happen.
    SoDB::enableRealTimeSensor(TRUE);
#endif

  // In this simple example the thread just runs continuously.
  // It will be cleaned up by the operating system when the program exits.

    while (1)
    {
#ifdef WIN32
    // Note the WIN32 sleep function takes milli-seconds, not seconds.
    Sleep(seconds * 1000);
#else
      sleep(seconds);
#endif
      updateText(pTextNode);
    }

  return NULL;
  }