view native/OpenHaptics/XYPad/uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.cpp @ 1:46671fc7d649 tip

fixed "window" message bug and brought the message outside the haptic device monitor
author Fiore Martin <f.martin@qmul.ac.uk>
date Fri, 13 Mar 2015 13:02:16 +0000
parents 011caca7515a
children
line wrap: on
line source
/*
XYPad - a haptic xy-pad that uses the jHapticGUI library

Copyright (C) 2015  Queen Mary University of London (http://depic.eecs.qmul.ac.uk/)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice.h"
#include "HapticScene.h"
#include "HapticSceneFactory.h"

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>

#include <iostream>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>


using namespace jhapticgui;
 

/* Function declarations */
void display(void);
void reshape(int width, int height);
void idle(void);

void send(const Message &m); // haptic scene callback function

void checkExceptions(JNIEnv *env, char* what);
void initJniVariables(void);
void die(char* msg);

void Notify(jobject *monitor); // macro for the java object.notify() 
void MonitorEnter(jobject *monitor); // macro for the entering a syncrhronized block in java
void MonitorExit(jobject *monitor); // macro for the exiting a syncrhronized block in java

/* Variables */
static JNIEnv *env;
static jobject *thisObj;
static jobject hapticListener;
static std::unique_ptr<HapticScene> scene;
static Message message;

/* JNI Variables */

static jfieldID disposeFieldID;
static jfieldID deviceInitFailedFieldID;
static jfieldID deviceInitDoneFieldID;
static jfieldID messageReadyFieldID;
static jfieldID commandFieldID;
static jfieldID argsFieldID;
static jfieldID IDFieldID;

/* synchronization methods ids */
static jmethodID notifyMethodID;
static jmethodID waitListenerMethodID;
static jmethodID messageToListenerMethodID;



JNIEXPORT void JNICALL Java_uk_ac_qmul_eecs_depic_jhapticgui_HapticDevice_init
  (JNIEnv *environment, jobject obj, jint w, jint h){
	
	env = environment;
	thisObj = &obj;

	/* JNI stuff */
	initJniVariables();

	scene = makeHapticScene(send);

	/* try to initialize the haptic, tell the java thread about the success/failure. The endeavour   *
	 * takes the thisObj on the haptics java object in order to access the nodes and edges concurrently */
	MonitorEnter(thisObj);

	bool mustSayGoodbye = false;

	if(!scene->initHaptics()){
		/* set the field in the java class to comunicate the main thread (waiting   *
		 * for the initialization to complete) that the initialization failed       */
		env->SetBooleanField(*thisObj, deviceInitFailedFieldID, JNI_TRUE);
		mustSayGoodbye = true;
	}

	if(mustSayGoodbye)
		std::cout << "Failed to initialize haptic device" << std::endl;
	else
		std::cout << "Haptic device successfully initialized" << std::endl;

	/* initialization is done, set the flag for the java thread*/
	env->SetBooleanField(*thisObj, deviceInitDoneFieldID, JNI_TRUE);

	/* notify the java thread */
	Notify(thisObj);
	
	/* release the lock */
	MonitorExit(thisObj);
	
	if(mustSayGoodbye){
		/* stops the haptic listener */
		send(Message("stop","",0));
		/* initialization failed: return */
		return;
	}

	/* haptic device is initialized, now build the openGL window */

	/* fake main argv and argc as this is a dll */
	char *argv[1] = {"Haptics"};
	int argc = 1;

	/* glut initialization */
    glutInit(&argc, argv);
	
	glutInitWindowSize( w, h );
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow( "Haptic Device Window" );

	/* set up GLUT callback functions */
	glutDisplayFunc ( display  );
	glutReshapeFunc ( reshape );
	glutIdleFunc( idle );

	scene->initGL();

#ifdef FREEGLUT
	/* with this option set freeglut allows to stop the main 
	   loop without teminating the whole application   */
	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
#endif
	glutMainLoop();
	
	return;
}

