|
@@ -21,7 +21,9 @@
|
|
|
//
|
|
//
|
|
|
|
|
|
|
|
#include "Precompiled.h"
|
|
#include "Precompiled.h"
|
|
|
|
|
+#include "CoreEvents.h"
|
|
|
#include "ProcessUtils.h"
|
|
#include "ProcessUtils.h"
|
|
|
|
|
+#include "Profiler.h"
|
|
|
#include "Thread.h"
|
|
#include "Thread.h"
|
|
|
#include "Timer.h"
|
|
#include "Timer.h"
|
|
|
#include "WorkQueue.h"
|
|
#include "WorkQueue.h"
|
|
@@ -29,6 +31,8 @@
|
|
|
namespace Urho3D
|
|
namespace Urho3D
|
|
|
{
|
|
{
|
|
|
|
|
|
|
|
|
|
+const unsigned MAX_NONTHREADED_WORK_USEC = 1000;
|
|
|
|
|
+
|
|
|
/// Worker thread managed by the work queue.
|
|
/// Worker thread managed by the work queue.
|
|
|
class WorkerThread : public Thread, public RefCounted
|
|
class WorkerThread : public Thread, public RefCounted
|
|
|
{
|
|
{
|
|
@@ -62,11 +66,11 @@ OBJECTTYPESTATIC(WorkQueue);
|
|
|
|
|
|
|
|
WorkQueue::WorkQueue(Context* context) :
|
|
WorkQueue::WorkQueue(Context* context) :
|
|
|
Object(context),
|
|
Object(context),
|
|
|
- numActive_(0),
|
|
|
|
|
shutDown_(false),
|
|
shutDown_(false),
|
|
|
pausing_(false),
|
|
pausing_(false),
|
|
|
paused_(false)
|
|
paused_(false)
|
|
|
{
|
|
{
|
|
|
|
|
+ SubscribeToEvent(E_BEGINFRAME, HANDLER(WorkQueue, HandleBeginFrame));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
WorkQueue::~WorkQueue()
|
|
WorkQueue::~WorkQueue()
|
|
@@ -99,23 +103,36 @@ void WorkQueue::CreateThreads(unsigned numThreads)
|
|
|
|
|
|
|
|
void WorkQueue::AddWorkItem(const WorkItem& item)
|
|
void WorkQueue::AddWorkItem(const WorkItem& item)
|
|
|
{
|
|
{
|
|
|
- if (threads_.Size())
|
|
|
|
|
|
|
+ // Push to the main thread list to keep item alive
|
|
|
|
|
+ // Clear completed flag in case item is reused
|
|
|
|
|
+ workItems_.Push(item);
|
|
|
|
|
+ WorkItem* itemPtr = &workItems_.Back();
|
|
|
|
|
+ itemPtr->completed_ = false;
|
|
|
|
|
+
|
|
|
|
|
+ // Make sure worker threads' list is safe to modify
|
|
|
|
|
+ if (threads_.Size() && !paused_)
|
|
|
|
|
+ queueMutex_.Acquire();
|
|
|
|
|
+
|
|
|
|
|
+ // Find position for new item
|
|
|
|
|
+ if (queue_.Empty())
|
|
|
|
|
+ queue_.Push(itemPtr);
|
|
|
|
|
+ else
|
|
|
{
|
|
{
|
|
|
- if (paused_)
|
|
|
|
|
- {
|
|
|
|
|
- queue_.Push(item);
|
|
|
|
|
- queueMutex_.Release();
|
|
|
|
|
- paused_ = false;
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
|
|
+ for (List<WorkItem*>::Iterator i = queue_.Begin(); i != queue_.End(); ++i)
|
|
|
{
|
|
{
|
|
|
- queueMutex_.Acquire();
|
|
|
|
|
- queue_.Push(item);
|
|
|
|
|
- queueMutex_.Release();
|
|
|
|
|
|
|
+ if ((*i)->priority_ <= itemPtr->priority_)
|
|
|
|
|
+ {
|
|
|
|
|
+ queue_.Insert(i, itemPtr);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
- item.workFunction_(&item, 0);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (threads_.Size())
|
|
|
|
|
+ {
|
|
|
|
|
+ queueMutex_.Release();
|
|
|
|
|
+ paused_ = false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void WorkQueue::Pause()
|
|
void WorkQueue::Pause()
|
|
@@ -141,43 +158,64 @@ void WorkQueue::Resume()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-void WorkQueue::Complete()
|
|
|
|
|
|
|
+void WorkQueue::Complete(unsigned priority)
|
|
|
{
|
|
{
|
|
|
if (threads_.Size())
|
|
if (threads_.Size())
|
|
|
{
|
|
{
|
|
|
Resume();
|
|
Resume();
|
|
|
|
|
|
|
|
- // Take work items in the main thread until queue empty
|
|
|
|
|
|
|
+ // Take work items also in the main thread until queue empty or no high-priority items anymore
|
|
|
while (!queue_.Empty())
|
|
while (!queue_.Empty())
|
|
|
{
|
|
{
|
|
|
queueMutex_.Acquire();
|
|
queueMutex_.Acquire();
|
|
|
- if (!queue_.Empty())
|
|
|
|
|
|
|
+ if (!queue_.Empty() && queue_.Front()->priority_ >= priority)
|
|
|
{
|
|
{
|
|
|
- WorkItem item = queue_.Front();
|
|
|
|
|
|
|
+ WorkItem* item = queue_.Front();
|
|
|
queue_.PopFront();
|
|
queue_.PopFront();
|
|
|
queueMutex_.Release();
|
|
queueMutex_.Release();
|
|
|
- item.workFunction_(&item, 0);
|
|
|
|
|
|
|
+ item->workFunction_(item, 0);
|
|
|
|
|
+ item->completed_ = true;
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
|
|
+ {
|
|
|
queueMutex_.Release();
|
|
queueMutex_.Release();
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Wait for all work to finish
|
|
|
|
|
- while (!IsCompleted())
|
|
|
|
|
|
|
+ // Wait for threaded work to complete
|
|
|
|
|
+ while (!IsCompleted(priority))
|
|
|
{
|
|
{
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Pause worker threads by leaving the mutex locked
|
|
|
|
|
- Pause();
|
|
|
|
|
|
|
+ // If no work at all remaining, pause worker threads by leaving the mutex locked
|
|
|
|
|
+ if (queue_.Empty())
|
|
|
|
|
+ Pause();
|
|
|
}
|
|
}
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // No worker threads: ensure all high-priority items are completed in the main thread
|
|
|
|
|
+ while (!queue_.Empty() && queue_.Front()->priority_ >= priority)
|
|
|
|
|
+ {
|
|
|
|
|
+ WorkItem* item = queue_.Front();
|
|
|
|
|
+ queue_.PopFront();
|
|
|
|
|
+ item->workFunction_(item, 0);
|
|
|
|
|
+ item->completed_ = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ PurgeCompleted();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-bool WorkQueue::IsCompleted() const
|
|
|
|
|
|
|
+bool WorkQueue::IsCompleted(unsigned priority) const
|
|
|
{
|
|
{
|
|
|
- if (threads_.Size())
|
|
|
|
|
- return !numActive_ && queue_.Empty();
|
|
|
|
|
- else
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ for (List<WorkItem>::ConstIterator i = workItems_.Begin(); i != workItems_.End(); ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (i->priority_ >= priority && !i->completed_)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void WorkQueue::ProcessItems(unsigned threadIndex)
|
|
void WorkQueue::ProcessItems(unsigned threadIndex)
|
|
@@ -196,23 +234,18 @@ void WorkQueue::ProcessItems(unsigned threadIndex)
|
|
|
queueMutex_.Acquire();
|
|
queueMutex_.Acquire();
|
|
|
if (!queue_.Empty())
|
|
if (!queue_.Empty())
|
|
|
{
|
|
{
|
|
|
- if (!wasActive)
|
|
|
|
|
- {
|
|
|
|
|
- ++numActive_;
|
|
|
|
|
- wasActive = true;
|
|
|
|
|
- }
|
|
|
|
|
- WorkItem item = queue_.Front();
|
|
|
|
|
|
|
+ wasActive = true;
|
|
|
|
|
+
|
|
|
|
|
+ WorkItem* item = queue_.Front();
|
|
|
queue_.PopFront();
|
|
queue_.PopFront();
|
|
|
queueMutex_.Release();
|
|
queueMutex_.Release();
|
|
|
- item.workFunction_(&item, threadIndex);
|
|
|
|
|
|
|
+ item->workFunction_(item, threadIndex);
|
|
|
|
|
+ item->completed_ = true;
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- if (wasActive)
|
|
|
|
|
- {
|
|
|
|
|
- --numActive_;
|
|
|
|
|
- wasActive = false;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ wasActive = false;
|
|
|
|
|
+
|
|
|
queueMutex_.Release();
|
|
queueMutex_.Release();
|
|
|
Time::Sleep(0);
|
|
Time::Sleep(0);
|
|
|
}
|
|
}
|
|
@@ -220,4 +253,49 @@ void WorkQueue::ProcessItems(unsigned threadIndex)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void WorkQueue::PurgeCompleted()
|
|
|
|
|
+{
|
|
|
|
|
+ using namespace WorkItemCompleted;
|
|
|
|
|
+
|
|
|
|
|
+ VariantMap eventData;
|
|
|
|
|
+
|
|
|
|
|
+ // Purge completed work items and send completion events.
|
|
|
|
|
+ for (List<WorkItem>::Iterator i = workItems_.Begin(); i != workItems_.End();)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (i->completed_)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (i->sendEvent_)
|
|
|
|
|
+ {
|
|
|
|
|
+ eventData[P_ITEM] = (void*)(&(*i));
|
|
|
|
|
+ SendEvent(E_WORKITEMCOMPLETED, eventData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = workItems_.Erase(i);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ ++i;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkQueue::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
|
|
|
|
|
+{
|
|
|
|
|
+ // If no worker threads, complete low-priority work here
|
|
|
|
|
+ if (threads_.Empty() && !queue_.Empty())
|
|
|
|
|
+ {
|
|
|
|
|
+ PROFILE(CompleteWorkNonthreaded);
|
|
|
|
|
+
|
|
|
|
|
+ HiresTimer timer;
|
|
|
|
|
+
|
|
|
|
|
+ while (!queue_.Empty() && timer.GetUSec(false) < MAX_NONTHREADED_WORK_USEC)
|
|
|
|
|
+ {
|
|
|
|
|
+ WorkItem* item = queue_.Front();
|
|
|
|
|
+ queue_.PopFront();
|
|
|
|
|
+ item->workFunction_(item, 0);
|
|
|
|
|
+ item->completed_ = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ PurgeCompleted();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
}
|
|
}
|