fiore@5: #include "uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.h" fiore@5: #include "stdafx.h" fiore@5: #include "GraphicManager.h" fiore@5: #include "HapticManager.h" fiore@5: #include "HapticException.h" fiore@5: #include fiore@5: #include fiore@5: #include fiore@5: #include fiore@5: #include "utils.h" fiore@5: fiore@5: #define CURSOR_SIZE_PIXELS 20 fiore@5: fiore@5: enum {JMP_OK =0, JMP_EXIT }; fiore@5: fiore@5: /************************** fiore@5: Function prototypes. fiore@5: ***************************/ fiore@5: /* callbacks */ fiore@5: void displayCallback(void); fiore@5: void reshapeCallback(int width, int height); fiore@5: void idleCallback(void); fiore@5: void exitProcedure(void); fiore@5: void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); fiore@5: fiore@5: void drawCursor(); fiore@5: void updateWorkspace(); fiore@5: fiore@5: void initJniVariables(void); fiore@5: /************************* fiore@5: global variables fiore@5: **************************/ fiore@5: static GraphicManager *gManager; fiore@5: static HapticManager *hManager; fiore@5: static CollectionsManager *cManager; fiore@5: JNIEnv *env; fiore@5: jobject *lock; fiore@5: static int width; fiore@5: static int height; fiore@5: static jmp_buf jmpenv; fiore@5: static int jmpval = 0; fiore@5: /* jni variables */ fiore@5: jclass hapticClass; fiore@5: jclass hapticListenerClass; fiore@5: jfieldID shutdownfieldId; fiore@5: jfieldID newHapticIdfieldId; fiore@5: jfieldID dumpHapticIdfieldId; fiore@5: jfieldID currentHapticIdfieldId; fiore@5: jfieldID hapticInitFailedfieldId; fiore@5: jfieldID attractTofieldId; fiore@5: jfieldID attractToHapticIdFieldId; fiore@5: jfieldID pickUpfieldId; fiore@5: jfieldID pickUpHapticIdFieldId; fiore@5: jfieldID hapticListenerFieldId; fiore@5: jfieldID cmdFieldId; // belongs to the haptic listener fiore@5: jfieldID diagramElementFieldId; // belongs to the haptic listener fiore@5: jfieldID xFieldId; // belongs to the haptic listener fiore@5: jfieldID yFieldId; // belongs to the haptic listener fiore@5: jfieldID startXFieldId; // belongs to the haptic listener fiore@5: jfieldID startYFieldId; // belongs to the haptic listener fiore@5: jobject hapticListener; fiore@5: jmethodID notifyMethodId; fiore@5: jmethodID notifyListenerMethodId; fiore@5: jmethodID waitMethodId; fiore@5: fiore@5: /******************************************************************************* fiore@5: Initializes GLUT for displaying a simple haptic scene. fiore@5: *******************************************************************************/ fiore@5: JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_initOmni fiore@5: (JNIEnv *environment, jobject obj, jint w, jint h){ fiore@5: env = environment; fiore@5: lock = &obj; fiore@5: /* fake main argv and argc as this is a dll */ fiore@5: char *argv[1] = {"OmniHaptics"}; fiore@5: int argc = 1; fiore@5: fiore@5: initJniVariables(); fiore@5: fiore@5: /* glut initialization */ fiore@5: glutInit(&argc, argv); fiore@5: glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); fiore@5: glutInitWindowSize(w, h); fiore@5: glutCreateWindow("CCmI Diagram Haptics"); fiore@5: fiore@5: /* glut callbacks */ fiore@5: glutDisplayFunc(displayCallback); fiore@5: glutReshapeFunc(reshapeCallback); fiore@5: glutIdleFunc(idleCallback); fiore@5: fiore@5: cManager = new CollectionsManager(env,lock); fiore@5: hManager = new HapticManager(cManager,hapticCommandCB); fiore@5: gManager = new GraphicManager(cManager,hManager); fiore@5: cManager->init(); fiore@5: gManager->init(); fiore@5: fiore@5: // try to initialize the haptic, tell the java thread about the success/failure. The endeavour fiore@5: // takes the lock on the haptics java object in order to access the nodes and edges concurrently fiore@5: if(env->MonitorEnter(*lock) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic thread monitor"); fiore@5: } fiore@5: checkExceptions(env, "Could not enter monitor on Haptics"); fiore@5: fiore@5: bool mustSayGoodbye = false; fiore@5: try{ fiore@5: hManager->init(); fiore@5: }catch (HapticException e){ fiore@5: /* set the field in the java class to comunicate the main thread (waiting * fiore@5: * for the initialization to complete) that the initialization failed */ fiore@5: env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE); fiore@5: mustSayGoodbye = true; fiore@5: } fiore@5: fiore@5: if(mustSayGoodbye) fiore@5: std::cout << "Failed to initialize Omni Haptic device" << std::endl; fiore@5: else fiore@5: std::cout << "Omni Haptic device successfully initialized" << std::endl; fiore@5: fiore@5: // notify the other thread fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env,"Could not call notify() on Haptics"); fiore@5: fiore@5: //release the lock fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: std::cerr << "Could not release memory for haptic thread monitor" << std::endl; fiore@5: exit(-1); fiore@5: } fiore@5: fiore@5: if(mustSayGoodbye) fiore@5: /* initialization failed: return */ fiore@5: return -1; fiore@5: fiore@5: /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ fiore@5: jmpval = setjmp(jmpenv); fiore@5: fiore@5: /* star the loop*/ fiore@5: if(jmpval == JMP_OK){ fiore@5: glutMainLoop(); fiore@5: } fiore@5: fiore@5: exitProcedure(); fiore@5: fiore@5: return 0; fiore@5: } fiore@5: fiore@5: /******************************************************************************* fiore@5: GLUT callback for redrawing the view. fiore@5: *******************************************************************************/ fiore@5: void displayCallback(){ fiore@5: fiore@5: // takes the lock on the haptics java obejct in order to access the nodes and edges concurrently fiore@5: if(env->MonitorEnter(*lock) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic thread monitor"); fiore@5: } fiore@5: checkExceptions(env,"Could not enter monitor on Haptics"); fiore@5: fiore@5: // check if there is a shutdown request fiore@5: if( env->GetBooleanField(*lock,shutdownfieldId) == JNI_TRUE ){ fiore@5: // notify the other thread that this thread is about to die fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env, "Could not call notify() on Haptics"); fiore@5: // release the lock fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: std::cerr << "Could not release memory for haptic thread monitor" << std::endl; fiore@5: } fiore@5: longjmp(jmpenv,JMP_EXIT); fiore@5: } fiore@5: fiore@5: // check if the user asked to be attracted to a node fiore@5: jboolean attractTo = env->GetBooleanField(*lock,attractTofieldId); fiore@5: jint attractToDiagramId = 0; fiore@5: if(attractTo == JNI_TRUE){ fiore@5: env->SetBooleanField(*lock,attractTofieldId,JNI_FALSE); fiore@5: jint attractToHapticId = env->GetIntField(*lock,attractToHapticIdFieldId); fiore@5: if(cManager->isNode(attractToHapticId)){ fiore@5: attractToDiagramId = cManager->getNodeDataFromID(attractToHapticId).diagramId; fiore@5: }else{ fiore@5: attractToDiagramId = cManager->getEdgeDataFromID(attractToHapticId).diagramId; fiore@5: } fiore@5: hManager->setAttractTo(attractToHapticId); fiore@5: } fiore@5: // check if the user picked up a node fiore@5: jboolean pickUp = env->GetBooleanField(*lock,pickUpfieldId); fiore@5: jint pickUpDiagramId = 0; fiore@5: if(pickUp == JNI_TRUE){ fiore@5: env->SetBooleanField(*lock,pickUpfieldId,JNI_FALSE); fiore@5: jint pickUpHapticId = env->GetIntField(*lock,pickUpHapticIdFieldId); fiore@5: if(cManager->isNode(pickUpHapticId)){ fiore@5: pickUpDiagramId = cManager->getNodeDataFromID(pickUpHapticId).diagramId; fiore@5: }else{ fiore@5: pickUpDiagramId = cManager->getEdgeDataFromID(pickUpHapticId).diagramId; fiore@5: } fiore@5: hManager->pickUp(pickUpDiagramId); fiore@5: } fiore@5: fiore@5: // draw the scene graphically and haptically fiore@5: hManager->draw(); fiore@5: gManager->draw(); fiore@5: fiore@5: // check whether the java thread needs to either create or dump an haptic id fiore@5: jboolean needsNewHapticId = env->GetBooleanField(*lock,newHapticIdfieldId); fiore@5: if(needsNewHapticId == JNI_TRUE){ fiore@5: // set int currentHapticId of class Haptics with a new generated id fiore@5: jint newHapticid = hlGenShapes(1); fiore@5: env->SetIntField(*lock, currentHapticIdfieldId, newHapticid); fiore@5: //set the boolean field to false as the other thread now has an id fiore@5: env->SetBooleanField(*lock, newHapticIdfieldId, JNI_FALSE); fiore@5: //notify the other thread fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env, "Could not call notify() on Haptics"); fiore@5: } fiore@5: jboolean needsDumpOldHapticId = env->GetBooleanField(*lock, dumpHapticIdfieldId); fiore@5: if(needsDumpOldHapticId == JNI_TRUE){ fiore@5: // get the id of the deleted element from the other thread fiore@5: jint oldHapticId = env->GetIntField(*lock,currentHapticIdfieldId); fiore@5: // free the old haptic id fiore@5: hlDeleteShapes(oldHapticId,1); fiore@5: // set the boolean field as the id has been cleaned up fiore@5: env->SetBooleanField(*lock,dumpHapticIdfieldId,JNI_FALSE); fiore@5: // notify the other thread fiore@5: env->CallVoidMethod(*lock,notifyMethodId); fiore@5: checkExceptions(env, "Could not call notify() on Haptics"); fiore@5: } fiore@5: fiore@5: /* release lock */ fiore@5: if(env->MonitorExit(*lock) != JNI_OK){ fiore@5: stopExecution("Could not release memory for haptic thread monitor"); fiore@5: } fiore@5: fiore@5: /* it's important that this call be outside the monitors, else a deadlock occurs */ fiore@5: // Call any event callbacks that have been triggered. fiore@5: if(attractTo == JNI_TRUE){ fiore@5: if(hManager->wasAlreadyTouchingAttractingElement()){ fiore@5: hapticCommandCB('t',attractToDiagramId,0,0,0,0); fiore@5: } fiore@5: } fiore@5: fiore@5: hlCheckEvents(); fiore@5: /* button one is not based on events only , but also on time lapse as the single click is effective * fiore@5: * after an amount of time (if another click is not issued). Therefore we need to continuosly for the * fiore@5: * button status, hence the work must be done here and not in the click callbacks */ fiore@5: HapticManager::ClickStatus click = hManager->getButton1Status(); fiore@5: if(click == HapticManager::ONE_CLICK){ fiore@5: hManager->doButton1Click(false); fiore@5: }else if(click == HapticManager::TWO_CLICK){ fiore@5: hManager->doButton1Click(true); fiore@5: } fiore@5: fiore@5: glutSwapBuffers(); fiore@5: } fiore@5: /******************************************************************************* fiore@5: GLUT callback for reshaping the window. This is the main place where the fiore@5: viewing and workspace transforms get initialized. fiore@5: *******************************************************************************/ fiore@5: void reshapeCallback(int w, int h){ fiore@5: static const double kPI = 3.1415926535897932384626433832795; fiore@5: static const double kFovY = 40; fiore@5: fiore@5: static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */); fiore@5: static const double farDist = nearDist + 2.0; fiore@5: double aspect; fiore@5: width = w; fiore@5: height = h; fiore@5: fiore@5: cManager->setScreenHeight(h); fiore@5: glViewport(0, 0, width, height); fiore@5: // Compute the viewing parameters based on a fixed fov and viewing fiore@5: // a canonical box centered at the origin. fiore@5: aspect = (double) width/height ; fiore@5: fiore@5: glMatrixMode(GL_PROJECTION); fiore@5: glLoadIdentity(); fiore@5: gluPerspective(kFovY, aspect,nearDist, farDist); fiore@5: // Place the camera down the Z axis looking at the origin. fiore@5: glMatrixMode(GL_MODELVIEW); fiore@5: glLoadIdentity(); fiore@5: gluLookAt(0, 0, nearDist + 1.0, fiore@5: 0, 0, 0, fiore@5: 0, 1, 0); fiore@5: fiore@5: hduVector3Dd origin; fiore@5: fromScreen(hduVector3Dd(0, 0, 0),origin); fiore@5: glLoadIdentity(); fiore@5: gluLookAt(-origin[0],origin[1], nearDist + 1.0, fiore@5: -origin[0],origin[1], 0, fiore@5: 0, 1, 0); fiore@5: fiore@5: hduVector3Dd end; fiore@5: fromScreen(hduVector3Dd(w, h, 0),end); fiore@5: fiore@5: //glTranslatef(end[0]-origin[0],-(end[1]-origin[1]),0); fiore@5: fiore@5: updateWorkspace(); fiore@5: } fiore@5: fiore@5: fiore@5: /******************************************************************************* fiore@5: Use the current OpenGL viewing transforms to initialize a transform for the fiore@5: haptic device workspace so that it's properly mapped to world coordinates. fiore@5: *******************************************************************************/ fiore@5: void updateWorkspace(){ fiore@5: GLdouble modelview[16]; fiore@5: GLdouble projection[16]; fiore@5: GLint viewport[4]; fiore@5: fiore@5: glGetDoublev(GL_MODELVIEW_MATRIX, modelview); fiore@5: glGetDoublev(GL_PROJECTION_MATRIX, projection); fiore@5: glGetIntegerv(GL_VIEWPORT, viewport); fiore@5: fiore@5: hlMatrixMode(HL_TOUCHWORKSPACE); fiore@5: hlLoadIdentity(); fiore@5: fiore@5: // Fit haptic workspace to view volume. fiore@5: hluFitWorkspace(projection); fiore@5: fiore@5: // Compute cursor scale. fiore@5: gManager->gCursorScale = hluScreenToModelScale(modelview, projection, viewport); fiore@5: gManager->gCursorScale *= CURSOR_SIZE_PIXELS; fiore@5: fiore@5: hduVector3Dd p0, p1; fiore@5: bool bNoError; fiore@5: fiore@5: bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0); fiore@5: assert(bNoError); fiore@5: fiore@5: bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1); fiore@5: assert(bNoError); fiore@5: fiore@5: double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0); fiore@5: gManager->gWorldScale = m_windowTworldScale; fiore@5: } fiore@5: /******************************************************************************* fiore@5: GLUT callback for idle state. Use this as an opportunity to request a redraw. fiore@5: Checks for HLAPI errors that have occurred since the last idle check. fiore@5: *******************************************************************************/ fiore@5: void idleCallback(){ fiore@5: HLerror error; fiore@5: fiore@5: while (HL_ERROR(error = hlGetError())){ fiore@5: std::cerr << "HL Error: " << error.errorCode << std::endl <GetObjectClass(*lock); fiore@5: if(hapticClass == NULL){ fiore@5: stopExecution("Could not find the Haptics class"); fiore@5: } fiore@5: // the haptic listener, member of this class fiore@5: hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); fiore@5: if(hapticListenerClass == NULL){ fiore@5: stopExecution("Could not find the haptic listener class"); fiore@5: } fiore@5: fiore@5: /* --- FIELD IDS --- */ fiore@5: // boolean set by the java thread when an element is added and it needs a new id from the haptic library fiore@5: newHapticIdfieldId = env->GetFieldID(hapticClass,"newHapticId", "Z"); fiore@5: if(newHapticIdfieldId == NULL){ fiore@5: stopExecution("failed to find the newHapticId field id"); fiore@5: } fiore@5: fiore@5: // boolean set by the java thread when an element is added and it needs a new id from the haptic library fiore@5: dumpHapticIdfieldId = env->GetFieldID(hapticClass,"dumpHapticId", "Z"); fiore@5: if(dumpHapticIdfieldId == NULL){ fiore@5: stopExecution("failed to find the dumpHapticId field id"); fiore@5: } fiore@5: fiore@5: // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device fiore@5: hapticInitFailedfieldId = env->GetFieldID(hapticClass,"hapticInitFailed", "Z"); fiore@5: if(hapticInitFailedfieldId == NULL){ fiore@5: stopExecution("failed to find the hapticInitFailedfieldId field id"); fiore@5: } fiore@5: fiore@5: // boolean set by the java thread to notify this thread the program has been shut down fiore@5: shutdownfieldId = env->GetFieldID(hapticClass, "shutdown", "Z"); fiore@5: if(shutdownfieldId == NULL){ fiore@5: stopExecution("failed to find the shutdownfieldId field id"); fiore@5: } fiore@5: fiore@5: // boolean set by the java thread when the user asks to sna fiore@5: attractTofieldId = env->GetFieldID(hapticClass, "attractTo" , "Z"); fiore@5: if(attractTofieldId == NULL){ fiore@5: stopExecution("failed to find the attractTo field id"); fiore@5: } fiore@5: fiore@5: attractToHapticIdFieldId = env->GetFieldID(hapticClass, "attractToHapticId", "I"); fiore@5: if(attractToHapticIdFieldId == NULL){ fiore@5: stopExecution("failed to find the attractToHapticId field id"); fiore@5: } fiore@5: fiore@5: pickUpfieldId = env->GetFieldID(hapticClass,"pickUp","Z"); fiore@5: if(pickUpfieldId == NULL){ fiore@5: stopExecution("failed to find the pickUp field id"); fiore@5: } fiore@5: fiore@5: pickUpHapticIdFieldId = env->GetFieldID(hapticClass,"pickUpHapticId","I"); fiore@5: if(pickUpHapticIdFieldId == NULL){ fiore@5: stopExecution("failed to find pickUpHapticId field id"); fiore@5: } fiore@5: fiore@5: hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); fiore@5: if(hapticListenerFieldId == NULL){ fiore@5: stopExecution("failed to find the hapticListenerThread field id"); fiore@5: } fiore@5: fiore@5: // variable to exchange values between threads fiore@5: currentHapticIdfieldId = env->GetFieldID(hapticClass,"currentHapticId","I"); fiore@5: if(currentHapticIdfieldId == NULL){ fiore@5: stopExecution("failed to find the currentHapticId field"); fiore@5: } fiore@5: fiore@5: cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C"); fiore@5: if(cmdFieldId == NULL){ fiore@5: stopExecution("failed to find the cmd field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I"); fiore@5: if(diagramElementFieldId == NULL){ fiore@5: stopExecution("failed to find the diagramElement field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: xFieldId = env->GetFieldID(hapticListenerClass, "x", "D"); fiore@5: if(xFieldId == NULL){ fiore@5: stopExecution("failed to find the x field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: yFieldId = env->GetFieldID(hapticListenerClass, "y", "D"); fiore@5: if(yFieldId == NULL){ fiore@5: stopExecution("failed to find the y field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D"); fiore@5: if(startXFieldId == NULL){ fiore@5: stopExecution("failed to find the x field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D"); fiore@5: if(startYFieldId == NULL){ fiore@5: stopExecution("failed to find the y field id of the hapticListener class"); fiore@5: } fiore@5: fiore@5: hapticListener = env->GetObjectField(*lock,hapticListenerFieldId); fiore@5: /* --- METHOD IDs --- */ fiore@5: // notify method fiore@5: notifyMethodId = env->GetMethodID(hapticClass,"notify","()V"); fiore@5: if(notifyMethodId == NULL){ fiore@5: stopExecution("failed to find the notify method id"); fiore@5: } fiore@5: fiore@5: notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V"); fiore@5: if(notifyListenerMethodId == NULL){ fiore@5: stopExecution("failed to find the notify method id"); fiore@5: } fiore@5: fiore@5: waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); fiore@5: if(waitMethodId == NULL){ fiore@5: stopExecution("failed to find the wait method id"); fiore@5: } fiore@5: } fiore@5: fiore@5: void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){ fiore@5: /* the haptic listener java thread is waiting for commands fiore@5: first set the variable, the Haptic Listener java thread will read after being notified, fiore@5: then notify and get it awake. Thus wait for the java thread to notify that the command fiore@5: has been accomplished. This is done as otherwise some commands might be neglected. as if the thread fiore@5: scheduler decides to execute twice this routine without executing the java thread in the middle then fiore@5: the former command gets overwritten by the latter. fiore@5: Note the monitor is hapticListener and not haptics as for the draw function. fiore@5: When in this routine, this thread does not hold the lock on haptics as if a command results in changing fiore@5: the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics. fiore@5: Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this fiore@5: thread to release the lock on haptics, that would result in a deadlock. fiore@5: */ fiore@5: /* now wake up the haptic listener */ fiore@5: if(env->MonitorEnter(hapticListener) != JNI_OK){ fiore@5: stopExecution("Could not allocate memory for haptic listener thread monitor"); fiore@5: } fiore@5: checkExceptions(env,"Could not enter monitor on the haptic listener"); fiore@5: env->SetCharField(hapticListener,cmdFieldId,cmd); fiore@5: env->SetIntField(hapticListener,diagramElementFieldId,ID); fiore@5: env->SetDoubleField(hapticListener,xFieldId,x); fiore@5: env->SetDoubleField(hapticListener,yFieldId,y); fiore@5: env->SetDoubleField(hapticListener,startXFieldId,startX); fiore@5: env->SetDoubleField(hapticListener,startYFieldId,startY); fiore@5: // wake the java thread up to execute the command fiore@5: env->CallVoidMethod(hapticListener,notifyListenerMethodId); fiore@5: checkExceptions(env, "Could not call notify() on HapticListener"); fiore@5: /* wait for the commands to be executed. Here is actually where the monitor is fiore@5: * freed and the java thread starts to execute the command, having been notified */ fiore@5: env->CallVoidMethod(hapticListener,waitMethodId); fiore@5: checkExceptions(env, "Could not call wait() on HapticListener"); fiore@5: if(env->MonitorExit(hapticListener) != JNI_OK){ fiore@5: stopExecution("Could not release memory for haptic listener thread monitor"); fiore@5: } fiore@5: }