aboutsummaryrefslogtreecommitdiff
path: root/Src/timer/timermul.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/timer/timermul.cpp')
-rw-r--r--Src/timer/timermul.cpp370
1 files changed, 370 insertions, 0 deletions
diff --git a/Src/timer/timermul.cpp b/Src/timer/timermul.cpp
new file mode 100644
index 00000000..6e7213a9
--- /dev/null
+++ b/Src/timer/timermul.cpp
@@ -0,0 +1,370 @@
+#include <bfc/platform/platform.h>
+#include "timermul.h"
+
+#include <api.h>
+#include <api/config/items/attribs.h>
+#include <api/config/items/cfgitem.h>
+
+ // {9149C445-3C30-4e04-8433-5A518ED0FDDE}
+ const GUID uioptions_guid =
+ { 0x9149c445, 0x3c30, 0x4e04, { 0x84, 0x33, 0x5a, 0x51, 0x8e, 0xd0, 0xfd, 0xde } };
+
+PtrListQuickSorted<MultiplexerServer, MultiplexerServerComparatorTID> servers_tid;
+PtrListQuickSorted<MultiplexerServer, MultiplexerServerComparatorTID> servers_mux;
+
+TimerMultiplexer::TimerMultiplexer() {
+ timerset = 0;
+ nslices = 0;
+ resolution = -1;
+ check_resolution = true;
+ client = NULL;
+ curslice = 0;
+ running_timer = NULL;
+ uioptions = NULL;
+ justexited = 0;
+ firstevent = 1;
+ resetTimer(50); // initial, is changed for config value on first event
+}
+
+TimerMultiplexer::~TimerMultiplexer() {
+ doShutdown();
+}
+
+void TimerMultiplexer::setClient(TimerMultiplexerClient *_client) {
+ client = _client;
+}
+
+void TimerMultiplexer::addTimer(int ms, void *data) {
+ //if (ms < 0) { DebugString("Timer with negative delay set, ignored coz the time machine service isn't ready yet\n"); }
+ MultiplexedTimer *t = new MultiplexedTimer(ms, data);
+ if (ms >= MAX_TIMER_DELAY) {
+ lptimers.addItem(t);
+ t->nexttick = Wasabi::Std::getTickCount() + t->ms;
+ } else {
+ timers.addItem(t);
+ if (nslices > 0)
+ distribute(t);
+ }
+}
+
+void TimerMultiplexer::removeTimer(void *data) {
+
+ if (running_timer && running_timer->data == data)
+ running_timer = NULL;
+
+ int i;
+ for (i=0;i<timers.getNumItems();i++) {
+ MultiplexedTimer *t = timers.enumItem(i);
+ if (t->data == data) {
+ removeFromWheel(t);
+ timers.removeByPos(i);
+ delete t;
+ return;
+ }
+ }
+ for (i=0;i<lptimers.getNumItems();i++) {
+ MultiplexedTimer *t = lptimers.enumItem(i);
+ if (t->data == data) {
+ removeFromLowPrecision(t);
+ delete t;
+ return;
+ }
+ }
+}
+
+void TimerMultiplexer::setResolution(int ms) {
+ resolution = ms;
+}
+
+void TimerMultiplexer::shutdown() {
+ doShutdown();
+}
+
+void TimerMultiplexer::doShutdown() {
+ timers.deleteAll();
+ wheel.deleteAll();
+ lptimers.deleteAll();
+ if (timerset) {
+ MultiplexerServer *s = servers_mux.findItem((const wchar_t *)this);
+ if (s) {
+#ifdef WIN32
+ KillTimer(NULL, s->getId());
+#elif defined(LINUX)
+
+#endif
+ }
+ timerset = 0;
+ }
+}
+
+void TimerMultiplexer::checkResolution(DWORD now) {
+ if (check_resolution == true)
+ {
+ if (WASABI_API_CONFIG)
+ {
+ if (uioptions == NULL)
+ {
+ uioptions = WASABI_API_CONFIG->config_getCfgItemByGuid(uioptions_guid);
+ if (uioptions)
+ {
+ ifc_dependent *ui_change = uioptions->getDependencyPtr();
+ ui_change->dependent_regViewer(this, 1);
+ }
+ }
+ check_resolution = uioptions?false:true;
+ int nresolution = uioptions ? _intVal(uioptions, L"Multiplexed timers resolution") : DEF_RES;
+
+ nresolution = MAX(10, MIN(MAX_TIMER_DELAY/LOW_RES_DIV, nresolution));
+ if (nresolution != resolution) {
+ resetTimer(nresolution);
+ resolution = nresolution;
+ resetWheel();
+ }
+ }
+ }
+}
+
+VOID CALLBACK timerMultiplexerServerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
+ MultiplexerServer *s = servers_tid.findItem((const wchar_t *)&idEvent);
+ if (s) s->getMultiplexer()->onServerTimer();
+}
+
+void TimerMultiplexer::resetTimer(int newresolution) {
+ if (timerset) {
+ MultiplexerServer *s = servers_mux.findItem((const wchar_t *)this);
+ if (s)
+ KillTimer(NULL, s->getId());
+ }
+
+ // linux port implements settimer
+ UINT_PTR id = SetTimer(NULL, 0, newresolution, timerMultiplexerServerProc);
+ MultiplexerServer *s = servers_mux.findItem((const wchar_t *)this);
+ if (!s) {
+ s = new MultiplexerServer(this, (UINT)id);
+ servers_mux.addItem(s);
+ servers_tid.addItem(s);
+ } else {
+ s->setId(id);
+ servers_tid.sort();
+ }
+ timerset = 1;
+}
+
+PtrList<MultiplexedTimer> *TimerMultiplexer::getSlice(int n) {
+ ASSERT(nslices > 0);
+ return wheel.enumItem(n % nslices);
+}
+
+void TimerMultiplexer::resetWheel() {
+
+ wheel.deleteAll();
+
+ nslices = MAX_TIMER_DELAY / resolution;
+
+ for (int i=0;i<nslices;i++)
+ wheel.addItem(new PtrList< MultiplexedTimer >);
+
+ curslice = 0;
+ distributeAll();
+}
+
+void TimerMultiplexer::distributeAll() {
+ for (int i=0;i<timers.getNumItems();i++) {
+ distribute(timers.enumItem(i));
+ }
+}
+
+void TimerMultiplexer::distribute(MultiplexedTimer *t) {
+ ASSERT(t != NULL);
+
+ int delay = t->ms;
+
+ int slice = delay / resolution + curslice;
+ PtrList<MultiplexedTimer> *l = getSlice(slice);
+
+ ASSERT(l != NULL);
+
+ l->addItem(t);
+}
+
+void TimerMultiplexer::onServerTimer() {
+
+ justexited = 0;
+
+ DWORD now = Wasabi::Std::getTickCount();
+
+ checkResolution(now);
+
+ runCurSlice(now);
+
+ if ((curslice % (nslices/LOW_RES_DIV)) == 0) { // execute low precision timers every MAX_TIMER_DELAY/LOW_RES_DIV
+ runLowPrecisionTimers(now);
+ }
+
+ if (!justexited) {
+ curslice++;
+ curslice %= nslices;
+ }
+
+ justexited = 1;
+
+ if (firstevent) {
+ firstevent = 0;
+ checkResolution(Wasabi::Std::getTickCount());
+ }
+}
+
+void TimerMultiplexer::runCurSlice(DWORD now) {
+ //DebugString("Running slice %d\n", curslice);
+ PtrList<MultiplexedTimer> *slice = getSlice(curslice);
+ ASSERT(slice != NULL);
+
+ // mark them clean
+ int i;
+ for (i=0;i<slice->getNumItems();i++)
+ slice->enumItem(i)->flag = 0;
+
+ // run events
+ int n;
+ do {
+ n = 0;
+ for (i=0;i<slice->getNumItems();i++) {
+ MultiplexedTimer *t = slice->enumItem(i);
+ if (t == NULL) break; // do not remove this line even if you think it's useless
+ // t might have been removed by a previous runTimer in this slice, so see if it's still here and if not, ignore
+ if (!timers.haveItem(t)) { slice->removeItem(t); i--; continue; }
+ if (t->flag == 1) continue;
+ t->flag = 1;
+ int lastdelay = MAX(0, (int)(now - t->lastmscount));
+ DWORD last = t->lastmscount;
+ if (last == 0) last = now;
+ t->lastmscount = now;
+ t->lastdelay = lastdelay;
+ running_timer = t;
+ runTimer(now, last, t, slice, i);
+// -----------------------------------------------------------------------
+// WARNING
+//
+// below this line, you can no longer assume that t is pointing at valid
+// memory, because runTimer can eventually call removeTimer
+// -----------------------------------------------------------------------
+ n++;
+ }
+ } while (n > 0);
+}
+
+void TimerMultiplexer::runTimer(DWORD now, DWORD last, MultiplexedTimer *t, PtrList<MultiplexedTimer> *slice, int pos) {
+
+ int nextslice = curslice + t->ms / resolution;
+ int spent = now - last;
+ int lost = spent - t->ms;
+
+ if (lost > 0) {
+ t->lost += (float)lost / (float)t->ms;
+ }
+
+ PtrList<MultiplexedTimer> *next = getSlice(nextslice);
+ ASSERT(next != NULL);
+
+ if (slice == next) {
+ nextslice++;
+ next = getSlice(nextslice);
+ }
+
+ slice->removeByPos(pos);
+ next->addItem(t);
+
+ int skip = (int)t->lost;
+ t->lost -= (int)t->lost;
+ if (client) {
+ client->onMultiplexedTimer(t->data, skip, t->lastdelay);
+// -----------------------------------------------------------------------
+// WARNING
+//
+// below this line, you can no longer assume that t is pointing at valid
+// memory, because onMultiplexedTimer can eventually call removeTimer
+// -----------------------------------------------------------------------
+ }
+}
+
+void TimerMultiplexer::removeFromWheel(MultiplexedTimer *t) {
+ for (int i=0;i<nslices;i++) {
+ PtrList<MultiplexedTimer> *slice = getSlice(i);
+ for (int j=0;j<slice->getNumItems();j++) {
+ if (slice->enumItem(j) == t) {
+ slice->removeByPos(j);
+ j--;
+ }
+ }
+ }
+}
+
+void TimerMultiplexer::removeFromLowPrecision(MultiplexedTimer *t) {
+ for (int i=0;i<lptimers.getNumItems();i++) {
+ if (lptimers.enumItem(i) == t) {
+ lptimers.removeByPos(i);
+ i--;
+ }
+ }
+}
+
+void TimerMultiplexer::runLowPrecisionTimers(DWORD now) {
+ int restart;
+ do {
+ restart = 0;
+ for (int i=0;i<lptimers.getNumItems();i++) {
+ MultiplexedTimer *t = lptimers.enumItem(i);
+ if (t->nexttick < now) {
+ if (client) {
+ running_timer = t;
+ t->lost += (now - t->nexttick) / t->ms;
+ int skip = (int)t->lost;
+ t->lost -= skip; // remove integer part
+ DWORD last = t->lastmscount;
+ t->lastdelay = now-last;
+ t->lastmscount = now;
+ t->nexttick = t->nexttick+(t->ms)*(skip+1);
+ client->onMultiplexedTimer(t->data, skip, t->lastdelay);
+// -----------------------------------------------------------------------
+// WARNING
+//
+// below this line, you can no longer assume that t is pointing at valid
+// memory, because onMultiplexedTimer can eventually call removeTimer
+// -----------------------------------------------------------------------
+ }
+ if (running_timer == NULL) { // onMultiplexedTimer called removeTimer
+ restart =1;
+ break;
+ }
+ }
+ }
+ } while (restart);
+}
+
+
+int TimerMultiplexer::getNumTimers() {
+ return timers.getNumItems();
+}
+
+int TimerMultiplexer::getNumTimersLP() {
+ return lptimers.getNumItems();
+}
+
+int TimerMultiplexer::dependentViewer_callback(ifc_dependent *item, const GUID *classguid, int cb, intptr_t param1, intptr_t param2 , void *ptr, size_t ptrlen)
+{
+ if (param1 == CfgItem::Event_ATTRIBUTE_CHANGED)
+ {
+ check_resolution=true;
+ }
+ else if (param1 == CfgItem::Event_ATTRIBUTE_REMOVED)
+ {
+ uioptions=0;
+ }
+ return 1;
+}
+
+#define CBCLASS TimerMultiplexer
+START_DISPATCH;
+CB(DEPENDENTVIEWER_CALLBACK, dependentViewer_callback)
+END_DISPATCH;
+#undef CBCLASS