diff --git a/.idea/ArduinoThread.iml b/.idea/ArduinoThread.iml new file mode 100644 index 0000000..bc2cd87 --- /dev/null +++ b/.idea/ArduinoThread.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..25b67c6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/serialmonitor_settings.xml b/.idea/serialmonitor_settings.xml new file mode 100644 index 0000000..b45564e --- /dev/null +++ b/.idea/serialmonitor_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..d644d68 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1566244945461 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index b866337..f40ac01 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,47 @@ -![ArduinoThread Logo](https://raw.githubusercontent.com/ivanseidel/ArduinoThread/master/extras/ArduinoThread.png) +## ArduinoAsync -## ArduinoThreads Motivation -Arduino does not support isolated parallel tasks ([Threads](https://en.wikipedia.org/wiki/Thread_(computing))), -but we can make the main `loop` switch function execution conditionally and -thus simulate threading with [Protothread](https://en.wikipedia.org/wiki/Protothread) mechanism. -This library implements it and helps you to: +This is a fork of Ivan Seidel's [ArduinoThread](https://github.com/ivanseidel/ArduinoThread). -- schedule, manage and simplify parallel, periodic tasks -- define fixed or variable time between runs -- organize the code in any type of project - - put all sensor readings in a thread - - keep the main loop clean -- hide the complexity of thread management -- run "pseudo-background" tasks using Timer interrupts +The protothread mechanism used in ArduinoThread is used to create asynchronous contexts. With C++ +define statements, it is possible to create Python-like async code that runs on an Arduino. -Blinking an LED is often the very first thing an Arduino user learns. -And this demonstrates that periodically performing one single task, like toggling the LED state, is really easy. -However, one may quickly discover that managing multiple periodic tasks is not so simple -if the tasks have different schedule. +Static Thread Controllers have been for the time being removed. -The user defines a Thread object for each of those tasks, then lets the library manage their scheduled execution. +The name thread sticks around from the original but it may be renamed in a future release. -It should be noted that these are not “threads” in the real computer-science meaning of the term: -tasks are implemented as functions that are run periodically. -On the one hand, this means that the only way a task can *yield* the CPU is by returning to the caller, -and it is thus inadvisable to `delay()` or do long waits inside any task. -On the other hand, this makes ArduinoThreads memory friendly, as no stack need to be allocated per task. +Documentation and library are both still very WIP. This may become its own seperate thing in the future. -## Installation +### Concepts +Like in ArduinoThread, the `run` method of a thread is called once per loop. This allows a programmmer +to think of several tasks as "running at once" and the loop context switching between tasks that +are ready to run. -1. Download [the Master branch](https://github.com/ivanseidel/ArduinoThread/archive/master.zip) from gitHub. -2. Unzip and modify the Folder name to "ArduinoThread" (Remove the '-master' suffix) -3. Paste the modified folder on your Library folder (On your `Libraries` folder inside Sketchbooks or Arduino software). -4. Restart the Arduino IDE +To accomplish this context switching, all threads have an internal `m_flag` int variable that makes +them all act like finite state machines. Switch case statements or if statements can be +used on the flag to allow the thread to execute code based on its state. -**If you are here just because another library requires a class from ArduinoThread, then you are done now -.** +Setting the flag is as simple as calling `YIELD(flag_number)`, which yields execution back to the +`ThreadController` and sets the flag. `YIELD_DEFAULT` can be used for simple applications and simply +sets the flag to 0. +If `m_flag` is set to -1 and the thread has yielded execution back to the `ThreadController`, +the ThreadController removes the thread from the queue. The `HALT` keyword does exactly this. -## Getting Started +Threads can also be paused in this framework for a certain number of milliseconds using the +`WAIT` keyword. Unlike `delay` `WAIT` will not block execution of the `ThreadController` and allows +context switching to continue for other tasks. Threads can also be paused indefinetely with +`FREEZE`. Threads paused with `FREEZE` will not enter an awake state again until another thread +uses `UNFREEZE(thread_ptr)`. -There are many examples showing many ways to use it. We will explain Class itself, -what it does and how it does. +In addition to these mechanisms which control the control flow, ArduinoAsync also supports producer +consumer semantics. -There are three main classes included in the library: -`Thread`, `ThreadController` and `StaticThreadController` (both controllers inherit from `Thread`). +All threads have an internal buffer of type void*. This is used to gather the return of an +asynchronously executing thread. Users of the library will not have to worry about this, however. -- `Thread`: Basic class, witch contains methods to set and run callbacks, - check if the Thread should be run, and also creates a unique ThreadID on the instantiation. +The `AWAIT` statement will take a pointer to a thread and check its buffer for the result which is +otherwise `nulltpr`. If multiple consumers `AWAIT` on a producer, a semaphore internal to the producer +will block the producer from clearing the result and entering a sleeping state until all consumers have +gotten the result. -- `ThreadController`: Responsible for managing multiple Threads. Can also be thought of - as "a group of Threads", and is used to perform `run` in every Thread ONLY when needed. - -- `StaticThreadController`: Slightly faster and smaller version of the `ThreadController`. - It works similar to `ThreadController`, but once constructed it can't add or remove threads to run. - -#### Create Thread instance: - -```c++ -Thread myThread = Thread(); -// or, if initializing a pointer -Thread* myThread = new Thread(); -``` - -#### Setup thread behaviour -You can configure many things: - -```c++ -myThread.enabled = true; // Default enabled value is true -myThread.setInterval(10); // Setts the wanted interval to be 10ms -/* - This is useful for debugging - (Thread Name is disabled by default, to use less memory) - (Enable it by definint USE_THREAD_NAMES on 'Thread.h') -*/ -myThread.ThreadName = "myThread tag"; -// This will set the callback of the Thread: "What should I run"? -myThread.onRun(callback_function); // callback_function is the name of the function -``` - -#### Running threads manually -Ok, creating threads isn't too hard, but what do we do with them? - -```c++ -// First check if our Thread should be run -if(myThread.shouldRun()){ - // Yes, the Thread should run, let's run it - myThread.run(); -} -``` - -#### Running threads via a controller -If you had 3, 5 or 100 threads, managing them manually could become tedious. -That's when `ThreadController` or `StaticThreadController` comes into play and saves you the repetitive thread management parts of code. - -```c++ -// Instantiate new ThreadController -ThreadController controller = ThreadController(); -// Now, put bunch of Threads inside it, FEED it! -controller.add(&myThread); // Notice the '&' sign before the thread, IF it's not instantied as a pointer. -controller.add(&hisThread); -controller.add(&sensorReadings); -... -``` -or -```c++ -// Instantiate a new StaticThreadController with the number of threads to be supplied as template parameter -StaticThreadController<3> controller (&myThread, &hisThread, &sensorReadings); -// You don't need to do anything else, controller now contains all the threads. -... -``` - -You have created, configured, grouped it. What is missing? Yes, whe should RUN it! -The following will run all the threads that NEED to run. - -```c++ -// call run on a Thread, a ThreadController or a StaticThreadController to run it -controller.run(); -``` - -Congratulations, you have learned the basics of the `ArduinoThread` library. If you want to learn more, see bellow. - -### Tips and Warnings - -* `ThreadController` is not of a dynamic size (like a `LinkedList`). The maximum number of threads that it can manage - is defined in `ThreadController.h` (default is 15) - -* ☢ When extending the `Thread` class and overriding the `run()` function, - remember to always call `runned();` at the end, otherwise the thread will hang forever. - -* It's a good idea, to create a `Timer` interrupt and call a `ThreadController.run()` there. -That way, you don't need to worry about reading sensors and doing time-sensitive stuff -in your main code (`loop`). Check `ControllerWithTimer` example. - -* Inheriting from `Thread` or even `ThreadController` is always a good idea. -For example, I always create base classes of sensors that extends `Thread`, -so that I can "register" the sensors inside a `ThreadController`, and forget -about reading sensors, just having the values available in my main code. -Check the `SensorThread` example. - -* Remember that `ThreadController` is in fact, a `Thread` itself. If you want to group threads and -manage them together (enable or disable), think about putting all of them inside a `ThreadController`, -and adding this `ThreadController` to another `ThreadController` (YES! One inside another). -Check `ControllerInController` example. - -* `StaticThreadController` is optimal when you know the exact number of -threads to run. You cannot add or remove threads at runtime, but it -doesn't require additional memory to keep all the treads together, doesn't limit the number of thread -(except for available memory) and the code may be slightly -better optimized because all the threads always exist and no need to do any runtime checks. - -* Check the full example `CustomTimedThread` for a cool application of threads that run -for a period, after a button is pressed. - -* Running tasks on the `Timer` interrupts must be thought though REALLY carefully - - - You mustn't use `sleep()` inside an interrupt, because it would cause an infinite loop. - - - Things execute quickly. Waiting too loooong on a interrupt, means waiting too - loooong on the main code (`loop`) - - - Things might get "scrambled". Since Timers interrupts actually "BREAK" your code in half - and start running the interrupt, you might want to call `noInterrupts` and `interrupts` - on places where cannot be interrupted: - -```c++ -noInterrupts(); -// Put the code that CANNOT be interrupted... -interrupts(); // This will enable the interrupts egain. DO NOT FORGET! -``` -## Library Reference - -### Configuration options -#### Thread -- `bool Thread::enabled` - Enables or disables the thread. (doesn't prevent it from running, but will - return `false` when `shouldRun()` is called) -- `void Thread::setInterval()` - Schedules the thread run interval in milliseconds -- `bool Thread::shouldRun()` - Returns true, if the thread should be run. - (Basically,the logic is: (reached time AND is enabled?). -- `void Thread::onRun()` - The target callback function to be called. -- `void Thread::run()` - Runs the thread (executes the callback function). -- `int Thread::ThreadID` - Theoretically, it's the memory address. It's unique, and can - be used to compare if two threads are identical. -- `int Thread::ThreadName` - A human-readable thread name. - Default is "Thread ThreadID", eg.: "Thread 141515". - Note that to enable this attribute, you must uncomment the line that disables it on `Thread.h` -- protected: `void Thread::runned()` - Used to reset internal timer of the thread. - This is automatically called AFTER a call to `run()`. - -#### ThreadController -- `void ThreadController::run()` - Runs the all threads grouped by the controller, - but only if needed (if `shouldRun()` returns true); -- `bool ThreadController::add(Thread* _thread)` - Adds a the thread to the controller, - and returns `true` if succeeded (returns false if the array is full). -- `void ThreadController::remove(Thread* _thread)` - Removes the thread from the controller -- `void ThreadController::remove(int index)` - Removes the thread at the `index` position -- `void ThreadController::clear()` - Removes ALL threads from the controller -- `int ThreadController::size(bool cached = true)` - Returns number of threads allocated - in the ThreadController. Re-calculates thread count if `cached` is `false` -- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position - -#### StaticThreadController -- `void StaticThreadController::run()` - Runs all the threads within the controller, - but only if needed (if `shouldRun()` returns true); -- `int StaticThreadController::size()` - Returns how many Threads are allocated inside the controller. -- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position - or `nullptr` if `index` - is out of bounds. + diff --git a/Resource.cpp b/Resource.cpp new file mode 100644 index 0000000..da82fbd --- /dev/null +++ b/Resource.cpp @@ -0,0 +1,31 @@ +// +// Created by emdash00 on 9/1/19. +// + +#include "Resource.hpp" + +bool Resource::Acquire(Thread *thread, int priority) +{ + if (owner == nullptr || priority > owner_priority) + { + owner = thread; + return true; + } + else + { + return false; + } +} + +bool Resource::HaveOwnership(Thread *thread){ + return (owner == thread); +} + + +void Resource::Release(Thread *thread) +{ + if (owner == thread) + { + owner = nullptr; + } +} \ No newline at end of file diff --git a/Resource.hpp b/Resource.hpp new file mode 100644 index 0000000..0b6636e --- /dev/null +++ b/Resource.hpp @@ -0,0 +1,27 @@ +// +// Created by emdash00 on 8/24/19. +// + +#ifndef ARDUINOTHREAD_RESOURCE_HPP +#define ARDUINOTHREAD_RESOURCE_HPP + +#include "Thread.h" + +class Resource +{ + +public: + bool Acquire(Thread *thread, int priority); + bool HaveOwnership(Thread *thread); + void Release(Thread *thread); + +protected: + Resource( const Resource& ) = delete; // non construction-copyable + Resource& operator=( const Resource& ) = delete; // non copyable + + Thread *owner = nullptr; + int owner_priority = -1; +}; + + +#endif //ARDUINOTHREAD_RESOURCE_HPP diff --git a/SingularThread.cpp b/SingularThread.cpp new file mode 100644 index 0000000..61b8bec --- /dev/null +++ b/SingularThread.cpp @@ -0,0 +1,12 @@ +// +// Created by emdash00 on 9/1/19. +// + +#include "SingularThread.hpp" + +void SingularThread::runned(unsigned long time) +{ + // Mark thread for removal. No need to call HALT. + flag = -1; + super::runned(time); +} \ No newline at end of file diff --git a/SingularThread.hpp b/SingularThread.hpp new file mode 100644 index 0000000..8e6a4db --- /dev/null +++ b/SingularThread.hpp @@ -0,0 +1,20 @@ +// +// Created by emdash00 on 9/1/19. +// + +#ifndef ARDUINOTHREAD_SINGULARTHREAD_HPP +#define ARDUINOTHREAD_SINGULARTHREAD_HPP + +#include "Thread.h" + +class SingularThread : public Thread +{ +public: + void runned(unsigned long time); + +private: + typedef Thread super; +}; + + +#endif //ARDUINOTHREAD_SINGULARTHREAD_HPP diff --git a/StaticThreadController.h b/StaticThreadController.h deleted file mode 100644 index 6e316ea..0000000 --- a/StaticThreadController.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - StaticThreadController.h - Controlls a list of Threads with different timings - - Basicaly, what it does is to keep track of current Threads and run when - necessary. - - StaticThreadController is an extended class of Thread, because of that, - it allows you to add a StaticThreadController inside another kind of ThreadController... - - It works exact as ThreadController except you can't add or remove treads dynamically. - - Created by Alex Eremin, September, 2016. - Released into the public domain. -*/ - -#ifndef StaticThreadController_h -#define StaticThreadController_h - -#include "Thread.h" - -template -class StaticThreadController: public Thread{ -protected: - //since this is a static controller, the pointers themselves can be const - //it should be distinguished from 'const Thread* thread[N]' - Thread * const thread[N]; -public: - template - StaticThreadController(T... params) : - Thread(), - thread{params...} - { - #ifdef USE_THREAD_NAMES - // Overrides name - ThreadName = "StaticThreadController "; - ThreadName = ThreadName + ThreadID; - #endif - }; - - // run() Method is overrided - void run() override - { - // Run this thread before - if(_onRun != nullptr && shouldRun()) - _onRun(); - - for(int i = 0; i < N; i++){ - // Is enabled? Timeout exceeded? - if(thread[i]->shouldRun()){ - thread[i]->run(); - } - } - - // StaticThreadController extends Thread, so we should flag as runned thread - runned(); - } - - // Return the quantity of Threads - static constexpr int size() { return N; }; - - // Return the I Thread on the array - // Returns nullptr if index is out of bounds - Thread* get(int index) { - return (index >= 0 && index < N) ? thread[index] : nullptr; - }; - - // Return the I Thread on the array - // Doesn't perform any bounds checks and behaviour is - // unpredictable in case of index > N - Thread& operator[](int index) { - return *thread[index]; - }; -}; - -#endif diff --git a/Thread.cpp b/Thread.cpp index cd29d98..6c0fd65 100644 --- a/Thread.cpp +++ b/Thread.cpp @@ -1,12 +1,9 @@ #include "Thread.h" -Thread::Thread(void (*callback)(void), unsigned long _interval){ - enabled = true; - onRun(callback); - _cached_next_run = 0; +Thread::Thread(void (*callback)(void), unsigned long _interval, unsigned long _timeout) : +_onRun(callback), ThreadID((int)this), timeout(_timeout){ last_run = millis(); - ThreadID = (int)this; #ifdef USE_THREAD_NAMES ThreadName = "Thread "; ThreadName = ThreadName + ThreadID; @@ -16,11 +13,20 @@ Thread::Thread(void (*callback)(void), unsigned long _interval){ }; void Thread::runned(unsigned long time){ - // Saves last_run - last_run = time; + // Saves last_run + last_run = time; + + // Cache next run + switch (pause_interval) { + case 0: + _cached_next_run = last_run + interval; + break; + default: + _cached_next_run = last_run + pause_interval; + break; + + } - // Cache next run - _cached_next_run = last_run + interval; } void Thread::setInterval(unsigned long _interval){ @@ -31,12 +37,40 @@ void Thread::setInterval(unsigned long _interval){ _cached_next_run = last_run + interval; } +void Thread::setTimeout(unsigned long _timeout){ + timeout = _timeout; +} + bool Thread::shouldRun(unsigned long time){ + // Nothing below matters if the thread is frozen. + if (frozen){ + return false; + } + // If the "sign" bit is set the signed difference would be negative bool time_remaining = (time - _cached_next_run) & 0x80000000; - // Exceeded the time limit, AND is enabled? Then should run... - return !time_remaining && enabled; + if(!time_remaining && enabled && !_started){ + _started = true; + _t0 = time; + } + + if (!time_remaining && pause_interval != 0){ + pause_interval = 0; + } + + switch (timeout) + { + // A timeout of 0 doesn't make sense and instead signifies a timeout that hasn't been set. + case 0: + // Exceeded the time limit, AND is enabled? Then should run... + return !time_remaining && enabled; + default: + bool timed_out = !((time - (_t0 + timeout)) & 0x80000000); + + // Exceeded the time limit, AND not timed out, AND is enabled? Then should run... + return !time_remaining && !timeout && enabled; + } } void Thread::onRun(void (*callback)(void)){ @@ -44,9 +78,12 @@ void Thread::onRun(void (*callback)(void)){ } void Thread::run(){ - if(_onRun != NULL) + if(_onRun != nullptr) _onRun(); + + + // Update last_run and _cached_next_run runned(); } diff --git a/Thread.h b/Thread.h index 0e580a9..b03554b 100644 --- a/Thread.h +++ b/Thread.h @@ -22,6 +22,87 @@ #include +#include "ThreadController.h" + + +/* + The following macros provide async style programming using ThreadController with some limitations. See below. + Unfortunately, AWAIT, YIELD_TO, and HALT_TO cannot be used in a static context. + Since threads cannot be removed in a static context, HALT is the same as FREEZE in StaticThreadControllers. +*/ + + +/* + YIELDs execution back to the ThreadController through the use of a return statement, + also setting the specified flag. Note: flag -1 is special. See HALT. + This and macros that depend on it must be used in run() because the return won't work as intended otherwise. +*/ +#define YIELD(_flag) (flag = _flag); runned(); return + +// Called when YIELD is called without a custom flag. Sets default flag 0. +#define YIELD_DEFAULT YIELD(0) + +// Links the specified thread and YIELDs. The linked thread is called immediately from the main loop. +#define YIELD_TO(_thread, _flag) (_linked_thread = thread), YIELD(_flag) + +/* + Gets a pointer to the eventual result of a WorkerThread's work of type 'type'. A "future" if you may. + The pointer is from thread->result and is nullptr until the thread is done working. + The work will pause and wait for all awaiting threads to retrieve the result before continuing work. + Warning, if the thread doesn't have a result any code after the AWAIT in run() will never be executed. + + CANNOT be used as a function parameter due to having returns within the macro body. +*/ +#define AWAIT(type, _thread, _flag) \ + (_thread->result ? (type)_thread->result : nullptr); \ + awaiting = true; \ + ({ \ + if (!linked_thread) \ + { \ + (_thread->num_awaiting)++; \ + YIELD_TO(_thread, _flag); \ + } \ + else \ + { \ + if (_thread_result == nullptr) \ + { \ + YIELD(_flag); \ + } \ + else \ + { \ + linked_thread = nullptr; \ + awaiting = false; \ + (_thread->num_awaiting)--; \ + } \ + } \ + }) + +#define ACQUIRE_RESOURCE(_resource, _flag) \ + \ + ({ \ + if (!_resource.Acquire()){ \ + YIELD(_flag); \ + } \ + }) + +// Suspends the thread's execution for pause_interval ms. +#define PAUSE(_pause_interval, _flag) (pause_interval = _pause_interval), YIELD(_flag) + +// Suspends the thread's execution indefinetely. Thread will resume on a call to RESUME +#define FREEZE(_flag) (frozen = true), YIELD(_flag) + +// Unsuspends a suspended thread by either unfreezing and/or unpausing it. +#define RESUME(_thread) (_thread->frozen = false, _thread->pause_interval = 0;) + +/* + YIELDs with flag -1. ThreadController will remove any thread with flag -1 after calling run() + In StaticThreadController, it will simply FREEZE the thread since it cannot be removed. +*/ +#define HALT YIELD(-1) + +// HALTs the thread, offshooting another thread. Similar to YIELD_To but will remove the current thread. +#define HALT_TO(_thread) YIELD_TO(_thread, -1) + /* Uncomment this line to enable ThreadName Strings. @@ -30,16 +111,40 @@ */ // #define USE_THREAD_NAMES 1 +Thread _curentThread = nullptr; + class Thread{ protected: // Desired interval between runs unsigned long interval; + // Desired interval to pause a thread. + // shouldRun will return false until this interval is over, upon which the thread is immediately run. + unsigned long pause_interval = 0; + + // If time exceeds timeout (ms), shouldRun() will always return false + unsigned long timeout; + + // If the thread is frozen or not. + bool frozen = false; + // Last runned time in Ms unsigned long last_run; // Scheduled run in Ms (MUST BE CACHED) - unsigned long _cached_next_run; + unsigned long _cached_next_run = 0; + + // Time that the thread was started + unsigned long _t0; + + // True when shouldRun returns true for the first time. + bool _started = false; + + // The number of threads awaiting this one. + // Used by workers to determine when every thread that wants the results gets it before clearing it and continuing. + int num_awaiting = 0; + + /* IMPORTANT! Run after all calls to run() @@ -53,26 +158,40 @@ class Thread{ void runned() { runned(millis()); } // Callback for run() if not implemented - void (*_onRun)(void); + void (*_onRun)(void) = nullptr; public: // If the current Thread is enabled or not - bool enabled; + bool enabled = true; + + // If the current thread is awaiting another thread. + bool awaiting = false; // ID of the Thread (initialized from memory adr.) int ThreadID; - #ifdef USE_THREAD_NAMES + // Used to store and restore the state of a thread. + int flag = 0; + + // Thread this thread has linked via YIELD_TO + Thread *linked_thread = nullptr; + + //Represents the result of a thread's work, if any. "Void" threads have result always equal to nullptr. + void *result = nullptr; + +#ifdef USE_THREAD_NAMES // Thread Name (used for better UI). String ThreadName; #endif - Thread(void (*callback)(void) = NULL, unsigned long _interval = 0); + Thread(void (*callback)(void) = NULL, unsigned long _interval = 0, unsigned long _timeout = 0); // Set the desired interval for calls, and update _cached_next_run virtual void setInterval(unsigned long _interval); + void setTimeout(unsigned long _timeout); + // Return if the Thread should be runned or not virtual bool shouldRun(unsigned long time); @@ -84,6 +203,9 @@ class Thread{ // Runs Thread virtual void run(); + + // Runs before the thread is removed. + virtual void cleanup(); }; #endif diff --git a/ThreadController.cpp b/ThreadController.cpp index 7d8e41c..0f03df3 100644 --- a/ThreadController.cpp +++ b/ThreadController.cpp @@ -2,8 +2,6 @@ #include "ThreadController.h" ThreadController::ThreadController(unsigned long _interval): Thread(){ - cached_size = 0; - clear(); setInterval(_interval); @@ -28,8 +26,24 @@ void ThreadController::run(){ // Object exists? Is enabled? Timeout exceeded? if(thread[i]){ checks++; + if(thread[i]->shouldRun(time)){ thread[i]->run(); + + + // Thread was linked using YIELD_TO or AWAIT + if (thread[i]->linked_thread) + { + add(thread[i]->linked_thread); + + // Don't remove the linked thread if it's AWAITing a result. + if (!thread[i]->awaiting) + thread[i]->linked_thread = nullptr; + + } + // HALT was called within the thread. Remove it. + if (thread[i]->flag == -1) + remove(thread[i]->ThreadID); } } } @@ -67,7 +81,8 @@ void ThreadController::remove(int id){ // Find Threads with the id, and removes for(int i = 0; i < MAX_THREADS; i++){ if(thread[i]->ThreadID == id){ - thread[i] = NULL; + thread[i]->cleanup(); + thread[i] = nullptr; cached_size--; return; } diff --git a/ThreadController.h b/ThreadController.h index 8e2888c..5a9e970 100644 --- a/ThreadController.h +++ b/ThreadController.h @@ -24,7 +24,7 @@ class ThreadController: public Thread{ protected: Thread* thread[MAX_THREADS]; - int cached_size; + int cached_size = 0; public: ThreadController(unsigned long _interval = 0);