openclonk/src/platform/StdScheduler.cpp

729 lines
16 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2006-2009 Peter Wortmann
* Copyright (c) 2006-2007, 2009, 2011 Günther Brammer
* Copyright (c) 2008 Sven Eberhardt
* Copyright (c) 2009 Nicolas Hake
* Copyright (c) 2010 Benjamin Herr
* Copyright (c) 2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include "C4Include.h"
#include "StdScheduler.h"
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <vector>
#ifdef HAVE_IO_H
#include <io.h>
#endif
#ifdef HAVE_SHARE_H
#include <share.h>
#endif
#ifdef _WIN32
#include <process.h>
#include <mmsystem.h>
static int pipe(int *phandles)
{
// This doesn't work with select(), rendering the non-event-solution
// unusable for Win32. Oh well, it isn't desirable performance-wise, anyway.
return _pipe(phandles, 10, O_BINARY);
}
#endif
#ifdef HAVE_UNISTD_H
// For pipe()
#include <unistd.h>
#endif
// *** StdSchedulerProc
// Keep calling Execute until timeout has elapsed
bool StdSchedulerProc::ExecuteUntil(int iTimeout)
{
// Infinite?
if (iTimeout < 0)
for (;;)
if (!Execute())
return false;
// Calculate endpoint
unsigned int iStopTime = GetTime() + iTimeout;
for (;;)
{
// Call execute with given timeout
if (!Execute(Max(iTimeout, 0)))
return false;
// Calculate timeout
unsigned int iTime = GetTime();
if (iTime >= iStopTime)
break;
iTimeout = int(iStopTime - iTime);
}
// All ok.
return true;
}
// Is this process currently signaled?
bool StdSchedulerProc::IsSignaled()
{
#ifdef STDSCHEDULER_USE_EVENTS
return GetEvent() && WaitForSingleObject(GetEvent(), 0) == WAIT_OBJECT_0;
#else
// Initialize file descriptor sets
std::vector<struct pollfd> fds;
// Get file descriptors
GetFDs(fds);
// Test
return poll(&fds[0], fds.size(), 0) > 0;
#endif
}
// *** StdScheduler
StdScheduler::StdScheduler()
: ppProcs(NULL), iProcCnt(0), iProcCapacity(0)
{
#ifdef STDSCHEDULER_USE_EVENTS
pEventHandles = NULL;
ppEventProcs = NULL;
#endif
Add(&Unblocker);
}
StdScheduler::~StdScheduler()
{
Clear();
}
int StdScheduler::getProc(StdSchedulerProc *pProc)
{
for (int i = 0; i < iProcCnt; i++)
if (ppProcs[i] == pProc)
return i;
return -1;
}
void StdScheduler::Clear()
{
delete[] ppProcs; ppProcs = NULL;
#ifdef STDSCHEDULER_USE_EVENTS
delete[] pEventHandles; pEventHandles = NULL;
delete[] ppEventProcs; ppEventProcs = NULL;
#endif
iProcCnt = iProcCapacity = 0;
}
void StdScheduler::Set(StdSchedulerProc **ppnProcs, int inProcCnt)
{
// Remove previous data
Clear();
// Set size
Enlarge(inProcCnt - iProcCapacity);
// Copy new
iProcCnt = inProcCnt;
for (int i = 0; i < iProcCnt; i++)
ppProcs[i] = ppnProcs[i];
}
void StdScheduler::Add(StdSchedulerProc *pProc)
{
// Alrady in list?
if (hasProc(pProc)) return;
// Enlarge
if (iProcCnt >= iProcCapacity) Enlarge(10);
// Add
ppProcs[iProcCnt] = pProc;
iProcCnt++;
}
void StdScheduler::Remove(StdSchedulerProc *pProc)
{
// Search
int iPos = getProc(pProc);
// Not found?
if (iPos < 0 || iPos >= iProcCnt) return;
// Remove
for (int i = iPos + 1; i < iProcCnt; i++)
ppProcs[i-1] = ppProcs[i];
iProcCnt--;
}
bool StdScheduler::ScheduleProcs(int iTimeout)
{
// Needs at least one process to work properly
if (!iProcCnt) return false;
// Get timeout
int i; int iProcTick; int Now = GetTime();
for (i = 0; i < iProcCnt; i++)
if ((iProcTick = ppProcs[i]->GetNextTick(Now)) >= 0)
if (iTimeout == -1 || iTimeout + Now > iProcTick)
iTimeout = Max(iProcTick - Now, 0);
#ifdef STDSCHEDULER_USE_EVENTS
// Collect event handles
int iEventCnt = 0; HANDLE hEvent;
StdSchedulerProc *pMessageProc = NULL;
for (i = 0; i < iProcCnt; i++)
if ( (hEvent = ppProcs[i]->GetEvent()) )
{
if (hEvent == STDSCHEDULER_EVENT_MESSAGE)
pMessageProc = ppProcs[i];
else
{
pEventHandles[iEventCnt] = hEvent;
ppEventProcs[iEventCnt] = ppProcs[i];
iEventCnt++;
}
}
// Wait for something to happen
DWORD ret; DWORD dwMsec = iTimeout < 0 ? INFINITE : iTimeout;
if (pMessageProc)
ret = MsgWaitForMultipleObjects(iEventCnt, pEventHandles, false, dwMsec, QS_ALLINPUT);
else
ret = WaitForMultipleObjects(iEventCnt, pEventHandles, false, dwMsec);
bool fSuccess = true;
// Event?
if (ret != WAIT_TIMEOUT)
{
// Which event?
int iEventNr = ret - WAIT_OBJECT_0;
// Execute the signaled process
StdSchedulerProc *pProc = iEventNr < iEventCnt ? ppEventProcs[iEventNr] : pMessageProc;
if (!pProc->Execute(0))
{
OnError(pProc);
fSuccess = false;
}
}
// Execute all processes with timeout
Now = GetTime();
for (i = 0; i < iProcCnt; i++)
{
iProcTick = ppProcs[i]->GetNextTick(Now);
if (iProcTick >= 0 && iProcTick <= Now)
if (!ppProcs[i]->Execute(0))
{
OnError(ppProcs[i]);
fSuccess = false;
}
}
#else
// Initialize file descriptor sets
std::vector<struct pollfd> fds;
std::map<StdSchedulerProc *, std::pair<unsigned int, unsigned int> > fds_for_proc;
// Collect file descriptors
for (i = 0; i < iProcCnt; i++)
{
unsigned int os = fds.size();
ppProcs[i]->GetFDs(fds);
if (os != fds.size())
fds_for_proc[ppProcs[i]] = std::pair<unsigned int, unsigned int>(os, fds.size());
}
// Wait for something to happen
int cnt = poll(&fds[0], fds.size(), iTimeout);
bool fSuccess = true;
if (cnt >= 0)
{
bool any_executed = false;
Now = GetTime();
// Which process?
for (i = 0; i < iProcCnt; i++)
{
iProcTick = ppProcs[i]->GetNextTick(Now);
if (iProcTick >= 0 && iProcTick <= Now)
{
struct pollfd * pfd = 0;
if (fds_for_proc.find(ppProcs[i]) != fds_for_proc.end())
pfd = &fds[fds_for_proc[ppProcs[i]].first];
if (!ppProcs[i]->Execute(0, pfd))
{
OnError(ppProcs[i]);
fSuccess = false;
}
any_executed = true;
continue;
}
// no fds?
if (fds_for_proc.find(ppProcs[i]) == fds_for_proc.end())
continue;
// Check intersection
unsigned int begin = fds_for_proc[ppProcs[i]].first;
unsigned int end = fds_for_proc[ppProcs[i]].second;
for (unsigned int j = begin; j < end; ++j)
{
if (fds[j].events & fds[j].revents)
{
if (any_executed && ppProcs[i]->IsLowPriority())
break;
if (!ppProcs[i]->Execute(0, &fds[begin]))
{
OnError(ppProcs[i]);
fSuccess = false;
}
any_executed = true;
// the list of procs might have been changed, but procs must be in both ppProcs and
// fds_for_proc to be executed, which prevents execution of any proc not polled this round
// or deleted. Some procs might be skipped or executed twice, but that should be save.
break;
}
}
}
}
else if (cnt < 0)
{
printf("StdScheduler::Execute: poll failed: %s\n",strerror(errno));
}
#endif
return fSuccess;
}
void StdScheduler::UnBlock()
{
Unblocker.Notify();
}
void StdScheduler::Enlarge(int iBy)
{
iProcCapacity += iBy;
// Realloc
StdSchedulerProc **ppnProcs = new StdSchedulerProc *[iProcCapacity];
// Set data
for (int i = 0; i < iProcCnt; i++)
ppnProcs[i] = ppProcs[i];
delete[] ppProcs;
ppProcs = ppnProcs;
#ifdef STDSCHEDULER_USE_EVENTS
// Allocate dummy arrays (one handle neede for unlocker!)
delete[] pEventHandles; pEventHandles = new HANDLE[iProcCapacity + 1];
delete[] ppEventProcs; ppEventProcs = new StdSchedulerProc *[iProcCapacity];
#endif
}
// *** StdSchedulerThread
StdSchedulerThread::StdSchedulerThread()
: fThread(false)
{
}
StdSchedulerThread::~StdSchedulerThread()
{
Clear();
}
void StdSchedulerThread::Clear()
{
// Stop thread
if (fThread) Stop();
// Clear scheduler
StdScheduler::Clear();
}
void StdSchedulerThread::Set(StdSchedulerProc **ppProcs, int iProcCnt)
{
// Thread is running? Stop it first
bool fGotThread = fThread;
if (fGotThread) Stop();
// Set
StdScheduler::Set(ppProcs, iProcCnt);
// Restart
if (fGotThread) Start();
}
void StdSchedulerThread::Add(StdSchedulerProc *pProc)
{
// Thread is running? Stop it first
bool fGotThread = fThread;
if (fGotThread) Stop();
// Set
StdScheduler::Add(pProc);
// Restart
if (fGotThread) Start();
}
void StdSchedulerThread::Remove(StdSchedulerProc *pProc)
{
// Thread is running? Stop it first
bool fGotThread = fThread;
if (fGotThread) Stop();
// Set
StdScheduler::Remove(pProc);
// Restart
if (fGotThread) Start();
}
bool StdSchedulerThread::Start()
{
// already running? stop
if (fThread) Stop();
// begin thread
fRunThreadRun = true;
#ifdef HAVE_WINTHREAD
iThread = _beginthread(_ThreadFunc, 0, this);
fThread = (iThread != -1);
#elif defined(HAVE_PTHREAD)
fThread = !pthread_create(&Thread, NULL, _ThreadFunc, this);
#endif
// success?
return fThread;
}
void StdSchedulerThread::Stop()
{
// Not running?
if (!fThread) return;
// Set flag
fRunThreadRun = false;
// Unblock
UnBlock();
#ifdef HAVE_WINTHREAD
// Wait for thread to terminate itself
HANDLE hThread = reinterpret_cast<HANDLE>(iThread);
if (WaitForSingleObject(hThread, 10000) == WAIT_TIMEOUT)
// ... or kill it in case it refuses to do so
TerminateThread(hThread, -1);
#elif defined(HAVE_PTHREAD)
// wait for thread to terminate itself
// (without security - let's trust these unwashed hackers for once)
pthread_join(Thread, NULL);
#endif
fThread = false;
// ok
return;
}
#ifdef HAVE_WINTHREAD
void __cdecl StdSchedulerThread::_ThreadFunc(void *pPar)
{
StdSchedulerThread *pThread = reinterpret_cast<StdSchedulerThread *>(pPar);
_endthreadex(pThread->ThreadFunc());
}
#elif defined(HAVE_PTHREAD)
void *StdSchedulerThread::_ThreadFunc(void *pPar)
{
StdSchedulerThread *pThread = reinterpret_cast<StdSchedulerThread *>(pPar);
return reinterpret_cast<void *>(pThread->ThreadFunc());
}
#endif
unsigned int StdSchedulerThread::ThreadFunc()
{
// Keep calling Execute until someone gets fed up and calls StopThread()
while (fRunThreadRun)
ScheduleProcs();
return(0);
}
StdThread::StdThread() : fStarted(false), fStopSignaled(false)
{
}
bool StdThread::Start()
{
// already running? stop
if (fStarted) Stop();
// begin thread
fStopSignaled = false;
#ifdef HAVE_WINTHREAD
iThread = _beginthread(_ThreadFunc, 0, this);
fStarted = (iThread != -1);
#elif defined(HAVE_PTHREAD)
fStarted = !pthread_create(&Thread, NULL, _ThreadFunc, this);
#endif
// success?
return fStarted;
}
void StdThread::SignalStop()
{
// Not running?
if (!fStarted) return;
// Set flag
fStopSignaled = true;
}
void StdThread::Stop()
{
// Not running?
if (!fStarted) return;
// Set flag
fStopSignaled = true;
#ifdef HAVE_WINTHREAD
// Wait for thread to terminate itself
HANDLE hThread = reinterpret_cast<HANDLE>(iThread);
if (WaitForSingleObject(hThread, 10000) == WAIT_TIMEOUT)
// ... or kill him in case he refuses to do so
TerminateThread(hThread, -1);
#elif defined(HAVE_PTHREAD)
// wait for thread to terminate itself
// (whithout security - let's trust these unwashed hackers for once)
pthread_join(Thread, NULL);
#endif
fStarted = false;
// ok
return;
}
#ifdef HAVE_WINTHREAD
void __cdecl StdThread::_ThreadFunc(void *pPar)
{
StdThread *pThread = reinterpret_cast<StdThread *>(pPar);
_endthreadex(pThread->ThreadFunc());
}
#elif defined(HAVE_PTHREAD)
void *StdThread::_ThreadFunc(void *pPar)
{
StdThread *pThread = reinterpret_cast<StdThread *>(pPar);
return reinterpret_cast<void *>(pThread->ThreadFunc());
}
#endif
unsigned int StdThread::ThreadFunc()
{
// Keep calling Execute until someone gets fed up and calls Stop()
while (!IsStopSignaled())
Execute();
return(0);
}
bool StdThread::IsStopSignaled()
{
return fStopSignaled;
}
namespace
{
void Fail(const char* msg)
{
Log(msg);
}
}
#ifdef STDSCHEDULER_USE_EVENTS
CStdNotifyProc::CStdNotifyProc() : Event(true) {}
void CStdNotifyProc::Notify() { Event.Set(); }
bool CStdNotifyProc::CheckAndReset()
{
if (!Event.WaitFor(0)) return false;
Event.Reset();
return true;
}
#else // STDSCHEDULER_USE_EVENTS
#ifdef HAVE_SYS_EVENTFD_H
#include <sys/eventfd.h>
CStdNotifyProc::CStdNotifyProc()
{
fds[0] = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (fds[0] == -1)
Fail("eventfd failed");
}
CStdNotifyProc::~CStdNotifyProc()
{
close(fds[0]);
}
void CStdNotifyProc::Notify()
{
uint64_t n = 1;
if (write(fds[0], &n, 8) == -1)
Fail("write failed");
}
bool CStdNotifyProc::CheckAndReset()
{
uint64_t n;
return (read(fds[0], &n, 8) != -1);
}
#else
CStdNotifyProc::CStdNotifyProc()
{
if (pipe(fds) == -1)
Fail("pipe failed");
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
fcntl(fds[0], F_SETFD, FD_CLOEXEC);
fcntl(fds[1], F_SETFD, FD_CLOEXEC);
}
CStdNotifyProc::~CStdNotifyProc()
{
close(fds[0]);
close(fds[1]);
}
void CStdNotifyProc::Notify()
{
char c = 42;
if (write(fds[1], &c, 1) == -1)
Fail("write failed");
}
bool CStdNotifyProc::CheckAndReset()
{
bool r = false;
while (1)
{
char c;
if (read(fds[0], &c, 1) <= 0)
break;
else
r = true;
}
return r;
}
#endif
void CStdNotifyProc::GetFDs(std::vector<struct pollfd> & checkfds)
{
pollfd pfd = { fds[0], POLLIN, 0 };
checkfds.push_back(pfd);
}
#endif
/* CStdMultimediaTimerProc */
#ifdef STDSCHEDULER_USE_EVENTS
int CStdMultimediaTimerProc::iTimePeriod = 0;
CStdMultimediaTimerProc::CStdMultimediaTimerProc(uint32_t iDelay) :
uCriticalTimerDelay(28),
idCriticalTimer(0),
uCriticalTimerResolution(5),
Event(true)
{
if (!iTimePeriod)
{
// Get resolution caps
TIMECAPS tc;
timeGetDevCaps(&tc, sizeof(tc));
// Establish minimum resolution
uCriticalTimerResolution = BoundBy(uCriticalTimerResolution, tc.wPeriodMin, tc.wPeriodMax);
timeBeginPeriod(uCriticalTimerResolution);
}
iTimePeriod++;
SetDelay(iDelay);
}
CStdMultimediaTimerProc::~CStdMultimediaTimerProc()
{
if (idCriticalTimer)
{
timeKillEvent(idCriticalTimer);
idCriticalTimer = 0;
iTimePeriod--;
if (!iTimePeriod)
timeEndPeriod(uCriticalTimerResolution);
}
}
void CStdMultimediaTimerProc::SetDelay(uint32_t iDelay)
{
// Kill old timer (of any)
if (idCriticalTimer)
timeKillEvent(idCriticalTimer);
// Set new delay
uCriticalTimerDelay = iDelay;
// Set critical timer
idCriticalTimer=timeSetEvent(
uCriticalTimerDelay,uCriticalTimerResolution,
(LPTIMECALLBACK) Event.GetEvent(),0,TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);
if(idCriticalTimer == 0)
DebugLogF("Creating Critical Timer failed: %d", GetLastError());
}
bool CStdMultimediaTimerProc::CheckAndReset()
{
if (!Check()) return false;
Event.Reset();
return true;
}
#elif defined(HAVE_SYS_TIMERFD_H)
#include <sys/timerfd.h>
#include <unistd.h>
#include <fcntl.h>
CStdMultimediaTimerProc::CStdMultimediaTimerProc(uint32_t iDelay)
{
fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (fd == -1)
Log("timerfd_create failed");
SetDelay(iDelay);
}
CStdMultimediaTimerProc::~CStdMultimediaTimerProc()
{
close(fd);
}
void CStdMultimediaTimerProc::SetDelay(uint32_t inDelay)
{
struct itimerspec nv, ov;
nv.it_interval.tv_sec = inDelay / 1000;
nv.it_interval.tv_nsec = (inDelay % 1000) * 1000000;
nv.it_value = nv.it_interval;
timerfd_settime(fd, 0, &nv, &ov);
}
void CStdMultimediaTimerProc::Set()
{
struct itimerspec nv, ov;
timerfd_gettime(fd, &nv);
nv.it_value.tv_sec = 0;
nv.it_value.tv_nsec = 1;
timerfd_settime(fd, 0, &nv, &ov);
}
bool CStdMultimediaTimerProc::CheckAndReset()
{
uint64_t n;
return read(fd, &n, 8) != -1;
}
void CStdMultimediaTimerProc::GetFDs(std::vector<struct pollfd> & checkfds)
{
pollfd pfd = { fd, POLLIN, 0 };
checkfds.push_back(pfd);
}
#endif