X-Git-Url: http://nitlanguage.org diff --git a/lib/pnacl.nit b/lib/pnacl.nit index c7fb2ab..89ff3cf 100644 --- a/lib/pnacl.nit +++ b/lib/pnacl.nit @@ -13,21 +13,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# Targets the PNaCl platform + +# Provides PNaCl support for Nit. # # To use this module and compile for PNaCl, you must install the # NaCl SDK (This file is based on Pepper 33). # If NACL_SDK_ROOT is not set in your PATH, you have to work in # 'nacl_sdk/pepper_your_pepper_version/getting_started/your_project_folder'. -# -# Provides PNaCl support for Nit module pnacl is platform -`{ - #include - #include - #include - #include + +import standard +intrude import standard::stream + +in "C Header" `{ #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppp.h" #include "ppapi/c/ppp_instance.h" @@ -37,9 +35,60 @@ module pnacl is platform #include "ppapi/c/ppp_messaging.h" #include "ppapi/c/ppb_var_dictionary.h" #include "ppapi/c/ppb_var_array.h" +`} + +`{ + #include + #include + #include + #include + #include + #include + #include + + #define MAX_DICTIONARY_QUEUE_SIZE 200 + #define MAX_MESSAGE_QUEUE_SIZE 10 extern int nit_main(int, char**); + /* A working thread for Nit. */ + static pthread_t g_nit_thread; + + /* Mutex that guards the queues. */ + static pthread_mutex_t g_dictionary_queue_mutex; + static pthread_mutex_t g_message_queue_mutex; + + /* Condition variables that are signalled when the queues are not empty. */ + static pthread_cond_t g_dictionary_queue_not_empty_cond; + static pthread_cond_t g_message_queue_not_empty_cond; + + /** Circular queues of dictionaries and messages from JavaScript to be handled. + * + * If g_queue_start < g_queue_end: + * all elements in the range [g_queue_start, g_queue_end) are valid. + * If g_queue_start > g_queue_end: + * all elements in the ranges [0, g_queue_end) and + * [g_queue_start, MAX_QUEUE_SIZE) are valid. + * If g_queue_start == g_queue_end, and g_queue_size > 0: + * all elements in the g_queue are valid. + * If g_queue_start == g_queue_end, and g_queue_size == 0: + * No elements are valid. */ + static struct PP_Var g_dictionary_queue[MAX_DICTIONARY_QUEUE_SIZE]; + static char* g_message_queue[MAX_MESSAGE_QUEUE_SIZE]; + + /* The index of the head of the queues. */ + static int g_dictionary_queue_start = 0; + static int g_message_queue_start = 0; + + /* The index of the tail of the queues, non-inclusive. */ + static int g_dictionary_queue_end = 0; + static int g_message_queue_end = 0; + + /* The size of the queues. */ + static int g_dictionary_queue_size = 0; + static int g_message_queue_size = 0; + + /* PNaCl interfaces. */ const PPB_Messaging* g_varMessagingInterface; const PPB_Var* g_varInterface; const PPB_VarDictionary* g_varDictionaryInterface; @@ -48,6 +97,140 @@ module pnacl is platform PP_Instance g_instance; PnaclApp app; + /* A wrapper to launch the Nit main on a new thread. */ + void* WrapperNitMain(void* arg) { + nit_main(0, NULL); + return NULL; + } + + /** Return whether the queues are empty. + * + * NOTE: this function assumes g_queue_mutex lock is held. + * @return non-zero if the queue is empty. */ + static int IsDictionaryQueueEmpty() { return g_dictionary_queue_size == 0; } + static int IsMessageQueueEmpty() { return g_message_queue_size == 0; } + + /** Return whether the queues are full. + * + * NOTE: this function assumes g_queue_mutex lock is held. + * @return non-zero if the queue is full. */ + static int IsDictionaryQueueFull() { return g_dictionary_queue_size == MAX_DICTIONARY_QUEUE_SIZE; } + static int IsMessageQueueFull() { return g_message_queue_size == MAX_MESSAGE_QUEUE_SIZE; } + + /* Initialize the queues. */ + void InitializeQueues() { + pthread_mutex_init(&g_dictionary_queue_mutex, NULL); + pthread_cond_init(&g_dictionary_queue_not_empty_cond, NULL); + pthread_mutex_init(&g_message_queue_mutex, NULL); + pthread_cond_init(&g_message_queue_not_empty_cond, NULL); + } + + /** Enqueue a dictionary (i.e. add to the end) + * + * If the queue is full, the dictionary will be dropped. + * + * NOTE: this function assumes g_dictionary_queue_mutex is _NOT_ held. + * @param[in] dictionary, the dictionary to enqueue. + * @return non-zero if the dictionary was added to the queue. */ + int EnqueueDictionary(struct PP_Var dictionary) { + pthread_mutex_lock(&g_dictionary_queue_mutex); + + /* We shouldn't block the main thread waiting for the queue to not be full, + * so just drop the dictionary. */ + if (IsDictionaryQueueFull()) { + pthread_mutex_unlock(&g_dictionary_queue_mutex); + return 0; + } + + g_dictionary_queue[g_dictionary_queue_end] = dictionary; + g_dictionary_queue_end = (g_dictionary_queue_end + 1) % MAX_DICTIONARY_QUEUE_SIZE; + g_dictionary_queue_size++; + + pthread_cond_signal(&g_dictionary_queue_not_empty_cond); + + pthread_mutex_unlock(&g_dictionary_queue_mutex); + + return 1; + } + + /** Enqueue a message (i.e. add to the end) + * + * If the queue is full, the message will be dropped. + * + * NOTE: this function assumes g_message_queue_mutex is _NOT_ held. + * @param[in] message The message to enqueue. + * @return non-zero if the message was added to the queue. */ + int EnqueueMessage(char* message) { + pthread_mutex_lock(&g_message_queue_mutex); + + /* We shouldn't block the main thread waiting for the queue to not be full, + * so just drop the message. */ + if (IsMessageQueueFull()) { + pthread_mutex_unlock(&g_message_queue_mutex); + return 0; + } + + g_message_queue[g_message_queue_end] = message; + g_message_queue_end = (g_message_queue_end + 1) % MAX_MESSAGE_QUEUE_SIZE; + g_message_queue_size++; + + pthread_cond_signal(&g_message_queue_not_empty_cond); + + pthread_mutex_unlock(&g_message_queue_mutex); + + return 1; + } + + /** Dequeue a dictionary and return it. + * + * This function blocks until a dictionary is available. It should not be called + * on the main thread. + * + * NOTE: this function assumes g_dictionary_queue_mutex is _NOT_ held. + * @return The dictionary at the head of the queue. */ + struct PP_Var DequeueDictionary() { + struct PP_Var dictionary = g_varDictionaryInterface->Create(); + + pthread_mutex_lock(&g_dictionary_queue_mutex); + + while (IsDictionaryQueueEmpty()) { + pthread_cond_wait(&g_dictionary_queue_not_empty_cond, &g_dictionary_queue_mutex); + } + + dictionary = g_dictionary_queue[g_dictionary_queue_start]; + g_dictionary_queue_start = (g_dictionary_queue_start + 1) % MAX_DICTIONARY_QUEUE_SIZE; + g_dictionary_queue_size--; + + pthread_mutex_unlock(&g_dictionary_queue_mutex); + + return dictionary; + } + + /** Dequeue a message and return it. + * + * This function blocks until a message is available. It should not be called + * on the main thread. + * + * NOTE: this function assumes g_queue_mutex is _NOT_ held. + * @return The message at the head of the queue. */ + char* DequeueMessage() { + char* message = NULL; + + pthread_mutex_lock(&g_message_queue_mutex); + + while (IsMessageQueueEmpty()) { + pthread_cond_wait(&g_message_queue_not_empty_cond, &g_message_queue_mutex); + } + + message = g_message_queue[g_message_queue_start]; + g_message_queue_start = (g_message_queue_start + 1) % MAX_MESSAGE_QUEUE_SIZE; + g_message_queue_size--; + + pthread_mutex_unlock(&g_message_queue_mutex); + + return message; + } + /* Posts a string message to JS. */ void PostMessage(char* message) { /* Create PP_Var containing the message body. */ @@ -69,9 +252,21 @@ module pnacl is platform g_varMessagingInterface->PostMessage(g_instance, v); } + /* char* to PP_Var. */ + static struct PP_Var CStrToVar(const char* str) { + if (g_varInterface != NULL) { + return g_varInterface->VarFromUtf8(str, strlen(str)); + } + return PP_MakeUndefined(); + } + static PP_Bool Instance_DidCreate(PP_Instance instance, uint32_t argc, const char* argn[], const char* argv[]) { g_instance = instance; - nit_main(0, NULL); + + /* Initialization of the queues and creation of the thread for Nit. */ + InitializeQueues(); + pthread_create(&g_nit_thread, NULL, &WrapperNitMain, NULL); + return PP_TRUE; } @@ -92,24 +287,22 @@ module pnacl is platform return PP_FALSE; } - /* char* to PP_Var. */ - static struct PP_Var CStrToVar(const char* str) { - if (g_varInterface != NULL) { - return g_varInterface->VarFromUtf8(str, strlen(str)); - } - return PP_MakeUndefined(); - } - /* Called when JS sends something, is set to accept Strings or Dictionaries, returns an error if received object is not a String or Dictionary. */ void Messaging_HandleMessage(PP_Instance instance, struct PP_Var varMessage) { if(varMessage.type == PP_VARTYPE_DICTIONARY) { - PnaclApp_handle_dictionary(app, &varMessage); + if(!EnqueueDictionary(varMessage)) { + struct PP_Var errorMessage = CStrToVar("QueueFull : dropped dictionary because the queue was full."); + g_varMessagingInterface->PostMessage(g_instance, errorMessage); + } } else if(varMessage.type == PP_VARTYPE_STRING) { uint32_t len; char* message = (char*)g_varInterface->VarToUtf8(varMessage, &len); - PnaclApp_handle_message(app, NativeString_to_s_with_length(message, len)); + if(!EnqueueMessage(message)) { + struct PP_Var errorMessage = CStrToVar("QueueFull : dropped message because the queue was full."); + g_varMessagingInterface->PostMessage(g_instance, errorMessage); + } } else { struct PP_Var errorMessage = CStrToVar("TypeError : only accepts JS objects or Strings"); @@ -117,9 +310,22 @@ module pnacl is platform } } + /* This function is called by Nit when using check_dictionary, + returns the dictionary at the head of the queue. */ + void* NitHandleDictionary() { + struct PP_Var dictionary = DequeueDictionary(); + PnaclApp_handle_dictionary(app, &dictionary); + return 0; + } + + /* This function is called By Nit when waiting for a user input. */ + char* NitHandleMessage() { + return DequeueMessage(); + } + /* Entry point */ PP_EXPORT int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface) { - /* Initializing global pointers */ + /* Initializing global pointers. */ g_varMessagingInterface = (const PPB_Messaging*) get_browser_interface(PPB_MESSAGING_INTERFACE); g_varInterface = (const PPB_Var*) get_browser_interface(PPB_VAR_INTERFACE); g_varDictionaryInterface = (const PPB_VarDictionary*) get_browser_interface(PPB_VAR_DICTIONARY_INTERFACE); @@ -151,15 +357,18 @@ module pnacl is platform } return NULL; } + + /* Hack in order to avoid the problem with file. */ + int poll(struct pollfd* fds, nfds_t nfds, int timeout) { return 0; } `} # Nit class representing a Pepper C API PP_Var typed as a Dictionary. extern class PepperDictionary `{ struct PP_Var* `} new `{ - struct PP_Var* recv = malloc( sizeof( struct PP_Var ) ); - *recv = g_varDictionaryInterface->Create(); - return recv; + struct PP_Var* self = malloc( sizeof( struct PP_Var ) ); + *self = g_varDictionaryInterface->Create(); + return self; `} # Get fonction using PepperVars. @@ -168,7 +377,7 @@ extern class PepperDictionary `{ struct PP_Var* `} # If 'key' is not a String typed PepperVar, or doesn't exist in the Dictionary, an undefined PepperVar is returned. fun native_get(key: PepperVar): PepperVar `{ struct PP_Var* value = malloc( sizeof ( struct PP_Var ) ); - *value = g_varDictionaryInterface->Get(*recv, *key); + *value = g_varDictionaryInterface->Get(*self, *key); return value; `} @@ -192,7 +401,7 @@ extern class PepperDictionary `{ struct PP_Var* `} # Returns a Boolean indicating whether the operation succeeds. fun native_set(key: PepperVar, value: PepperVar): Bool `{ PP_Bool b; - b = g_varDictionaryInterface->Set(*recv, *key, *value); + b = g_varDictionaryInterface->Set(*self, *key, *value); return b; `} @@ -213,7 +422,7 @@ extern class PepperDictionary `{ struct PP_Var* `} # # Takes a String typed PepperVar. fun native_delete(key: PepperVar) `{ - g_varDictionaryInterface->Delete(*recv, *key); + g_varDictionaryInterface->Delete(*self, *key); `} # Deletes the specified key and its associated value, if the key exists. @@ -230,7 +439,7 @@ extern class PepperDictionary `{ struct PP_Var* `} # Takes a String typed PepperVar. fun native_has_key(key: PepperVar): Bool `{ PP_Bool b; - b = g_varDictionaryInterface->HasKey(*recv, *key); + b = g_varDictionaryInterface->HasKey(*self, *key); return b; `} @@ -248,7 +457,7 @@ extern class PepperDictionary `{ struct PP_Var* `} # Returns a PepperArray which contains all the keys of the Dictionary. The elements are string vars. fun get_keys: PepperArray `{ struct PP_Var* array = malloc( sizeof( struct PP_Var ) ); - *array = g_varDictionaryInterface->GetKeys(*recv); + *array = g_varDictionaryInterface->GetKeys(*self); return array; `} @@ -256,7 +465,7 @@ extern class PepperDictionary `{ struct PP_Var* `} fun copy: PepperDictionary `{ struct PP_Var* varDictionary = malloc( sizeof( struct PP_Var ) ); *varDictionary = g_varDictionaryInterface->Create(); - *varDictionary = *recv; + *varDictionary = *self; return varDictionary; `} end @@ -265,9 +474,9 @@ end extern class PepperArray `{ struct PP_Var* `} new `{ - struct PP_Var* recv = malloc( sizeof( struct PP_Var ) ); - *recv = g_varArrayInterface->Create(); - return recv; + struct PP_Var* self = malloc( sizeof( struct PP_Var ) ); + *self = g_varArrayInterface->Create(); + return self; `} # Returns the element at the specified position as a PepperVar. @@ -275,7 +484,7 @@ extern class PepperArray `{ struct PP_Var* `} # If 'index' is larger than or equal to the array length, an undefined PepperVar is returned. fun native_get(index: Int): PepperVar `{ struct PP_Var* value = malloc( sizeof( struct PP_Var ) ); - *value = g_varArrayInterface->Get(*recv, index); + *value = g_varArrayInterface->Get(*self, index); return value; `} @@ -290,7 +499,7 @@ extern class PepperArray `{ struct PP_Var* `} # Returns an int containing the length of the PepperArray. fun length: Int `{ - int length = g_varArrayInterface->GetLength(*recv); + int length = g_varArrayInterface->GetLength(*self); return length; `} @@ -302,7 +511,7 @@ extern class PepperArray `{ struct PP_Var* `} # Returns a Boolean indicating whether the operation succeeds. fun native_set(index: Int, value: PepperVar): Bool `{ PP_Bool b; - b = g_varArrayInterface->Set(*recv, index, *value); + b = g_varArrayInterface->Set(*self, index, *value); return b; `} @@ -325,7 +534,7 @@ extern class PepperArray `{ struct PP_Var* `} # Returns a Boolean indicating whether the operation succeeds. fun length=(length: Int): Bool `{ PP_Bool b; - b = g_varArrayInterface->SetLength(*recv, length); + b = g_varArrayInterface->SetLength(*self, length); return b; `} end @@ -352,19 +561,19 @@ extern class PepperVar `{ struct PP_Var* `} return null end - private fun isa_null: Bool `{ return recv->type == PP_VARTYPE_NULL; `} - private fun isa_bool: Bool `{ return recv->type == PP_VARTYPE_BOOL; `} - private fun isa_int: Bool `{ return recv->type == PP_VARTYPE_INT32; `} - private fun isa_float: Bool `{ return recv->type == PP_VARTYPE_DOUBLE; `} - private fun isa_string: Bool `{ return recv->type == PP_VARTYPE_STRING; `} - private fun is_undefined: Bool `{ return recv->type == PP_VARTYPE_UNDEFINED; `} + private fun isa_null: Bool `{ return self->type == PP_VARTYPE_NULL; `} + private fun isa_bool: Bool `{ return self->type == PP_VARTYPE_BOOL; `} + private fun isa_int: Bool `{ return self->type == PP_VARTYPE_INT32; `} + private fun isa_float: Bool `{ return self->type == PP_VARTYPE_DOUBLE; `} + private fun isa_string: Bool `{ return self->type == PP_VARTYPE_STRING; `} + private fun is_undefined: Bool `{ return self->type == PP_VARTYPE_UNDEFINED; `} - private fun as_bool: Bool `{ return recv->value.as_bool; `} - private fun as_int: Int `{ return recv->value.as_int; `} - private fun as_float: Float `{ return recv->value.as_double; `} + private fun as_bool: Bool `{ return self->value.as_bool; `} + private fun as_int: Int `{ return self->value.as_int; `} + private fun as_float: Float `{ return self->value.as_double; `} private fun as_string: String import NativeString.to_s_with_length `{ uint32_t len; - char* str = (char*)g_varInterface->VarToUtf8(*recv, &len); + char* str = (char*)g_varInterface->VarToUtf8(*self, &len); return NativeString_to_s_with_length(str, len); `} end @@ -380,7 +589,7 @@ redef class Int # Converts a Int into a PepperVar with Int type. redef fun to_pepper `{ struct PP_Var* var = malloc( sizeof( struct PP_Var ) ); - *var = PP_MakeInt32(recv); + *var = PP_MakeInt32(self); return var; `} end @@ -391,7 +600,7 @@ redef class Float # Converts a Float into a PepperVar with Float type. redef fun to_pepper `{ struct PP_Var* var = malloc( sizeof( struct PP_Var ) ); - *var = PP_MakeDouble(recv); + *var = PP_MakeDouble(self); return var; `} end @@ -402,7 +611,7 @@ redef class Bool # Converts a Bool into a PepperVar with Bool type. redef fun to_pepper `{ struct PP_Var* var = malloc( sizeof( struct PP_Var ) ); - *var = PP_MakeBool(recv); + *var = PP_MakeBool(self); return var; `} end @@ -412,19 +621,66 @@ redef class String # Converts a String into a PepperVar with String type. redef fun to_pepper: PepperVar import String.to_cstring, String.length `{ - char *str = String_to_cstring(recv); + char *str = String_to_cstring(self); struct PP_Var* var = malloc( sizeof( struct PP_Var ) ); - *var = g_varInterface->VarFromUtf8(str, String_length(recv)); + *var = g_varInterface->VarFromUtf8(str, String_length(self)); return var; `} end +# A stream for PNaCl, redefines basic input and output methods. +class PnaclStream + super PollableReader + super Writer + super BufferedReader + + init do prepare_buffer(10) + + redef var end_reached: Bool = false + + redef fun eof do return end_reached + + # Redefintion of 'write' to send messages to the browser. + redef fun write(s: Text) do app.post_message s.to_s + + redef fun is_writable: Bool do return true + + # Checks if there is a message in the queue, and if so the message is handled automatically. + fun check_message: NativeString `{ + return NitHandleMessage(); + `} + + # fill_buffer now checks for a message in the message queue which is filled by user inputs. + redef fun fill_buffer + do + _buffer_pos = 0 + var nns = check_message + var nslen = nns.cstring_length + _buffer_length = nslen + nns.copy_to(buffer, nslen, 0, 0) + end +end + +# For a PNaCl app, Sys uses PnaclStreams. +redef class Sys + fun pnacl_stdstr: PnaclStream do return once new PnaclStream + + # NaCl input. + redef fun stdin do return pnacl_stdstr + + # NaCl output. + redef fun stdout do return pnacl_stdstr + + # NaCl output for errors. + redef fun stderr do return pnacl_stdstr +end + # Class that provides the tools to interact with PNaCl. class PnaclApp # Sets everything up to work, need to be called at first. fun initialize import PnaclApp.handle_message, PnaclApp.handle_dictionary, NativeString.to_s_with_length `{ - app = recv; + app = self; `} # Posts a message to JS. @@ -461,4 +717,47 @@ class PnaclApp do # To be Implemented by user. end + + # Checks if there is a dictionary in the queue, and if so the dictionary is handled automatically. + fun check_dictionary `{ + NitHandleDictionary(); + `} + + # Infinite loop on check_dictionary + fun run + do + loop + check_dictionary + end + end +end + +# Creates a new thread for Nit. +# +# This function launches the Nit main on a new thread. +# Its purpose is to allow Nit to be still operational after an exit when needed, +# because reloading the page may not be an option. +# +# Should only be used within the 'exit' before stopping the current thread +# when the Nit execution causes a crash. +# +# REQUIRE: g_nit_thread and WrapperNitMain are set. +fun create_thread `{ + pthread_create(&g_nit_thread, NULL, &WrapperNitMain, NULL); +`} + +# Calls 'pthread_exit on current thread. +fun exit_thread(exit_value: Int) `{ + pthread_exit((void*) exit_value); +`} + +# Redef of exit in order to avoid the module to crash by terminating only the Nit thread. +redef fun exit(exit_value: Int) +do + var dictionary = new PepperDictionary + dictionary["exit"] = exit_value + app.post_dictionary dictionary + exit_thread exit_value end + +fun app: PnaclApp do return once new PnaclApp