changeset 2933:3cdb02171b01

C++: Implement Thread class and add the last page Thread/Event.
author bsmith@81767d24-ef19-dc11-ae90-00e081727c95
date Sat, 31 Dec 2022 00:41:28 +0000
parents 3f660f47a45f
children 6d3da42f63af
files dw.hpp dwtestoo.cpp
diffstat 2 files changed, 276 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/dw.hpp	Fri Dec 30 22:53:19 2022 +0000
+++ b/dw.hpp	Sat Dec 31 00:41:28 2022 +0000
@@ -37,6 +37,8 @@
 namespace DW 
 {
 
+#define THREAD_STACK 10000
+
 // Forward declare these so they can be referenced
 class Render;
 class Pixmap;
@@ -1972,6 +1974,91 @@
     }
 };
 
+class Thread : public Handle
+{
+private:
+    DWTID tid;
+#ifdef DW_LAMBDA
+    std::function<void(Thread *)> _ConnectThread;
+#endif
+    void (*_ConnectThreadOld)(Thread *);
+    static void _OnThread(void *data) {
+        Thread *classptr = reinterpret_cast<Thread *>(data);
+
+#ifdef DW_LAMBDA
+        if(classptr->_ConnectThread)
+            classptr->_ConnectThread(classptr);
+        else
+#endif
+        if(classptr->_ConnectThreadOld)
+            classptr->_ConnectThreadOld(classptr);
+        else
+            classptr->OnThread(classptr);
+
+        delete classptr;
+    }
+public:
+    // Constructors
+#ifdef DW_LAMBDA
+    Thread(std::function<void(Thread *)> userfunc) {
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, THREAD_STACK);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThread = userfunc;
+        _ConnectThreadOld = 0;
+    }
+    Thread(std::function<void(Thread *)> userfunc, int stack) {
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, stack);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThread = userfunc;
+        _ConnectThreadOld = 0;
+    }
+#endif
+    Thread(void (*userfunc)(Thread *)) { 
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, THREAD_STACK);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThreadOld = userfunc;
+#ifdef DW_LAMBDA
+        _ConnectThread = 0;
+#endif
+    }
+    Thread(void (*userfunc)(Thread *), int stack) { 
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, stack);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThreadOld = userfunc;
+#ifdef DW_LAMBDA
+        _ConnectThread = 0;
+#endif
+    }
+    Thread(int stack) { 
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, stack);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThreadOld = 0;
+#ifdef DW_LAMBDA
+        _ConnectThread = 0;
+#endif
+    }
+    Thread() { 
+        tid = dw_thread_new(DW_SIGNAL_FUNC(_OnThread), this, THREAD_STACK);
+        SetHandle(reinterpret_cast<void *>(tid));
+        _ConnectThreadOld = 0;
+#ifdef DW_LAMBDA
+        _ConnectThread = 0;
+#endif
+    }
+    // Destructor
+    virtual ~Thread() { if(tid != 0 && dw_thread_id() == tid) dw_thread_end(); tid = 0; }
+
+    // User functions
+    void End() { if(tid != 0 && dw_thread_id() == tid) delete this; }
+    DWTID GetTID() { return tid; }
+protected:
+    // Our signal handler functions to be overriden...
+    // If they are not overridden and an event is generated, remove the unused handler
+    virtual void OnThread(Thread *classptr) {
+        delete this;
+    }
+};
+
 class App
 {
 protected:
@@ -1999,6 +2086,7 @@
     void Main() { dw_main(); }
     void MainIteration() { dw_main_iteration(); }
     void MainQuit() { dw_main_quit(); }
+    void MainSleep(int milliseconds) { dw_main_sleep(milliseconds); }
     void Exit(int exitcode) { dw_exit(exitcode); }
     void Shutdown() { dw_shutdown(); }
     int MessageBox(const char *title, int flags, const char *format, ...) { 
--- a/dwtestoo.cpp	Fri Dec 30 22:53:19 2022 +0000
+++ b/dwtestoo.cpp	Sat Dec 31 00:41:28 2022 +0000
@@ -32,6 +32,7 @@
 #define APP_EXIT "Are you sure you want to exit?"
 
 #define MAX_WIDGETS 20
+#define BUF_SIZE 1024
 
 // Handle the case of very old compilers by using
 // A simple non-lambda example instead.
@@ -466,6 +467,118 @@
         mle->SetFont(fontname ? font : NULL);
     }
 
+    // Thread and Event functions
+    void UpdateMLE(DW::MLE *threadmle, char *text, DW::Mutex *mutex)
+    {
+        static unsigned int pos = 0;
+
+        // Protect pos from being changed by different threads
+        if(mutex)
+            mutex->Lock();
+        pos = threadmle->Import(text, pos);
+        threadmle->SetCursor(pos);
+        if(mutex)
+            mutex->Unlock();
+    }
+
+    void RunThread(int threadnum, DW::Mutex *mutex, DW::Event *controlevent, DW::Event *workevent, DW::MLE *threadmle) {
+        char buf[BUF_SIZE+1] = {0};
+
+        snprintf(buf, BUF_SIZE, "Thread %d started.\r\n", threadnum);
+        UpdateMLE(threadmle, buf, mutex);
+
+        // Increment the ready count while protected by mutex
+        dw_mutex_lock(mutex);
+        ready++;
+        // If all 4 threads have incrememted the ready count...
+        // Post the control event semaphore so things will get started.
+        if(ready == 4)
+            controlevent->Post();
+        mutex->Unlock();
+
+        while(!finished)
+        {
+            int result = workevent->Wait(2000);
+
+            if(result == DW_ERROR_TIMEOUT)
+            {
+                snprintf(buf, BUF_SIZE, "Thread %d timeout waiting for event.\r\n", threadnum);
+                UpdateMLE(threadmle, buf, mutex);
+            }
+            else if(result == DW_ERROR_NONE)
+            {
+                snprintf(buf, BUF_SIZE, "Thread %d doing some work.\r\n", threadnum);
+                UpdateMLE(threadmle, buf, mutex);
+                // Pretend to do some work
+                app->MainSleep(1000 * threadnum);
+
+                // Increment the ready count while protected by mutex
+                mutex->Lock();
+                ready++;
+                sprintf(buf, "Thread %d work done. ready=%d", threadnum, ready);
+                // If all 4 threads have incrememted the ready count...
+                // Post the control event semaphore so things will get started.
+                if(ready == 4)
+                {
+                    controlevent->Post();
+                    strcat(buf, " Control posted.");
+                }
+                mutex->Unlock();
+                strcat(buf, "\r\n");
+                UpdateMLE(threadmle, buf, mutex);
+            }
+            else
+            {
+                snprintf(buf, BUF_SIZE, "Thread %d error %d.\r\n", threadnum, result);
+                UpdateMLE(threadmle, buf, mutex);
+                app->MainSleep(10000);
+            }
+        }
+        snprintf(buf, BUF_SIZE, "Thread %d finished.\r\n", threadnum);
+        UpdateMLE(threadmle, buf, mutex);
+    }
+
+    void ControlThread(DW::Mutex *mutex, DW::Event *controlevent, DW::Event *workevent, DW::MLE *threadmle) {
+        int inprogress = 5;
+        char buf[BUF_SIZE+1] = {0};
+
+        while(inprogress)
+        {
+            int result = controlevent->Wait(2000);
+
+            if(result == DW_ERROR_TIMEOUT)
+            {
+                UpdateMLE(threadmle, "Control thread timeout waiting for event.\r\n", mutex);
+            }
+            else if(result == DW_ERROR_NONE)
+            {
+                // Reset the control event
+                controlevent->Reset();
+                ready = 0;
+                snprintf(buf, BUF_SIZE, "Control thread starting worker threads. Inprogress=%d\r\n", inprogress);
+                UpdateMLE(threadmle, buf, mutex);
+                // Start the work threads
+                workevent->Post();
+                app->MainSleep(100);
+                // Reset the work event
+                workevent->Reset();
+                inprogress--;
+            }
+            else
+            {
+                snprintf(buf, BUF_SIZE, "Control thread error %d.\r\n", result);
+                UpdateMLE(threadmle, buf, mutex);
+                app->MainSleep(10000);
+            }
+        }
+        // Tell the other threads we are done
+        finished = TRUE;
+        workevent->Post();
+        // Close the control event
+        controlevent->Close();
+        UpdateMLE(threadmle, "Control thread finished.\r\n", mutex);
+    }
+
     // Add the menus to the window
     void CreateMenus() {
         // Setup the menu
@@ -541,7 +654,7 @@
 
         notebookbox->PackStart(lbbox, 150, 70, TRUE, TRUE, 0);
 
-        /* Copy and Paste */
+        // Copy and Paste
         DW::Box *browsebox = new DW::Box(DW_HORZ, 0);
         lbbox->PackStart(browsebox, 0, 0, FALSE, FALSE, 0);
 
@@ -555,7 +668,7 @@
         DW::Button *pastebutton = new DW::Button("Paste");
         browsebox->PackStart(pastebutton, FALSE, FALSE, 0);
 
-        /* Archive Name */
+        // Archive Name
         DW::Text *stext = new DW::Text("File to browse");
         stext->SetStyle(DW_DT_VCENTER);
         lbbox->PackStart(stext, 130, 15, TRUE, TRUE, 2);
@@ -596,7 +709,7 @@
         DW::Button *colorchoosebutton = new DW::Button("Color Chooser Dialog");
         buttonbox->PackStart(colorchoosebutton, 130, 30, TRUE, TRUE, 2);
 
-        /* Set some nice fonts and colors */
+        // Set some nice fonts and colors
         lbbox->SetColor(DW_CLR_DARKCYAN, DW_CLR_PALEGRAY);
         buttonbox->SetColor(DW_CLR_DARKCYAN, DW_CLR_PALEGRAY);
         okbutton->SetColor(DW_CLR_PALEGRAY, DW_CLR_DARKCYAN);
@@ -992,24 +1105,24 @@
                }
                else if(page_num == 1)
                {
-                   /* Get the font size for this printer context... */
+                   // Get the font size for this printer context...
                    int fheight, fwidth;
 
-                   /* If we have a file to display... */
+                   // If we have a file to display...
                    if(current_file)
                    {
                        int nrows;
 
-                       /* Calculate new dimensions */
+                       // Calculate new dimensions
                        pixmap->GetTextExtents("(g", NULL, &fheight);
                        nrows = (int)(pixmap->GetHeight() / fheight);
 
-                       /* Do the actual drawing */
+                       // Do the actual drawing
                        this->DrawFile(0, 0, nrows, fheight, pixmap);
                    }
                    else
                    {
-                       /* We don't have a file so center an error message on the page */
+                       // We don't have a file so center an error message on the page
                        const char *text = "No file currently selected!";
                        int posx, posy;
 
@@ -1074,7 +1187,7 @@
         {
             notebookbox->PackStart(tree, 500, 200, TRUE, TRUE, 1);
 
-            /* and a status area to see whats going on */
+            // and a status area to see whats going on
             DW::StatusText *tree_status = new DW::StatusText();
             notebookbox->PackStart(tree_status, 100, DW_SIZE_AUTO, TRUE, FALSE, 1);
 
@@ -1490,7 +1603,7 @@
         // now insert a couple of items
         combobox2->Insert("inserted item 2", 2);
         combobox2->Insert("inserted item 5", 5);
-        /* make a spinbutton */
+        // make a spinbutton
         DW::SpinButton *spinbutton = new DW::SpinButton();
         combox->PackStart(spinbutton, TRUE, FALSE, 0);
         spinbutton->SetLimits(100, 1);
@@ -1501,7 +1614,7 @@
             this->app->MessageBox("DWTest", DW_MB_OK, "New value from spinbutton: %d\n", value);
             return TRUE;
         });
-        /// make a slider
+        // make a slider
         DW::Slider *slider = new DW::Slider(FALSE, 11, 0); 
         combox->PackStart(slider, TRUE, FALSE, 0);
 
@@ -1648,7 +1761,7 @@
     {
         char buf[101] = {0};
 
-        /* create a box to pack into the notebook page */
+        // create a box to pack into the notebook page
         DW::ScrollBox *scrollbox = new DW::ScrollBox(DW_VERT, 0);
         notebookbox->PackStart(scrollbox, 0, 0, TRUE, TRUE, 1);
 
@@ -1674,6 +1787,56 @@
             tmpbox->PackStart(entry, 0, DW_SIZE_AUTO, TRUE, FALSE, 0);
         }
     }
+
+    // Page 8 - Thread and Event
+    void CreateThreadEvent(DW::Box *notebookbox)
+    {
+        // create a box to pack into the notebook page
+        DW::Box *tmpbox = new DW::Box(DW_VERT, 0);
+        notebookbox->PackStart(tmpbox, 0, 0, TRUE, TRUE, 1);
+
+        DW::Button *startbutton = new DW::Button("Start Threads");
+        tmpbox->PackStart(startbutton, FALSE, FALSE, 0);
+
+        // Create the base threading components
+        DW::MLE *threadmle = new DW::MLE();
+        tmpbox->PackStart(threadmle, 1, 1, TRUE, TRUE, 0);
+        DW::Mutex *mutex = new DW::Mutex();
+        DW::Event *workevent = new DW::Event();
+
+        startbutton->ConnectClicked([this, mutex, workevent, threadmle, startbutton]() -> int
+        {
+            startbutton->Disable();
+            mutex->Lock();
+            DW::Event *controlevent = new DW::Event();
+            workevent->Reset();
+            finished = FALSE;
+            ready = 0;
+            UpdateMLE(threadmle, "Starting thread 1\r\n", DW_NULL);
+            new DW::Thread([this, startbutton, mutex, controlevent, workevent, threadmle](DW::Thread *thread) -> void {
+                this->RunThread(1, mutex, controlevent, workevent, threadmle);
+            });
+            UpdateMLE(threadmle, "Starting thread 2\r\n", DW_NULL);
+            new DW::Thread([this, startbutton, mutex, controlevent, workevent, threadmle](DW::Thread *thread) -> void {
+                this->RunThread(2, mutex, controlevent, workevent, threadmle);
+            });
+            UpdateMLE(threadmle, "Starting thread 3\r\n", DW_NULL);
+            new DW::Thread([this, startbutton, mutex, controlevent, workevent, threadmle](DW::Thread *thread) -> void {
+                this->RunThread(3, mutex, controlevent, workevent, threadmle);
+            });
+            UpdateMLE(threadmle, "Starting thread 4\r\n", DW_NULL);
+            new DW::Thread([this, startbutton, mutex, controlevent, workevent, threadmle](DW::Thread *thread) -> void {
+                this->RunThread(4, mutex, controlevent, workevent, threadmle);
+            });
+            UpdateMLE(threadmle, "Starting control thread\r\n", DW_NULL);
+            new DW::Thread([this, startbutton, mutex, controlevent, workevent, threadmle](DW::Thread *thread) -> void {
+                this->ControlThread(mutex, controlevent, workevent, threadmle);
+                startbutton->Enable();
+            });
+            mutex->Unlock();
+            return FALSE;
+        });
+    }
 public:
     // Constructor creates the application
     DWTest(const char *title): DW::Window(title) {
@@ -1692,9 +1855,8 @@
         fileicon = app->LoadIcon(fileiconpath);
 
 #ifdef PLATFORMFOLDER
-        /* In case we are running from the build directory...
-         * also check the appropriate platform subfolder
-         */
+        // In case we are running from the build directory...
+        // also check the appropriate platform subfolder
         if(!foldericon)
         {
             strncpy(foldericonpath, PLATFORMFOLDER "folder", 1024);
@@ -1707,7 +1869,7 @@
         }
 #endif
 
-        /* Finally try from the platform application directory */
+        // Finally try from the platform application directory
         if(!foldericon && !fileicon)
         {
             char *appdir = app->GetDir();
@@ -1784,6 +1946,13 @@
         notebook->Pack(notebookpage, notebookbox);
         notebook->PageSetText(notebookpage, "scrollbox");
 
+        // Create Notebook Page 8 - Thread and Event
+        notebookbox = new DW::Box(DW_VERT, 5);
+        CreateThreadEvent(notebookbox);
+        notebookpage = notebook->PageNew();
+        notebook->Pack(notebookpage, notebookbox);
+        notebook->PageSetText(notebookpage, "thread/event");
+
         // Finalize the window
         this->SetSize(640, 550);
 
@@ -1822,6 +1991,9 @@
     // Page 4
     int mle_point=-1;
 
+    // Page 8
+    int finished = FALSE, ready = 0;
+
     // Miscellaneous
     int menu_enabled = TRUE;
     HICN fileicon, foldericon;