mirror of https://github.com/PCSX2/pcsx2.git
Misc: Move pxThread and friends to gui
This commit is contained in:
parent
096696bed7
commit
1e8332f36a
|
@ -88,7 +88,6 @@ target_sources(common PRIVATE
|
|||
RwMutex.h
|
||||
SafeArray.h
|
||||
ScopedGuard.h
|
||||
ScopedPtrMT.h
|
||||
SettingsInterface.h
|
||||
SettingsWrapper.h
|
||||
StringHelpers.h
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include <mach/mach_time.h> // mach_absolute_time()
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/ThreadingInternal.h"
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Semaphore Implementation for Darwin/OSX
|
||||
|
@ -86,231 +85,5 @@ bool Threading::KernelSemaphore::TryWait()
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Wait up to the given time
|
||||
/// Returns true if successful, false if timed out
|
||||
static bool WaitUpTo(semaphore_t sema, wxTimeSpan wxtime)
|
||||
{
|
||||
mach_timespec_t time;
|
||||
u64 ms = wxtime.GetMilliseconds().GetValue();
|
||||
time.tv_sec = ms / 1000;
|
||||
time.tv_nsec = (ms % 1000) * 1000000;
|
||||
kern_return_t res = semaphore_timedwait(sema, time);
|
||||
if (res == KERN_OPERATION_TIMED_OUT)
|
||||
return false;
|
||||
MACH_CHECK(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
Threading::Semaphore::Semaphore()
|
||||
{
|
||||
// other platforms explicitly make a thread-private (unshared) semaphore
|
||||
// here. But it seems Mach doesn't support that.
|
||||
MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0));
|
||||
__atomic_store_n(&m_counter, 0, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
Threading::Semaphore::~Semaphore()
|
||||
{
|
||||
MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema));
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Reset()
|
||||
{
|
||||
MACH_CHECK(semaphore_destroy(mach_task_self(), (semaphore_t)m_sema));
|
||||
MACH_CHECK(semaphore_create(mach_task_self(), (semaphore_t*)&m_sema, SYNC_POLICY_FIFO, 0));
|
||||
__atomic_store_n(&m_counter, 0, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Post()
|
||||
{
|
||||
if (__atomic_fetch_add(&m_counter, 1, __ATOMIC_RELEASE) < 0)
|
||||
MACH_CHECK(semaphore_signal(m_sema));
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Post(int multiple)
|
||||
{
|
||||
for (int i = 0; i < multiple; ++i)
|
||||
{
|
||||
Post();
|
||||
}
|
||||
}
|
||||
|
||||
void Threading::Semaphore::WaitWithoutYield()
|
||||
{
|
||||
pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead.");
|
||||
if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) < 0)
|
||||
MACH_CHECK(semaphore_wait(m_sema));
|
||||
}
|
||||
|
||||
bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout)
|
||||
{
|
||||
// This method is the reason why there has to be a special Darwin
|
||||
// implementation of Semaphore. Note that semaphore_timedwait() is prone
|
||||
// to returning with KERN_ABORTED, which basically signifies that some
|
||||
// signal has worken it up. The best official "documentation" for
|
||||
// semaphore_timedwait() is the way it's used in Grand Central Dispatch,
|
||||
// which is open-source.
|
||||
|
||||
if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0)
|
||||
return true;
|
||||
|
||||
// on x86 platforms, mach_absolute_time() returns nanoseconds
|
||||
// TODO(aktau): on iOS a scale value from mach_timebase_info will be necessary
|
||||
u64 const kOneThousand = 1000;
|
||||
u64 const kOneBillion = kOneThousand * kOneThousand * kOneThousand;
|
||||
u64 const delta = timeout.GetMilliseconds().GetValue() * (kOneThousand * kOneThousand);
|
||||
mach_timespec_t ts;
|
||||
kern_return_t kr = KERN_ABORTED;
|
||||
for (u64 now = mach_absolute_time(), deadline = now + delta;
|
||||
kr == KERN_ABORTED; now = mach_absolute_time())
|
||||
{
|
||||
if (now > deadline)
|
||||
{
|
||||
// timed out by definition
|
||||
kr = KERN_OPERATION_TIMED_OUT;
|
||||
break;
|
||||
}
|
||||
|
||||
u64 timeleft = deadline - now;
|
||||
ts.tv_sec = timeleft / kOneBillion;
|
||||
ts.tv_nsec = timeleft % kOneBillion;
|
||||
|
||||
// possible return values of semaphore_timedwait() (from XNU sources):
|
||||
// internal kernel val -> return value
|
||||
// THREAD_INTERRUPTED -> KERN_ABORTED
|
||||
// THREAD_TIMED_OUT -> KERN_OPERATION_TIMED_OUT
|
||||
// THREAD_AWAKENED -> KERN_SUCCESS
|
||||
// THREAD_RESTART -> KERN_TERMINATED
|
||||
// default -> KERN_FAILURE
|
||||
kr = semaphore_timedwait(m_sema, ts);
|
||||
}
|
||||
|
||||
if (kr == KERN_OPERATION_TIMED_OUT)
|
||||
{
|
||||
int orig = __atomic_load_n(&m_counter, __ATOMIC_RELAXED);
|
||||
while (orig < 0)
|
||||
{
|
||||
if (__atomic_compare_exchange_n(&m_counter, &orig, orig + 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
return false;
|
||||
}
|
||||
// Semaphore was signalled between our wait expiring and now, keep kernel sema in sync
|
||||
kr = semaphore_wait(m_sema);
|
||||
}
|
||||
|
||||
// while it's entirely possible to have KERN_FAILURE here, we should
|
||||
// probably assert so we can study and correct the actual error here
|
||||
// (the thread dying while someone is wainting for it).
|
||||
MACH_CHECK(kr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's
|
||||
// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that
|
||||
// user input continues to be handled and that windows continue to repaint. If the Wait is
|
||||
// called from another thread, no message pumping is performed.
|
||||
void Threading::Semaphore::Wait()
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
WaitWithoutYield();
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Semaphore::Wait"))
|
||||
{
|
||||
WaitWithoutYield();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (__atomic_sub_fetch(&m_counter, 1, __ATOMIC_ACQUIRE) >= 0)
|
||||
return;
|
||||
while (!WaitUpTo(m_sema, def_yieldgui_interval))
|
||||
{
|
||||
YieldToMain();
|
||||
}
|
||||
}
|
||||
#else
|
||||
WaitWithoutYield();
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's
|
||||
// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that
|
||||
// user input continues to be handled and that windows continue to repaint. If the Wait is
|
||||
// called from another thread, no message pumping is performed.
|
||||
//
|
||||
// Returns:
|
||||
// false if the wait timed out before the semaphore was signaled, or true if the signal was
|
||||
// reached prior to timeout.
|
||||
//
|
||||
bool Threading::Semaphore::Wait(const wxTimeSpan& timeout)
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
return WaitWithoutYield(timeout);
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait"))
|
||||
{
|
||||
return WaitWithoutYield(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
wxTimeSpan countdown((timeout));
|
||||
|
||||
do
|
||||
{
|
||||
if (WaitWithoutYield(def_yieldgui_interval))
|
||||
break;
|
||||
YieldToMain();
|
||||
countdown -= def_yieldgui_interval;
|
||||
} while (countdown.GetMilliseconds() > 0);
|
||||
|
||||
return countdown.GetMilliseconds() > 0;
|
||||
}
|
||||
#else
|
||||
return WaitWithoutYield(timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Threading::Semaphore::TryWait()
|
||||
{
|
||||
int counter = __atomic_load_n(&m_counter, __ATOMIC_RELAXED);
|
||||
while (counter > 0 && !__atomic_compare_exchange_n(&m_counter, &counter, counter - 1, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
;
|
||||
return counter > 0;
|
||||
}
|
||||
|
||||
// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state
|
||||
// after the wait has completed. Useful for situations where the semaphore itself is stored on
|
||||
// the stack and passed to another thread via GUI message or such, avoiding complications where
|
||||
// the thread might be canceled and the stack value becomes invalid.
|
||||
//
|
||||
// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so
|
||||
// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need
|
||||
// to do a lot of no-cancel waits in a tight loop worker thread, for example.
|
||||
//
|
||||
// I'm unsure how to do this with pure Mach primitives, the docs in
|
||||
// osfmk/man seem a bit out of date so perhaps there's a possibility, but
|
||||
// since as far as I know Mach threads are 1-to-1 on BSD uthreads (and thus
|
||||
// POSIX threads), this should work. -- aktau
|
||||
void Threading::Semaphore::WaitNoCancel()
|
||||
{
|
||||
int oldstate;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
||||
Wait();
|
||||
pthread_setcancelstate(oldstate, NULL);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout)
|
||||
{
|
||||
int oldstate;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
||||
Wait(timeout);
|
||||
pthread_setcancelstate(oldstate, NULL);
|
||||
}
|
||||
|
||||
int Threading::Semaphore::Count()
|
||||
{
|
||||
return __atomic_load_n(&m_counter, __ATOMIC_RELAXED);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <mach/mach_port.h>
|
||||
|
||||
#include "common/PrecompiledHeader.h"
|
||||
#include "common/PersistentThread.h"
|
||||
#include "common/Threading.h"
|
||||
|
||||
// Note: assuming multicore is safer because it forces the interlocked routines to use
|
||||
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
|
||||
|
@ -140,32 +140,6 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
|||
return false;
|
||||
}
|
||||
|
||||
u64 Threading::pxThread::GetCpuTime() const
|
||||
{
|
||||
// Get the cpu time for the thread belonging to this object. Use m_native_id and/or
|
||||
// m_native_handle to implement it. Return value should be a measure of total time the
|
||||
// thread has used on the CPU (scaled by the value returned by GetThreadTicksPerSecond(),
|
||||
// which typically would be an OS-provided scalar or some sort).
|
||||
if (!m_native_id)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getthreadtime((thread_port_t)m_native_id);
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnStartInThread()
|
||||
{
|
||||
m_native_id = (uptr)mach_thread_self();
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnCleanupInThread()
|
||||
{
|
||||
// cleanup of handles that were upened in
|
||||
// _platform_specific_OnStartInThread
|
||||
mach_port_deallocate(mach_task_self(), (thread_port_t)m_native_id);
|
||||
}
|
||||
|
||||
// name can be up to 16 bytes
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
|
|
|
@ -38,10 +38,6 @@ Fnptr_OutOfMemory pxDoOutOfMemory = NULL;
|
|||
#define DEVASSERT_INLINE __fi
|
||||
#endif
|
||||
|
||||
// Using a threadlocal assertion guard. Separate threads can assert at the same time.
|
||||
// That's ok. What we don't want is the *same* thread recurse-asserting.
|
||||
static DeclareTls(int) s_assert_guard(0);
|
||||
|
||||
pxDoAssertFnType* pxDoAssert = pxAssertImpl_LogIt;
|
||||
|
||||
// make life easier for people using VC++ IDE by using this format, which allows double-click
|
||||
|
@ -55,8 +51,6 @@ wxString DiagnosticOrigin::ToString(const wxChar* msg) const
|
|||
if (function != NULL)
|
||||
message.Write(" Function: %s\n", function);
|
||||
|
||||
message.Write(L" Thread: %s\n", WX_STR(Threading::pxGetCurrentThreadName()));
|
||||
|
||||
if (condition != NULL)
|
||||
message.Write(L" Condition: %ls\n", condition);
|
||||
|
||||
|
@ -99,15 +93,6 @@ bool pxAssertImpl_LogIt(const DiagnosticOrigin& origin, const wxChar* msg)
|
|||
|
||||
DEVASSERT_INLINE void pxOnAssert(const DiagnosticOrigin& origin, const wxString& msg)
|
||||
{
|
||||
// Recursion guard: Allow at least one recursive call. This is useful because sometimes
|
||||
// we get meaningless assertions while unwinding stack traces after exceptions have occurred.
|
||||
|
||||
RecursionGuard guard(s_assert_guard);
|
||||
if (guard.Counter > 2)
|
||||
{
|
||||
return pxTrap();
|
||||
}
|
||||
|
||||
// wxWidgets doesn't come with debug builds on some Linux distros, and other distros make
|
||||
// it difficult to use the debug build (compilation failures). To handle these I've had to
|
||||
// bypass the internal wxWidgets assertion handler entirely, since it may not exist even if
|
||||
|
|
|
@ -36,68 +36,6 @@
|
|||
}; \
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// RecursionGuard - Basic protection against function recursion
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Thread safety note: If used in a threaded environment, you shoud use a handle to a __threadlocal
|
||||
// storage variable (protects aaginst race conditions and, in *most* cases, is more desirable
|
||||
// behavior as well.
|
||||
//
|
||||
// Rationale: wxWidgets has its own wxRecursionGuard, but it has a sloppy implementation with
|
||||
// entirely unnecessary assertion checks.
|
||||
//
|
||||
class RecursionGuard
|
||||
{
|
||||
public:
|
||||
int& Counter;
|
||||
|
||||
RecursionGuard(int& counter)
|
||||
: Counter(counter)
|
||||
{
|
||||
++Counter;
|
||||
}
|
||||
|
||||
virtual ~RecursionGuard()
|
||||
{
|
||||
--Counter;
|
||||
}
|
||||
|
||||
bool IsReentrant() const { return Counter > 1; }
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ICloneable / IActionInvocation / IDeletableObject
|
||||
// --------------------------------------------------------------------------------------
|
||||
class IActionInvocation
|
||||
{
|
||||
public:
|
||||
virtual ~IActionInvocation() = default;
|
||||
virtual void InvokeAction() = 0;
|
||||
};
|
||||
|
||||
class ICloneable
|
||||
{
|
||||
public:
|
||||
virtual ICloneable* Clone() const = 0;
|
||||
};
|
||||
|
||||
class IDeletableObject
|
||||
{
|
||||
public:
|
||||
virtual ~IDeletableObject() = default;
|
||||
|
||||
virtual void DeleteSelf() = 0;
|
||||
virtual bool IsBeingDeleted() = 0;
|
||||
|
||||
protected:
|
||||
// This function is GUI implementation dependent! It's implemented by PCSX2's AppHost,
|
||||
// but if the SysCore is being linked to another front end, you'll need to implement this
|
||||
// yourself. Most GUIs have built in message pumps. If a platform lacks one then you'll
|
||||
// need to implement one yourself (yay?).
|
||||
virtual void DoDeletion() = 0;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// PageProtectionMode
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#include "common/PersistentThread.h"
|
||||
#include "common/Threading.h"
|
||||
|
||||
// We wont need this until we actually have this more then just stubbed out, so I'm commenting this out
|
||||
// to remove an unneeded dependency.
|
||||
|
@ -199,31 +199,6 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
|||
#endif
|
||||
}
|
||||
|
||||
u64 Threading::pxThread::GetCpuTime() const
|
||||
{
|
||||
// Get the cpu time for the thread belonging to this object. Use m_native_id and/or
|
||||
// m_native_handle to implement it. Return value should be a measure of total time the
|
||||
// thread has used on the CPU (scaled by the value returned by GetThreadTicksPerSecond(),
|
||||
// which typically would be an OS-provided scalar or some sort).
|
||||
|
||||
if (!m_native_id)
|
||||
return 0;
|
||||
|
||||
return get_thread_time(m_native_id);
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnStartInThread()
|
||||
{
|
||||
// Obtain linux-specific thread IDs or Handles here, which can be used to query
|
||||
// kernel scheduler performance information.
|
||||
m_native_id = (uptr)pthread_self();
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnCleanupInThread()
|
||||
{
|
||||
// Cleanup handles here, which were opened above.
|
||||
}
|
||||
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
*/
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/ThreadingInternal.h"
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
|
@ -195,58 +194,14 @@ bool Threading::Mutex::TryAcquire()
|
|||
// and messages *if* the lock is performed from the main GUI thread.
|
||||
//
|
||||
void Threading::Mutex::Acquire()
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Mutex::Acquire"))
|
||||
{
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
//ScopedBusyCursor hourglass( Cursor_KindaBusy );
|
||||
while (!AcquireWithoutYield(def_yieldgui_interval))
|
||||
YieldToMain();
|
||||
}
|
||||
#else
|
||||
pthread_mutex_lock(&m_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Threading::Mutex::Acquire(const wxTimeSpan& timeout)
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
return AcquireWithoutYield(timeout);
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Mutex::TimedAcquire"))
|
||||
{
|
||||
return AcquireWithoutYield(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
//ScopedBusyCursor hourglass( Cursor_KindaBusy );
|
||||
wxTimeSpan countdown((timeout));
|
||||
|
||||
do
|
||||
{
|
||||
if (AcquireWithoutYield(def_yieldgui_interval))
|
||||
break;
|
||||
YieldToMain();
|
||||
countdown -= def_yieldgui_interval;
|
||||
} while (countdown.GetMilliseconds() > 0);
|
||||
|
||||
return countdown.GetMilliseconds() > 0;
|
||||
}
|
||||
|
||||
#else
|
||||
return AcquireWithoutYield(timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked.
|
||||
// Typically this action is used to determine if a thread is currently performing some
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
*/
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/ThreadingInternal.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Semaphore Implementations
|
||||
|
@ -165,157 +168,4 @@ bool Threading::KernelSemaphore::TryWait()
|
|||
#endif
|
||||
}
|
||||
|
||||
Threading::Semaphore::Semaphore()
|
||||
{
|
||||
sem_init(&m_sema, false, 0);
|
||||
}
|
||||
|
||||
Threading::Semaphore::~Semaphore()
|
||||
{
|
||||
sem_destroy(&m_sema);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Reset()
|
||||
{
|
||||
sem_destroy(&m_sema);
|
||||
sem_init(&m_sema, false, 0);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Post()
|
||||
{
|
||||
sem_post(&m_sema);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::Post(int multiple)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
sem_post_multiple(&m_sema, multiple);
|
||||
#else
|
||||
// Only w32pthreads has the post_multiple, but it's easy enough to fake:
|
||||
while (multiple > 0)
|
||||
{
|
||||
multiple--;
|
||||
sem_post(&m_sema);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::Semaphore::WaitWithoutYield()
|
||||
{
|
||||
pxAssertMsg(!wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead.");
|
||||
sem_wait(&m_sema);
|
||||
}
|
||||
|
||||
bool Threading::Semaphore::WaitWithoutYield(const wxTimeSpan& timeout)
|
||||
{
|
||||
wxDateTime megafail(wxDateTime::UNow() + timeout);
|
||||
const timespec fail = {megafail.GetTicks(), megafail.GetMillisecond() * 1000000};
|
||||
return sem_timedwait(&m_sema, &fail) == 0;
|
||||
}
|
||||
|
||||
|
||||
// This is a wxApp-safe implementation of Wait, which makes sure and executes the App's
|
||||
// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that
|
||||
// user input continues to be handled and that windoes continue to repaint. If the Wait is
|
||||
// called from another thread, no message pumping is performed.
|
||||
//
|
||||
void Threading::Semaphore::Wait()
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
sem_wait(&m_sema);
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Semaphore::Wait"))
|
||||
{
|
||||
sem_wait(&m_sema);
|
||||
}
|
||||
else
|
||||
{
|
||||
//ScopedBusyCursor hourglass( Cursor_KindaBusy );
|
||||
while (!WaitWithoutYield(def_yieldgui_interval))
|
||||
YieldToMain();
|
||||
}
|
||||
#else
|
||||
sem_wait(&m_sema);
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's
|
||||
// pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that
|
||||
// user input continues to be handled and that windows continue to repaint. If the Wait is
|
||||
// called from another thread, no message pumping is performed.
|
||||
//
|
||||
// Returns:
|
||||
// false if the wait timed out before the semaphore was signaled, or true if the signal was
|
||||
// reached prior to timeout.
|
||||
//
|
||||
bool Threading::Semaphore::Wait(const wxTimeSpan& timeout)
|
||||
{
|
||||
#if wxUSE_GUI
|
||||
if (!wxThread::IsMain() || (wxTheApp == NULL))
|
||||
{
|
||||
return WaitWithoutYield(timeout);
|
||||
}
|
||||
else if (_WaitGui_RecursionGuard(L"Semaphore::TimedWait"))
|
||||
{
|
||||
return WaitWithoutYield(timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
//ScopedBusyCursor hourglass( Cursor_KindaBusy );
|
||||
wxTimeSpan countdown((timeout));
|
||||
|
||||
do
|
||||
{
|
||||
if (WaitWithoutYield(def_yieldgui_interval))
|
||||
break;
|
||||
YieldToMain();
|
||||
countdown -= def_yieldgui_interval;
|
||||
} while (countdown.GetMilliseconds() > 0);
|
||||
|
||||
return countdown.GetMilliseconds() > 0;
|
||||
}
|
||||
#else
|
||||
return WaitWithoutYield(timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state
|
||||
// after the wait has completed. Useful for situations where the semaphore itself is stored on
|
||||
// the stack and passed to another thread via GUI message or such, avoiding complications where
|
||||
// the thread might be canceled and the stack value becomes invalid.
|
||||
//
|
||||
// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so
|
||||
// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need
|
||||
// to do a lot of no-cancel waits in a tight loop worker thread, for example.
|
||||
void Threading::Semaphore::WaitNoCancel()
|
||||
{
|
||||
int oldstate;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
||||
//WaitWithoutYield();
|
||||
Wait();
|
||||
pthread_setcancelstate(oldstate, NULL);
|
||||
}
|
||||
|
||||
void Threading::Semaphore::WaitNoCancel(const wxTimeSpan& timeout)
|
||||
{
|
||||
int oldstate;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
|
||||
//WaitWithoutYield( timeout );
|
||||
Wait(timeout);
|
||||
pthread_setcancelstate(oldstate, NULL);
|
||||
}
|
||||
|
||||
bool Threading::Semaphore::TryWait()
|
||||
{
|
||||
return sem_trywait(&m_sema) == 0;
|
||||
}
|
||||
|
||||
int Threading::Semaphore::Count()
|
||||
{
|
||||
int retval;
|
||||
sem_getvalue(&m_sema, &retval);
|
||||
return retval;
|
||||
}
|
||||
#endif
|
|
@ -13,777 +13,14 @@
|
|||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
#include <signal.h> // for pthread_kill, which is in pthread.h on w32-pthreads
|
||||
#endif
|
||||
|
||||
#include "common/PersistentThread.h"
|
||||
#include "common/ThreadingInternal.h"
|
||||
#include "common/EventSource.inl"
|
||||
#include "common/General.h"
|
||||
|
||||
using namespace Threading;
|
||||
|
||||
template class EventSource<EventListener_Thread>;
|
||||
|
||||
// 100ms interval for waitgui (issued from blocking semaphore waits on the main thread,
|
||||
// to avoid gui deadlock).
|
||||
const wxTimeSpan Threading::def_yieldgui_interval(0, 0, 0, 100);
|
||||
|
||||
ConsoleLogSource_Threading::ConsoleLogSource_Threading()
|
||||
{
|
||||
static const TraceLogDescriptor myDesc =
|
||||
{
|
||||
L"p&xThread", L"pxThread",
|
||||
pxLt("Threading activity: start, detach, sync, deletion, etc.")};
|
||||
|
||||
m_Descriptor = &myDesc;
|
||||
}
|
||||
|
||||
ConsoleLogSource_Threading pxConLog_Thread;
|
||||
|
||||
|
||||
class StaticMutex : public Mutex
|
||||
{
|
||||
protected:
|
||||
bool& m_DeletedFlag;
|
||||
|
||||
public:
|
||||
StaticMutex(bool& deletedFlag)
|
||||
: m_DeletedFlag(deletedFlag)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~StaticMutex()
|
||||
{
|
||||
m_DeletedFlag = true;
|
||||
}
|
||||
};
|
||||
|
||||
static pthread_key_t curthread_key = 0;
|
||||
static s32 total_key_count = 0;
|
||||
|
||||
static bool tkl_destructed = false;
|
||||
static StaticMutex total_key_lock(tkl_destructed);
|
||||
|
||||
static void make_curthread_key(const pxThread* thr)
|
||||
{
|
||||
pxAssumeDev(!tkl_destructed, "total_key_lock is destroyed; program is shutting down; cannot create new thread key.");
|
||||
|
||||
ScopedLock lock(total_key_lock);
|
||||
if (total_key_count++ != 0)
|
||||
return;
|
||||
|
||||
if (0 != pthread_key_create(&curthread_key, NULL))
|
||||
{
|
||||
pxThreadLog.Error(thr->GetName(), L"Thread key creation failed (probably out of memory >_<)");
|
||||
curthread_key = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void unmake_curthread_key()
|
||||
{
|
||||
ScopedLock lock;
|
||||
if (!tkl_destructed)
|
||||
lock.AssignAndLock(total_key_lock);
|
||||
|
||||
if (--total_key_count > 0)
|
||||
return;
|
||||
|
||||
if (curthread_key)
|
||||
pthread_key_delete(curthread_key);
|
||||
|
||||
curthread_key = 0;
|
||||
}
|
||||
#include "common/Threading.h"
|
||||
|
||||
void Threading::pxTestCancel()
|
||||
{
|
||||
pthread_testcancel();
|
||||
}
|
||||
|
||||
// Returns a handle to the current persistent thread. If the current thread does not belong
|
||||
// to the pxThread table, NULL is returned. Since the main/ui thread is not created
|
||||
// through pxThread it will also return NULL. Callers can use wxThread::IsMain() to
|
||||
// test if the NULL thread is the main thread.
|
||||
pxThread* Threading::pxGetCurrentThread()
|
||||
{
|
||||
return !curthread_key ? NULL : (pxThread*)pthread_getspecific(curthread_key);
|
||||
}
|
||||
|
||||
// returns the name of the current thread, or "Unknown" if the thread is neither a pxThread
|
||||
// nor the Main/UI thread.
|
||||
wxString Threading::pxGetCurrentThreadName()
|
||||
{
|
||||
if (pxThread* thr = pxGetCurrentThread())
|
||||
{
|
||||
return thr->GetName();
|
||||
}
|
||||
else if (wxThread::IsMain())
|
||||
{
|
||||
return L"Main/UI";
|
||||
}
|
||||
|
||||
return L"Unknown";
|
||||
}
|
||||
|
||||
void Threading::pxYield(int ms)
|
||||
{
|
||||
if (pxThread* thr = pxGetCurrentThread())
|
||||
thr->Yield(ms);
|
||||
else
|
||||
Sleep(ms);
|
||||
}
|
||||
|
||||
// (intended for internal use only)
|
||||
// Returns true if the Wait is recursive, or false if the Wait is safe and should be
|
||||
// handled via normal yielding methods.
|
||||
bool Threading::_WaitGui_RecursionGuard(const wxChar* name)
|
||||
{
|
||||
AffinityAssert_AllowFrom_MainUI();
|
||||
|
||||
// In order to avoid deadlock we need to make sure we cut some time to handle messages.
|
||||
// But this can result in recursive yield calls, which would crash the app. Protect
|
||||
// against them here and, if recursion is detected, perform a standard blocking wait.
|
||||
|
||||
static int __Guard = 0;
|
||||
RecursionGuard guard(__Guard);
|
||||
|
||||
//if( pxAssertDev( !guard.IsReentrant(), "Recursion during UI-bound threading wait object." ) ) return false;
|
||||
|
||||
if (!guard.IsReentrant())
|
||||
return false;
|
||||
pxThreadLog.Write(pxGetCurrentThreadName(),
|
||||
pxsFmt(L"Yield recursion in %s; opening modal dialog.", name));
|
||||
return true;
|
||||
}
|
||||
|
||||
__fi void Threading::Timeslice()
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
void Threading::pxThread::_pt_callback_cleanup(void* handle)
|
||||
{
|
||||
((pxThread*)handle)->_ThreadCleanup();
|
||||
}
|
||||
|
||||
|
||||
Threading::pxThread::pxThread(const wxString& name)
|
||||
: m_name(name)
|
||||
, m_thread()
|
||||
, m_native_id(0)
|
||||
, m_native_handle(0)
|
||||
, m_detached(true) // start out with m_thread in detached/invalid state
|
||||
, m_running(false)
|
||||
{
|
||||
}
|
||||
|
||||
// This destructor performs basic "last chance" cleanup, which is a blocking join
|
||||
// against the thread. Extending classes should almost always implement their own
|
||||
// thread closure process, since any pxThread will, by design, not terminate
|
||||
// unless it has been properly canceled (resulting in deadlock).
|
||||
//
|
||||
// Thread safety: This class must not be deleted from its own thread. That would be
|
||||
// like marrying your sister, and then cheating on her with your daughter.
|
||||
Threading::pxThread::~pxThread()
|
||||
{
|
||||
try
|
||||
{
|
||||
pxThreadLog.Write(GetName(), L"Executing default destructor!");
|
||||
|
||||
if (m_running)
|
||||
{
|
||||
pxThreadLog.Write(GetName(), L"Waiting for running thread to end...");
|
||||
m_mtx_InThread.Wait();
|
||||
pxThreadLog.Write(GetName(), L"Thread ended gracefully.");
|
||||
}
|
||||
Threading::Sleep(1);
|
||||
Detach();
|
||||
}
|
||||
DESTRUCTOR_CATCHALL
|
||||
}
|
||||
|
||||
bool Threading::pxThread::AffinityAssert_AllowFromSelf(const DiagnosticOrigin& origin) const
|
||||
{
|
||||
if (IsSelf())
|
||||
return true;
|
||||
|
||||
if (IsDevBuild)
|
||||
pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call allowed from '%s' thread only.", WX_STR(GetName())));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Threading::pxThread::AffinityAssert_DisallowFromSelf(const DiagnosticOrigin& origin) const
|
||||
{
|
||||
if (!IsSelf())
|
||||
return true;
|
||||
|
||||
if (IsDevBuild)
|
||||
pxOnAssert(origin, pxsFmt(L"Thread affinity violation: Call is *not* allowed from '%s' thread.", WX_STR(GetName())));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Threading::pxThread::FrankenMutex(Mutex& mutex)
|
||||
{
|
||||
if (mutex.RecreateIfLocked())
|
||||
{
|
||||
// Our lock is bupkis, which means the previous thread probably deadlocked.
|
||||
// Let's create a new mutex lock to replace it.
|
||||
|
||||
pxThreadLog.Error(GetName(), L"Possible deadlock detected on restarted mutex!");
|
||||
}
|
||||
}
|
||||
|
||||
// Main entry point for starting or e-starting a persistent thread. This function performs necessary
|
||||
// locks and checks for avoiding race conditions, and then calls OnStart() immediately before
|
||||
// the actual thread creation. Extending classes should generally not override Start(), and should
|
||||
// instead override DoPrepStart instead.
|
||||
//
|
||||
// This function should not be called from the owner thread.
|
||||
void Threading::pxThread::Start()
|
||||
{
|
||||
// Prevents sudden parallel startup, and or parallel startup + cancel:
|
||||
ScopedLock startlock(m_mtx_start);
|
||||
if (m_running)
|
||||
{
|
||||
pxThreadLog.Write(GetName(), L"Start() called on running thread; ignorning...");
|
||||
return;
|
||||
}
|
||||
|
||||
Detach(); // clean up previous thread handle, if one exists.
|
||||
OnStart();
|
||||
|
||||
m_except = NULL;
|
||||
|
||||
pxThreadLog.Write(GetName(), L"Calling pthread_create...");
|
||||
if (pthread_create(&m_thread, NULL, _internal_callback, this) != 0)
|
||||
throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: " + wxString(std::strerror(errno)));
|
||||
|
||||
#ifdef ASAN_WORKAROUND
|
||||
// Recent Asan + libc6 do pretty bad stuff on the thread init => https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77982
|
||||
//
|
||||
// In our case, the semaphore was posted (counter is 1) but thread is still
|
||||
// waiting... So waits 100ms and checks the counter value manually
|
||||
if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 0, 100)))
|
||||
{
|
||||
if (m_sem_startup.Count() == 0)
|
||||
throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore.");
|
||||
}
|
||||
#else
|
||||
if (!m_sem_startup.WaitWithoutYield(wxTimeSpan(0, 0, 3, 0)))
|
||||
{
|
||||
RethrowException();
|
||||
|
||||
// And if the thread threw nothing of its own:
|
||||
throw Exception::ThreadCreationError(this).SetDiagMsg(L"Thread creation error: %s thread never posted startup semaphore.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Event Rationale (above): Performing this semaphore wait on the created thread is "slow" in the
|
||||
// sense that it stalls the calling thread completely until the new thread is created
|
||||
// (which may not always be desirable). But too bad. In order to safely use 'running' locks
|
||||
// and detachment management, this *has* to be done. By rule, starting new threads shouldn't
|
||||
// be done very often anyway, hence the concept of Threadpooling for rapidly rotating tasks.
|
||||
// (and indeed, this semaphore wait might, in fact, be very swift compared to other kernel
|
||||
// overhead in starting threads).
|
||||
|
||||
// (this could also be done using operating system specific calls, since any threaded OS has
|
||||
// functions that allow us to see if a thread is running or not, and to block against it even if
|
||||
// it's been detached -- removing the need for m_mtx_InThread and the semaphore wait above. But
|
||||
// pthreads kinda lacks that stuff, since pthread_join() has no timeout option making it im-
|
||||
// possible to safely block against a running thread)
|
||||
}
|
||||
|
||||
// Returns: TRUE if the detachment was performed, or FALSE if the thread was
|
||||
// already detached or isn't running at all.
|
||||
// This function should not be called from the owner thread.
|
||||
bool Threading::pxThread::Detach()
|
||||
{
|
||||
AffinityAssert_DisallowFromSelf(pxDiagSpot);
|
||||
|
||||
if (m_detached.exchange(true))
|
||||
return false;
|
||||
pthread_detach(m_thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Threading::pxThread::_basecancel()
|
||||
{
|
||||
if (!m_running)
|
||||
return false;
|
||||
|
||||
if (m_detached)
|
||||
{
|
||||
pxThreadLog.Warn(GetName(), L"Ignoring attempted cancellation of detached thread.");
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_cancel(m_thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remarks:
|
||||
// Provision of non-blocking Cancel() is probably academic, since destroying a pxThread
|
||||
// object performs a blocking Cancel regardless of if you explicitly do a non-blocking Cancel()
|
||||
// prior, since the ExecuteTaskInThread() method requires a valid object state. If you really need
|
||||
// fire-and-forget behavior on threads, use pthreads directly for now.
|
||||
//
|
||||
// This function should not be called from the owner thread.
|
||||
//
|
||||
// Parameters:
|
||||
// isBlocking - indicates if the Cancel action should block for thread completion or not.
|
||||
//
|
||||
// Exceptions raised by the blocking thread will be re-thrown into the main thread. If isBlocking
|
||||
// is false then no exceptions will occur.
|
||||
//
|
||||
void Threading::pxThread::Cancel(bool isBlocking)
|
||||
{
|
||||
AffinityAssert_DisallowFromSelf(pxDiagSpot);
|
||||
|
||||
// Prevent simultaneous startup and cancel, necessary to avoid
|
||||
ScopedLock startlock(m_mtx_start);
|
||||
|
||||
if (!_basecancel())
|
||||
return;
|
||||
|
||||
if (isBlocking)
|
||||
{
|
||||
WaitOnSelf(m_mtx_InThread);
|
||||
Detach();
|
||||
}
|
||||
}
|
||||
|
||||
bool Threading::pxThread::Cancel(const wxTimeSpan& timespan)
|
||||
{
|
||||
AffinityAssert_DisallowFromSelf(pxDiagSpot);
|
||||
|
||||
// Prevent simultaneous startup and cancel:
|
||||
ScopedLock startlock(m_mtx_start);
|
||||
|
||||
if (!_basecancel())
|
||||
return true;
|
||||
|
||||
if (!WaitOnSelf(m_mtx_InThread, timespan))
|
||||
return false;
|
||||
Detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Blocks execution of the calling thread until this thread completes its task. The
|
||||
// caller should make sure to signal the thread to exit, or else blocking may deadlock the
|
||||
// calling thread. Classes which extend pxThread should override this method
|
||||
// and signal any necessary thread exit variables prior to blocking.
|
||||
//
|
||||
// Returns the return code of the thread.
|
||||
// This method is roughly the equivalent of pthread_join().
|
||||
//
|
||||
// Exceptions raised by the blocking thread will be re-thrown into the main thread.
|
||||
//
|
||||
void Threading::pxThread::Block()
|
||||
{
|
||||
AffinityAssert_DisallowFromSelf(pxDiagSpot);
|
||||
WaitOnSelf(m_mtx_InThread);
|
||||
}
|
||||
|
||||
bool Threading::pxThread::Block(const wxTimeSpan& timeout)
|
||||
{
|
||||
AffinityAssert_DisallowFromSelf(pxDiagSpot);
|
||||
return WaitOnSelf(m_mtx_InThread, timeout);
|
||||
}
|
||||
|
||||
bool Threading::pxThread::IsSelf() const
|
||||
{
|
||||
// Detached threads may have their pthread handles recycled as newer threads, causing
|
||||
// false IsSelf reports.
|
||||
return !m_detached && (pthread_self() == m_thread);
|
||||
}
|
||||
|
||||
bool Threading::pxThread::IsRunning() const
|
||||
{
|
||||
return m_running;
|
||||
}
|
||||
|
||||
void Threading::pxThread::AddListener(EventListener_Thread& evt)
|
||||
{
|
||||
evt.SetThread(this);
|
||||
m_evtsrc_OnDelete.Add(evt);
|
||||
}
|
||||
|
||||
// Throws an exception if the thread encountered one. Uses the BaseException's Rethrow() method,
|
||||
// which ensures the exception type remains consistent. Debuggable stacktraces will be lost, since
|
||||
// the thread will have allowed itself to terminate properly.
|
||||
void Threading::pxThread::RethrowException() const
|
||||
{
|
||||
// Thread safety note: always detach the m_except pointer. If we checked it for NULL, the
|
||||
// pointer might still be invalid after detachment, so might as well just detach and check
|
||||
// after.
|
||||
|
||||
ScopedExcept ptr(const_cast<pxThread*>(this)->m_except.DetachPtr());
|
||||
if (ptr)
|
||||
ptr->Rethrow();
|
||||
}
|
||||
|
||||
static bool m_BlockDeletions = false;
|
||||
|
||||
bool Threading::AllowDeletions()
|
||||
{
|
||||
AffinityAssert_AllowFrom_MainUI();
|
||||
return !m_BlockDeletions;
|
||||
}
|
||||
|
||||
void Threading::YieldToMain()
|
||||
{
|
||||
m_BlockDeletions = true;
|
||||
wxTheApp->Yield(true);
|
||||
m_BlockDeletions = false;
|
||||
}
|
||||
|
||||
void Threading::pxThread::_selfRunningTest(const wxChar* name) const
|
||||
{
|
||||
if (HasPendingException())
|
||||
{
|
||||
pxThreadLog.Error(GetName(), pxsFmt(L"An exception was thrown while waiting on a %s.", name));
|
||||
RethrowException();
|
||||
}
|
||||
|
||||
if (!m_running)
|
||||
{
|
||||
throw Exception::CancelEvent(pxsFmt(
|
||||
L"Blocking thread %s was terminated while another thread was waiting on a %s.",
|
||||
WX_STR(GetName()), name));
|
||||
}
|
||||
|
||||
// Thread is still alive and kicking (for now) -- yield to other messages and hope
|
||||
// that impending chaos does not ensue. [it shouldn't since we block pxThread
|
||||
// objects from being deleted until outside the scope of a mutex/semaphore wait).
|
||||
|
||||
if ((wxTheApp != NULL) && wxThread::IsMain() && !_WaitGui_RecursionGuard(L"WaitForSelf"))
|
||||
Threading::YieldToMain();
|
||||
}
|
||||
|
||||
// This helper function is a deadlock-safe method of waiting on a semaphore in a pxThread. If the
|
||||
// thread is terminated or canceled by another thread or a nested action prior to the semaphore being
|
||||
// posted, this function will detect that and throw a CancelEvent exception is thrown.
|
||||
//
|
||||
// Note: Use of this function only applies to semaphores which are posted by the worker thread. Calling
|
||||
// this function from the context of the thread itself is an error, and a dev assertion will be generated.
|
||||
//
|
||||
// Exceptions:
|
||||
// This function will rethrow exceptions raised by the persistent thread, if it throws an error
|
||||
// while the calling thread is blocking (which also means the persistent thread has terminated).
|
||||
//
|
||||
void Threading::pxThread::WaitOnSelf(Semaphore& sem) const
|
||||
{
|
||||
if (!AffinityAssert_DisallowFromSelf(pxDiagSpot))
|
||||
return;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (sem.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333)))
|
||||
return;
|
||||
_selfRunningTest(L"semaphore");
|
||||
}
|
||||
}
|
||||
|
||||
// This helper function is a deadlock-safe method of waiting on a mutex in a pxThread.
|
||||
// If the thread is terminated or canceled by another thread or a nested action prior to the
|
||||
// mutex being unlocked, this function will detect that and a CancelEvent exception is thrown.
|
||||
//
|
||||
// Note: Use of this function only applies to mutexes which are acquired by a worker thread.
|
||||
// Calling this function from the context of the thread itself is an error, and a dev assertion
|
||||
// will be generated.
|
||||
//
|
||||
// Exceptions:
|
||||
// This function will rethrow exceptions raised by the persistent thread, if it throws an
|
||||
// error while the calling thread is blocking (which also means the persistent thread has
|
||||
// terminated).
|
||||
//
|
||||
void Threading::pxThread::WaitOnSelf(Mutex& mutex) const
|
||||
{
|
||||
if (!AffinityAssert_DisallowFromSelf(pxDiagSpot))
|
||||
return;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (mutex.WaitWithoutYield(wxTimeSpan(0, 0, 0, 333)))
|
||||
return;
|
||||
_selfRunningTest(L"mutex");
|
||||
}
|
||||
}
|
||||
|
||||
static const wxTimeSpan SelfWaitInterval(0, 0, 0, 333);
|
||||
|
||||
bool Threading::pxThread::WaitOnSelf(Semaphore& sem, const wxTimeSpan& timeout) const
|
||||
{
|
||||
if (!AffinityAssert_DisallowFromSelf(pxDiagSpot))
|
||||
return true;
|
||||
|
||||
wxTimeSpan runningout(timeout);
|
||||
|
||||
while (runningout.GetMilliseconds() > 0)
|
||||
{
|
||||
const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout);
|
||||
if (sem.WaitWithoutYield(interval))
|
||||
return true;
|
||||
_selfRunningTest(L"semaphore");
|
||||
runningout -= interval;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Threading::pxThread::WaitOnSelf(Mutex& mutex, const wxTimeSpan& timeout) const
|
||||
{
|
||||
if (!AffinityAssert_DisallowFromSelf(pxDiagSpot))
|
||||
return true;
|
||||
|
||||
wxTimeSpan runningout(timeout);
|
||||
|
||||
while (runningout.GetMilliseconds() > 0)
|
||||
{
|
||||
const wxTimeSpan interval((SelfWaitInterval < runningout) ? SelfWaitInterval : runningout);
|
||||
if (mutex.WaitWithoutYield(interval))
|
||||
return true;
|
||||
_selfRunningTest(L"mutex");
|
||||
runningout -= interval;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inserts a thread cancellation point. If the thread has received a cancel request, this
|
||||
// function will throw an SEH exception designed to exit the thread (so make sure to use C++
|
||||
// object encapsulation for anything that could leak resources, to ensure object unwinding
|
||||
// and cleanup, or use the DoThreadCleanup() override to perform resource cleanup).
|
||||
void Threading::pxThread::TestCancel() const
|
||||
{
|
||||
AffinityAssert_AllowFromSelf(pxDiagSpot);
|
||||
pthread_testcancel();
|
||||
}
|
||||
|
||||
// Executes the virtual member method
|
||||
void Threading::pxThread::_try_virtual_invoke(void (pxThread::*method)())
|
||||
{
|
||||
try
|
||||
{
|
||||
(this->*method)();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Neat repackaging for STL Runtime errors...
|
||||
//
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
m_except = new Exception::RuntimeError(ex, WX_STR(GetName()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
catch (Exception::RuntimeError& ex)
|
||||
{
|
||||
BaseException* woot = ex.Clone();
|
||||
woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName()));
|
||||
m_except = woot;
|
||||
}
|
||||
#ifndef PCSX2_DEVBUILD
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bleh... don't bother with std::exception. runtime_error should catch anything
|
||||
// useful coming out of the core STL libraries anyway, and these are best handled by
|
||||
// the MSVC debugger (or by silent random annoying fail on debug-less linux).
|
||||
/*catch( std::logic_error& ex )
|
||||
{
|
||||
throw BaseException( pxsFmt( L"STL Logic Error (thread:%s): %s",
|
||||
WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) )
|
||||
);
|
||||
}
|
||||
catch( std::exception& ex )
|
||||
{
|
||||
throw BaseException( pxsFmt( L"STL exception (thread:%s): %s",
|
||||
WX_STR(GetName()), WX_STR(fromUTF8( ex.what() )) )
|
||||
);
|
||||
}*/
|
||||
// ----------------------------------------------------------------------------
|
||||
// BaseException -- same deal as LogicErrors.
|
||||
//
|
||||
catch (BaseException& ex)
|
||||
{
|
||||
BaseException* woot = ex.Clone();
|
||||
woot->DiagMsg() += pxsFmt(L"(thread:%s)", WX_STR(GetName()));
|
||||
m_except = woot;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// invoked internally when canceling or exiting the thread. Extending classes should implement
|
||||
// OnCleanupInThread() to extend cleanup functionality.
|
||||
void Threading::pxThread::_ThreadCleanup()
|
||||
{
|
||||
AffinityAssert_AllowFromSelf(pxDiagSpot);
|
||||
_try_virtual_invoke(&pxThread::OnCleanupInThread);
|
||||
m_mtx_InThread.Release();
|
||||
|
||||
// Must set m_running LAST, as thread destructors depend on this value (it is used
|
||||
// to avoid destruction of the thread until all internal data use has stopped.
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
wxString Threading::pxThread::GetName() const
|
||||
{
|
||||
ScopedLock lock(m_mtx_ThreadName);
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Threading::pxThread::SetName(const wxString& newname)
|
||||
{
|
||||
ScopedLock lock(m_mtx_ThreadName);
|
||||
m_name = newname;
|
||||
}
|
||||
|
||||
// This override is called by PeristentThread when the thread is first created, prior to
|
||||
// calling ExecuteTaskInThread, and after the initial InThread lock has been claimed.
|
||||
// This code is also executed within a "safe" environment, where the creating thread is
|
||||
// blocked against m_sem_event. Make sure to do any necessary variable setup here, without
|
||||
// worry that the calling thread might attempt to test the status of those variables
|
||||
// before initialization has completed.
|
||||
//
|
||||
void Threading::pxThread::OnStartInThread()
|
||||
{
|
||||
m_detached = false;
|
||||
m_running = true;
|
||||
|
||||
_platform_specific_OnStartInThread();
|
||||
}
|
||||
|
||||
void Threading::pxThread::_internal_execute()
|
||||
{
|
||||
m_mtx_InThread.Acquire();
|
||||
|
||||
_DoSetThreadName(GetName());
|
||||
make_curthread_key(this);
|
||||
if (curthread_key)
|
||||
pthread_setspecific(curthread_key, this);
|
||||
|
||||
OnStartInThread();
|
||||
m_sem_startup.Post();
|
||||
|
||||
_try_virtual_invoke(&pxThread::ExecuteTaskInThread);
|
||||
}
|
||||
|
||||
// Called by Start, prior to actual starting of the thread, and after any previous
|
||||
// running thread has been canceled or detached.
|
||||
void Threading::pxThread::OnStart()
|
||||
{
|
||||
m_native_handle = 0;
|
||||
m_native_id = 0;
|
||||
|
||||
FrankenMutex(m_mtx_InThread);
|
||||
m_sem_event.Reset();
|
||||
m_sem_startup.Reset();
|
||||
}
|
||||
|
||||
// Extending classes that override this method should always call it last from their
|
||||
// personal implementations.
|
||||
void Threading::pxThread::OnCleanupInThread()
|
||||
{
|
||||
if (curthread_key)
|
||||
pthread_setspecific(curthread_key, NULL);
|
||||
|
||||
unmake_curthread_key();
|
||||
|
||||
_platform_specific_OnCleanupInThread();
|
||||
|
||||
m_native_handle = 0;
|
||||
m_native_id = 0;
|
||||
|
||||
m_evtsrc_OnDelete.Dispatch(0);
|
||||
}
|
||||
|
||||
// passed into pthread_create, and is used to dispatch the thread's object oriented
|
||||
// callback function
|
||||
void* Threading::pxThread::_internal_callback(void* itsme)
|
||||
{
|
||||
if (!pxAssertDev(itsme != NULL, wxNullChar))
|
||||
return NULL;
|
||||
|
||||
internal_callback_helper(itsme);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// __try is used in pthread_cleanup_push when CLEANUP_SEH is used as the cleanup model.
|
||||
// That can't be used in a function that has objects that require unwinding (compile
|
||||
// error C2712), so move it into a separate function.
|
||||
void Threading::pxThread::internal_callback_helper(void* itsme)
|
||||
{
|
||||
pxThread& owner = *static_cast<pxThread*>(itsme);
|
||||
|
||||
pthread_cleanup_push(_pt_callback_cleanup, itsme);
|
||||
owner._internal_execute();
|
||||
pthread_cleanup_pop(true);
|
||||
}
|
||||
|
||||
void Threading::pxThread::_DoSetThreadName(const wxString& name)
|
||||
{
|
||||
_DoSetThreadName(static_cast<const char*>(name.ToUTF8()));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// pthread Cond is an evil api that is not suited for Pcsx2 needs.
|
||||
// Let's not use it. (Air)
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
#if 0
|
||||
Threading::WaitEvent::WaitEvent()
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = pthread_cond_init(&cond, NULL);
|
||||
err = pthread_mutex_init(&mutex, NULL);
|
||||
}
|
||||
|
||||
Threading::WaitEvent::~WaitEvent()
|
||||
{
|
||||
pthread_cond_destroy( &cond );
|
||||
pthread_mutex_destroy( &mutex );
|
||||
}
|
||||
|
||||
void Threading::WaitEvent::Set()
|
||||
{
|
||||
pthread_mutex_lock( &mutex );
|
||||
pthread_cond_signal( &cond );
|
||||
pthread_mutex_unlock( &mutex );
|
||||
}
|
||||
|
||||
void Threading::WaitEvent::Wait()
|
||||
{
|
||||
pthread_mutex_lock( &mutex );
|
||||
pthread_cond_wait( &cond, &mutex );
|
||||
pthread_mutex_unlock( &mutex );
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// BaseThreadError
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
wxString Exception::BaseThreadError::FormatDiagnosticMessage() const
|
||||
{
|
||||
wxString null_str(L"Null Thread Object");
|
||||
return pxsFmt(m_message_diag, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName()));
|
||||
}
|
||||
|
||||
wxString Exception::BaseThreadError::FormatDisplayMessage() const
|
||||
{
|
||||
wxString null_str(L"Null Thread Object");
|
||||
return pxsFmt(m_message_user, (m_thread == NULL) ? WX_STR(null_str) : WX_STR(m_thread->GetName()));
|
||||
}
|
||||
|
||||
pxThread& Exception::BaseThreadError::Thread()
|
||||
{
|
||||
pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception.");
|
||||
return *m_thread;
|
||||
}
|
||||
const pxThread& Exception::BaseThreadError::Thread() const
|
||||
{
|
||||
pxAssertDev(m_thread != NULL, "NULL thread object on ThreadError exception.");
|
||||
return *m_thread;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <wx/datetime.h>
|
||||
#ifdef _WIN32
|
||||
// thanks I hate it.
|
||||
#include <wx/filefn.h>
|
||||
|
@ -31,43 +32,6 @@
|
|||
#include "common/General.h"
|
||||
#include <atomic>
|
||||
|
||||
#undef Yield // release the burden of windows.h global namespace spam.
|
||||
|
||||
#define AffinityAssert_AllowFrom_MainUI() \
|
||||
pxAssertMsg(wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only.")
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// pxThreadLog / ConsoleLogSource_Threading
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
class ConsoleLogSource_Threading : ConsoleLogSource
|
||||
{
|
||||
typedef ConsoleLogSource _parent;
|
||||
|
||||
public:
|
||||
using _parent::IsActive;
|
||||
|
||||
ConsoleLogSource_Threading();
|
||||
|
||||
bool Write(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Write(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
bool Warn(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Warn(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
bool Error(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Error(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
};
|
||||
|
||||
extern ConsoleLogSource_Threading pxConLog_Thread;
|
||||
|
||||
#define pxThreadLog pxConLog_Thread.IsActive() && pxConLog_Thread
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// PCSX2_THREAD_LOCAL - Defines platform/operating system support for Thread Local Storage
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -86,8 +50,6 @@ extern ConsoleLogSource_Threading pxConLog_Thread;
|
|||
#define DeclareTls(x) x
|
||||
#endif
|
||||
|
||||
class wxTimeSpan;
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
class ThreadHandle;
|
||||
|
@ -95,75 +57,17 @@ namespace Threading
|
|||
class RwMutex;
|
||||
|
||||
extern void pxTestCancel();
|
||||
extern pxThread* pxGetCurrentThread();
|
||||
extern wxString pxGetCurrentThreadName();
|
||||
extern void YieldToMain();
|
||||
|
||||
extern u64 GetThreadCpuTime();
|
||||
extern u64 GetThreadTicksPerSecond();
|
||||
|
||||
/// Set the name of the current thread
|
||||
extern void SetNameOfCurrentThread(const char* name);
|
||||
|
||||
// Yields the current thread and provides cancellation points if the thread is managed by
|
||||
// pxThread. Unmanaged threads use standard Sleep.
|
||||
extern void pxYield(int ms);
|
||||
extern const wxTimeSpan def_yieldgui_interval;
|
||||
} // namespace Threading
|
||||
|
||||
namespace Exception
|
||||
{
|
||||
class BaseThreadError : public RuntimeError
|
||||
{
|
||||
DEFINE_EXCEPTION_COPYTORS(BaseThreadError, RuntimeError)
|
||||
DEFINE_EXCEPTION_MESSAGES(BaseThreadError)
|
||||
|
||||
public:
|
||||
Threading::pxThread* m_thread;
|
||||
|
||||
protected:
|
||||
BaseThreadError()
|
||||
{
|
||||
m_thread = NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit BaseThreadError(Threading::pxThread* _thread)
|
||||
{
|
||||
m_thread = _thread;
|
||||
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
|
||||
}
|
||||
|
||||
explicit BaseThreadError(Threading::pxThread& _thread)
|
||||
{
|
||||
m_thread = &_thread;
|
||||
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
|
||||
}
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
|
||||
Threading::pxThread& Thread();
|
||||
const Threading::pxThread& Thread() const;
|
||||
};
|
||||
|
||||
class ThreadCreationError : public BaseThreadError
|
||||
{
|
||||
DEFINE_EXCEPTION_COPYTORS(ThreadCreationError, BaseThreadError)
|
||||
|
||||
public:
|
||||
explicit ThreadCreationError(Threading::pxThread* _thread)
|
||||
{
|
||||
m_thread = _thread;
|
||||
SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread.");
|
||||
}
|
||||
|
||||
explicit ThreadCreationError(Threading::pxThread& _thread)
|
||||
{
|
||||
m_thread = &_thread;
|
||||
SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread.");
|
||||
}
|
||||
};
|
||||
} // namespace Exception
|
||||
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -186,22 +90,6 @@ namespace Threading
|
|||
// sleeps the current thread for the given number of milliseconds.
|
||||
extern void Sleep(int ms);
|
||||
|
||||
// pthread Cond is an evil api that is not suited for Pcsx2 needs.
|
||||
// Let's not use it. Use mutexes and semaphores instead to create waits. (Air)
|
||||
#if 0
|
||||
struct WaitEvent
|
||||
{
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
WaitEvent();
|
||||
~WaitEvent();
|
||||
|
||||
void Set();
|
||||
void Wait();
|
||||
};
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ThreadHandle
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -374,48 +262,6 @@ namespace Threading
|
|||
void Reset();
|
||||
};
|
||||
|
||||
class Semaphore
|
||||
{
|
||||
protected:
|
||||
#ifdef __APPLE__
|
||||
semaphore_t m_sema;
|
||||
int m_counter;
|
||||
#else
|
||||
sem_t m_sema;
|
||||
#endif
|
||||
|
||||
public:
|
||||
Semaphore();
|
||||
virtual ~Semaphore();
|
||||
|
||||
void Reset();
|
||||
void Post();
|
||||
void Post(int multiple);
|
||||
|
||||
void WaitWithoutYield();
|
||||
bool WaitWithoutYield(const wxTimeSpan& timeout);
|
||||
void WaitNoCancel();
|
||||
void WaitNoCancel(const wxTimeSpan& timeout);
|
||||
void WaitWithoutYieldWithSpin()
|
||||
{
|
||||
u32 waited = 0;
|
||||
while (true)
|
||||
{
|
||||
if (TryWait())
|
||||
return;
|
||||
if (waited >= SPIN_TIME_NS)
|
||||
break;
|
||||
waited += ShortSpin();
|
||||
}
|
||||
WaitWithoutYield();
|
||||
}
|
||||
bool TryWait();
|
||||
int Count();
|
||||
|
||||
void Wait();
|
||||
bool Wait(const wxTimeSpan& timeout);
|
||||
};
|
||||
|
||||
class Mutex
|
||||
{
|
||||
protected:
|
||||
|
@ -503,78 +349,4 @@ namespace Threading
|
|||
// Special constructor used by ScopedTryLock
|
||||
ScopedLock(const Mutex& locker, bool isTryLock);
|
||||
};
|
||||
|
||||
class ScopedTryLock : public ScopedLock
|
||||
{
|
||||
public:
|
||||
ScopedTryLock(const Mutex& locker)
|
||||
: ScopedLock(locker, true)
|
||||
{
|
||||
}
|
||||
virtual ~ScopedTryLock() = default;
|
||||
bool Failed() const { return !m_IsLocked; }
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ScopedNonblockingLock
|
||||
// --------------------------------------------------------------------------------------
|
||||
// A ScopedTryLock branded for use with Nonblocking mutexes. See ScopedTryLock for details.
|
||||
//
|
||||
class ScopedNonblockingLock
|
||||
{
|
||||
DeclareNoncopyableObject(ScopedNonblockingLock);
|
||||
|
||||
protected:
|
||||
NonblockingMutex& m_lock;
|
||||
bool m_IsLocked;
|
||||
|
||||
public:
|
||||
ScopedNonblockingLock(NonblockingMutex& locker)
|
||||
: m_lock(locker)
|
||||
, m_IsLocked(m_lock.TryAcquire())
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~ScopedNonblockingLock()
|
||||
{
|
||||
if (m_IsLocked)
|
||||
m_lock.Release();
|
||||
}
|
||||
|
||||
bool Failed() const { return !m_IsLocked; }
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ScopedLockBool
|
||||
// --------------------------------------------------------------------------------------
|
||||
// A ScopedLock in which you specify an external bool to get updated on locks/unlocks.
|
||||
// Note that the isLockedBool should only be used as an indicator for the locked status,
|
||||
// and not actually depended on for thread synchronization...
|
||||
|
||||
struct ScopedLockBool
|
||||
{
|
||||
ScopedLock m_lock;
|
||||
std::atomic<bool>& m_bool;
|
||||
|
||||
ScopedLockBool(Mutex& mutexToLock, std::atomic<bool>& isLockedBool)
|
||||
: m_lock(mutexToLock)
|
||||
, m_bool(isLockedBool)
|
||||
{
|
||||
m_bool.store(m_lock.IsLocked(), std::memory_order_relaxed);
|
||||
}
|
||||
virtual ~ScopedLockBool()
|
||||
{
|
||||
m_bool.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
void Acquire()
|
||||
{
|
||||
m_lock.Acquire();
|
||||
m_bool.store(m_lock.IsLocked(), std::memory_order_relaxed);
|
||||
}
|
||||
void Release()
|
||||
{
|
||||
m_bool.store(false, std::memory_order_relaxed);
|
||||
m_lock.Release();
|
||||
}
|
||||
};
|
||||
} // namespace Threading
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/datetime.h>
|
||||
#include <wx/thread.h>
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
extern const wxTimeSpan def_yieldgui_interval;
|
||||
|
||||
extern bool _WaitGui_RecursionGuard(const wxChar* name);
|
||||
|
||||
extern void YieldToMain();
|
||||
extern bool AllowDeletions();
|
||||
} // namespace Threading
|
|
@ -16,7 +16,7 @@
|
|||
#if defined(_WIN32)
|
||||
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/PersistentThread.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/emitter/tools.h"
|
||||
|
||||
__fi void Threading::Sleep(int ms)
|
||||
|
@ -142,30 +142,6 @@ u64 Threading::GetThreadTicksPerSecond()
|
|||
return frequency;
|
||||
}
|
||||
|
||||
u64 Threading::pxThread::GetCpuTime() const
|
||||
{
|
||||
u64 ret = 0;
|
||||
if (m_native_handle)
|
||||
QueryThreadCycleTime((HANDLE)m_native_handle, &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnStartInThread()
|
||||
{
|
||||
// OpenThread Note: Vista and Win7 need only THREAD_QUERY_LIMITED_INFORMATION (XP and 2k need more),
|
||||
// however we own our process threads, so shouldn't matter in any case...
|
||||
|
||||
m_native_id = (uptr)GetCurrentThreadId();
|
||||
m_native_handle = (uptr)OpenThread(THREAD_QUERY_INFORMATION, false, (DWORD)m_native_id);
|
||||
|
||||
pxAssertDev(m_native_handle, wxNullChar);
|
||||
}
|
||||
|
||||
void Threading::pxThread::_platform_specific_OnCleanupInThread()
|
||||
{
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
}
|
||||
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
// This feature needs Windows headers and MSVC's SEH support:
|
||||
|
|
|
@ -151,7 +151,6 @@
|
|||
<ClInclude Include="StringUtil.h" />
|
||||
<ClInclude Include="SettingsInterface.h" />
|
||||
<ClInclude Include="SettingsWrapper.h" />
|
||||
<ClInclude Include="ThreadingInternal.h" />
|
||||
<ClInclude Include="Assertions.h" />
|
||||
<ClInclude Include="Console.h" />
|
||||
<ClInclude Include="Dependencies.h" />
|
||||
|
@ -178,7 +177,6 @@
|
|||
<ClInclude Include="Vulkan\Util.h" />
|
||||
<ClInclude Include="WindowInfo.h" />
|
||||
<ClInclude Include="Threading.h" />
|
||||
<ClInclude Include="PersistentThread.h" />
|
||||
<ClInclude Include="RwMutex.h" />
|
||||
<ClInclude Include="emitter\implement\bmi.h" />
|
||||
<ClInclude Include="emitter\cpudetect_internal.h" />
|
||||
|
|
|
@ -285,9 +285,6 @@
|
|||
<ClInclude Include="Path.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PersistentThread.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PrecompiledHeader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
@ -324,9 +321,6 @@
|
|||
<ClInclude Include="emitter\implement\test.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThreadingInternal.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\tools.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include "PrecompiledHeader.h"
|
||||
#include "ThreadedFileReader.h"
|
||||
|
||||
#include "common/Threading.h"
|
||||
|
||||
// Make sure buffer size is bigger than the cutoff where PCSX2 emulates a seek
|
||||
// If buffers are smaller than that, we can't keep up with linear reads
|
||||
static constexpr u32 MINIMUM_SIZE = 128 * 1024;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "AsyncFileReader.h"
|
||||
#include "common/PersistentThread.h"
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
|
|
@ -1133,6 +1133,7 @@ set(pcsx2GuiSources
|
|||
gui/Panels/PathsPanel.cpp
|
||||
gui/Panels/SpeedhacksPanel.cpp
|
||||
gui/Panels/VideoPanel.cpp
|
||||
gui/PersistentThread.cpp
|
||||
gui/pxCheckBox.cpp
|
||||
gui/pxRadioPanel.cpp
|
||||
gui/pxStaticText.cpp
|
||||
|
@ -1186,6 +1187,7 @@ set(pcsx2GuiHeaders
|
|||
gui/Panels/ConfigurationPanels.h
|
||||
gui/Panels/LogOptionsPanels.h
|
||||
gui/Panels/MemoryCardPanels.h
|
||||
gui/PersistentThread.h
|
||||
gui/pxCheckBox.h
|
||||
gui/pxEvents.h
|
||||
gui/pxEventThread.h
|
||||
|
@ -1193,6 +1195,7 @@ set(pcsx2GuiHeaders
|
|||
gui/pxStaticText.h
|
||||
gui/RecentIsoList.h
|
||||
gui/Saveslots.h
|
||||
gui/ScopedPtrMT.h
|
||||
gui/SysThreads.h
|
||||
gui/ThreadingDialogs.h
|
||||
gui/ThreadingDialogs.cpp
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "GS/GSExtra.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/PersistentThread.h"
|
||||
|
||||
#define ENABLE_DRAW_STATS 0
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#define PINE_DEFAULT_SLOT 28011
|
||||
#define PINE_EMULATOR_NAME "pcsx2"
|
||||
|
||||
#include "common/PersistentThread.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
#include "gui/SysThreads.h"
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
#include "common/SafeArray.h"
|
||||
#include "common/EventSource.h"
|
||||
#include "common/PersistentThread.h"
|
||||
|
||||
#include "gui/wxGuiTools.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
#include "gui/pxRadioPanel.h"
|
||||
#include "gui/pxCheckBox.h"
|
||||
#include "gui/pxStaticText.h"
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
|
||||
#pragma once
|
||||
#include <wx/wx.h>
|
||||
#include "common/PersistentThread.h"
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#include "DebugTools/DisassemblyManager.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
|
||||
enum class SEARCHTYPE
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,11 +16,137 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/ScopedPtrMT.h"
|
||||
#include "common/EventSource.h"
|
||||
#include "ScopedPtrMT.h"
|
||||
|
||||
#undef Yield // release the burden of windows.h global namespace spam.
|
||||
|
||||
#define AffinityAssert_AllowFrom_MainUI() \
|
||||
pxAssertMsg(wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only.")
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// pxThreadLog / ConsoleLogSource_Threading
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
class ConsoleLogSource_Threading : ConsoleLogSource
|
||||
{
|
||||
typedef ConsoleLogSource _parent;
|
||||
|
||||
public:
|
||||
using _parent::IsActive;
|
||||
|
||||
ConsoleLogSource_Threading();
|
||||
|
||||
bool Write(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Write(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
bool Warn(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Warn(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
bool Error(const wxString& thrname, const wxChar* msg)
|
||||
{
|
||||
return _parent::Error(wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg);
|
||||
}
|
||||
};
|
||||
|
||||
extern ConsoleLogSource_Threading pxConLog_Thread;
|
||||
|
||||
#define pxThreadLog pxConLog_Thread.IsActive() && pxConLog_Thread
|
||||
|
||||
|
||||
namespace Exception
|
||||
{
|
||||
class BaseThreadError : public RuntimeError
|
||||
{
|
||||
DEFINE_EXCEPTION_COPYTORS(BaseThreadError, RuntimeError)
|
||||
DEFINE_EXCEPTION_MESSAGES(BaseThreadError)
|
||||
|
||||
public:
|
||||
Threading::pxThread* m_thread;
|
||||
|
||||
protected:
|
||||
BaseThreadError()
|
||||
{
|
||||
m_thread = NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit BaseThreadError(Threading::pxThread* _thread)
|
||||
{
|
||||
m_thread = _thread;
|
||||
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
|
||||
}
|
||||
|
||||
explicit BaseThreadError(Threading::pxThread& _thread)
|
||||
{
|
||||
m_thread = &_thread;
|
||||
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
|
||||
}
|
||||
|
||||
virtual wxString FormatDiagnosticMessage() const;
|
||||
virtual wxString FormatDisplayMessage() const;
|
||||
|
||||
Threading::pxThread& Thread();
|
||||
const Threading::pxThread& Thread() const;
|
||||
};
|
||||
|
||||
class ThreadCreationError : public BaseThreadError
|
||||
{
|
||||
DEFINE_EXCEPTION_COPYTORS(ThreadCreationError, BaseThreadError)
|
||||
|
||||
public:
|
||||
explicit ThreadCreationError(Threading::pxThread* _thread)
|
||||
{
|
||||
m_thread = _thread;
|
||||
SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread.");
|
||||
}
|
||||
|
||||
explicit ThreadCreationError(Threading::pxThread& _thread)
|
||||
{
|
||||
m_thread = &_thread;
|
||||
SetBothMsgs(L"Thread creation failure. An unspecified error occurred while trying to create the %s thread.");
|
||||
}
|
||||
};
|
||||
} // namespace Exception
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
extern pxThread* pxGetCurrentThread();
|
||||
extern wxString pxGetCurrentThreadName();
|
||||
extern bool _WaitGui_RecursionGuard(const wxChar* name);
|
||||
|
||||
extern bool AllowDeletions();
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// RecursionGuard - Basic protection against function recursion
|
||||
// ----------------------------------------------------------------------------------------
|
||||
// Thread safety note: If used in a threaded environment, you shoud use a handle to a __threadlocal
|
||||
// storage variable (protects aaginst race conditions and, in *most* cases, is more desirable
|
||||
// behavior as well.
|
||||
//
|
||||
// Rationale: wxWidgets has its own wxRecursionGuard, but it has a sloppy implementation with
|
||||
// entirely unnecessary assertion checks.
|
||||
//
|
||||
class RecursionGuard
|
||||
{
|
||||
public:
|
||||
int& Counter;
|
||||
|
||||
RecursionGuard(int& counter)
|
||||
: Counter(counter)
|
||||
{
|
||||
++Counter;
|
||||
}
|
||||
|
||||
virtual ~RecursionGuard()
|
||||
{
|
||||
--Counter;
|
||||
}
|
||||
|
||||
bool IsReentrant() const { return Counter > 1; }
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ThreadDeleteEvent
|
||||
|
@ -58,6 +184,48 @@ namespace Threading
|
|||
virtual void OnThreadCleanup() = 0;
|
||||
};
|
||||
|
||||
class Semaphore
|
||||
{
|
||||
protected:
|
||||
#ifdef __APPLE__
|
||||
semaphore_t m_sema;
|
||||
int m_counter;
|
||||
#else
|
||||
sem_t m_sema;
|
||||
#endif
|
||||
|
||||
public:
|
||||
Semaphore();
|
||||
virtual ~Semaphore();
|
||||
|
||||
void Reset();
|
||||
void Post();
|
||||
void Post(int multiple);
|
||||
|
||||
void WaitWithoutYield();
|
||||
bool WaitWithoutYield(const wxTimeSpan& timeout);
|
||||
void WaitNoCancel();
|
||||
void WaitNoCancel(const wxTimeSpan& timeout);
|
||||
void WaitWithoutYieldWithSpin()
|
||||
{
|
||||
u32 waited = 0;
|
||||
while (true)
|
||||
{
|
||||
if (TryWait())
|
||||
return;
|
||||
if (waited >= SPIN_TIME_NS)
|
||||
break;
|
||||
waited += ShortSpin();
|
||||
}
|
||||
WaitWithoutYield();
|
||||
}
|
||||
bool TryWait();
|
||||
int Count();
|
||||
|
||||
void Wait();
|
||||
bool Wait(const wxTimeSpan& timeout);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// pxThread - Helper class for the basics of starting/managing persistent threads.
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
@ -89,13 +257,9 @@ namespace Threading
|
|||
{
|
||||
DeclareNoncopyableObject(pxThread);
|
||||
|
||||
friend void pxYield(int ms);
|
||||
|
||||
protected:
|
||||
wxString m_name; // diagnostic name for our thread.
|
||||
pthread_t m_thread;
|
||||
uptr m_native_id; // typically an id, but implementing platforms can do whatever.
|
||||
uptr m_native_handle; // typically a pointer/handle, but implementing platforms can do whatever.
|
||||
|
||||
WorkSema m_sem_event; // general wait event that's needed by most threads
|
||||
Semaphore m_sem_startup; // startup sync tool
|
||||
|
@ -118,7 +282,6 @@ namespace Threading
|
|||
pxThread(const wxString& name = L"pxThread");
|
||||
|
||||
pthread_t GetId() const { return m_thread; }
|
||||
u64 GetCpuTime() const;
|
||||
|
||||
virtual void Start();
|
||||
virtual void Cancel(bool isBlocking = true);
|
||||
|
@ -189,8 +352,6 @@ namespace Threading
|
|||
// ----------------------------------------------------------------------------
|
||||
// Section of methods for internal use only.
|
||||
|
||||
void _platform_specific_OnStartInThread();
|
||||
void _platform_specific_OnCleanupInThread();
|
||||
bool _basecancel();
|
||||
void _selfRunningTest(const wxChar* name) const;
|
||||
void _DoSetThreadName(const wxString& name);
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Threading.h"
|
||||
#include "common/Threading.h"
|
||||
using Threading::ScopedLock;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
|
@ -197,12 +197,9 @@ void SysCoreThread::ApplySettings(const Pcsx2Config& src)
|
|||
// handle GS setting changes
|
||||
if (GetMTGS().IsOpen() && gs_settings_changed)
|
||||
{
|
||||
// if by change we reopen the GS, the window handles will invalidate.
|
||||
// so, we should block here until GS has finished reinitializing, if needed.
|
||||
Console.WriteLn("Applying GS settings...");
|
||||
GetMTGS().ApplySettings();
|
||||
GetMTGS().SetVSync(EmuConfig.GetEffectiveVsyncMode());
|
||||
GetMTGS().WaitGS();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +337,9 @@ void SysCoreThread::TearDownSystems(SystemsMask systemsToTearDown)
|
|||
if (systemsToTearDown & System_SPU2) SPU2close();
|
||||
if (systemsToTearDown & System_MCD) FileMcd_EmuClose();
|
||||
|
||||
if (GetMTGS().IsOpen())
|
||||
GetMTGS().WaitGS();
|
||||
|
||||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle());
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,12 @@ void SysCoreThread::OnResumeInThread(SystemsMask systemsToReinstate)
|
|||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread());
|
||||
PerformanceMetrics::Reset();
|
||||
|
||||
// if GS is open, wait for it to finish (could be applying settings)
|
||||
if (GetMTGS().IsOpen())
|
||||
GetMTGS().WaitGS();
|
||||
else
|
||||
GetMTGS().WaitForOpen();
|
||||
|
||||
if (systemsToReinstate & System_DEV9) DEV9open();
|
||||
if (systemsToReinstate & System_USB) USBopen(g_gs_window_info);
|
||||
if (systemsToReinstate & System_FW) FWopen();
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
#include "System.h"
|
||||
|
||||
#include "common/PersistentThread.h"
|
||||
#include "common/emitter/tools.h"
|
||||
#include "PersistentThread.h"
|
||||
#include "PINE.h"
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/PersistentThread.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
#include "gui/pxEvents.h"
|
||||
|
||||
#include <wx/timer.h>
|
||||
|
@ -75,14 +75,14 @@ extern ConsoleLogSource_Event pxConLog_Event;
|
|||
// the other class you want to turn into an event within it. It might feel like more work
|
||||
// but it *will* be less work in the long run.
|
||||
//
|
||||
class SysExecEvent : public ICloneable
|
||||
class SysExecEvent
|
||||
{
|
||||
protected:
|
||||
SynchronousActionState* m_sync;
|
||||
|
||||
public:
|
||||
virtual ~SysExecEvent() = default;
|
||||
SysExecEvent* Clone() const { return new SysExecEvent( *this ); }
|
||||
virtual SysExecEvent* Clone() const { return new SysExecEvent( *this ); }
|
||||
|
||||
SysExecEvent( SynchronousActionState* sync=NULL )
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "common/General.h"
|
||||
#include "common/Dependencies.h"
|
||||
#include "common/Threading.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
|
||||
wxDECLARE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
||||
wxDECLARE_EVENT(pxEvt_DeleteObject, wxCommandEvent);
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "gui/wxAppWithHelpers.h"
|
||||
#include "common/ThreadingInternal.h"
|
||||
#include "common/PersistentThread.h"
|
||||
#include "gui/PersistentThread.h"
|
||||
|
||||
wxDEFINE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
||||
wxDEFINE_EVENT(pxEvt_DeleteObject, wxCommandEvent);
|
||||
|
|
|
@ -49,7 +49,7 @@ class pxSynchronousCommandEvent;
|
|||
// (sigh). And, finally, it requires quite a bit of red tape to implement wxObjects because
|
||||
// of the wx-custom runtime type information. So I made my own.
|
||||
//
|
||||
class BaseDeletableObject : public virtual IDeletableObject
|
||||
class BaseDeletableObject
|
||||
{
|
||||
protected:
|
||||
std::atomic<bool> m_IsBeingDeleted;
|
||||
|
@ -58,8 +58,8 @@ public:
|
|||
BaseDeletableObject();
|
||||
virtual ~BaseDeletableObject();
|
||||
|
||||
void DeleteSelf();
|
||||
bool IsBeingDeleted() { return !!m_IsBeingDeleted; }
|
||||
virtual void DeleteSelf();
|
||||
virtual bool IsBeingDeleted() { return !!m_IsBeingDeleted; }
|
||||
|
||||
// Returns FALSE if the object is already marked for deletion, or TRUE if the app
|
||||
// should schedule the object for deletion. Only schedule if TRUE is returned, otherwise
|
||||
|
|
|
@ -355,6 +355,7 @@
|
|||
<ClCompile Include="gui\DriveList.cpp" />
|
||||
<ClCompile Include="gui\IniInterface.cpp" />
|
||||
<ClCompile Include="gui\Panels\MemoryCardListView.cpp" />
|
||||
<ClCompile Include="gui\PersistentThread.cpp" />
|
||||
<ClCompile Include="gui\pxCheckBox.cpp" />
|
||||
<ClCompile Include="gui\pxRadioPanel.cpp" />
|
||||
<ClCompile Include="gui\pxStaticText.cpp" />
|
||||
|
@ -802,6 +803,7 @@
|
|||
<ClInclude Include="gui\i18n.h" />
|
||||
<ClInclude Include="gui\DriveList.h" />
|
||||
<ClInclude Include="gui\IniInterface.h" />
|
||||
<ClInclude Include="gui\PersistentThread.h" />
|
||||
<ClInclude Include="gui\pxCheckBox.h" />
|
||||
<ClInclude Include="gui\pxEvents.h" />
|
||||
<ClInclude Include="gui\pxRadioPanel.h" />
|
||||
|
@ -816,6 +818,7 @@
|
|||
<ClInclude Include="gui\Debugger\DebuggerLists.h" />
|
||||
<ClInclude Include="gui\Debugger\DisassemblyDialog.h" />
|
||||
<ClInclude Include="gui\Panels\MemoryCardPanels.h" />
|
||||
<ClInclude Include="gui\ScopedPtrMT.h" />
|
||||
<ClInclude Include="gui\ThreadingDialogs.h" />
|
||||
<ClInclude Include="gui\wxAppWithHelpers.h" />
|
||||
<ClInclude Include="gui\wxSettingsInterface.h" />
|
||||
|
|
|
@ -1769,6 +1769,7 @@
|
|||
<ClCompile Include="GS\Renderers\DX12\GSDevice12.cpp">
|
||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gui\PersistentThread.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Patch.h">
|
||||
|
@ -2945,6 +2946,8 @@
|
|||
<ClInclude Include="GS\Renderers\DX12\GSTexture12.h">
|
||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gui\ScopedPtrMT.h" />
|
||||
<ClInclude Include="gui\PersistentThread.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="windows\wxResources.rc">
|
||||
|
|
Loading…
Reference in New Issue