# 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 <unistd.h>
- #include <stddef.h>
- #include <string.h>
- #include <stdlib.h>
+
+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"
#include "ppapi/c/ppp_messaging.h"
#include "ppapi/c/ppb_var_dictionary.h"
#include "ppapi/c/ppb_var_array.h"
+`}
+
+`{
+ #include <unistd.h>
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <string.h>
+ #include <stdlib.h>
+ #include <pthread.h>
+ #include <poll.h>
+
+ #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;
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. */
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;
}
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");
}
}
+ /* 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);
return NULL;
}
- // Hack poll
- int poll(void *fds, int nfds, int timeout) { return 0; }
+ /* 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.
# 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;
`}
# 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;
`}
#
# 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.
# 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;
`}
# 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;
`}
fun copy: PepperDictionary `{
struct PP_Var* varDictionary = malloc( sizeof( struct PP_Var ) );
*varDictionary = g_varDictionaryInterface->Create();
- *varDictionary = *recv;
+ *varDictionary = *self;
return varDictionary;
`}
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.
# 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;
`}
# 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;
`}
# 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;
`}
# 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
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
# 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
# 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
# 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
# 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.
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