winamp/Src/timer/timermul.cpp

371 lines
9.4 KiB
C++
Raw Normal View History

2024-09-24 13:54:57 +01:00
#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