/* GLUT display callback */
void display(){
	
	/* takes the lock on thisObj */
	MonitorEnter(thisObj);
	
	/* check if there is a dispose request */
	if( env->GetBooleanField(*thisObj,disposeFieldID) == JNI_TRUE ){
		/* set back to false to prevent the java thread from waiting again */
		env->SetBooleanField(*thisObj,disposeFieldID,JNI_FALSE);
		/* stops the haptic listener */
		send(Message("stop","",0));
		/* notify the java thread that this thread is about to die */
		Notify(thisObj);

		/* release the lock on this object */
		MonitorExit(thisObj);
#ifdef FREEGLUT
		/* onlu FREEGLUT allows to exit the main loop */
		glutLeaveMainLoop ();
		return;
#else
		/* if freeglut is not used, the whole application must be terminated */
		die("Haptic device disposed");
#endif

	}

	/* check if there is a message ready for the java thread */
	bool messageReady = (env->GetBooleanField(*thisObj,messageReadyFieldID) == JNI_TRUE);
	if(messageReady){
		/* get the id of the field */
		jint ID = env->GetIntField(*thisObj, IDFieldID);
		jstring command = static_cast<jstring>(env->GetObjectField(*thisObj, commandFieldID));
		jstring args = static_cast<jstring>(env->GetObjectField(*thisObj, argsFieldID));

		/* get lenght of the strings */
		int commandLen = env->GetStringLength(command);
		int argsLen = env->GetStringLength(args);

		/* read the command fields into the command struct */
		env->GetStringUTFRegion(command, 0, commandLen, message.command);
		env->GetStringUTFRegion(args, 0, argsLen, message.args);
		message.ID = ID;
		
		/* set the flag back to false */
		env->SetBooleanField(*thisObj, messageReadyFieldID, JNI_FALSE);

		/* clean up references */
		env->DeleteLocalRef(command);
		env->DeleteLocalRef(args);
		/* notify the java thread, waiting for the command to be read */
		Notify(thisObj);
	}
	
	/* release the lock. HapticScene methods from now on can execute commands without deadlock risk */
	MonitorExit(thisObj);

	static bool once = false;
	if (!once){
		once = true;
		send(Message("window", "", 0));
	}

	unsigned int messageUpdate = 0;
	if(messageReady)
		messageUpdate = scene->processMessage(message);

	/* --- start drawing --- */
	scene->beginFrame();

	/* draw the diagram graphicly and, if the collection has changed, haptically too */
	scene->drawCursor();

	/* the drawing can be affected by a message from the Java thread but also 
	   by the user actions (move, push button etc.). That's why callbacksUpdate
	   value from the previous loop is passed as an arg as well */
	static unsigned int callbacksUpdate = 0;
	scene->drawScene(messageUpdate, callbacksUpdate);
	
	scene->endFrame();

	/* check if any callback has been issued :  */
	callbacksUpdate = scene->checkCallbacks();

	/* swap graphic buffers. */
	glutSwapBuffers();
}

/* GLUT reshape callback */
void reshape (int w, int h){
	scene->setSize(w, h);
}

/* GLUT idle callback */
void idle(){
	glutPostRedisplay();
}


