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
|
RwMutex.h
|
||||||
SafeArray.h
|
SafeArray.h
|
||||||
ScopedGuard.h
|
ScopedGuard.h
|
||||||
ScopedPtrMT.h
|
|
||||||
SettingsInterface.h
|
SettingsInterface.h
|
||||||
SettingsWrapper.h
|
SettingsWrapper.h
|
||||||
StringHelpers.h
|
StringHelpers.h
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#include <mach/mach_time.h> // mach_absolute_time()
|
#include <mach/mach_time.h> // mach_absolute_time()
|
||||||
|
|
||||||
#include "common/Threading.h"
|
#include "common/Threading.h"
|
||||||
#include "common/ThreadingInternal.h"
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
// Semaphore Implementation for Darwin/OSX
|
// Semaphore Implementation for Darwin/OSX
|
||||||
|
@ -86,231 +85,5 @@ bool Threading::KernelSemaphore::TryWait()
|
||||||
return true;
|
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
|
#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 <mach/mach_port.h>
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.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
|
// 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
|
// 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;
|
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
|
// name can be up to 16 bytes
|
||||||
void Threading::SetNameOfCurrentThread(const char* name)
|
void Threading::SetNameOfCurrentThread(const char* name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,10 +38,6 @@ Fnptr_OutOfMemory pxDoOutOfMemory = NULL;
|
||||||
#define DEVASSERT_INLINE __fi
|
#define DEVASSERT_INLINE __fi
|
||||||
#endif
|
#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;
|
pxDoAssertFnType* pxDoAssert = pxAssertImpl_LogIt;
|
||||||
|
|
||||||
// make life easier for people using VC++ IDE by using this format, which allows double-click
|
// 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)
|
if (function != NULL)
|
||||||
message.Write(" Function: %s\n", function);
|
message.Write(" Function: %s\n", function);
|
||||||
|
|
||||||
message.Write(L" Thread: %s\n", WX_STR(Threading::pxGetCurrentThreadName()));
|
|
||||||
|
|
||||||
if (condition != NULL)
|
if (condition != NULL)
|
||||||
message.Write(L" Condition: %ls\n", condition);
|
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)
|
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
|
// 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
|
// 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
|
// 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
|
// PageProtectionMode
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
#include <pthread_np.h>
|
#include <pthread_np.h>
|
||||||
#endif
|
#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
|
// 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.
|
// to remove an unneeded dependency.
|
||||||
|
@ -199,31 +199,6 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
||||||
#endif
|
#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)
|
void Threading::SetNameOfCurrentThread(const char* name)
|
||||||
{
|
{
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common/Threading.h"
|
#include "common/Threading.h"
|
||||||
#include "common/ThreadingInternal.h"
|
|
||||||
|
|
||||||
namespace Threading
|
namespace Threading
|
||||||
{
|
{
|
||||||
|
@ -196,56 +195,12 @@ bool Threading::Mutex::TryAcquire()
|
||||||
//
|
//
|
||||||
void Threading::Mutex::Acquire()
|
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);
|
pthread_mutex_lock(&m_mutex);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Threading::Mutex::Acquire(const wxTimeSpan& timeout)
|
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);
|
return AcquireWithoutYield(timeout);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked.
|
// Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked.
|
||||||
|
|
|
@ -14,7 +14,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "common/Threading.h"
|
#include "common/Threading.h"
|
||||||
#include "common/ThreadingInternal.h"
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "common/RedtapeWindows.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
// Semaphore Implementations
|
// Semaphore Implementations
|
||||||
|
@ -165,157 +168,4 @@ bool Threading::KernelSemaphore::TryWait()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Threading::Semaphore::Semaphore()
|
#endif
|
||||||
{
|
|
||||||
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/>.
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef __linux__
|
#include "common/Threading.h"
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Threading::pxTestCancel()
|
void Threading::pxTestCancel()
|
||||||
{
|
{
|
||||||
pthread_testcancel();
|
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()
|
__fi void Threading::Timeslice()
|
||||||
{
|
{
|
||||||
sched_yield();
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/datetime.h>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// thanks I hate it.
|
// thanks I hate it.
|
||||||
#include <wx/filefn.h>
|
#include <wx/filefn.h>
|
||||||
|
@ -31,43 +32,6 @@
|
||||||
#include "common/General.h"
|
#include "common/General.h"
|
||||||
#include <atomic>
|
#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
|
// 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
|
#define DeclareTls(x) x
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class wxTimeSpan;
|
|
||||||
|
|
||||||
namespace Threading
|
namespace Threading
|
||||||
{
|
{
|
||||||
class ThreadHandle;
|
class ThreadHandle;
|
||||||
|
@ -95,75 +57,17 @@ namespace Threading
|
||||||
class RwMutex;
|
class RwMutex;
|
||||||
|
|
||||||
extern void pxTestCancel();
|
extern void pxTestCancel();
|
||||||
extern pxThread* pxGetCurrentThread();
|
extern void YieldToMain();
|
||||||
extern wxString pxGetCurrentThreadName();
|
|
||||||
extern u64 GetThreadCpuTime();
|
extern u64 GetThreadCpuTime();
|
||||||
extern u64 GetThreadTicksPerSecond();
|
extern u64 GetThreadTicksPerSecond();
|
||||||
|
|
||||||
/// Set the name of the current thread
|
/// Set the name of the current thread
|
||||||
extern void SetNameOfCurrentThread(const char* name);
|
extern void SetNameOfCurrentThread(const char* name);
|
||||||
|
|
||||||
// Yields the current thread and provides cancellation points if the thread is managed by
|
extern const wxTimeSpan def_yieldgui_interval;
|
||||||
// pxThread. Unmanaged threads use standard Sleep.
|
|
||||||
extern void pxYield(int ms);
|
|
||||||
} // namespace Threading
|
} // 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
|
namespace Threading
|
||||||
{
|
{
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
@ -186,22 +90,6 @@ namespace Threading
|
||||||
// sleeps the current thread for the given number of milliseconds.
|
// sleeps the current thread for the given number of milliseconds.
|
||||||
extern void Sleep(int ms);
|
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
|
// ThreadHandle
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
@ -374,48 +262,6 @@ namespace Threading
|
||||||
void Reset();
|
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
|
class Mutex
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -503,78 +349,4 @@ namespace Threading
|
||||||
// Special constructor used by ScopedTryLock
|
// Special constructor used by ScopedTryLock
|
||||||
ScopedLock(const Mutex& locker, bool isTryLock);
|
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
|
} // 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)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
#include "common/RedtapeWindows.h"
|
#include "common/RedtapeWindows.h"
|
||||||
#include "common/PersistentThread.h"
|
#include "common/Threading.h"
|
||||||
#include "common/emitter/tools.h"
|
#include "common/emitter/tools.h"
|
||||||
|
|
||||||
__fi void Threading::Sleep(int ms)
|
__fi void Threading::Sleep(int ms)
|
||||||
|
@ -142,30 +142,6 @@ u64 Threading::GetThreadTicksPerSecond()
|
||||||
return frequency;
|
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)
|
void Threading::SetNameOfCurrentThread(const char* name)
|
||||||
{
|
{
|
||||||
// This feature needs Windows headers and MSVC's SEH support:
|
// This feature needs Windows headers and MSVC's SEH support:
|
||||||
|
|
|
@ -151,7 +151,6 @@
|
||||||
<ClInclude Include="StringUtil.h" />
|
<ClInclude Include="StringUtil.h" />
|
||||||
<ClInclude Include="SettingsInterface.h" />
|
<ClInclude Include="SettingsInterface.h" />
|
||||||
<ClInclude Include="SettingsWrapper.h" />
|
<ClInclude Include="SettingsWrapper.h" />
|
||||||
<ClInclude Include="ThreadingInternal.h" />
|
|
||||||
<ClInclude Include="Assertions.h" />
|
<ClInclude Include="Assertions.h" />
|
||||||
<ClInclude Include="Console.h" />
|
<ClInclude Include="Console.h" />
|
||||||
<ClInclude Include="Dependencies.h" />
|
<ClInclude Include="Dependencies.h" />
|
||||||
|
@ -178,7 +177,6 @@
|
||||||
<ClInclude Include="Vulkan\Util.h" />
|
<ClInclude Include="Vulkan\Util.h" />
|
||||||
<ClInclude Include="WindowInfo.h" />
|
<ClInclude Include="WindowInfo.h" />
|
||||||
<ClInclude Include="Threading.h" />
|
<ClInclude Include="Threading.h" />
|
||||||
<ClInclude Include="PersistentThread.h" />
|
|
||||||
<ClInclude Include="RwMutex.h" />
|
<ClInclude Include="RwMutex.h" />
|
||||||
<ClInclude Include="emitter\implement\bmi.h" />
|
<ClInclude Include="emitter\implement\bmi.h" />
|
||||||
<ClInclude Include="emitter\cpudetect_internal.h" />
|
<ClInclude Include="emitter\cpudetect_internal.h" />
|
||||||
|
|
|
@ -285,9 +285,6 @@
|
||||||
<ClInclude Include="Path.h">
|
<ClInclude Include="Path.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="PersistentThread.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="PrecompiledHeader.h">
|
<ClInclude Include="PrecompiledHeader.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -324,9 +321,6 @@
|
||||||
<ClInclude Include="emitter\implement\test.h">
|
<ClInclude Include="emitter\implement\test.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="ThreadingInternal.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="emitter\tools.h">
|
<ClInclude Include="emitter\tools.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -514,4 +508,4 @@
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</MASM>
|
</MASM>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -16,6 +16,8 @@
|
||||||
#include "PrecompiledHeader.h"
|
#include "PrecompiledHeader.h"
|
||||||
#include "ThreadedFileReader.h"
|
#include "ThreadedFileReader.h"
|
||||||
|
|
||||||
|
#include "common/Threading.h"
|
||||||
|
|
||||||
// Make sure buffer size is bigger than the cutoff where PCSX2 emulates a seek
|
// 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
|
// If buffers are smaller than that, we can't keep up with linear reads
|
||||||
static constexpr u32 MINIMUM_SIZE = 128 * 1024;
|
static constexpr u32 MINIMUM_SIZE = 128 * 1024;
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AsyncFileReader.h"
|
#include "AsyncFileReader.h"
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
|
@ -1133,6 +1133,7 @@ set(pcsx2GuiSources
|
||||||
gui/Panels/PathsPanel.cpp
|
gui/Panels/PathsPanel.cpp
|
||||||
gui/Panels/SpeedhacksPanel.cpp
|
gui/Panels/SpeedhacksPanel.cpp
|
||||||
gui/Panels/VideoPanel.cpp
|
gui/Panels/VideoPanel.cpp
|
||||||
|
gui/PersistentThread.cpp
|
||||||
gui/pxCheckBox.cpp
|
gui/pxCheckBox.cpp
|
||||||
gui/pxRadioPanel.cpp
|
gui/pxRadioPanel.cpp
|
||||||
gui/pxStaticText.cpp
|
gui/pxStaticText.cpp
|
||||||
|
@ -1186,6 +1187,7 @@ set(pcsx2GuiHeaders
|
||||||
gui/Panels/ConfigurationPanels.h
|
gui/Panels/ConfigurationPanels.h
|
||||||
gui/Panels/LogOptionsPanels.h
|
gui/Panels/LogOptionsPanels.h
|
||||||
gui/Panels/MemoryCardPanels.h
|
gui/Panels/MemoryCardPanels.h
|
||||||
|
gui/PersistentThread.h
|
||||||
gui/pxCheckBox.h
|
gui/pxCheckBox.h
|
||||||
gui/pxEvents.h
|
gui/pxEvents.h
|
||||||
gui/pxEventThread.h
|
gui/pxEventThread.h
|
||||||
|
@ -1193,6 +1195,7 @@ set(pcsx2GuiHeaders
|
||||||
gui/pxStaticText.h
|
gui/pxStaticText.h
|
||||||
gui/RecentIsoList.h
|
gui/RecentIsoList.h
|
||||||
gui/Saveslots.h
|
gui/Saveslots.h
|
||||||
|
gui/ScopedPtrMT.h
|
||||||
gui/SysThreads.h
|
gui/SysThreads.h
|
||||||
gui/ThreadingDialogs.h
|
gui/ThreadingDialogs.h
|
||||||
gui/ThreadingDialogs.cpp
|
gui/ThreadingDialogs.cpp
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
#include "GS/GSExtra.h"
|
#include "GS/GSExtra.h"
|
||||||
#include "PerformanceMetrics.h"
|
#include "PerformanceMetrics.h"
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
|
|
||||||
#define ENABLE_DRAW_STATS 0
|
#define ENABLE_DRAW_STATS 0
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#define PINE_DEFAULT_SLOT 28011
|
#define PINE_DEFAULT_SLOT 28011
|
||||||
#define PINE_EMULATOR_NAME "pcsx2"
|
#define PINE_EMULATOR_NAME "pcsx2"
|
||||||
|
|
||||||
#include "common/PersistentThread.h"
|
#include "gui/PersistentThread.h"
|
||||||
#include "gui/SysThreads.h"
|
#include "gui/SysThreads.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
|
|
||||||
#include "common/SafeArray.h"
|
#include "common/SafeArray.h"
|
||||||
#include "common/EventSource.h"
|
#include "common/EventSource.h"
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
|
|
||||||
#include "gui/wxGuiTools.h"
|
#include "gui/wxGuiTools.h"
|
||||||
|
#include "gui/PersistentThread.h"
|
||||||
#include "gui/pxRadioPanel.h"
|
#include "gui/pxRadioPanel.h"
|
||||||
#include "gui/pxCheckBox.h"
|
#include "gui/pxCheckBox.h"
|
||||||
#include "gui/pxStaticText.h"
|
#include "gui/pxStaticText.h"
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
#include "DebugTools/DebugInterface.h"
|
#include "DebugTools/DebugInterface.h"
|
||||||
#include "DebugTools/DisassemblyManager.h"
|
#include "DebugTools/DisassemblyManager.h"
|
||||||
|
#include "gui/PersistentThread.h"
|
||||||
|
|
||||||
enum class SEARCHTYPE
|
enum class SEARCHTYPE
|
||||||
{
|
{
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,11 +16,137 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Threading.h"
|
#include "common/Threading.h"
|
||||||
#include "common/ScopedPtrMT.h"
|
|
||||||
#include "common/EventSource.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
|
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
|
// ThreadDeleteEvent
|
||||||
|
@ -58,6 +184,48 @@ namespace Threading
|
||||||
virtual void OnThreadCleanup() = 0;
|
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.
|
// pxThread - Helper class for the basics of starting/managing persistent threads.
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
||||||
|
@ -89,13 +257,9 @@ namespace Threading
|
||||||
{
|
{
|
||||||
DeclareNoncopyableObject(pxThread);
|
DeclareNoncopyableObject(pxThread);
|
||||||
|
|
||||||
friend void pxYield(int ms);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
wxString m_name; // diagnostic name for our thread.
|
wxString m_name; // diagnostic name for our thread.
|
||||||
pthread_t m_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
|
WorkSema m_sem_event; // general wait event that's needed by most threads
|
||||||
Semaphore m_sem_startup; // startup sync tool
|
Semaphore m_sem_startup; // startup sync tool
|
||||||
|
@ -118,7 +282,6 @@ namespace Threading
|
||||||
pxThread(const wxString& name = L"pxThread");
|
pxThread(const wxString& name = L"pxThread");
|
||||||
|
|
||||||
pthread_t GetId() const { return m_thread; }
|
pthread_t GetId() const { return m_thread; }
|
||||||
u64 GetCpuTime() const;
|
|
||||||
|
|
||||||
virtual void Start();
|
virtual void Start();
|
||||||
virtual void Cancel(bool isBlocking = true);
|
virtual void Cancel(bool isBlocking = true);
|
||||||
|
@ -189,8 +352,6 @@ namespace Threading
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Section of methods for internal use only.
|
// Section of methods for internal use only.
|
||||||
|
|
||||||
void _platform_specific_OnStartInThread();
|
|
||||||
void _platform_specific_OnCleanupInThread();
|
|
||||||
bool _basecancel();
|
bool _basecancel();
|
||||||
void _selfRunningTest(const wxChar* name) const;
|
void _selfRunningTest(const wxChar* name) const;
|
||||||
void _DoSetThreadName(const wxString& name);
|
void _DoSetThreadName(const wxString& name);
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Threading.h"
|
#include "common/Threading.h"
|
||||||
using Threading::ScopedLock;
|
using Threading::ScopedLock;
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------
|
|
@ -197,12 +197,9 @@ void SysCoreThread::ApplySettings(const Pcsx2Config& src)
|
||||||
// handle GS setting changes
|
// handle GS setting changes
|
||||||
if (GetMTGS().IsOpen() && gs_settings_changed)
|
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...");
|
Console.WriteLn("Applying GS settings...");
|
||||||
GetMTGS().ApplySettings();
|
GetMTGS().ApplySettings();
|
||||||
GetMTGS().SetVSync(EmuConfig.GetEffectiveVsyncMode());
|
GetMTGS().SetVSync(EmuConfig.GetEffectiveVsyncMode());
|
||||||
GetMTGS().WaitGS();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,6 +337,9 @@ void SysCoreThread::TearDownSystems(SystemsMask systemsToTearDown)
|
||||||
if (systemsToTearDown & System_SPU2) SPU2close();
|
if (systemsToTearDown & System_SPU2) SPU2close();
|
||||||
if (systemsToTearDown & System_MCD) FileMcd_EmuClose();
|
if (systemsToTearDown & System_MCD) FileMcd_EmuClose();
|
||||||
|
|
||||||
|
if (GetMTGS().IsOpen())
|
||||||
|
GetMTGS().WaitGS();
|
||||||
|
|
||||||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle());
|
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +348,12 @@ void SysCoreThread::OnResumeInThread(SystemsMask systemsToReinstate)
|
||||||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread());
|
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread());
|
||||||
PerformanceMetrics::Reset();
|
PerformanceMetrics::Reset();
|
||||||
|
|
||||||
GetMTGS().WaitForOpen();
|
// 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_DEV9) DEV9open();
|
||||||
if (systemsToReinstate & System_USB) USBopen(g_gs_window_info);
|
if (systemsToReinstate & System_USB) USBopen(g_gs_window_info);
|
||||||
if (systemsToReinstate & System_FW) FWopen();
|
if (systemsToReinstate & System_FW) FWopen();
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
|
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
#include "common/emitter/tools.h"
|
#include "common/emitter/tools.h"
|
||||||
|
#include "PersistentThread.h"
|
||||||
#include "PINE.h"
|
#include "PINE.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/PersistentThread.h"
|
#include "gui/PersistentThread.h"
|
||||||
#include "gui/pxEvents.h"
|
#include "gui/pxEvents.h"
|
||||||
|
|
||||||
#include <wx/timer.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
|
// 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.
|
// but it *will* be less work in the long run.
|
||||||
//
|
//
|
||||||
class SysExecEvent : public ICloneable
|
class SysExecEvent
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
SynchronousActionState* m_sync;
|
SynchronousActionState* m_sync;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~SysExecEvent() = default;
|
virtual ~SysExecEvent() = default;
|
||||||
SysExecEvent* Clone() const { return new SysExecEvent( *this ); }
|
virtual SysExecEvent* Clone() const { return new SysExecEvent( *this ); }
|
||||||
|
|
||||||
SysExecEvent( SynchronousActionState* sync=NULL )
|
SysExecEvent( SynchronousActionState* sync=NULL )
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "common/General.h"
|
#include "common/General.h"
|
||||||
#include "common/Dependencies.h"
|
#include "common/Dependencies.h"
|
||||||
#include "common/Threading.h"
|
#include "common/Threading.h"
|
||||||
|
#include "gui/PersistentThread.h"
|
||||||
|
|
||||||
wxDECLARE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
wxDECLARE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
||||||
wxDECLARE_EVENT(pxEvt_DeleteObject, wxCommandEvent);
|
wxDECLARE_EVENT(pxEvt_DeleteObject, wxCommandEvent);
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
#include "PrecompiledHeader.h"
|
#include "PrecompiledHeader.h"
|
||||||
#include "gui/wxAppWithHelpers.h"
|
#include "gui/wxAppWithHelpers.h"
|
||||||
#include "common/ThreadingInternal.h"
|
#include "gui/PersistentThread.h"
|
||||||
#include "common/PersistentThread.h"
|
|
||||||
|
|
||||||
wxDEFINE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
wxDEFINE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent);
|
||||||
wxDEFINE_EVENT(pxEvt_DeleteObject, 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
|
// (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.
|
// of the wx-custom runtime type information. So I made my own.
|
||||||
//
|
//
|
||||||
class BaseDeletableObject : public virtual IDeletableObject
|
class BaseDeletableObject
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::atomic<bool> m_IsBeingDeleted;
|
std::atomic<bool> m_IsBeingDeleted;
|
||||||
|
@ -58,8 +58,8 @@ public:
|
||||||
BaseDeletableObject();
|
BaseDeletableObject();
|
||||||
virtual ~BaseDeletableObject();
|
virtual ~BaseDeletableObject();
|
||||||
|
|
||||||
void DeleteSelf();
|
virtual void DeleteSelf();
|
||||||
bool IsBeingDeleted() { return !!m_IsBeingDeleted; }
|
virtual bool IsBeingDeleted() { return !!m_IsBeingDeleted; }
|
||||||
|
|
||||||
// Returns FALSE if the object is already marked for deletion, or TRUE if the app
|
// 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
|
// 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\DriveList.cpp" />
|
||||||
<ClCompile Include="gui\IniInterface.cpp" />
|
<ClCompile Include="gui\IniInterface.cpp" />
|
||||||
<ClCompile Include="gui\Panels\MemoryCardListView.cpp" />
|
<ClCompile Include="gui\Panels\MemoryCardListView.cpp" />
|
||||||
|
<ClCompile Include="gui\PersistentThread.cpp" />
|
||||||
<ClCompile Include="gui\pxCheckBox.cpp" />
|
<ClCompile Include="gui\pxCheckBox.cpp" />
|
||||||
<ClCompile Include="gui\pxRadioPanel.cpp" />
|
<ClCompile Include="gui\pxRadioPanel.cpp" />
|
||||||
<ClCompile Include="gui\pxStaticText.cpp" />
|
<ClCompile Include="gui\pxStaticText.cpp" />
|
||||||
|
@ -802,6 +803,7 @@
|
||||||
<ClInclude Include="gui\i18n.h" />
|
<ClInclude Include="gui\i18n.h" />
|
||||||
<ClInclude Include="gui\DriveList.h" />
|
<ClInclude Include="gui\DriveList.h" />
|
||||||
<ClInclude Include="gui\IniInterface.h" />
|
<ClInclude Include="gui\IniInterface.h" />
|
||||||
|
<ClInclude Include="gui\PersistentThread.h" />
|
||||||
<ClInclude Include="gui\pxCheckBox.h" />
|
<ClInclude Include="gui\pxCheckBox.h" />
|
||||||
<ClInclude Include="gui\pxEvents.h" />
|
<ClInclude Include="gui\pxEvents.h" />
|
||||||
<ClInclude Include="gui\pxRadioPanel.h" />
|
<ClInclude Include="gui\pxRadioPanel.h" />
|
||||||
|
@ -816,6 +818,7 @@
|
||||||
<ClInclude Include="gui\Debugger\DebuggerLists.h" />
|
<ClInclude Include="gui\Debugger\DebuggerLists.h" />
|
||||||
<ClInclude Include="gui\Debugger\DisassemblyDialog.h" />
|
<ClInclude Include="gui\Debugger\DisassemblyDialog.h" />
|
||||||
<ClInclude Include="gui\Panels\MemoryCardPanels.h" />
|
<ClInclude Include="gui\Panels\MemoryCardPanels.h" />
|
||||||
|
<ClInclude Include="gui\ScopedPtrMT.h" />
|
||||||
<ClInclude Include="gui\ThreadingDialogs.h" />
|
<ClInclude Include="gui\ThreadingDialogs.h" />
|
||||||
<ClInclude Include="gui\wxAppWithHelpers.h" />
|
<ClInclude Include="gui\wxAppWithHelpers.h" />
|
||||||
<ClInclude Include="gui\wxSettingsInterface.h" />
|
<ClInclude Include="gui\wxSettingsInterface.h" />
|
||||||
|
@ -1224,4 +1227,4 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets" />
|
<ImportGroup Label="ExtensionTargets" />
|
||||||
</Project>
|
</Project>
|
|
@ -1769,6 +1769,7 @@
|
||||||
<ClCompile Include="GS\Renderers\DX12\GSDevice12.cpp">
|
<ClCompile Include="GS\Renderers\DX12\GSDevice12.cpp">
|
||||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="gui\PersistentThread.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Patch.h">
|
<ClInclude Include="Patch.h">
|
||||||
|
@ -2945,6 +2946,8 @@
|
||||||
<ClInclude Include="GS\Renderers\DX12\GSTexture12.h">
|
<ClInclude Include="GS\Renderers\DX12\GSTexture12.h">
|
||||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="gui\ScopedPtrMT.h" />
|
||||||
|
<ClInclude Include="gui\PersistentThread.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="windows\wxResources.rc">
|
<ResourceCompile Include="windows\wxResources.rc">
|
||||||
|
@ -3107,4 +3110,4 @@
|
||||||
<Filter>AppHost\Resources</Filter>
|
<Filter>AppHost\Resources</Filter>
|
||||||
</Manifest>
|
</Manifest>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue