mirror of
https://github.com/libretro/ppsspp.git
synced 2024-11-29 19:30:48 +00:00
Merge pull request #586 from unknownbrackets/threadqueue
Implement a proper thread ready queue
This commit is contained in:
commit
5dd15d7e25
@ -182,10 +182,8 @@ void hleEnterVblank(u64 userdata, int cyclesLate) {
|
||||
}
|
||||
vblankWaitingThreads.clear();
|
||||
|
||||
// We want to "interrupt" even if there are no installed vblank handlers.
|
||||
__KernelSwitchOffThread("vblank");
|
||||
// Trigger VBlank interrupt handlers.
|
||||
__TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED, PSP_VBLANK_INTR);
|
||||
// Trigger VBlank interrupt handlers - and resched even if there's none registered.
|
||||
__TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR);
|
||||
|
||||
CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount+1);
|
||||
|
||||
|
@ -386,6 +386,11 @@ void __TriggerRunInterrupts(int type)
|
||||
{
|
||||
if ((type & PSP_INTR_HLE) != 0)
|
||||
hleRunInterrupts();
|
||||
else if ((type & PSP_INTR_ALWAYS_RESCHED) != 0)
|
||||
{
|
||||
if (!__RunOnePendingInterrupt())
|
||||
__KernelSwitchOffThread("interrupt");
|
||||
}
|
||||
else
|
||||
__RunOnePendingInterrupt();
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ enum PSPInterruptTriggerType {
|
||||
PSP_INTR_HLE = 0x1,
|
||||
// Only trigger (as above) if interrupts are not suspended.
|
||||
PSP_INTR_ONLY_IF_ENABLED = 0x2,
|
||||
// Always reschedule, even if there's no handler registered.
|
||||
// TODO: Maybe this should just always do this? Not sure.
|
||||
PSP_INTR_ALWAYS_RESCHED = 0x4,
|
||||
};
|
||||
|
||||
struct PendingInterrupt {
|
||||
|
@ -397,11 +397,11 @@ public:
|
||||
ThreadWaitInfo getWaitInfo();
|
||||
|
||||
// Utils
|
||||
bool isRunning() const { return (nt.status & THREADSTATUS_RUNNING) != 0; }
|
||||
bool isStopped() const { return (nt.status & THREADSTATUS_DORMANT) != 0; }
|
||||
bool isReady() const { return (nt.status & THREADSTATUS_READY) != 0; }
|
||||
bool isWaiting() const { return (nt.status & THREADSTATUS_WAIT) != 0; }
|
||||
bool isSuspended() const { return (nt.status & THREADSTATUS_SUSPEND) != 0; }
|
||||
inline bool isRunning() const { return (nt.status & THREADSTATUS_RUNNING) != 0; }
|
||||
inline bool isStopped() const { return (nt.status & THREADSTATUS_DORMANT) != 0; }
|
||||
inline bool isReady() const { return (nt.status & THREADSTATUS_READY) != 0; }
|
||||
inline bool isWaiting() const { return (nt.status & THREADSTATUS_WAIT) != 0; }
|
||||
inline bool isSuspended() const { return (nt.status & THREADSTATUS_SUSPEND) != 0; }
|
||||
|
||||
virtual void DoState(PointerWrap &p)
|
||||
{
|
||||
@ -470,9 +470,14 @@ u32 idleThreadHackAddr;
|
||||
u32 threadReturnHackAddr;
|
||||
u32 cbReturnHackAddr;
|
||||
u32 intReturnHackAddr;
|
||||
std::vector<SceUID> threadqueue;
|
||||
std::vector<ThreadCallback> threadEndListeners;
|
||||
|
||||
typedef std::vector<SceUID> ThreadList;
|
||||
// Lists all thread ids that aren't deleted/etc.
|
||||
ThreadList threadqueue;
|
||||
// Lists only ready thread ids.
|
||||
std::map<u32, ThreadList> threadReadyQueue;
|
||||
|
||||
SceUID threadIdleID[2];
|
||||
|
||||
int eventScheduledWakeup;
|
||||
@ -625,6 +630,29 @@ void __KernelThreadingDoState(PointerWrap &p)
|
||||
p.Do(dispatchEnabled);
|
||||
p.Do(curModule);
|
||||
|
||||
int n = (int) threadReadyQueue.size();
|
||||
p.Do(n);
|
||||
if (p.mode == p.MODE_READ)
|
||||
{
|
||||
threadReadyQueue.clear();
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
u32 prio;
|
||||
p.Do(prio);
|
||||
ThreadList threads;
|
||||
p.Do(threads, dv);
|
||||
threadReadyQueue[prio] = threads;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto it = threadReadyQueue.begin(), end = threadReadyQueue.end(); it != end; ++it)
|
||||
{
|
||||
p.Do(it->first);
|
||||
p.Do(it->second, dv);
|
||||
}
|
||||
}
|
||||
|
||||
p.Do(eventScheduledWakeup);
|
||||
CoreTiming::RestoreRegisterEvent(eventScheduledWakeup, "ScheduledWakeup", &hleScheduledWakeup);
|
||||
p.Do(eventThreadEndTimeout);
|
||||
@ -668,6 +696,43 @@ void __KernelFireThreadEnd(SceUID threadID)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use __KernelChangeThreadState instead? It has other affects...
|
||||
void __KernelChangeReadyState(Thread *thread, SceUID threadID, bool ready, bool atStart = false)
|
||||
{
|
||||
int prio = thread->nt.currentPriority;
|
||||
|
||||
if (thread->isReady())
|
||||
{
|
||||
if (!ready)
|
||||
threadReadyQueue[prio].erase(std::remove(threadReadyQueue[prio].begin(), threadReadyQueue[prio].end(), threadID), threadReadyQueue[prio].end());
|
||||
}
|
||||
else if (ready)
|
||||
{
|
||||
if (atStart)
|
||||
{
|
||||
size_t oldSize = threadReadyQueue[prio].size();
|
||||
threadReadyQueue[prio].resize(oldSize + 1);
|
||||
if (oldSize > 0)
|
||||
memmove(&threadReadyQueue[prio][1], &threadReadyQueue[prio][0], oldSize * sizeof(SceUID));
|
||||
threadReadyQueue[prio][0] = threadID;
|
||||
}
|
||||
else
|
||||
threadReadyQueue[prio].push_back(threadID);
|
||||
}
|
||||
|
||||
thread->nt.status = THREADSTATUS_READY;
|
||||
}
|
||||
|
||||
void __KernelChangeReadyState(SceUID threadID, bool ready)
|
||||
{
|
||||
u32 error;
|
||||
Thread *thread = kernelObjects.Get<Thread>(threadID, error);
|
||||
if (thread)
|
||||
__KernelChangeReadyState(thread, threadID, ready);
|
||||
else
|
||||
WARN_LOG(HLE, "Trying to change the ready state of an unknown thread?");
|
||||
}
|
||||
|
||||
void __KernelStartIdleThreads()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
@ -677,7 +742,7 @@ void __KernelStartIdleThreads()
|
||||
t->nt.gpreg = __KernelGetModuleGP(curModule);
|
||||
t->context.r[MIPS_REG_GP] = t->nt.gpreg;
|
||||
//t->context.pc += 4; // ADJUSTPC
|
||||
t->nt.status = THREADSTATUS_READY;
|
||||
__KernelChangeReadyState(t, threadIdleID[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -692,7 +757,7 @@ bool __KernelSwitchOffThread(const char *reason)
|
||||
{
|
||||
Thread *current = __GetCurrentThread();
|
||||
if (current && current->isRunning())
|
||||
current->nt.status = (current->nt.status | THREADSTATUS_READY) & ~THREADSTATUS_RUNNING;
|
||||
__KernelChangeReadyState(current, threadID, true);
|
||||
|
||||
u32 error;
|
||||
// Idle 0 chosen entirely arbitrarily.
|
||||
@ -723,6 +788,7 @@ void __KernelIdle()
|
||||
Thread *t = kernelObjects.Get<Thread>(currentCallbackThreadID, error);
|
||||
if (t)
|
||||
{
|
||||
__KernelChangeReadyState(t, currentCallbackThreadID, false);
|
||||
t->nt.status = (t->nt.status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY;
|
||||
__KernelSwitchContext(t, "idle");
|
||||
}
|
||||
@ -744,6 +810,7 @@ void __KernelThreadingShutdown()
|
||||
{
|
||||
kernelMemory.Free(threadReturnHackAddr);
|
||||
threadqueue.clear();
|
||||
threadReadyQueue.clear();
|
||||
threadEndListeners.clear();
|
||||
mipsCalls.clear();
|
||||
threadReturnHackAddr = 0;
|
||||
@ -1101,15 +1168,11 @@ void __KernelCancelThreadEndTimeout(SceUID threadID)
|
||||
|
||||
void __KernelRemoveFromThreadQueue(SceUID threadID)
|
||||
{
|
||||
for (size_t i = 0; i < threadqueue.size(); i++)
|
||||
{
|
||||
if (threadqueue[i] == threadID)
|
||||
{
|
||||
DEBUG_LOG(HLE, "Deleted thread %08x (%i) from thread queue", threadID, threadID);
|
||||
threadqueue.erase(threadqueue.begin() + i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
int prio = __KernelGetThreadPrio(threadID);
|
||||
if (prio != 0)
|
||||
threadReadyQueue[prio].erase(std::remove(threadReadyQueue[prio].begin(), threadReadyQueue[prio].end(), threadID), threadReadyQueue[prio].end());
|
||||
|
||||
threadqueue.erase(std::remove(threadqueue.begin(), threadqueue.end(), threadID), threadqueue.end());
|
||||
}
|
||||
|
||||
u32 __KernelDeleteThread(SceUID threadID, int exitStatus, const char *reason, bool dontSwitch)
|
||||
@ -1139,46 +1202,30 @@ u32 __KernelDeleteThread(SceUID threadID, int exitStatus, const char *reason, bo
|
||||
}
|
||||
|
||||
Thread *__KernelNextThread() {
|
||||
// round-robin scheduler
|
||||
// seems to work ?
|
||||
// not accurate!
|
||||
int bestthread = -1;
|
||||
int prio = 0xffffff;
|
||||
|
||||
int next = 0;
|
||||
for (size_t i = 0; i < threadqueue.size(); i++)
|
||||
SceUID bestThread = -1;
|
||||
// This goes in priority order.
|
||||
for (auto it = threadReadyQueue.begin(), end = threadReadyQueue.end(); it != end; ++it)
|
||||
{
|
||||
if (currentThread == threadqueue[i])
|
||||
if (!it->second.empty())
|
||||
{
|
||||
next = (int)i;
|
||||
bestThread = it->second[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 error;
|
||||
for (size_t i = 0; i < threadqueue.size(); i++)
|
||||
{
|
||||
next = (next + 1) % threadqueue.size();
|
||||
|
||||
Thread *t = kernelObjects.Get<Thread>(threadqueue[next], error);
|
||||
if (t && t->nt.currentPriority < prio)
|
||||
{
|
||||
if (t->nt.status & THREADSTATUS_READY)
|
||||
{
|
||||
bestthread = next;
|
||||
prio = t->nt.currentPriority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestthread != -1)
|
||||
return kernelObjects.Get<Thread>(threadqueue[bestthread], error);
|
||||
if (bestThread != -1)
|
||||
return kernelObjects.Get<Thread>(bestThread, error);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __KernelReSchedule(const char *reason)
|
||||
{
|
||||
// TODO: Not sure if this is correct?
|
||||
if (__GetCurrentThread() && __GetCurrentThread()->isRunning())
|
||||
__KernelChangeReadyState(currentThread, true);
|
||||
|
||||
// cancel rescheduling when in interrupt or callback, otherwise everything will be fucked up
|
||||
if (__IsInInterrupt() || __KernelInCallback())
|
||||
{
|
||||
@ -1355,7 +1402,7 @@ void __KernelSetupRootThread(SceUID moduleID, int args, const char *argp, int pr
|
||||
__KernelResetThread(thread);
|
||||
|
||||
currentThread = id;
|
||||
thread->nt.status = THREADSTATUS_READY; // do not schedule
|
||||
__KernelChangeReadyState(thread, id, true); // do not schedule
|
||||
|
||||
strcpy(thread->nt.name, "root");
|
||||
|
||||
@ -1432,8 +1479,8 @@ int sceKernelStartThread(SceUID threadToStartID, u32 argSize, u32 argBlockPtr)
|
||||
threadToStartID,argSize,argBlockPtr);
|
||||
|
||||
__KernelResetThread(startThread);
|
||||
__KernelChangeReadyState(startThread, threadToStartID, true, true);
|
||||
|
||||
startThread->nt.status = THREADSTATUS_READY;
|
||||
u32 sp = startThread->context.r[MIPS_REG_SP];
|
||||
if (argBlockPtr && argSize > 0)
|
||||
{
|
||||
@ -1511,11 +1558,10 @@ void __KernelReturnFromThread()
|
||||
}
|
||||
|
||||
thread->nt.exitStatus = currentMIPS->r[2];
|
||||
__KernelChangeReadyState(thread, currentThread, false);
|
||||
thread->nt.status = THREADSTATUS_DORMANT;
|
||||
__KernelFireThreadEnd(thread->GetUID());
|
||||
|
||||
// TODO: Need to remove the thread from any ready queues.
|
||||
|
||||
__KernelTriggerWait(WAITTYPE_THREADEND, __KernelGetCurThread(), thread->nt.exitStatus, "thread returned", true);
|
||||
hleReSchedule("thread returned");
|
||||
|
||||
@ -1528,6 +1574,7 @@ void sceKernelExitThread()
|
||||
_dbg_assert_msg_(HLE, thread != NULL, "Exited from a NULL thread.");
|
||||
|
||||
ERROR_LOG(HLE,"sceKernelExitThread FAKED");
|
||||
__KernelChangeReadyState(thread, currentThread, false);
|
||||
thread->nt.status = THREADSTATUS_DORMANT;
|
||||
thread->nt.exitStatus = PARAM(0);
|
||||
__KernelFireThreadEnd(thread->GetUID());
|
||||
@ -1562,6 +1609,7 @@ void sceKernelExitDeleteThread()
|
||||
if (t)
|
||||
{
|
||||
INFO_LOG(HLE,"sceKernelExitDeleteThread()");
|
||||
__KernelChangeReadyState(t, threadHandle, false);
|
||||
t->nt.status = THREADSTATUS_DORMANT;
|
||||
t->nt.exitStatus = PARAM(0);
|
||||
error = __KernelDeleteThread(threadHandle, PARAM(0), "thread exited with delete", true);
|
||||
@ -1602,7 +1650,6 @@ int sceKernelDeleteThread(int threadHandle)
|
||||
{
|
||||
if (threadHandle != currentThread)
|
||||
{
|
||||
//TODO: remove from threadqueue!
|
||||
DEBUG_LOG(HLE,"sceKernelDeleteThread(%i)",threadHandle);
|
||||
|
||||
u32 error;
|
||||
@ -1627,7 +1674,6 @@ int sceKernelTerminateDeleteThread(int threadno)
|
||||
{
|
||||
if (threadno != currentThread)
|
||||
{
|
||||
//TODO: remove from threadqueue!
|
||||
INFO_LOG(HLE, "sceKernelTerminateDeleteThread(%i)", threadno);
|
||||
|
||||
u32 error;
|
||||
@ -1662,6 +1708,7 @@ int sceKernelTerminateThread(u32 threadID)
|
||||
if (t)
|
||||
{
|
||||
t->nt.exitStatus = SCE_KERNEL_ERROR_THREAD_TERMINATED;
|
||||
__KernelChangeReadyState(t, threadID, false);
|
||||
t->nt.status = THREADSTATUS_DORMANT;
|
||||
__KernelFireThreadEnd(threadID);
|
||||
// TODO: Should this really reschedule?
|
||||
@ -1728,7 +1775,15 @@ void sceKernelChangeThreadPriority()
|
||||
if (thread)
|
||||
{
|
||||
DEBUG_LOG(HLE,"sceKernelChangeThreadPriority(%i, %i)", id, PARAM(1));
|
||||
|
||||
int prio = thread->nt.currentPriority;
|
||||
threadReadyQueue[prio].erase(std::remove(threadReadyQueue[prio].begin(), threadReadyQueue[prio].end(), id), threadReadyQueue[prio].end());
|
||||
|
||||
thread->nt.currentPriority = PARAM(1);
|
||||
|
||||
if (thread->isReady())
|
||||
threadReadyQueue[thread->nt.currentPriority].push_back(id);
|
||||
|
||||
RETURN(0);
|
||||
}
|
||||
else
|
||||
@ -2102,6 +2157,7 @@ void ActionAfterMipsCall::run(MipsCall &call) {
|
||||
u32 error;
|
||||
Thread *thread = kernelObjects.Get<Thread>(threadID, error);
|
||||
if (thread) {
|
||||
__KernelChangeReadyState(thread, threadID, (status & THREADSTATUS_READY) != 0);
|
||||
thread->nt.status = status;
|
||||
thread->nt.waitType = waitType;
|
||||
thread->nt.waitID = waitID;
|
||||
@ -2174,7 +2230,7 @@ void Thread::resumeFromWait()
|
||||
this->nt.status &= ~THREADSTATUS_WAIT;
|
||||
// TODO: What if DORMANT or DEAD?
|
||||
if (!(this->nt.status & THREADSTATUS_WAITSUSPEND))
|
||||
this->nt.status = THREADSTATUS_READY;
|
||||
__KernelChangeReadyState(this, this->GetUID(), true);
|
||||
|
||||
// Non-waiting threads do not process callbacks.
|
||||
this->isProcessingCallbacks = false;
|
||||
@ -2241,17 +2297,27 @@ void __KernelSwitchContext(Thread *target, const char *reason)
|
||||
oldName = cur->GetName();
|
||||
|
||||
if (cur->isRunning())
|
||||
{
|
||||
__KernelChangeReadyState(cur, oldUID, false);
|
||||
cur->nt.status = (cur->nt.status | THREADSTATUS_READY) & ~THREADSTATUS_RUNNING;
|
||||
}
|
||||
}
|
||||
if (target && target->isRunning())
|
||||
cur->nt.status = (cur->nt.status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY;
|
||||
|
||||
currentThread = target->GetUID();
|
||||
if (target && target->isRunning())
|
||||
__KernelChangeReadyState(target, currentThread, true);
|
||||
|
||||
__KernelLoadContext(&target->context);
|
||||
DEBUG_LOG(HLE,"Context switched: %s -> %s (%s) (%i - pc: %08x -> %i - pc: %08x)",
|
||||
oldName, target->GetName(),
|
||||
reason,
|
||||
oldUID, oldPC, target->GetUID(), currentMIPS->pc);
|
||||
|
||||
bool fromIdle = oldUID == threadIdleID[0] || oldUID == threadIdleID[1];
|
||||
bool toIdle = currentThread == threadIdleID[0] || currentThread == threadIdleID[1];
|
||||
if (!(fromIdle && toIdle))
|
||||
{
|
||||
DEBUG_LOG(HLE,"Context switched: %s -> %s (%s) (%i - pc: %08x -> %i - pc: %08x)",
|
||||
oldName, target->GetName(),
|
||||
reason,
|
||||
oldUID, oldPC, currentThread, currentMIPS->pc);
|
||||
}
|
||||
|
||||
// No longer waiting.
|
||||
target->nt.waitType = WAITTYPE_NONE;
|
||||
@ -2272,6 +2338,7 @@ void __KernelChangeThreadState(Thread *thread, ThreadStatus newStatus) {
|
||||
// TODO: JPSCP has many conditions here, like removing wait timeout actions etc.
|
||||
// if (thread->nt.status == THREADSTATUS_WAIT && newStatus != THREADSTATUS_WAITSUSPEND) {
|
||||
|
||||
__KernelChangeReadyState(thread, thread->GetUID(), (newStatus & THREADSTATUS_READY) != 0);
|
||||
thread->nt.status = newStatus;
|
||||
|
||||
if (newStatus == THREADSTATUS_WAIT) {
|
||||
|
Loading…
Reference in New Issue
Block a user