/* initialize all the variables needed for the jni access to java fields */
void initJniVariables(void){
	/* --- CLASSES --- */
	/* this class */

	jclass hapticDeviceClass = env->GetObjectClass(*thisObj);
	if(hapticDeviceClass == NULL){
		die("Could not find the Haptics class");
	}
	/* the haptic listener, member of this class */
	jclass hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/depic/jhapticgui/HapticListener;");
	if(hapticListenerClass == NULL){
		die("Could not find the haptic listener class");
	}
	
	/* the Object class for wait and notify methods */
	jclass objectClass = env->FindClass("Ljava/lang/Object;");
	if(objectClass == NULL ){
		die("Could not find te Object class");
	}

	/* --- FIELD ID's --- */

	/* boolean set by this thread to notify the java thread the unsuccessful initialization of haptic device */
	deviceInitFailedFieldID = env->GetFieldID(hapticDeviceClass,"initFailed", "Z");
	if(deviceInitFailedFieldID == NULL){
		die("failed to find the initFailed field id");
	}

	/* boolean set by this thread to notify the java thread the initialization of haptic device is done */
	deviceInitDoneFieldID = env->GetFieldID(hapticDeviceClass,"initDone", "Z");
	if(deviceInitDoneFieldID == NULL){
		die("failed to find initDone field id");
	}

	/* boolean set by the java thread to notify this thread the program has been shut down */
	disposeFieldID = env->GetFieldID(hapticDeviceClass, "dispose", "Z");
	if(disposeFieldID == NULL){
		die("failed to find the disposeFieldID field id");
	}

	messageReadyFieldID = env->GetFieldID(hapticDeviceClass,"messageReady","Z");
	if(messageReadyFieldID == NULL){
		die("failed to find the messageReadyFieldID field id");
	}

	commandFieldID = env->GetFieldID(hapticDeviceClass,"command","Ljava/lang/String;");
	if(commandFieldID == NULL){
		die("failed to find the commandFieldID field id");
	}

	argsFieldID = env->GetFieldID(hapticDeviceClass,"args","Ljava/lang/String;");
	if(argsFieldID == NULL){
		die("failed to find the argsFieldID field id");
	}

	IDFieldID = env->GetFieldID(hapticDeviceClass,"ID","I");
	if(IDFieldID == NULL){
		die("failed to find the IDFieldID field id");
	}

	jfieldID hapticListenerFieldID = env->GetFieldID(hapticDeviceClass,"hapticListener","Luk/ac/qmul/eecs/depic/jhapticgui/HapticListener;"); 
	hapticListener = env->GetObjectField(*thisObj,hapticListenerFieldID);
	if(hapticListener == NULL){
		die("failed to find hapticListener field id");
	}

	/* --- SYNCHRONIZATION METHODS ID --- */
	/* this.notify() */
	notifyMethodID = env->GetMethodID(objectClass,"notify","()V");
	if(notifyMethodID == NULL){
		die("failed to find the notify method id");
	}

	/* listener.wait() */
	waitListenerMethodID = env->GetMethodID(objectClass,"wait","()V");
	if(waitListenerMethodID == NULL){
		die("failed to find the wait method id");
	}

	messageToListenerMethodID = env->GetMethodID(hapticListenerClass,"addMessage","(Ljava/lang/String;Ljava/lang/String;I)V");
	if(messageToListenerMethodID == NULL){
		die("failed to find the addMessage method id");
	}
	
	/* clean up local references */
	env->DeleteLocalRef(hapticDeviceClass);
	env->DeleteLocalRef(hapticListenerClass);
	env->DeleteLocalRef(objectClass);
}

/* sends a message to the java thread */
void send(const Message &m){
	
	jstring jstrCommand = env->NewStringUTF(m.command);
	jstring jstrArgs = env->NewStringUTF(m.args); 

	env->CallVoidMethod(hapticListener,messageToListenerMethodID,jstrCommand,jstrArgs,m.ID);
	checkExceptions(env,"Could not call hapticListener.send()");

	env->DeleteLocalRef(jstrCommand);
	env->DeleteLocalRef(jstrArgs);
}

void checkExceptions(JNIEnv *env, char* what){
	if(env->ExceptionOccurred()){
		std::cerr << "Exception occurred!!!" << std::endl;
		std::cerr << what << std::endl;
		env->ExceptionDescribe();
		exit(-1);
	}
}


void die(char* msg){
	std::cerr << msg << std::endl;
	exit(-1);
}


void Notify(jobject *monitor){
	env->CallVoidMethod(*monitor,notifyMethodID);
	checkExceptions(env, "Could not call notify()");
}


void MonitorEnter(jobject *monitor){
	if(env->MonitorEnter(*monitor) != JNI_OK){
		die("Could not allocate memory for monitor enter");
	}
}

void MonitorExit(jobject *monitor){
	if(env->MonitorExit(*monitor) != JNI_OK){
		die("Could not release memory for monitor exit ");
	}
}