/* #Specification: uithread / principles uithread means "User Interface Thread". They are less generic threads and potentially less problematic ones as well. They exist so multiple dialog may interact asynchronoulsly. These dialogs are using a very simple programming model called modal programming. uithread allows one to keep this simple model and yet produce fancy GUI software as well. Uithreads are created using the uithread function. This function accept a function pointer and optionally, a void pointer. In the first case, the function pointer is of type void (*fct)(); while in the other case, it is of type void (*fct)(void *) A thread may be in 3 states: Starting, running, and waiting. The following restriction also exists: Only one thread may be running at once. All the others are either in the starting mode or waiting. Waiting threads are always waiting at the same place. They are waiting in diagui_wait(). A thread can't be preempted. So uithreads are simpler to use then ordinary threads. They are less flexible for sure. */ #include #include #include #include #include "internal.h" #include "dialog.h" #include "dialog.m" static const int MAX_UITR=100; // Maximum number of uithreads static jmp_buf tbjmp[MAX_UITR]; static void (*tbfct[MAX_UITR])(void *); static void *tbdata[MAX_UITR]; static LINUXCONF_CONTEXT tbctx[MAX_UITR]; static short tbparent[MAX_UITR]; static bool tbactif[MAX_UITR]; static int nbid = 0; static jmp_buf reeditjmp[MAX_UITR]; static bool tbyield[MAX_UITR]; static unsigned *tbcheck[MAX_UITR]; // Used to detect stack overrun // (One thread has over-used its stack) int uithread_id = 0; #ifdef TEST static int uithread_editfct() { printf ("Active thread : "); for (int i=0; i<=nbid; i++){ if (tbactif[i]) printf ("%d ",i); } printf ("\n"); int ret; while (1){ char buf[10]; printf ("Entrez a thread number : "); fgets (buf,sizeof(buf)-1,stdin); int ctx = atoi(buf); if (ctx <= nbid && tbactif[ctx]){ ret = ctx; break; } } return ret; } #endif /* Start one new thread if any. A thread is always in one of 3 states 1-Not used: tbfct[i] == NULL 2-Used, but inactive (not started yet) In this case, we start it by switching to it. This function will never regain control as the current thread will regain control at a different place 3-active, waiting for an input */ static void uithread_startnew () { for (int i=1; i<=nbid; i++){ if (!tbactif[i] && tbfct[i] != NULL){ uithread_id = i; longjmp (tbjmp[i],1); break; } } } /* Wait for all User interface thread to meet here and then call the input function editfct(). The editfct must return the ID of the thread which must resume. */ void uithread_sync (int (*editfct)()) { static jmp_buf retjmp[MAX_UITR]; tbctx[uithread_id].set (ui_context); if (!setjmp(retjmp[uithread_id])){ //int ctx = uithread_id; //printf ("edit thread %d %p\n",uithread_id,&ctx); tbactif[uithread_id] = true; if (!setjmp(reeditjmp[uithread_id])){ uithread_startnew (); } uithread_id = -1; for (int i=0; i 0 && uithread_id <= nbid && tbfct[uithread_id] != NULL){ break; } } } ui_context.set (tbctx[uithread_id]); longjmp (retjmp[uithread_id],1); } } /* Reserve a stack area for a new thread. This simply allocate a large array on the stack. Nothing else. No fancy assembly tricks. This function precreate all the thread stack area. This function is called only once at the begin and it will recurse until all the stack have been created. */ static void uithread_setup (int nbtr) { /* #Specification: uithread / stack size All uithread's are created at startup time (using uithread_init itself called by diagui_init...). A fixed amount is created (20). Each thread gets a 100000 bytes stack. Real memory is not really allocated though. This 100000 is arbitrary. A much larger value could probably used I guess (few megs). Allocation thread stack as needed is probably doable, although not as easily I guess (less portable). Input welcome. */ static int notr = 1; char stack[100000]; tbactif[notr] = false; tbfct[notr] = NULL; tbcheck[notr] = (unsigned*)(stack); *tbcheck[notr] = 0xdeadbeef; if (setjmp(tbjmp[notr])){ int curid = uithread_id; ui_context.set (tbctx[curid]); (*tbfct[curid])(tbdata[curid]); help_context_delmark (curid); //fprintf (stderr,"Thread %d termine\n",curid); tbactif[curid] = false; tbfct[curid] = NULL; uithread_startnew(); // No new thread. Give control to one of the thread in edit mode for (int i=0; i<=nbid; i++){ if (tbactif[i]) longjmp (reeditjmp[i],1); } fprintf (stderr,"No more thread!!!!\n"); exit (-1); // When this come back, we kill the last thread }else if (notr < nbtr){ notr++; uithread_setup (nbtr); } } /* Initialise the user interface thread with the amount of thread which will be used. For now it can't grow. It is believed that an application with more than say 20 user interface thread (no necessarily windows) is over complicate for anyone to manage. */ EXPORT void uithread_init (int nbtr) { static bool done = false; if (!done){ nbid=nbtr; uithread_setup (nbtr); done = true; } } EXPORT bool uithread_check() { bool ret = true; for (int i=1; i 0 && id < MAX_UITR){ ret = tbparent[id]; } return ret; } /* Return the parent thread ID of the current thread */ int uithread_getparent() { return uithread_getparent(uithread_id); } /* Register a new thread. The fct function will start the processing for the thread. It return the ID number of the new thread. */ EXPORT int uithread (void (*fct)(void *), void *data) { int ret = -1; /* #Specification: uithread / GUI mode Uithreads only work in GUI mode. In the other modes (HTML, Text) the uithread function simply call the thread function and wait until it complete. At some point, uithreads could be implement in text mode, but we would need a way for the user to switch between threads, maybe using a task bar at the top of the screen. */ if (dialog_mode == DIALOG_GUI && !uithread_modal && (ui_context.treejump_level == 0 || ui_context.treejump_level < ui_context.treemenu_level)){ ret = uithread_ok (fct,data); }else{ LINUXCONF_CONTEXT tmp (ui_context); (*fct)(data); ui_context.set(tmp); ret = 0; } return ret; } EXPORT int uithread (void (*fct)()) { return uithread ((void (*)(void*))fct,NULL); } /* Let all the other threads run. This function will return as soon as all the other thread reach the focus point (the UI event loop generally) */ EXPORT void uithread_yield() { if (dialog_mode == DIALOG_GUI){ tbyield[uithread_id] = true; // fprintf (stderr,"thread yielding %d\n",uithread_id); uithread_sync (NULL); // fprintf (stderr,"thread yield continue %d\n",uithread_id); } } #ifdef TEST static void test0(const char *nom) { int nb = 0; while (1){ uithread_sync (uithread_editfct); printf ("edit %s %d\n",nom,nb++); if (nb == 3) break; } } static void test1(void *) { test0 ("test1"); } static void test2(void *) { test0 ("test2"); } static void test3(void *) { test0 ("test3"); } int main (int, char *[]) { uithread_init(20); uithread(test1,""); uithread(test2,""); uithread(test3,""); test0 ("main 0"); return 0; } #endif