core/logic/thread/ThreadWorker.cpp
author David Anderson <dvander@alliedmods.net>
Mon Aug 19 20:58:02 2013 -0700 (2013-08-19)
changeset 4025 542dde661d5c
parent 3704 dd325be8f8a1
permissions -rw-r--r--
Rewrite IThreader implementation around new synchronization primitives (bug 5862, r=fyren).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "ThreadWorker.h"
    33 
    34 ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks) : BaseWorker(hooks),
    35 	m_Threader(NULL),
    36 	me(NULL),
    37 	m_think_time(DEFAULT_THINK_TIME_MS)
    38 {
    39 	m_state = Worker_Invalid;
    40 }
    41 
    42 ThreadWorker::ThreadWorker(IThreadWorkerCallbacks *hooks, IThreader *pThreader, unsigned int thinktime) : 
    43 	BaseWorker(hooks),
    44 	m_Threader(pThreader),
    45 	me(NULL),
    46 	m_think_time(thinktime)
    47 {
    48 	m_state = m_Threader ? Worker_Stopped : Worker_Invalid;
    49 }
    50 
    51 ThreadWorker::~ThreadWorker()
    52 {
    53 	if (m_state != Worker_Stopped || m_state != Worker_Invalid)
    54 		Stop(true);
    55 	
    56 	if (m_ThreadQueue.size())
    57 		Flush(true);
    58 }
    59 
    60 void ThreadWorker::OnTerminate(IThreadHandle *pHandle, bool cancel)
    61 {
    62 	//we don't particularly care
    63 	return;
    64 }
    65 
    66 void ThreadWorker::RunThread(IThreadHandle *pHandle)
    67 {
    68 	if (m_pHooks)
    69 		m_pHooks->OnWorkerStart(this);
    70 
    71 	ke::AutoLock lock(&monitor_);
    72 
    73 	while (true)
    74 	{
    75 		if (m_state == Worker_Paused)
    76 		{
    77 			// Wait until we're told to wake up.
    78 			monitor_.Wait();
    79 			continue;
    80 		}
    81 
    82 		if (m_state == Worker_Stopped)
    83 		{
    84 			// We've been told to stop entirely. If we've also been told to
    85 			// flush the queue, do that now.
    86 			while (!m_ThreadQueue.empty())
    87 			{
    88 				// Release the lock since PopThreadFromQueue() will re-acquire it. The
    89 				// main thread is blocking anyway.
    90 				ke::AutoUnlock unlock(&monitor_);
    91 				RunFrame();
    92 			}
    93 			assert(m_state == Worker_Stopped);
    94 			return;
    95 		}
    96 
    97 		assert(m_state == Worker_Running);
    98 
    99 		// Process one frame.
   100 		WorkerState oldstate = m_state;
   101 		{
   102 			ke::AutoUnlock unlock(&monitor_);
   103 			RunFrame();
   104 		}
   105 
   106 		// If the state changed, loop back and process the new state.
   107 		if (m_state != oldstate)
   108 			continue;
   109 		
   110 		// If the thread queue is now empty, wait for a signal. Otherwise, if
   111 		// we're on a delay, wait for either a notification or a timeout to
   112 		// process the next item. If the queue has items and we don't have a
   113 		// delay, then we just loop around and keep processing.
   114 		if (m_ThreadQueue.empty())
   115 			monitor_.Wait();
   116 		else if (m_think_time)
   117 			monitor_.Wait(m_think_time);
   118 	}
   119 
   120 	{
   121 		ke::AutoUnlock unlock(&monitor_);
   122 		if (m_pHooks)
   123 			m_pHooks->OnWorkerStop(this);
   124 	}
   125 }
   126 
   127 SWThreadHandle *ThreadWorker::PopThreadFromQueue()
   128 {
   129 	ke::AutoLock lock(&monitor_);
   130 	if (m_state <= Worker_Stopped)
   131 		return NULL;
   132 
   133 	return BaseWorker::PopThreadFromQueue();
   134 }
   135 
   136 void ThreadWorker::AddThreadToQueue(SWThreadHandle *pHandle)
   137 {
   138 	ke::AutoLock lock(&monitor_);
   139 	if (m_state <= Worker_Stopped)
   140 		return;
   141 
   142 	BaseWorker::AddThreadToQueue(pHandle);
   143 	monitor_.Notify();
   144 }
   145 
   146 WorkerState ThreadWorker::GetStatus(unsigned int *threads)
   147 {
   148 	ke::AutoLock lock(&monitor_);
   149 	return BaseWorker::GetStatus(threads);
   150 }
   151 
   152 void ThreadWorker::SetThinkTimePerFrame(unsigned int thinktime)
   153 {
   154 	m_think_time = thinktime;
   155 }
   156 
   157 bool ThreadWorker::Start()
   158 {
   159 	if (m_state == Worker_Invalid && m_Threader == NULL)
   160 		return false;
   161 
   162 	if (m_state != Worker_Stopped)
   163 		return false;
   164 
   165 	m_state = Worker_Running;
   166 
   167 	ThreadParams pt;
   168 	pt.flags = Thread_Default;
   169 	pt.prio = ThreadPrio_Normal;
   170 	me = m_Threader->MakeThread(this, &pt);
   171 	return true;
   172 }
   173 
   174 bool ThreadWorker::Stop(bool flush_cancel)
   175 {
   176 	// Change the state to signal a stop, and then trigger a notify.
   177 	{
   178 		ke::AutoLock lock(&monitor_);
   179 		if (m_state == Worker_Invalid || m_state == Worker_Stopped)
   180 			return false;
   181 
   182 		m_state = Worker_Stopped;
   183 		m_FlushType = flush_cancel;
   184 		monitor_.Notify();
   185 	}
   186 
   187 	me->WaitForThread();
   188 	//destroy it
   189 	me->DestroyThis();
   190 	//flush all remaining events
   191 	Flush(true);
   192 
   193 	me = NULL;
   194 	return true;
   195 }
   196 
   197 bool ThreadWorker::Pause()
   198 {
   199 	if (m_state != Worker_Running)
   200 		return false;
   201 
   202 	ke::AutoLock lock(&monitor_);
   203 	m_state = Worker_Paused;
   204 	monitor_.Notify();
   205 	return true;
   206 }
   207 
   208 
   209 bool ThreadWorker::Unpause()
   210 {
   211 	if (m_state != Worker_Paused)
   212 		return false;
   213 
   214 	ke::AutoLock lock(&monitor_);
   215 	m_state = Worker_Running;
   216 	monitor_.Notify();
   217 	return true;
   218 }