This is a breaking change - see BREAKING-CHANGES.txtv7.0.9
| @@ -1,9 +1,37 @@ | |||
| JUCE breaking changes | |||
| ===================== | |||
| develop | |||
| Develop | |||
| ======= | |||
| Change | |||
| ------ | |||
| The Thread::startThread (int) and Thread::setPriority (int) methods have been | |||
| removed. A new Thread priority API has been introduced. | |||
| Possible Issues | |||
| --------------- | |||
| Code will fail to compile. | |||
| Workaround | |||
| ---------- | |||
| Rather than using an integer thread priority you must instead use a value from | |||
| the Thread::Priority enum. Thread::setPriority and Thread::getPriority should | |||
| only be called from the target thread. To start a Thread with a realtime | |||
| performance profile you must call startRealtimeThread. | |||
| Rationale | |||
| --------- | |||
| Operating systems are moving away from a specific thread priority and towards | |||
| more granular control over which types of cores can be used and things like | |||
| power throttling options. In particular, it is no longer possible to map a 0-10 | |||
| integer to a meaningful performance range on macOS ARM using the pthread | |||
| interface. Using a more modern interface grants us access to more runtime | |||
| options, but also changes how we can work with threads. The two most | |||
| significant changes are that we cannot mix operations using the new and old | |||
| interfaces, and that changing a priority using the new interface can only be | |||
| done on the currently running thread. | |||
| Change | |||
| ------ | |||
| The constructor of WebBrowserComponent now requires passing in an instance of | |||
| @@ -40,7 +68,6 @@ browser backend is more intuitive then requiring the user to derive from a | |||
| special class, especially if additional browser backends are added in the | |||
| future. | |||
| Change | |||
| ------ | |||
| The function AudioIODeviceCallback::audioDeviceIOCallback() was removed. | |||
| @@ -325,7 +325,7 @@ public: | |||
| // audio setup | |||
| formatManager.registerBasicFormats(); | |||
| thread.startThread (3); | |||
| thread.startThread (Thread::Priority::normal); | |||
| #ifndef JUCE_DEMO_RUNNER | |||
| RuntimePermissions::request (RuntimePermissions::recordAudio, | |||
| @@ -57,7 +57,7 @@ public: | |||
| { | |||
| setOpaque (true); | |||
| imageList.setDirectory (File::getSpecialLocation (File::userPicturesDirectory), true, true); | |||
| directoryThread.startThread (1); | |||
| directoryThread.startThread (Thread::Priority::background); | |||
| fileTree.setTitle ("Files"); | |||
| fileTree.addListener (this); | |||
| @@ -154,7 +154,7 @@ public: | |||
| setOpaque (true); | |||
| movieList.setDirectory (File::getSpecialLocation (File::userMoviesDirectory), true, true); | |||
| directoryThread.startThread (1); | |||
| directoryThread.startThread (Thread::Priority::background); | |||
| fileTree.setTitle ("Files"); | |||
| fileTree.addListener (this); | |||
| @@ -63,7 +63,7 @@ public: | |||
| SharedTimeSliceThread() | |||
| : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread") | |||
| { | |||
| startThread (7); // Above default priority so playback is fluent, but below realtime | |||
| startThread (Priority::high); // Above default priority so playback is fluent, but below realtime | |||
| } | |||
| }; | |||
| @@ -148,9 +148,7 @@ public: | |||
| : BouncingBall (containerComp), | |||
| Thread ("JUCE Demo Thread") | |||
| { | |||
| // give the threads a random priority, so some will move more | |||
| // smoothly than others.. | |||
| startThread (Random::getSystemRandom().nextInt (3) + 3); | |||
| startThread(); | |||
| } | |||
| ~DemoThread() override | |||
| @@ -44,7 +44,7 @@ void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background) | |||
| if (! isThreadRunning()) | |||
| { | |||
| backgroundCheck = background; | |||
| startThread (3); | |||
| startThread (Priority::low); | |||
| } | |||
| } | |||
| @@ -373,7 +373,7 @@ public: | |||
| : ThreadWithProgressWindow ("Downloading New Version", true, true), | |||
| asset (a), targetFolder (t), completionCallback (std::move (cb)) | |||
| { | |||
| launchThread (3); | |||
| launchThread (Priority::low); | |||
| } | |||
| private: | |||
| @@ -87,7 +87,7 @@ void MidiOutput::clearAllPendingMessages() | |||
| void MidiOutput::startBackgroundThread() | |||
| { | |||
| startThread (9); | |||
| startThread (Priority::high); | |||
| } | |||
| void MidiOutput::stopBackgroundThread() | |||
| @@ -253,7 +253,7 @@ public: | |||
| if (inputDevice != nullptr) | |||
| env->CallVoidMethod (inputDevice, AudioRecord.startRecording); | |||
| startThread (8); | |||
| startThread (Priority::high); | |||
| } | |||
| else | |||
| { | |||
| @@ -618,7 +618,7 @@ public: | |||
| if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle))) | |||
| return; | |||
| startThread (9); | |||
| startThread (Priority::high); | |||
| int count = 1000; | |||
| @@ -1196,7 +1196,7 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, | |||
| for (int i = 0; i < inChans.size(); ++i) | |||
| inChans.getUnchecked(i)->synchronisePosition(); | |||
| startThread (9); | |||
| startThread (Priority::highest); | |||
| sleep (10); | |||
| notify(); | |||
| @@ -1387,7 +1387,7 @@ public: | |||
| shouldShutdown = false; | |||
| deviceSampleRateChanged = false; | |||
| startThread (8); | |||
| startThread (Priority::high); | |||
| Thread::sleep (5); | |||
| if (inputDevice != nullptr && inputDevice->client != nullptr) | |||
| @@ -254,7 +254,7 @@ public: | |||
| void runTest() override | |||
| { | |||
| TimeSliceThread timeSlice ("TestBackgroundThread"); | |||
| timeSlice.startThread (5); | |||
| timeSlice.startThread (Thread::Priority::normal); | |||
| beginTest ("Timeout"); | |||
| { | |||
| @@ -25,8 +25,6 @@ | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| #include <thread> | |||
| namespace juce | |||
| { | |||
| @@ -34,15 +32,15 @@ namespace juce | |||
| bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); | |||
| /** @internal */ | |||
| class MessageThread | |||
| class MessageThread : public Thread | |||
| { | |||
| public: | |||
| MessageThread() | |||
| MessageThread() : Thread ("JUCE Plugin Message Thread") | |||
| { | |||
| start(); | |||
| } | |||
| ~MessageThread() | |||
| ~MessageThread() override | |||
| { | |||
| MessageManager::getInstance()->stopDispatchLoop(); | |||
| stop(); | |||
| @@ -50,51 +48,37 @@ public: | |||
| void start() | |||
| { | |||
| if (isRunning()) | |||
| stop(); | |||
| shouldExit = false; | |||
| thread = std::thread { [this] | |||
| { | |||
| Thread::setCurrentThreadPriority (7); | |||
| Thread::setCurrentThreadName ("JUCE Plugin Message Thread"); | |||
| MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
| XWindowSystem::getInstance(); | |||
| startThread (Priority::high); | |||
| threadInitialised.signal(); | |||
| for (;;) | |||
| { | |||
| if (! dispatchNextMessageOnSystemQueue (true)) | |||
| Thread::sleep (1); | |||
| if (shouldExit) | |||
| break; | |||
| } | |||
| } }; | |||
| threadInitialised.wait(); | |||
| // Wait for setCurrentThreadAsMessageThread() and getInstance to be executed | |||
| // before leaving this method | |||
| threadInitialised.wait (10000); | |||
| } | |||
| void stop() | |||
| { | |||
| if (! isRunning()) | |||
| return; | |||
| shouldExit = true; | |||
| thread.join(); | |||
| signalThreadShouldExit(); | |||
| stopThread (-1); | |||
| } | |||
| bool isRunning() const noexcept { return thread.joinable(); } | |||
| bool isRunning() const noexcept { return isThreadRunning(); } | |||
| private: | |||
| WaitableEvent threadInitialised; | |||
| std::thread thread; | |||
| void run() override | |||
| { | |||
| MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
| XWindowSystem::getInstance(); | |||
| std::atomic<bool> shouldExit { false }; | |||
| threadInitialised.signal(); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (! dispatchNextMessageOnSystemQueue (true)) | |||
| Thread::sleep (1); | |||
| } | |||
| } | |||
| private: | |||
| WaitableEvent threadInitialised; | |||
| JUCE_DECLARE_NON_MOVEABLE (MessageThread) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageThread) | |||
| }; | |||
| @@ -64,7 +64,7 @@ AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbs) | |||
| maxNumThumbsToStore (maxNumThumbs) | |||
| { | |||
| jassert (maxNumThumbsToStore > 0); | |||
| thread.startThread (2); | |||
| thread.startThread (Thread::Priority::low); | |||
| } | |||
| AudioThumbnailCache::~AudioThumbnailCache() | |||
| @@ -186,6 +186,7 @@ | |||
| #include "zip/juce_ZipFile.cpp" | |||
| #include "files/juce_FileFilter.cpp" | |||
| #include "files/juce_WildcardFileFilter.cpp" | |||
| #include "native/juce_native_ThreadPriorities.h" | |||
| //============================================================================== | |||
| #if ! JUCE_WINDOWS | |||
| @@ -203,6 +203,7 @@ | |||
| #include <sys/ptrace.h> | |||
| #include <sys/socket.h> | |||
| #include <sys/stat.h> | |||
| #include <sys/syscall.h> | |||
| #include <sys/sysinfo.h> | |||
| #include <sys/time.h> | |||
| #include <sys/types.h> | |||
| @@ -344,36 +344,79 @@ LocalRef<jobject> getMainActivity() noexcept | |||
| } | |||
| //============================================================================== | |||
| // sets the process to 0=low priority, 1=normal, 2=high, 3=realtime | |||
| JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) | |||
| #if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE) | |||
| #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1 | |||
| #endif | |||
| #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE | |||
| pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); | |||
| #endif | |||
| extern JavaVM* androidJNIJavaVM; | |||
| bool Thread::createNativeThread (Priority) | |||
| { | |||
| // TODO | |||
| if (isRealtime()) | |||
| { | |||
| #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE | |||
| threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this); | |||
| threadId = (ThreadID) threadHandle.get(); | |||
| return threadId != nullptr; | |||
| #else | |||
| jassertfalse; | |||
| #endif | |||
| } | |||
| struct sched_param param; | |||
| int policy, maxp, minp; | |||
| PosixThreadAttribute attr { threadStackSize }; | |||
| threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* | |||
| { | |||
| auto* myself = static_cast<Thread*> (userData); | |||
| const int p = (int) prior; | |||
| juce_threadEntryPoint (myself); | |||
| if (p <= 1) | |||
| policy = SCHED_OTHER; | |||
| else | |||
| policy = SCHED_RR; | |||
| if (androidJNIJavaVM != nullptr) | |||
| { | |||
| void* env = nullptr; | |||
| androidJNIJavaVM->GetEnv (&env, JNI_VERSION_1_2); | |||
| minp = sched_get_priority_min (policy); | |||
| maxp = sched_get_priority_max (policy); | |||
| // only detach if we have actually been attached | |||
| if (env != nullptr) | |||
| androidJNIJavaVM->DetachCurrentThread(); | |||
| } | |||
| if (p < 2) | |||
| param.sched_priority = 0; | |||
| else if (p == 2 ) | |||
| // Set to middle of lower realtime priority range | |||
| param.sched_priority = minp + (maxp - minp) / 4; | |||
| else | |||
| // Set to middle of higher realtime priority range | |||
| param.sched_priority = minp + (3 * (maxp - minp) / 4); | |||
| return nullptr; | |||
| }); | |||
| pthread_setschedparam (pthread_self(), policy, ¶m); | |||
| return threadId != nullptr; | |||
| } | |||
| void Thread::killThread() | |||
| { | |||
| if (threadHandle != nullptr) | |||
| jassertfalse; // pthread_cancel not available! | |||
| } | |||
| Thread::Priority Thread::getPriority() const | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| const auto native = getpriority (PRIO_PROCESS, (id_t) gettid()); | |||
| return ThreadPriorities::getJucePriority (native); | |||
| } | |||
| bool Thread::setPriority (Priority) | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| if (isRealtime()) | |||
| return false; | |||
| return setpriority (PRIO_PROCESS, (id_t) gettid(), ThreadPriorities::getNativePriority (priority)) == 0; | |||
| } | |||
| //============================================================================== | |||
| JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {} | |||
| JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept | |||
| { | |||
| StringArray lines; | |||
| @@ -28,27 +28,49 @@ namespace juce | |||
| live in juce_posix_SharedCode.h! | |||
| */ | |||
| //============================================================================== | |||
| JUCE_API void JUCE_CALLTYPE Process::setPriority (const ProcessPriority prior) | |||
| bool Thread::createNativeThread (Priority) | |||
| { | |||
| auto policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR; | |||
| auto minp = sched_get_priority_min (policy); | |||
| auto maxp = sched_get_priority_max (policy); | |||
| struct sched_param param; | |||
| PosixThreadAttribute attr { threadStackSize }; | |||
| PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, {}).apply (attr); | |||
| switch (prior) | |||
| threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* | |||
| { | |||
| case LowPriority: | |||
| case NormalPriority: param.sched_priority = 0; break; | |||
| case HighPriority: param.sched_priority = minp + (maxp - minp) / 4; break; | |||
| case RealtimePriority: param.sched_priority = minp + (3 * (maxp - minp) / 4); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| pthread_setschedparam (pthread_self(), policy, ¶m); | |||
| auto* myself = static_cast<Thread*> (userData); | |||
| juce_threadEntryPoint (myself); | |||
| return nullptr; | |||
| }); | |||
| return threadId != nullptr; | |||
| } | |||
| void Thread::killThread() | |||
| { | |||
| if (threadHandle != nullptr) | |||
| pthread_cancel ((pthread_t) threadHandle.load()); | |||
| } | |||
| // Until we implement Nice awareness, these don't do anything on Linux. | |||
| Thread::Priority Thread::getPriority() const | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| return priority; | |||
| } | |||
| bool Thread::setPriority (Priority newPriority) | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| // Return true to make it compatible with other platforms. | |||
| priority = newPriority; | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {} | |||
| static bool swapUserAndEffectiveUser() | |||
| { | |||
| auto result1 = setreuid (geteuid(), getuid()); | |||
| @@ -29,9 +29,139 @@ namespace juce | |||
| */ | |||
| #if JUCE_IOS | |||
| bool isIOSAppActive = true; | |||
| bool isIOSAppActive = true; | |||
| #endif | |||
| API_AVAILABLE (macos (10.10)) | |||
| static auto getNativeQOS (Thread::Priority priority) | |||
| { | |||
| switch (priority) | |||
| { | |||
| case Thread::Priority::highest: return QOS_CLASS_USER_INTERACTIVE; | |||
| case Thread::Priority::high: return QOS_CLASS_USER_INITIATED; | |||
| case Thread::Priority::low: return QOS_CLASS_UTILITY; | |||
| case Thread::Priority::background: return QOS_CLASS_BACKGROUND; | |||
| case Thread::Priority::normal: break; | |||
| } | |||
| return QOS_CLASS_DEFAULT; | |||
| } | |||
| API_AVAILABLE (macos (10.10)) | |||
| static auto getJucePriority (qos_class_t qos) | |||
| { | |||
| switch (qos) | |||
| { | |||
| case QOS_CLASS_USER_INTERACTIVE: return Thread::Priority::highest; | |||
| case QOS_CLASS_USER_INITIATED: return Thread::Priority::high; | |||
| case QOS_CLASS_UTILITY: return Thread::Priority::low; | |||
| case QOS_CLASS_BACKGROUND: return Thread::Priority::background; | |||
| case QOS_CLASS_UNSPECIFIED: | |||
| case QOS_CLASS_DEFAULT: break; | |||
| } | |||
| return Thread::Priority::normal; | |||
| } | |||
| bool Thread::createNativeThread (Priority priority) | |||
| { | |||
| PosixThreadAttribute attr { threadStackSize }; | |||
| if (@available (macos 10.10, *)) | |||
| pthread_attr_set_qos_class_np (attr.get(), getNativeQOS (priority), 0); | |||
| else | |||
| PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, priority).apply (attr); | |||
| threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* | |||
| { | |||
| auto* myself = static_cast<Thread*> (userData); | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| juce_threadEntryPoint (myself); | |||
| } | |||
| return nullptr; | |||
| }); | |||
| return threadId != nullptr; | |||
| } | |||
| void Thread::killThread() | |||
| { | |||
| if (threadHandle != nullptr) | |||
| pthread_cancel ((pthread_t) threadHandle.load()); | |||
| } | |||
| Thread::Priority Thread::getPriority() const | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| if (! isRealtime()) | |||
| { | |||
| if (@available (macOS 10.10, *)) | |||
| return getJucePriority (qos_class_self()); | |||
| // fallback for older versions of macOS | |||
| const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER)); | |||
| const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER)); | |||
| if (min != 0 && max != 0) | |||
| { | |||
| const auto native = PosixSchedulerPriority::findCurrentSchedulerAndPriority().getPriority(); | |||
| const auto mapped = jmap (native, min, max, 0, 4); | |||
| return ThreadPriorities::getJucePriority (mapped); | |||
| } | |||
| } | |||
| return {}; | |||
| } | |||
| bool Thread::setPriority (Priority priority) | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| if (isRealtime()) | |||
| { | |||
| // macOS/iOS needs to know how much time you need! | |||
| jassert (realtimeOptions->workDurationMs > 0); | |||
| mach_timebase_info_data_t timebase; | |||
| mach_timebase_info (&timebase); | |||
| const auto periodMs = realtimeOptions->workDurationMs; | |||
| const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; | |||
| const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits<uint32_t>::max(), periodMs * ticksPerMs); | |||
| thread_time_constraint_policy_data_t policy; | |||
| policy.period = periodTicks; | |||
| policy.computation = jmin ((uint32_t) 50000, policy.period); | |||
| policy.constraint = policy.period; | |||
| policy.preemptible = true; | |||
| return thread_policy_set (pthread_mach_thread_np (pthread_self()), | |||
| THREAD_TIME_CONSTRAINT_POLICY, | |||
| (thread_policy_t) &policy, | |||
| THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; | |||
| } | |||
| if (@available (macOS 10.10, *)) | |||
| return pthread_set_qos_class_self_np (getNativeQOS (priority), 0) == 0; | |||
| #if JUCE_ARM | |||
| // M1 platforms should never reach this code!!!!!! | |||
| jassertfalse; | |||
| #endif | |||
| // Just in case older versions of macOS support SCHED_OTHER priorities. | |||
| const auto psp = PosixSchedulerPriority::getNativeSchedulerAndPriority ({}, priority); | |||
| struct sched_param param; | |||
| param.sched_priority = psp.getPriority(); | |||
| return pthread_setschedparam (pthread_self(), psp.getScheduler(), ¶m) == 0; | |||
| } | |||
| //============================================================================== | |||
| JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() | |||
| { | |||
| @@ -0,0 +1,109 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| struct ThreadPriorities | |||
| { | |||
| struct Entry | |||
| { | |||
| Thread::Priority priority; | |||
| int native; | |||
| }; | |||
| #if JUCE_ANDROID | |||
| enum AndroidThreadPriority | |||
| { | |||
| THREAD_PRIORITY_AUDIO = -16, | |||
| THREAD_PRIORITY_FOREGROUND = -2, | |||
| THREAD_PRIORITY_MORE_FAVORABLE = -1, | |||
| THREAD_PRIORITY_DEFAULT = 0, | |||
| THREAD_PRIORITY_LESS_FAVORABLE = 1, | |||
| THREAD_PRIORITY_BACKGROUND = 10, | |||
| THREAD_PRIORITY_LOWEST = 19 | |||
| }; | |||
| #endif | |||
| inline static constexpr Entry table[] | |||
| { | |||
| #if JUCE_ANDROID | |||
| { Thread::Priority::highest, AndroidThreadPriority::THREAD_PRIORITY_AUDIO }, | |||
| { Thread::Priority::high, AndroidThreadPriority::THREAD_PRIORITY_FOREGROUND }, | |||
| { Thread::Priority::normal, AndroidThreadPriority::THREAD_PRIORITY_DEFAULT }, | |||
| { Thread::Priority::low, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND - 5 }, | |||
| { Thread::Priority::background, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND }, | |||
| #endif | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| { Thread::Priority::highest, 0 }, | |||
| { Thread::Priority::high, 0 }, | |||
| { Thread::Priority::normal, 0 }, | |||
| { Thread::Priority::low, 0 }, | |||
| { Thread::Priority::background, 0 }, | |||
| #endif | |||
| #if JUCE_MAC | JUCE_IOS | |||
| { Thread::Priority::highest, 4 }, | |||
| { Thread::Priority::high, 3 }, | |||
| { Thread::Priority::normal, 2 }, | |||
| { Thread::Priority::low, 1 }, | |||
| { Thread::Priority::background, 0 }, | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| { Thread::Priority::highest, THREAD_PRIORITY_TIME_CRITICAL }, | |||
| { Thread::Priority::high, THREAD_PRIORITY_HIGHEST }, | |||
| { Thread::Priority::normal, THREAD_PRIORITY_NORMAL }, | |||
| { Thread::Priority::low, THREAD_PRIORITY_LOWEST }, | |||
| { Thread::Priority::background, THREAD_PRIORITY_IDLE }, | |||
| #endif | |||
| }; | |||
| static_assert (std::size (table) == 5, | |||
| "The platform may be unsupported or there may be a priority entry missing."); | |||
| static Thread::Priority getJucePriority (const int value) | |||
| { | |||
| const auto iter = std::min_element (std::begin (table), | |||
| std::end (table), | |||
| [value] (const auto& a, const auto& b) | |||
| { | |||
| return std::abs (a.native - value) < std::abs (b.native - value); | |||
| }); | |||
| jassert (iter != std::end (table)); | |||
| return iter != std::end (table) ? iter->priority : Thread::Priority{}; | |||
| } | |||
| static int getNativePriority (const Thread::Priority value) | |||
| { | |||
| const auto iter = std::find_if (std::begin (table), | |||
| std::end (table), | |||
| [value] (const auto& entry) { return entry.priority == value; }); | |||
| jassert (iter != std::end (table)); | |||
| return iter != std::end (table) ? iter->native : 0; | |||
| } | |||
| }; | |||
| } // namespace juce | |||
| @@ -851,98 +851,131 @@ void InterProcessLock::exit() | |||
| pimpl.reset(); | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_ANDROID | |||
| extern JavaVM* androidJNIJavaVM; | |||
| #endif | |||
| static void* threadEntryProc (void* userData) | |||
| class PosixThreadAttribute | |||
| { | |||
| auto* myself = static_cast<Thread*> (userData); | |||
| JUCE_AUTORELEASEPOOL | |||
| public: | |||
| explicit PosixThreadAttribute (size_t stackSize) | |||
| { | |||
| juce_threadEntryPoint (myself); | |||
| if (valid) | |||
| pthread_attr_setstacksize (&attr, stackSize); | |||
| } | |||
| #if JUCE_ANDROID | |||
| if (androidJNIJavaVM != nullptr) | |||
| ~PosixThreadAttribute() | |||
| { | |||
| void* env = nullptr; | |||
| androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2); | |||
| // only detach if we have actually been attached | |||
| if (env != nullptr) | |||
| androidJNIJavaVM->DetachCurrentThread(); | |||
| if (valid) | |||
| pthread_attr_destroy (&attr); | |||
| } | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| #if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE) | |||
| #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1 | |||
| #endif | |||
| auto* get() { return valid ? &attr : nullptr; } | |||
| #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE | |||
| extern pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); | |||
| #endif | |||
| private: | |||
| pthread_attr_t attr; | |||
| bool valid { pthread_attr_init (&attr) == 0 }; | |||
| }; | |||
| void Thread::launchThread() | |||
| class PosixSchedulerPriority | |||
| { | |||
| #if JUCE_ANDROID | |||
| if (isAndroidRealtimeThread) | |||
| public: | |||
| static PosixSchedulerPriority findCurrentSchedulerAndPriority() | |||
| { | |||
| #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE | |||
| threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this); | |||
| threadId = (ThreadID) threadHandle.get(); | |||
| return; | |||
| #else | |||
| jassertfalse; | |||
| #endif | |||
| int scheduler{}; | |||
| sched_param param{}; | |||
| pthread_getschedparam (pthread_self(), &scheduler, ¶m); | |||
| return { scheduler, param.sched_priority }; | |||
| } | |||
| #endif | |||
| threadHandle = {}; | |||
| pthread_t handle = {}; | |||
| pthread_attr_t attr; | |||
| pthread_attr_t* attrPtr = nullptr; | |||
| if (pthread_attr_init (&attr) == 0) | |||
| static PosixSchedulerPriority getNativeSchedulerAndPriority (const Optional<Thread::RealtimeOptions>& rt, | |||
| [[maybe_unused]] Thread::Priority prio) | |||
| { | |||
| attrPtr = &attr; | |||
| pthread_attr_setstacksize (attrPtr, threadStackSize); | |||
| } | |||
| const auto isRealtime = rt.hasValue(); | |||
| const auto priority = [&] | |||
| { | |||
| if (isRealtime) | |||
| { | |||
| const auto min = jmax (0, sched_get_priority_min (SCHED_RR)); | |||
| const auto max = jmax (1, sched_get_priority_max (SCHED_RR)); | |||
| return jmap (rt->priority, 0, 10, min, max); | |||
| } | |||
| // We only use this helper if we're on an old macos/ios platform that might | |||
| // still respect legacy pthread priorities for SCHED_OTHER. | |||
| #if JUCE_MAC || JUCE_IOS | |||
| const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER)); | |||
| const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER)); | |||
| const auto p = [prio] | |||
| { | |||
| switch (prio) | |||
| { | |||
| case Thread::Priority::highest: return 4; | |||
| case Thread::Priority::high: return 3; | |||
| case Thread::Priority::normal: return 2; | |||
| case Thread::Priority::low: return 1; | |||
| case Thread::Priority::background: return 0; | |||
| } | |||
| return 3; | |||
| }(); | |||
| if (min != 0 && max != 0) | |||
| return jmap (p, 0, 4, min, max); | |||
| #endif | |||
| return 0; | |||
| }(); | |||
| #if JUCE_MAC || JUCE_IOS | |||
| const auto scheduler = SCHED_OTHER; | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| const auto backgroundSched = prio == Thread::Priority::background ? SCHED_IDLE | |||
| : SCHED_OTHER; | |||
| const auto scheduler = isRealtime ? SCHED_RR : backgroundSched; | |||
| #else | |||
| const auto scheduler = 0; | |||
| #endif | |||
| return { scheduler, priority }; | |||
| } | |||
| if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) | |||
| void apply ([[maybe_unused]] PosixThreadAttribute& attr) const | |||
| { | |||
| pthread_detach (handle); | |||
| threadHandle = (void*) handle; | |||
| threadId = (ThreadID) threadHandle.get(); | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| const struct sched_param param { getPriority() }; | |||
| pthread_attr_setinheritsched (attr.get(), PTHREAD_EXPLICIT_SCHED); | |||
| pthread_attr_setschedpolicy (attr.get(), getScheduler()); | |||
| pthread_attr_setschedparam (attr.get(), ¶m); | |||
| #endif | |||
| } | |||
| if (attrPtr != nullptr) | |||
| pthread_attr_destroy (attrPtr); | |||
| } | |||
| constexpr int getScheduler() const { return scheduler; } | |||
| constexpr int getPriority() const { return priority; } | |||
| void Thread::closeThreadHandle() | |||
| private: | |||
| constexpr PosixSchedulerPriority (int schedulerIn, int priorityIn) | |||
| : scheduler (schedulerIn), priority (priorityIn) {} | |||
| int scheduler; | |||
| int priority; | |||
| }; | |||
| static void* makeThreadHandle (PosixThreadAttribute& attr, Thread* userData, void* (*threadEntryProc) (void*)) | |||
| { | |||
| threadId = {}; | |||
| threadHandle = {}; | |||
| pthread_t handle = {}; | |||
| if (pthread_create (&handle, attr.get(), threadEntryProc, userData) != 0) | |||
| return nullptr; | |||
| pthread_detach (handle); | |||
| return (void*) handle; | |||
| } | |||
| void Thread::killThread() | |||
| void Thread::closeThreadHandle() | |||
| { | |||
| if (threadHandle.get() != nullptr) | |||
| { | |||
| #if JUCE_ANDROID | |||
| jassertfalse; // pthread_cancel not available! | |||
| #else | |||
| pthread_cancel ((pthread_t) threadHandle.get()); | |||
| #endif | |||
| } | |||
| threadId = {}; | |||
| threadHandle = nullptr; | |||
| } | |||
| void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) | |||
| @@ -963,41 +996,6 @@ void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) | |||
| #endif | |||
| } | |||
| bool Thread::setThreadPriority (void* handle, int priority) | |||
| { | |||
| constexpr auto maxInputPriority = 10; | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| constexpr auto lowestRrPriority = 8; | |||
| #else | |||
| constexpr auto lowestRrPriority = 0; | |||
| #endif | |||
| struct sched_param param; | |||
| int policy; | |||
| if (handle == nullptr) | |||
| handle = (void*) pthread_self(); | |||
| if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) | |||
| return false; | |||
| policy = priority < lowestRrPriority ? SCHED_OTHER : SCHED_RR; | |||
| const auto minPriority = sched_get_priority_min (policy); | |||
| const auto maxPriority = sched_get_priority_max (policy); | |||
| param.sched_priority = [&] | |||
| { | |||
| if (policy == SCHED_OTHER) | |||
| return 0; | |||
| return jmap (priority, lowestRrPriority, maxInputPriority, minPriority, maxPriority); | |||
| }(); | |||
| return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | |||
| } | |||
| Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | |||
| { | |||
| return (ThreadID) pthread_self(); | |||
| @@ -1262,145 +1260,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
| #endif | |||
| //============================================================================== | |||
| struct HighResolutionTimer::Pimpl | |||
| { | |||
| explicit Pimpl (HighResolutionTimer& t) | |||
| : owner (t) | |||
| {} | |||
| ~Pimpl() | |||
| { | |||
| jassert (periodMs == 0); | |||
| stop(); | |||
| } | |||
| void start (int newPeriod) | |||
| { | |||
| if (periodMs == newPeriod) | |||
| return; | |||
| if (thread.get_id() == std::this_thread::get_id()) | |||
| { | |||
| periodMs = newPeriod; | |||
| return; | |||
| } | |||
| stop(); | |||
| periodMs = newPeriod; | |||
| thread = std::thread ([this, newPeriod] | |||
| { | |||
| setThisThreadToRealtime ((uint64) newPeriod); | |||
| auto lastPeriod = periodMs.load(); | |||
| Clock clock (lastPeriod); | |||
| std::unique_lock<std::mutex> unique_lock (timerMutex); | |||
| while (periodMs != 0) | |||
| { | |||
| clock.next(); | |||
| while (periodMs != 0 && clock.wait (stopCond, unique_lock)); | |||
| if (periodMs == 0) | |||
| break; | |||
| owner.hiResTimerCallback(); | |||
| auto nextPeriod = periodMs.load(); | |||
| if (lastPeriod != nextPeriod) | |||
| { | |||
| lastPeriod = nextPeriod; | |||
| clock = Clock (lastPeriod); | |||
| } | |||
| } | |||
| periodMs = 0; | |||
| }); | |||
| } | |||
| void stop() | |||
| { | |||
| periodMs = 0; | |||
| const auto thread_id = thread.get_id(); | |||
| if (thread_id == std::thread::id() || thread_id == std::this_thread::get_id()) | |||
| return; | |||
| { | |||
| std::unique_lock<std::mutex> unique_lock (timerMutex); | |||
| stopCond.notify_one(); | |||
| } | |||
| thread.join(); | |||
| } | |||
| HighResolutionTimer& owner; | |||
| std::atomic<int> periodMs { 0 }; | |||
| private: | |||
| std::thread thread; | |||
| std::condition_variable stopCond; | |||
| std::mutex timerMutex; | |||
| class Clock | |||
| { | |||
| public: | |||
| explicit Clock (std::chrono::steady_clock::rep millis) noexcept | |||
| : time (std::chrono::steady_clock::now()), | |||
| delta (std::chrono::milliseconds (millis)) | |||
| {} | |||
| bool wait (std::condition_variable& cond, std::unique_lock<std::mutex>& lock) noexcept | |||
| { | |||
| return cond.wait_until (lock, time) != std::cv_status::timeout; | |||
| } | |||
| void next() noexcept | |||
| { | |||
| time += delta; | |||
| } | |||
| private: | |||
| std::chrono::time_point<std::chrono::steady_clock> time; | |||
| std::chrono::steady_clock::duration delta; | |||
| }; | |||
| static bool setThisThreadToRealtime (uint64 periodMs) | |||
| { | |||
| const auto thread = pthread_self(); | |||
| #if JUCE_MAC || JUCE_IOS | |||
| mach_timebase_info_data_t timebase; | |||
| mach_timebase_info (&timebase); | |||
| const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; | |||
| const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits<uint32_t>::max(), periodMs * ticksPerMs); | |||
| thread_time_constraint_policy_data_t policy; | |||
| policy.period = periodTicks; | |||
| policy.computation = jmin ((uint32_t) 50000, policy.period); | |||
| policy.constraint = policy.period; | |||
| policy.preemptible = true; | |||
| return thread_policy_set (pthread_mach_thread_np (thread), | |||
| THREAD_TIME_CONSTRAINT_POLICY, | |||
| (thread_policy_t) &policy, | |||
| THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; | |||
| #else | |||
| ignoreUnused (periodMs); | |||
| struct sched_param param; | |||
| param.sched_priority = sched_get_priority_max (SCHED_RR); | |||
| return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; | |||
| #endif | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||
| }; | |||
| } // namespace juce | |||
| @@ -51,7 +51,6 @@ void CriticalSection::enter() const noexcept { EnterCriticalSection ((CRI | |||
| bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) &lock) != FALSE; } | |||
| void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) &lock); } | |||
| //============================================================================== | |||
| static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) | |||
| { | |||
| @@ -65,31 +64,72 @@ static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) | |||
| return 0; | |||
| } | |||
| void Thread::launchThread() | |||
| static bool setPriorityInternal (bool isRealtime, HANDLE handle, Thread::Priority priority) | |||
| { | |||
| auto nativeThreadFlag = isRealtime ? THREAD_PRIORITY_TIME_CRITICAL | |||
| : ThreadPriorities::getNativePriority (priority); | |||
| if (isRealtime) // This should probably be a fail state too? | |||
| Process::setPriority (Process::ProcessPriority::RealtimePriority); | |||
| return SetThreadPriority (handle, nativeThreadFlag); | |||
| } | |||
| bool Thread::createNativeThread (Priority priority) | |||
| { | |||
| unsigned int newThreadId; | |||
| threadHandle = (void*) _beginthreadex (nullptr, (unsigned int) threadStackSize, | |||
| &threadEntryProc, this, 0, &newThreadId); | |||
| threadId = (ThreadID) (pointer_sized_int) newThreadId; | |||
| &threadEntryProc, this, CREATE_SUSPENDED, | |||
| &newThreadId); | |||
| if (threadHandle != nullptr) | |||
| { | |||
| threadId = (ThreadID) (pointer_sized_int) newThreadId; | |||
| if (setPriorityInternal (isRealtime(), threadHandle, priority)) | |||
| { | |||
| ResumeThread (threadHandle); | |||
| return true; | |||
| } | |||
| killThread(); | |||
| closeThreadHandle(); | |||
| } | |||
| return false; | |||
| } | |||
| Thread::Priority Thread::getPriority() const | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| const auto native = GetThreadPriority (threadHandle); | |||
| return ThreadPriorities::getJucePriority (native); | |||
| } | |||
| bool Thread::setPriority (Priority priority) | |||
| { | |||
| jassert (Thread::getCurrentThreadId() == getThreadId()); | |||
| return setPriorityInternal (isRealtime(), this, priority); | |||
| } | |||
| void Thread::closeThreadHandle() | |||
| { | |||
| CloseHandle ((HANDLE) threadHandle.get()); | |||
| CloseHandle (threadHandle); | |||
| threadId = nullptr; | |||
| threadHandle = nullptr; | |||
| } | |||
| void Thread::killThread() | |||
| { | |||
| if (threadHandle.get() != nullptr) | |||
| if (threadHandle != nullptr) | |||
| { | |||
| #if JUCE_DEBUG | |||
| OutputDebugStringA ("** Warning - Forced thread termination **\n"); | |||
| #endif | |||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6258) | |||
| TerminateThread (threadHandle.get(), 0); | |||
| TerminateThread (threadHandle, 0); | |||
| JUCE_END_IGNORE_WARNINGS_MSVC | |||
| } | |||
| } | |||
| @@ -129,23 +169,6 @@ Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | |||
| return (ThreadID) (pointer_sized_int) GetCurrentThreadId(); | |||
| } | |||
| bool Thread::setThreadPriority (void* handle, int priority) | |||
| { | |||
| int pri = THREAD_PRIORITY_TIME_CRITICAL; | |||
| if (priority < 1) pri = THREAD_PRIORITY_IDLE; | |||
| else if (priority < 2) pri = THREAD_PRIORITY_LOWEST; | |||
| else if (priority < 5) pri = THREAD_PRIORITY_BELOW_NORMAL; | |||
| else if (priority < 7) pri = THREAD_PRIORITY_NORMAL; | |||
| else if (priority < 9) pri = THREAD_PRIORITY_ABOVE_NORMAL; | |||
| else if (priority < 10) pri = THREAD_PRIORITY_HIGHEST; | |||
| if (handle == nullptr) | |||
| handle = GetCurrentThread(); | |||
| return SetThreadPriority (handle, pri) != FALSE; | |||
| } | |||
| void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) | |||
| { | |||
| SetThreadAffinityMask (GetCurrentThread(), affinityMask); | |||
| @@ -217,11 +240,11 @@ void juce_repeatLastProcessPriority() | |||
| } | |||
| } | |||
| void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) | |||
| void JUCE_CALLTYPE Process::setPriority (ProcessPriority newPriority) | |||
| { | |||
| if (lastProcessPriority != (int) prior) | |||
| if (lastProcessPriority != (int) newPriority) | |||
| { | |||
| lastProcessPriority = (int) prior; | |||
| lastProcessPriority = (int) newPriority; | |||
| juce_repeatLastProcessPriority(); | |||
| } | |||
| } | |||
| @@ -527,56 +550,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
| return start (escaped.trim(), streamFlags); | |||
| } | |||
| //============================================================================== | |||
| struct HighResolutionTimer::Pimpl | |||
| { | |||
| Pimpl (HighResolutionTimer& t) noexcept : owner (t) | |||
| { | |||
| } | |||
| ~Pimpl() | |||
| { | |||
| jassert (periodMs == 0); | |||
| } | |||
| void start (int newPeriod) | |||
| { | |||
| if (newPeriod != periodMs) | |||
| { | |||
| stop(); | |||
| periodMs = newPeriod; | |||
| TIMECAPS tc; | |||
| if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR) | |||
| { | |||
| const int actualPeriod = jlimit ((int) tc.wPeriodMin, (int) tc.wPeriodMax, newPeriod); | |||
| timerID = timeSetEvent ((UINT) actualPeriod, tc.wPeriodMin, callbackFunction, (DWORD_PTR) this, | |||
| TIME_PERIODIC | TIME_CALLBACK_FUNCTION | 0x100 /*TIME_KILL_SYNCHRONOUS*/); | |||
| } | |||
| } | |||
| } | |||
| void stop() | |||
| { | |||
| periodMs = 0; | |||
| timeKillEvent (timerID); | |||
| } | |||
| HighResolutionTimer& owner; | |||
| int periodMs = 0; | |||
| private: | |||
| unsigned int timerID; | |||
| static void STDMETHODCALLTYPE callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) | |||
| { | |||
| if (Pimpl* const timer = reinterpret_cast<Pimpl*> (userInfo)) | |||
| if (timer->periodMs != 0) | |||
| timer->owner.hiResTimerCallback(); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||
| }; | |||
| } // namespace juce | |||
| @@ -243,7 +243,6 @@ public: | |||
| */ | |||
| static bool isRunningInAppExtensionSandbox() noexcept; | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| [[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]] | |||
| @@ -23,13 +23,96 @@ | |||
| namespace juce | |||
| { | |||
| HighResolutionTimer::HighResolutionTimer() : pimpl (new Pimpl (*this)) {} | |||
| HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } | |||
| class HighResolutionTimer::Pimpl : private Thread | |||
| { | |||
| using steady_clock = std::chrono::steady_clock; | |||
| using milliseconds = std::chrono::milliseconds; | |||
| public: | |||
| explicit Pimpl (HighResolutionTimer& ownerRef) | |||
| : Thread ("HighResolutionTimerThread"), | |||
| owner (ownerRef) | |||
| { | |||
| } | |||
| using Thread::isThreadRunning; | |||
| void start (int periodMs) | |||
| { | |||
| { | |||
| const std::scoped_lock lk { mutex }; | |||
| periodMillis = periodMs; | |||
| nextTickTime = steady_clock::now() + milliseconds (periodMillis); | |||
| } | |||
| if (! isThreadRunning()) | |||
| startThread (Thread::Priority::high); | |||
| } | |||
| void stop() | |||
| { | |||
| { | |||
| const std::scoped_lock lk { mutex }; | |||
| periodMillis = 0; | |||
| } | |||
| waitEvent.notify_one(); | |||
| if (Thread::getCurrentThreadId() != getThreadId()) | |||
| stopThread (-1); | |||
| } | |||
| int getPeriod() const | |||
| { | |||
| return periodMillis; | |||
| } | |||
| private: | |||
| void run() override | |||
| { | |||
| for (;;) | |||
| { | |||
| { | |||
| std::unique_lock lk { mutex }; | |||
| if (waitEvent.wait_until (lk, nextTickTime, [this] { return periodMillis == 0; })) | |||
| break; | |||
| void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); } | |||
| void HighResolutionTimer::stopTimer() { pimpl->stop(); } | |||
| nextTickTime = steady_clock::now() + milliseconds (periodMillis); | |||
| } | |||
| owner.hiResTimerCallback(); | |||
| } | |||
| } | |||
| HighResolutionTimer& owner; | |||
| std::atomic<int> periodMillis { 0 }; | |||
| steady_clock::time_point nextTickTime; | |||
| std::mutex mutex; | |||
| std::condition_variable waitEvent; | |||
| }; | |||
| HighResolutionTimer::HighResolutionTimer() | |||
| : pimpl (new Pimpl (*this)) | |||
| { | |||
| } | |||
| HighResolutionTimer::~HighResolutionTimer() | |||
| { | |||
| stopTimer(); | |||
| } | |||
| void HighResolutionTimer::startTimer (int periodMs) | |||
| { | |||
| pimpl->start (jmax (1, periodMs)); | |||
| } | |||
| void HighResolutionTimer::stopTimer() | |||
| { | |||
| pimpl->stop(); | |||
| } | |||
| bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; } | |||
| int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; } | |||
| bool HighResolutionTimer::isTimerRunning() const noexcept { return getTimerInterval() != 0; } | |||
| int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->getPeriod(); } | |||
| } // namespace juce | |||
| @@ -93,7 +93,7 @@ public: | |||
| int getTimerInterval() const noexcept; | |||
| private: | |||
| struct Pimpl; | |||
| class Pimpl; | |||
| std::unique_ptr<Pimpl> pimpl; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) | |||
| @@ -50,7 +50,7 @@ public: | |||
| @param priority the process priority, where | |||
| 0=low, 1=normal, 2=high, 3=realtime | |||
| */ | |||
| static void JUCE_CALLTYPE setPriority (const ProcessPriority priority); | |||
| static void JUCE_CALLTYPE setPriority (ProcessPriority priority); | |||
| /** Kills the current process immediately. | |||
| @@ -22,9 +22,9 @@ | |||
| namespace juce | |||
| { | |||
| Thread::Thread (const String& name, size_t stackSize) | |||
| : threadName (name), threadStackSize (stackSize) | |||
| //============================================================================== | |||
| Thread::Thread (const String& name, size_t stackSize) : threadName (name), | |||
| threadStackSize (stackSize) | |||
| { | |||
| } | |||
| @@ -81,12 +81,18 @@ void Thread::threadEntryPoint() | |||
| const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder()); | |||
| currentThreadHolder->value = this; | |||
| #if JUCE_ANDROID | |||
| setPriority (priority); | |||
| #endif | |||
| if (threadName.isNotEmpty()) | |||
| setCurrentThreadName (threadName); | |||
| // This 'startSuspensionEvent' protects 'threadId' which is initialised after the platform's native 'CreateThread' method. | |||
| // This ensures it has been initialised correctly before it reaches this point. | |||
| if (startSuspensionEvent.wait (10000)) | |||
| { | |||
| jassert (getCurrentThreadId() == threadId.get()); | |||
| jassert (getCurrentThreadId() == threadId); | |||
| if (affinityMask != 0) | |||
| setCurrentThreadAffinityMask (affinityMask); | |||
| @@ -119,42 +125,65 @@ void JUCE_API juce_threadEntryPoint (void* userData) | |||
| } | |||
| //============================================================================== | |||
| void Thread::startThread() | |||
| bool Thread::startThreadInternal (Priority threadPriority) | |||
| { | |||
| const ScopedLock sl (startStopLock); | |||
| shouldExit = false; | |||
| shouldExit = 0; | |||
| // 'priority' is essentially useless on Linux as only realtime | |||
| // has any options but we need to set this here to satsify | |||
| // later queries, otherwise we get inconsistent results across | |||
| // platforms. | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| priority = threadPriority; | |||
| #endif | |||
| if (threadHandle.get() == nullptr) | |||
| if (createNativeThread (threadPriority)) | |||
| { | |||
| launchThread(); | |||
| setThreadPriority (threadHandle.get(), threadPriority); | |||
| startSuspensionEvent.signal(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool Thread::startThread() | |||
| { | |||
| return startThread (Priority::normal); | |||
| } | |||
| void Thread::startThread (int priority) | |||
| bool Thread::startThread (Priority threadPriority) | |||
| { | |||
| const ScopedLock sl (startStopLock); | |||
| if (threadHandle.get() == nullptr) | |||
| if (threadHandle == nullptr) | |||
| { | |||
| #if JUCE_ANDROID | |||
| isAndroidRealtimeThread = (priority == realtimeAudioPriority); | |||
| #endif | |||
| threadPriority = getAdjustedPriority (priority); | |||
| startThread(); | |||
| realtimeOptions.reset(); | |||
| return startThreadInternal (threadPriority); | |||
| } | |||
| else | |||
| return false; | |||
| } | |||
| bool Thread::startRealtimeThread (const RealtimeOptions& options) | |||
| { | |||
| const ScopedLock sl (startStopLock); | |||
| if (threadHandle == nullptr) | |||
| { | |||
| setPriority (priority); | |||
| realtimeOptions = makeOptional (options); | |||
| if (startThreadInternal (Priority::normal)) | |||
| return true; | |||
| realtimeOptions.reset(); | |||
| } | |||
| return false; | |||
| } | |||
| bool Thread::isThreadRunning() const | |||
| { | |||
| return threadHandle.get() != nullptr; | |||
| return threadHandle != nullptr; | |||
| } | |||
| Thread* JUCE_CALLTYPE Thread::getCurrentThread() | |||
| @@ -164,19 +193,19 @@ Thread* JUCE_CALLTYPE Thread::getCurrentThread() | |||
| Thread::ThreadID Thread::getThreadId() const noexcept | |||
| { | |||
| return threadId.get(); | |||
| return threadId; | |||
| } | |||
| //============================================================================== | |||
| void Thread::signalThreadShouldExit() | |||
| { | |||
| shouldExit = 1; | |||
| shouldExit = true; | |||
| listeners.call ([] (Listener& l) { l.exitSignalSent(); }); | |||
| } | |||
| bool Thread::threadShouldExit() const | |||
| { | |||
| return shouldExit.get() != 0; | |||
| return shouldExit; | |||
| } | |||
| bool Thread::currentThreadShouldExit() | |||
| @@ -249,40 +278,9 @@ void Thread::removeListener (Listener* listener) | |||
| listeners.remove (listener); | |||
| } | |||
| //============================================================================== | |||
| bool Thread::setPriority (int newPriority) | |||
| bool Thread::isRealtime() const | |||
| { | |||
| newPriority = getAdjustedPriority (newPriority); | |||
| // NB: deadlock possible if you try to set the thread prio from the thread itself, | |||
| // so using setCurrentThreadPriority instead in that case. | |||
| if (getCurrentThreadId() == getThreadId()) | |||
| return setCurrentThreadPriority (newPriority); | |||
| const ScopedLock sl (startStopLock); | |||
| #if JUCE_ANDROID | |||
| bool isRealtime = (newPriority == realtimeAudioPriority); | |||
| // you cannot switch from or to an Android realtime thread once the | |||
| // thread is already running! | |||
| jassert (isThreadRunning() && (isRealtime == isAndroidRealtimeThread)); | |||
| isAndroidRealtimeThread = isRealtime; | |||
| #endif | |||
| if ((! isThreadRunning()) || setThreadPriority (threadHandle.get(), newPriority)) | |||
| { | |||
| threadPriority = newPriority; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool Thread::setCurrentThreadPriority (const int newPriority) | |||
| { | |||
| return setThreadPriority ({}, getAdjustedPriority (newPriority)); | |||
| return realtimeOptions.hasValue(); | |||
| } | |||
| void Thread::setAffinityMask (const uint32 newAffinityMask) | |||
| @@ -290,11 +288,6 @@ void Thread::setAffinityMask (const uint32 newAffinityMask) | |||
| affinityMask = newAffinityMask; | |||
| } | |||
| int Thread::getAdjustedPriority (int newPriority) | |||
| { | |||
| return jlimit (0, 10, newPriority == realtimeAudioPriority ? 9 : newPriority); | |||
| } | |||
| //============================================================================== | |||
| bool Thread::wait (const int timeOutMilliseconds) const | |||
| { | |||
| @@ -349,7 +342,6 @@ bool JUCE_CALLTYPE Process::isRunningUnderDebugger() noexcept | |||
| return juce_isRunningUnderDebugger(); | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| @@ -28,8 +28,8 @@ namespace juce | |||
| Encapsulates a thread. | |||
| Subclasses derive from Thread and implement the run() method, in which they | |||
| do their business. The thread can then be started with the startThread() method | |||
| and controlled with various other methods. | |||
| do their business. The thread can then be started with the startThread() or | |||
| startRealtimeThread() methods and controlled with various other methods. | |||
| This class also contains some thread-related static methods, such | |||
| as sleep(), yield(), getCurrentThreadId() etc. | |||
| @@ -42,6 +42,46 @@ namespace juce | |||
| class JUCE_API Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** The different runtime priorities of non-realtime threads. | |||
| @see startThread | |||
| */ | |||
| enum class Priority | |||
| { | |||
| /** The highest possible priority that isn't a dedicated realtime thread. */ | |||
| highest, | |||
| /** Makes use of performance cores and higher clocks. */ | |||
| high, | |||
| /** The OS default. It will balance out across all cores. */ | |||
| normal, | |||
| /** Uses efficiency cores when possible. */ | |||
| low, | |||
| /** Restricted to efficiency cores on platforms that have them. */ | |||
| background | |||
| }; | |||
| //============================================================================== | |||
| /** A selection of options available when creating realtime threads. | |||
| @see startRealtimeThread | |||
| */ | |||
| struct RealtimeOptions | |||
| { | |||
| /** Linux only: A value with a range of 0-10, where 10 is the highest priority. */ | |||
| int priority = 5; | |||
| /* iOS/macOS only: A millisecond value representing the estimated time between each | |||
| 'Thread::run' call. Your thread may be penalised if you frequently | |||
| overrun this. | |||
| */ | |||
| uint32_t workDurationMs = 0; | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Creates a thread. | |||
| @@ -78,23 +118,51 @@ public: | |||
| virtual void run() = 0; | |||
| //============================================================================== | |||
| /** Starts the thread running. | |||
| /** Attempts to start a new thread with default ('Priority::normal') priority. | |||
| This will cause the thread's run() method to be called by a new thread. | |||
| If this thread is already running, startThread() won't do anything. | |||
| If a thread cannot be created with the requested priority, this will return false | |||
| and Thread::run() will not be called. An exception to this is the Android platform, | |||
| which always starts a thread and attempts to upgrade the thread after creation. | |||
| @returns true if the thread started successfully. false if it was unsuccesful. | |||
| @see stopThread | |||
| */ | |||
| void startThread(); | |||
| bool startThread(); | |||
| /** Attempts to start a new thread with a given priority. | |||
| This will cause the thread's run() method to be called by a new thread. | |||
| If this thread is already running, startThread() won't do anything. | |||
| If a thread cannot be created with the requested priority, this will return false | |||
| and Thread::run() will not be called. An exception to this is the Android platform, | |||
| which always starts a thread and attempts to upgrade the thread after creation. | |||
| @param newPriority Priority the thread should be assigned. This parameter is ignored | |||
| on Linux. | |||
| @returns true if the thread started successfully, false if it was unsuccesful. | |||
| @see startThread, setPriority, startRealtimeThread | |||
| */ | |||
| bool startThread (Priority newPriority); | |||
| /** Starts the thread with realtime performance characteristics on platforms | |||
| that support it. | |||
| /** Starts the thread with a given priority. | |||
| You cannot change the options of a running realtime thread, nor switch | |||
| a non-realtime thread to a realtime thread. To make these changes you must | |||
| first stop the thread and then restart with different options. | |||
| Launches the thread with a given priority, where 0 = lowest, 10 = highest. | |||
| If the thread is already running, its priority will be changed. | |||
| @param options Realtime options the thread should be created with. | |||
| @see startThread, setPriority, realtimeAudioPriority | |||
| @see startThread, RealtimeOptions | |||
| */ | |||
| void startThread (int priority); | |||
| bool startRealtimeThread (const RealtimeOptions& options); | |||
| /** Attempts to stop the thread running. | |||
| @@ -198,50 +266,8 @@ public: | |||
| /** Removes a listener added with addListener. */ | |||
| void removeListener (Listener*); | |||
| //============================================================================== | |||
| /** Special realtime audio thread priority | |||
| This priority will create a high-priority thread which is best suited | |||
| for realtime audio processing. | |||
| Currently, this priority is identical to priority 9, except when building | |||
| for Android with OpenSL/Oboe support. | |||
| In this case, JUCE will ask OpenSL/Oboe to construct a super high priority thread | |||
| specifically for realtime audio processing. | |||
| Note that this priority can only be set **before** the thread has | |||
| started. Switching to this priority, or from this priority to a different | |||
| priority, is not supported under Android and will assert. | |||
| For best performance this thread should yield at regular intervals | |||
| and not call any blocking APIs. | |||
| @see startThread, setPriority, sleep, WaitableEvent | |||
| */ | |||
| enum | |||
| { | |||
| realtimeAudioPriority = -1 | |||
| }; | |||
| /** Changes the thread's priority. | |||
| May return false if for some reason the priority can't be changed. | |||
| @param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority | |||
| of 5 is normal. | |||
| @see realtimeAudioPriority | |||
| */ | |||
| bool setPriority (int priority); | |||
| /** Changes the priority of the caller thread. | |||
| Similar to setPriority(), but this static method acts on the caller thread. | |||
| May return false if for some reason the priority can't be changed. | |||
| @see setPriority | |||
| */ | |||
| static bool setCurrentThreadPriority (int priority); | |||
| /** Returns true if this Thread represents a realtime thread. */ | |||
| bool isRealtime() const; | |||
| //============================================================================== | |||
| /** Sets the affinity mask for the thread. | |||
| @@ -380,34 +406,58 @@ public: | |||
| static void initialiseJUCE (void* jniEnv, void* jContext); | |||
| #endif | |||
| protected: | |||
| //============================================================================== | |||
| /** Returns the current priority of this thread. | |||
| This can only be called from the target thread. Doing so from another thread | |||
| will cause an assert. | |||
| @see setPriority | |||
| */ | |||
| Priority getPriority() const; | |||
| /** Attempts to set the priority for this thread. Returns true if the new priority | |||
| was set successfully, false if not. | |||
| This can only be called from the target thread. Doing so from another thread | |||
| will cause an assert. | |||
| @param newPriority The new priority to be applied to the thread. Note: This | |||
| has no effect on Linux platforms, subsequent calls to | |||
| 'getPriority' will return this value. | |||
| @see Priority | |||
| */ | |||
| bool setPriority (Priority newPriority); | |||
| private: | |||
| //============================================================================== | |||
| const String threadName; | |||
| Atomic<void*> threadHandle { nullptr }; | |||
| Atomic<ThreadID> threadId = {}; | |||
| std::atomic<void*> threadHandle { nullptr }; | |||
| std::atomic<ThreadID> threadId { nullptr }; | |||
| Optional<RealtimeOptions> realtimeOptions = {}; | |||
| CriticalSection startStopLock; | |||
| WaitableEvent startSuspensionEvent, defaultEvent; | |||
| int threadPriority = 5; | |||
| size_t threadStackSize; | |||
| uint32 affinityMask = 0; | |||
| bool deleteOnThreadEnd = false; | |||
| Atomic<int32> shouldExit { 0 }; | |||
| std::atomic<bool> shouldExit { false }; | |||
| ListenerList<Listener, Array<Listener*, CriticalSection>> listeners; | |||
| #if JUCE_ANDROID | |||
| bool isAndroidRealtimeThread = false; | |||
| #if JUCE_ANDROID || JUCE_LINUX || JUCE_BSD | |||
| std::atomic<Priority> priority; | |||
| #endif | |||
| #ifndef DOXYGEN | |||
| friend void JUCE_API juce_threadEntryPoint (void*); | |||
| #endif | |||
| void launchThread(); | |||
| bool startThreadInternal (Priority); | |||
| bool createNativeThread (Priority); | |||
| void closeThreadHandle(); | |||
| void killThread(); | |||
| void threadEntryPoint(); | |||
| static bool setThreadPriority (void*, int); | |||
| static int getAdjustedPriority (int); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) | |||
| }; | |||
| @@ -33,11 +33,14 @@ struct ThreadPool::ThreadPoolThread : public Thread | |||
| void run() override | |||
| { | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (! pool.runNextJob (*this)) | |||
| wait (500); | |||
| } | |||
| } | |||
| std::atomic<ThreadPoolJob*> currentJob { nullptr }; | |||
| ThreadPool& pool; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) | |||
| @@ -90,16 +93,19 @@ ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() | |||
| } | |||
| //============================================================================== | |||
| ThreadPool::ThreadPool (int numThreads, size_t threadStackSize) | |||
| ThreadPool::ThreadPool (int numThreads, size_t threadStackSize, Thread::Priority priority) | |||
| { | |||
| jassert (numThreads > 0); // not much point having a pool without any threads! | |||
| createThreads (numThreads, threadStackSize); | |||
| for (int i = jmax (1, numThreads); --i >= 0;) | |||
| threads.add (new ThreadPoolThread (*this, threadStackSize)); | |||
| for (auto* t : threads) | |||
| t->startThread (priority); | |||
| } | |||
| ThreadPool::ThreadPool() | |||
| ThreadPool::ThreadPool() : ThreadPool (SystemStats::getNumCpus(), 0, Thread::Priority::normal) | |||
| { | |||
| createThreads (SystemStats::getNumCpus()); | |||
| } | |||
| ThreadPool::~ThreadPool() | |||
| @@ -108,15 +114,6 @@ ThreadPool::~ThreadPool() | |||
| stopThreads(); | |||
| } | |||
| void ThreadPool::createThreads (int numThreads, size_t threadStackSize) | |||
| { | |||
| for (int i = jmax (1, numThreads); --i >= 0;) | |||
| threads.add (new ThreadPoolThread (*this, threadStackSize)); | |||
| for (auto* t : threads) | |||
| t->startThread(); | |||
| } | |||
| void ThreadPool::stopThreads() | |||
| { | |||
| for (auto* t : threads) | |||
| @@ -330,17 +327,6 @@ StringArray ThreadPool::getNamesOfAllJobs (bool onlyReturnActiveJobs) const | |||
| return s; | |||
| } | |||
| bool ThreadPool::setThreadPriorities (int newPriority) | |||
| { | |||
| bool ok = true; | |||
| for (auto* t : threads) | |||
| if (! t->setPriority (newPriority)) | |||
| ok = false; | |||
| return ok; | |||
| } | |||
| ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||
| { | |||
| OwnedArray<ThreadPoolJob> deletionList; | |||
| @@ -164,8 +164,9 @@ public: | |||
| @param threadStackSize the size of the stack of each thread. If this value | |||
| is zero then the default stack size of the OS will | |||
| be used. | |||
| @param priority the desired priority of each thread in the pool. | |||
| */ | |||
| ThreadPool (int numberOfThreads, size_t threadStackSize = 0); | |||
| ThreadPool (int numberOfThreads, size_t threadStackSize = 0, Thread::Priority priority = Thread::Priority::normal); | |||
| /** Creates a thread pool with one thread per CPU core. | |||
| Once you've created a pool, you can give it some jobs by calling addJob(). | |||
| @@ -309,13 +310,6 @@ public: | |||
| */ | |||
| StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; | |||
| /** Changes the priority of all the threads. | |||
| This will call Thread::setPriority() for each thread in the pool. | |||
| May return false if for some reason the priority can't be changed. | |||
| */ | |||
| bool setThreadPriorities (int newPriority); | |||
| private: | |||
| //============================================================================== | |||
| Array<ThreadPoolJob*> jobs; | |||
| @@ -330,7 +324,6 @@ private: | |||
| bool runNextJob (ThreadPoolThread&); | |||
| ThreadPoolJob* pickNextJobToRun(); | |||
| void addToDeleteList (OwnedArray<ThreadPoolJob>&, ThreadPoolJob*) const; | |||
| void createThreads (int numThreads, size_t threadStackSize = 0); | |||
| void stopThreads(); | |||
| // Note that this method has changed, and no longer has a parameter to indicate | |||
| @@ -51,7 +51,7 @@ struct ChildProcessPingThread : public Thread, | |||
| pingReceived(); | |||
| } | |||
| void startPinging() { startThread (4); } | |||
| void startPinging() { startThread (Priority::low); } | |||
| void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } | |||
| void triggerConnectionLostMessage() { triggerAsyncUpdate(); } | |||
| @@ -41,7 +41,7 @@ NetworkServiceDiscovery::Advertiser::Advertiser (const String& serviceTypeUID, | |||
| message.setAttribute ("address", String()); | |||
| message.setAttribute ("port", connectionPort); | |||
| startThread (2); | |||
| startThread (Priority::background); | |||
| } | |||
| NetworkServiceDiscovery::Advertiser::~Advertiser() | |||
| @@ -92,7 +92,7 @@ NetworkServiceDiscovery::AvailableServiceList::AvailableServiceList (const Strin | |||
| #endif | |||
| socket.bindToPort (broadcastPort); | |||
| startThread (2); | |||
| startThread (Priority::background); | |||
| } | |||
| NetworkServiceDiscovery::AvailableServiceList::~AvailableServiceList() | |||
| @@ -303,7 +303,7 @@ private: | |||
| void handleAsyncUpdate() override | |||
| { | |||
| startThread (7); | |||
| startThread (Priority::high); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) | |||
| @@ -64,7 +64,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_, | |||
| } | |||
| // The thread must be started before the DirectoryContentsList attempts to scan any file | |||
| thread.startThread (4); | |||
| thread.startThread (Thread::Priority::low); | |||
| fileList.reset (new DirectoryContentsList (this, thread)); | |||
| fileList->setDirectory (currentRoot, true, true); | |||
| @@ -1403,7 +1403,7 @@ public: | |||
| monitor (mon) | |||
| { | |||
| listeners.push_back (listener); | |||
| startThread (10); | |||
| startThread (Priority::highest); | |||
| } | |||
| ~VSyncThread() override | |||
| @@ -56,11 +56,11 @@ ThreadWithProgressWindow::~ThreadWithProgressWindow() | |||
| stopThread (timeOutMsWhenCancelling); | |||
| } | |||
| void ThreadWithProgressWindow::launchThread (int priority) | |||
| void ThreadWithProgressWindow::launchThread (Priority threadPriority) | |||
| { | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| startThread (priority); | |||
| startThread (threadPriority); | |||
| startTimer (100); | |||
| { | |||
| @@ -105,9 +105,9 @@ void ThreadWithProgressWindow::timerCallback() | |||
| void ThreadWithProgressWindow::threadComplete (bool) {} | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| bool ThreadWithProgressWindow::runThread (const int priority) | |||
| bool ThreadWithProgressWindow::runThread (Priority threadPriority) | |||
| { | |||
| launchThread (priority); | |||
| launchThread (threadPriority); | |||
| while (isTimerRunning()) | |||
| MessageManager::getInstance()->runDispatchLoopUntil (5); | |||
| @@ -122,10 +122,10 @@ public: | |||
| Before returning, the dialog box will be hidden. | |||
| @param priority the priority to use when starting the thread - see | |||
| Thread::startThread() for values | |||
| Thread::Priority for values | |||
| @returns true if the thread finished normally; false if the user pressed cancel | |||
| */ | |||
| bool runThread (int priority = 5); | |||
| bool runThread (Priority Priority = Priority::normal); | |||
| #endif | |||
| /** Starts the thread and returns. | |||
| @@ -135,9 +135,9 @@ public: | |||
| hidden and the threadComplete() method will be called. | |||
| @param priority the priority to use when starting the thread - see | |||
| Thread::startThread() for values | |||
| Thread::Priority for values | |||
| */ | |||
| void launchThread (int priority = 5); | |||
| void launchThread (Priority priority = Priority::normal); | |||
| /** The thread should call this periodically to update the position of the progress bar. | |||
| @@ -58,7 +58,7 @@ struct OnlineUnlockForm::OverlayComp : public Component, | |||
| cancelButton->addListener (this); | |||
| } | |||
| startThread (4); | |||
| startThread (Priority::normal); | |||
| } | |||
| ~OverlayComp() override | |||