219 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * vim: set ts=4 :
 | |
|  * =============================================================================
 | |
|  * SourceMod
 | |
|  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
 | |
|  * =============================================================================
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify it under
 | |
|  * the terms of the GNU General Public License, version 3.0, as published by the
 | |
|  * Free Software Foundation.
 | |
|  * 
 | |
|  * This program 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
 | |
|  * this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * As a special exception, AlliedModders LLC gives you permission to link the
 | |
|  * code of this program (as well as its derivative works) to "Half-Life 2," the
 | |
|  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
 | |
|  * by the Valve Corporation.  You must obey the GNU General Public License in
 | |
|  * all respects for all other code used.  Additionally, AlliedModders LLC grants
 | |
|  * this exception to all derivative works.  AlliedModders LLC defines further
 | |
|  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
 | |
|  * or <http://www.sourcemod.net/license.php>.
 | |
|  *
 | |
|  * Version: $Id$
 | |
|  */
 | |
| 
 | |
| #include "ThreadWorker.h"
 | |
| 
 | |
| ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks) : BaseWorker(hooks),
 | |
| 	m_Threader(NULL),
 | |
| 	me(NULL),
 | |
| 	m_think_time(DEFAULT_THINK_TIME_MS)
 | |
| {
 | |
| 	m_state = Worker_Invalid;
 | |
| }
 | |
| 
 | |
| ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks, IThreader *pThreader, unsigned int thinktime) : 
 | |
| 	BaseWorker(hooks),
 | |
| 	m_Threader(pThreader),
 | |
| 	me(NULL),
 | |
| 	m_think_time(thinktime)
 | |
| {
 | |
| 	m_state = m_Threader ? Worker_Stopped : Worker_Invalid;
 | |
| }
 | |
| 
 | |
| ThreadWorker::~ThreadWorker()
 | |
| {
 | |
| 	if (m_state != Worker_Stopped || m_state != Worker_Invalid)
 | |
| 		Stop(true);
 | |
| 	
 | |
| 	if (m_ThreadQueue.size())
 | |
| 		Flush(true);
 | |
| }
 | |
| 
 | |
| void ThreadWorker::OnTerminate(IThreadHandle *pHandle, bool cancel)
 | |
| {
 | |
| 	//we don't particularly care
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| void ThreadWorker::RunThread(IThreadHandle *pHandle)
 | |
| {
 | |
| 	if (m_pHooks)
 | |
| 		m_pHooks->OnWorkerStart(this);
 | |
| 
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 
 | |
| 	while (true)
 | |
| 	{
 | |
| 		if (m_state == Worker_Paused)
 | |
| 		{
 | |
| 			// Wait until we're told to wake up.
 | |
| 			monitor_.Wait();
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (m_state == Worker_Stopped)
 | |
| 		{
 | |
| 			// We've been told to stop entirely. If we've also been told to
 | |
| 			// flush the queue, do that now.
 | |
| 			while (!m_ThreadQueue.empty())
 | |
| 			{
 | |
| 				// Release the lock since PopThreadFromQueue() will re-acquire it. The
 | |
| 				// main thread is blocking anyway.
 | |
| 				ke::AutoUnlock unlock(&monitor_);
 | |
| 				RunFrame();
 | |
| 			}
 | |
| 			assert(m_state == Worker_Stopped);
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		assert(m_state == Worker_Running);
 | |
| 
 | |
| 		// Process one frame.
 | |
| 		WorkerState oldstate = m_state;
 | |
| 		{
 | |
| 			ke::AutoUnlock unlock(&monitor_);
 | |
| 			RunFrame();
 | |
| 		}
 | |
| 
 | |
| 		// If the state changed, loop back and process the new state.
 | |
| 		if (m_state != oldstate)
 | |
| 			continue;
 | |
| 		
 | |
| 		// If the thread queue is now empty, wait for a signal. Otherwise, if
 | |
| 		// we're on a delay, wait for either a notification or a timeout to
 | |
| 		// process the next item. If the queue has items and we don't have a
 | |
| 		// delay, then we just loop around and keep processing.
 | |
| 		if (m_ThreadQueue.empty())
 | |
| 			monitor_.Wait();
 | |
| 		else if (m_think_time)
 | |
| 			monitor_.Wait(m_think_time);
 | |
| 	}
 | |
| 
 | |
| 	{
 | |
| 		ke::AutoUnlock unlock(&monitor_);
 | |
| 		if (m_pHooks)
 | |
| 			m_pHooks->OnWorkerStop(this);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| SWThreadHandle *ThreadWorker::PopThreadFromQueue()
 | |
| {
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 	if (m_state <= Worker_Stopped)
 | |
| 		return NULL;
 | |
| 
 | |
| 	return BaseWorker::PopThreadFromQueue();
 | |
| }
 | |
| 
 | |
| void ThreadWorker::AddThreadToQueue(SWThreadHandle *pHandle)
 | |
| {
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 	if (m_state <= Worker_Stopped)
 | |
| 		return;
 | |
| 
 | |
| 	BaseWorker::AddThreadToQueue(pHandle);
 | |
| 	monitor_.Notify();
 | |
| }
 | |
| 
 | |
| WorkerState ThreadWorker::GetStatus(unsigned int *threads)
 | |
| {
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 	return BaseWorker::GetStatus(threads);
 | |
| }
 | |
| 
 | |
| void ThreadWorker::SetThinkTimePerFrame(unsigned int thinktime)
 | |
| {
 | |
| 	m_think_time = thinktime;
 | |
| }
 | |
| 
 | |
| bool ThreadWorker::Start()
 | |
| {
 | |
| 	if (m_state == Worker_Invalid && m_Threader == NULL)
 | |
| 		return false;
 | |
| 
 | |
| 	if (m_state != Worker_Stopped)
 | |
| 		return false;
 | |
| 
 | |
| 	m_state = Worker_Running;
 | |
| 
 | |
| 	ThreadParams pt;
 | |
| 	pt.flags = Thread_Default;
 | |
| 	pt.prio = ThreadPrio_Normal;
 | |
| 	me = m_Threader->MakeThread(this, &pt);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool ThreadWorker::Stop(bool flush_cancel)
 | |
| {
 | |
| 	// Change the state to signal a stop, and then trigger a notify.
 | |
| 	{
 | |
| 		ke::AutoLock lock(&monitor_);
 | |
| 		if (m_state == Worker_Invalid || m_state == Worker_Stopped)
 | |
| 			return false;
 | |
| 
 | |
| 		m_state = Worker_Stopped;
 | |
| 		m_FlushType = flush_cancel;
 | |
| 		monitor_.Notify();
 | |
| 	}
 | |
| 
 | |
| 	me->WaitForThread();
 | |
| 	//destroy it
 | |
| 	me->DestroyThis();
 | |
| 	//flush all remaining events
 | |
| 	Flush(true);
 | |
| 
 | |
| 	me = NULL;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool ThreadWorker::Pause()
 | |
| {
 | |
| 	if (m_state != Worker_Running)
 | |
| 		return false;
 | |
| 
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 	m_state = Worker_Paused;
 | |
| 	monitor_.Notify();
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool ThreadWorker::Unpause()
 | |
| {
 | |
| 	if (m_state != Worker_Paused)
 | |
| 		return false;
 | |
| 
 | |
| 	ke::AutoLock lock(&monitor_);
 | |
| 	m_state = Worker_Running;
 | |
| 	monitor_.Notify();
 | |
| 	return true;
 | |
| }
 |