summaryrefslogtreecommitdiff
path: root/indra/llcommon/llcallbacklist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llcallbacklist.cpp')
-rw-r--r--indra/llcommon/llcallbacklist.cpp324
1 files changed, 193 insertions, 131 deletions
diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp
index 9f23ce5317..7f7fdc7370 100644
--- a/indra/llcommon/llcallbacklist.cpp
+++ b/indra/llcommon/llcallbacklist.cpp
@@ -25,17 +25,14 @@
*/
#include "llcallbacklist.h"
-#include "lleventtimer.h"
-#include "llerrorlegacy.h"
-
-// Globals
-//
-LLCallbackList gIdleCallbacks;
//
// Member functions
//
+/*****************************************************************************
+* LLCallbackList
+*****************************************************************************/
LLCallbackList::LLCallbackList()
{
// nothing
@@ -45,186 +42,251 @@ LLCallbackList::~LLCallbackList()
{
}
-
-void LLCallbackList::addFunction( callback_t func, void *data)
+LLCallbackList::handle_t LLCallbackList::addFunction( callback_t func, void *data)
{
if (!func)
{
- return;
+ return {};
}
// only add one callback per func/data pair
//
if (containsFunction(func, data))
{
- return;
+ return {};
}
-
- callback_pair_t t(func, data);
- mCallbackList.push_back(t);
+
+ auto handle = addFunction([func, data]{ func(data); });
+ mLookup.emplace(callback_pair_t(func, data), handle);
+ return handle;
}
-bool LLCallbackList::containsFunction( callback_t func, void *data)
+LLCallbackList::handle_t LLCallbackList::addFunction( const callable_t& func )
{
- callback_pair_t t(func, data);
- callback_list_t::iterator iter = find(func,data);
- if (iter != mCallbackList.end())
- {
- return TRUE;
- }
- else
- {
- return FALSE;
- }
+ return mCallbackList.connect(func);
}
+bool LLCallbackList::containsFunction( callback_t func, void *data)
+{
+ return mLookup.find(callback_pair_t(func, data)) != mLookup.end();
+}
bool LLCallbackList::deleteFunction( callback_t func, void *data)
{
- callback_list_t::iterator iter = find(func,data);
- if (iter != mCallbackList.end())
+ auto found = mLookup.find(callback_pair_t(func, data));
+ if (found != mLookup.end())
{
- mCallbackList.erase(iter);
- return TRUE;
+ mLookup.erase(found);
+ deleteFunction(found->second);
+ return true;
}
else
{
- return FALSE;
+ return false;
}
}
-inline
-LLCallbackList::callback_list_t::iterator
-LLCallbackList::find(callback_t func, void *data)
+void LLCallbackList::deleteFunction( const handle_t& handle )
{
- callback_pair_t t(func, data);
- return std::find(mCallbackList.begin(), mCallbackList.end(), t);
+ handle.disconnect();
}
void LLCallbackList::deleteAllFunctions()
{
- mCallbackList.clear();
+ mCallbackList = {};
+ mLookup.clear();
}
-
void LLCallbackList::callFunctions()
{
- for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); )
- {
- callback_list_t::iterator curiter = iter++;
- curiter->first(curiter->second);
- }
+ mCallbackList();
}
-// Shim class to allow arbitrary boost::bind
-// expressions to be run as one-time idle callbacks.
-class OnIdleCallbackOneTime
+LLCallbackList::handle_t LLCallbackList::doOnIdleOneTime( const callable_t& func )
{
-public:
- OnIdleCallbackOneTime(nullary_func_t callable):
- mCallable(callable)
- {
- }
- static void onIdle(void *data)
- {
- gIdleCallbacks.deleteFunction(onIdle, data);
- OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data);
- self->call();
- delete self;
- }
- void call()
- {
- mCallable();
- }
-private:
- nullary_func_t mCallable;
-};
+ // connect_extended() passes the connection to the callback
+ return mCallbackList.connect_extended(
+ [func](const handle_t& handle)
+ {
+ handle.disconnect();
+ func();
+ });
+}
-void doOnIdleOneTime(nullary_func_t callable)
+LLCallbackList::handle_t LLCallbackList::doOnIdleRepeating( const bool_func_t& func )
{
- OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
+ return mCallbackList.connect_extended(
+ [func](const handle_t& handle)
+ {
+ if (func())
+ {
+ handle.disconnect();
+ }
+ });
}
-// Shim class to allow generic boost functions to be run as
-// recurring idle callbacks. Callable should return true when done,
-// false to continue getting called.
-class OnIdleCallbackRepeating
-{
-public:
- OnIdleCallbackRepeating(bool_func_t callable):
- mCallable(callable)
- {
- }
- // Will keep getting called until the callable returns true.
- static void onIdle(void *data)
- {
- OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
- bool done = self->call();
- if (done)
- {
- gIdleCallbacks.deleteFunction(onIdle, data);
- delete self;
- }
- }
- bool call()
- {
- return mCallable();
- }
-private:
- bool_func_t mCallable;
-};
+/*****************************************************************************
+* LLLater
+*****************************************************************************/
+LLLater::LLLater() {}
-void doOnIdleRepeating(bool_func_t callable)
+// Call a given callable once at specified timestamp.
+LLLater::handle_t LLLater::doAtTime(nullary_func_t callable, LLDate::timestamp time)
{
- OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
+ bool first{ mQueue.empty() };
+ // Pick token FIRST to store a self-reference in mQueue's managed node as
+ // well as in mHandles. Pre-increment to distinguish 0 from any live
+ // handle_t.
+ token_t token{ ++mToken };
+ auto handle{ mQueue.emplace(callable, time, token) };
+ mHandles.emplace(token, handle);
+ if (first)
+ {
+ // If this is our first entry, register for regular callbacks.
+ mLive = LLCallbackList::instance().doOnIdleRepeating([this]{ return tick(); });
+ }
+ return handle_t{ token };
}
-class NullaryFuncEventTimer: public LLEventTimer
+// Call a given callable once after specified interval.
+LLLater::handle_t LLLater::doAfterInterval(nullary_func_t callable, F32 seconds)
{
-public:
- NullaryFuncEventTimer(nullary_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
- {
- }
+ // Passing 0 is a slightly more expensive way of calling
+ // LLCallbackList::doOnIdleOneTime(). Are we sure the caller is correct?
+ // (If there's a valid use case, remove the llassert() and carry on.)
+ llassert(seconds > 0);
+ return doAtTime(callable, LLDate::now().secondsSinceEpoch() + seconds);
+}
-private:
- BOOL tick()
- {
- mCallable();
- return TRUE;
- }
+// For doPeriodically(), we need a struct rather than a lambda because a
+// struct, unlike a lambda, has access to 'this'.
+struct Periodic
+{
+ LLLater* mLater;
+ bool_func_t mCallable;
+ LLDate::timestamp mNext;
+ F32 mSeconds;
- nullary_func_t mCallable;
+ void operator()()
+ {
+ if (! mCallable())
+ {
+ // Returning false means please schedule another call.
+ // Don't call doAfterInterval(), which rereads LLDate::now(),
+ // since that would defer by however long it took us to wake
+ // up and notice plus however long callable() took to run.
+ mNext += mSeconds;
+ mLater->doAtTime(*this, mNext);
+ }
+ }
};
-// Call a given callable once after specified interval.
-void doAfterInterval(nullary_func_t callable, F32 seconds)
+// Call a given callable every specified number of seconds, until it returns true.
+LLLater::handle_t LLLater::doPeriodically(bool_func_t callable, F32 seconds)
{
- new NullaryFuncEventTimer(callable, seconds);
+ // Passing seconds <= 0 will produce an infinite loop.
+ llassert(seconds > 0);
+ auto next{ LLDate::now().secondsSinceEpoch() + seconds };
+ return doAtTime(Periodic{ this, callable, next, seconds }, next);
}
-class BoolFuncEventTimer: public LLEventTimer
+bool LLLater::isRunning(handle_t timer)
{
-public:
- BoolFuncEventTimer(bool_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
- {
- }
-private:
- BOOL tick()
- {
- return mCallable();
- }
+ // A default-constructed timer isn't running.
+ // A timer we don't find in mHandles has fired or been canceled.
+ return timer && mHandles.find(timer.token) != mHandles.end();
+}
- bool_func_t mCallable;
-};
+// Cancel a future timer set by doAtTime(), doAfterInterval(), doPeriodically()
+bool LLLater::cancel(handle_t& timer)
+{
+ // For exception safety, capture and clear timer before canceling.
+ // Once we've canceled this handle, don't retain the live handle.
+ const handle_t ctimer{ timer };
+ timer = handle_t();
+ return cancel(ctimer);
+}
-// Call a given callable every specified number of seconds, until it returns true.
-void doPeriodically(bool_func_t callable, F32 seconds)
+bool LLLater::cancel(const handle_t& timer)
+{
+ if (! timer)
+ {
+ return false;
+ }
+
+ // fibonacci_heap documentation does not address the question of what
+ // happens if you call erase() twice with the same handle. Is it a no-op?
+ // Does it invalidate the heap? Is it UB?
+
+ // Nor do we find any documented way to ask whether a given handle still
+ // tracks a valid heap node. That's why we capture all returned handles in
+ // mHandles and validate against that collection. What about the pop()
+ // call in tick()? How to map from the top() value back to the
+ // corresponding handle_t? That's why we store func_at::mToken.
+
+ // fibonacci_heap provides a pair of begin()/end() methods to iterate over
+ // all nodes (NOT in heap order), plus a function to convert from such
+ // iterators to handles. Without mHandles, that would be our only chance
+ // to validate.
+ auto found{ mHandles.find(timer.token) };
+ if (found == mHandles.end())
+ {
+ // we don't recognize this handle -- maybe the timer has already
+ // fired, maybe it was previously canceled.
+ return false;
+ }
+
+ // erase from mQueue the handle_t referenced by timer.token
+ mQueue.erase(found->second);
+ // before erasing timer.token from mHandles
+ mHandles.erase(found);
+ if (mQueue.empty())
+ {
+ // If that was the last active timer, unregister for callbacks.
+ //LLCallbackList::instance().deleteFunction(mLive);
+ // Since we're in the source file that knows the true identity of an
+ // LLCallbackList::handle_t, we don't even need to call instance().
+ mLive.disconnect();
+ }
+ return true;
+}
+
+bool LLLater::tick()
{
- new BoolFuncEventTimer(callable, seconds);
+ // Fetch current time only on entry, even though running some mQueue task
+ // may take long enough that the next one after would become ready. We're
+ // sharing this thread with everything else, and there's a risk we might
+ // starve it if we have a sequence of tasks that take nontrivial time.
+ auto now{ LLDate::now().secondsSinceEpoch() };
+ auto cutoff{ now + TIMESLICE };
+ while (! mQueue.empty())
+ {
+ auto& top{ mQueue.top() };
+ if (top.mTime > now)
+ {
+ // we've hit an entry that's still in the future:
+ // done with this tick(), but schedule another call
+ return false;
+ }
+ if (LLDate::now().secondsSinceEpoch() > cutoff)
+ {
+ // we still have ready tasks, but we've already eaten too much
+ // time this tick() -- defer until next tick() -- call again
+ return false;
+ }
+
+ // Found a ready task. Hate to copy stuff, but -- what if the task
+ // indirectly ends up trying to cancel a handle referencing its own
+ // node in mQueue? If the task has any state, that would be Bad. Copy
+ // the node before running it.
+ auto current{ top };
+ // remove the mHandles entry referencing this task
+ mHandles.erase(current.mToken);
+ // before removing the mQueue task entry itself
+ mQueue.pop();
+ // okay, NOW run
+ current.mFunc();
+ }
+ // queue is empty: stop callbacks
+ return true;
}