From c231c97eeefc484b74198ba86251054b7dc0e6bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 2 May 2024 21:13:28 -0400 Subject: WIP: In llcallbacklist.h, add singleton LLLater for time delays. The big idea is to reduce the number of per-tick callbacks asking, "Is it time yet? Is it time yet?" We do that for LLEventTimer and LLEventTimeout. LLLater presents doAtTime(LLDate), with doAfterInterval() and doPeriodically() methods implemented using doAtTime(). All return handles. The free functions doAfterInterval() and doPeriodically() now forward to the corresponding LLLater methods. LLLater also presents isRunning(handle) and cancel(handle). LLLater borrows the tactic of LLEventTimer: while there's at least one running timer, it registers an LLCallbackList tick() callback to service ready timers. But instead of looping over all of them asking, "Are you ready?" it keeps them in a priority queue ordered by desired timestamp, and only touches those whose timestamp has been reached. Also, it honors a maximum time slice: once the ready timers have run for longer than the limit, it defers processing other ready timers to the next tick() call. The intent is to consume fewer cycles per tick() call, both by the management machinery and the timers themselves. Revamp LLCallbackList to accept C++ callables in addition to (classic C function pointer, void*) pairs. Make addFunction() return a handle (different than LLLater handles) that can be passed to a new deleteFunction() overload, since std::function instances can't be compared for equality. In fact, implement LLCallbackList using boost::signals2::signal, which provides almost exactly what we want. LLCallbackList continues to accept (function pointer, void*) pairs, but now we store a lambda that calls the function pointer with that void*. It takes less horsing around to create a C++ callable from a (function pointer, void*) pair than the other way around. For containsFunction() and deleteFunction(), such pairs are the keys for a lookup table whose values are handles. Instead of having a static global LLCallbackList gIdleCallbacks, make LLCallbackList an LLSingleton to guarantee initialization. For backwards compatibility, gIdleCallbacks is now a macro for LLCallbackList::instance(). Move doOnIdleOneTime() and doOnIdleRepeating() functions to LLCallbackList methods, but for backwards compatibility continue providing free functions. Reimplement LLEventTimer using LLLater::doPeriodically(). One implication is that LLEventTimer need no longer be derived from LLInstanceTracker, which we used to iterate over all instances every tick. Give it start() and stop() methods, since some subclasses (e.g. LLFlashTimer) used to call its member LLTimer's start() and stop(). Remove updateClass(): LLCallbackList::callFunctions() now takes care of that. Remove LLToastLifeTimer::start() and stop(), since LLEventTimer now provides those. Remove getRemainingTimeF32(), since LLLater does not (yet) provide that feature. While at it, make LLEventTimer::tick() return bool instead of BOOL, and change existing overrides. Make LLApp::stepFrame() call LLCallbackList::callFunctions() instead of LLEventTimer::updateClass(). We could have refactored LLEventTimer to use the mechanism now built into LLLater, but frankly the LLEventTimer API is rather clumsy. You MUST derive a subclass and override tick(), and you must instantiate your subclass on the heap because, when your tick() override returns false, LLEventTimer deletes its subclass instance. The LLLater API is simpler to use, and LLEventTimer is much simplified by using it. Merge lleventfilter.h's LLEventTimeoutBase into LLEventTimeout, and likewise merge LLEventThrottleBase into LLEventThrottle. The separation was for testability, but now that they're no longer based on LLTimer, it becomes harder to use dummy time for testing. Temporarily skip tests based on LLEventTimeoutBase and LLEventThrottleBase. Instead of listening for LLEventPump("mainloop") ticks and using LLTimer, LLEventTimeout now uses LLLater::doAfterInterval(). Instead of LLTimer and LLEventTimeout, LLEventThrottle likewise now uses LLLater::doAfterInterval(). Recast a couple local LLEventTimeout pre-lambda callable classes with lambdas. Dignify F64 with a new typedef LLDate::timestamp. LLDate heavily depends on that as its base time representation, but there are those who question use of floating-point for time. This is a step towards insulating us from any future change. --- indra/llcommon/llcallbacklist.h | 228 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 206 insertions(+), 22 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index 89716cd74c..522a9b838b 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -27,53 +27,237 @@ #ifndef LL_LLCALLBACKLIST_H #define LL_LLCALLBACKLIST_H +#include "lldate.h" +#include "llsingleton.h" #include "llstl.h" -#include -#include +#include +#include +#include +#include +#include -class LLCallbackList +/***************************************************************************** +* LLCallbackList: callbacks every idle tick (every callFunctions() call) +*****************************************************************************/ +class LLCallbackList: public LLSingleton { + LLSINGLETON(LLCallbackList); public: typedef void (*callback_t)(void*); - typedef std::pair< callback_t,void* > callback_pair_t; - // NOTE: It is confirmed that we DEPEND on the order provided by using a list :( - // - typedef std::list< callback_pair_t > callback_list_t; - - LLCallbackList(); + typedef boost::signals2::signal callback_list_t; + typedef callback_list_t::slot_type callable_t; + typedef boost::signals2::connection handle_t; + typedef boost::signals2::scoped_connection temp_handle_t; + typedef std::function bool_func_t; + ~LLCallbackList(); - void addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) + handle_t addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) + handle_t addFunction( const callable_t& func ); bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found - void callFunctions(); // calls all functions + void deleteFunction( const handle_t& handle ); + void callFunctions(); // calls all functions void deleteAllFunctions(); + handle_t doOnIdleOneTime( const callable_t& func ); + handle_t doOnIdleRepeating( const bool_func_t& func ); + bool isRunning(const handle_t& handle) const { return handle.connected(); }; + static void test(); protected: - - inline callback_list_t::iterator find(callback_t func, void *data); - callback_list_t mCallbackList; + + // "Additional specializations for std::pair and the standard container + // types, as well as utility functions to compose hashes are available in + // boost::hash." + // https://en.cppreference.com/w/cpp/utility/hash + typedef std::pair< callback_t,void* > callback_pair_t; + typedef std::unordered_map> lookup_table; + lookup_table mLookup; }; -typedef boost::function nullary_func_t; -typedef boost::function bool_func_t; +/*-------------------- legacy names in global namespace --------------------*/ +#define gIdleCallbacks (LLCallbackList::instance()) + +using nullary_func_t = LLCallbackList::callable_t; +using bool_func_t = LLCallbackList::bool_func_t; // Call a given callable once in idle loop. -void doOnIdleOneTime(nullary_func_t callable); +inline +LLCallbackList::handle_t doOnIdleOneTime(nullary_func_t callable) +{ + return gIdleCallbacks.doOnIdleOneTime(callable); +} // Repeatedly call a callable in idle loop until it returns true. -void doOnIdleRepeating(bool_func_t callable); +inline +LLCallbackList::handle_t doOnIdleRepeating(bool_func_t callable) +{ + return gIdleCallbacks.doOnIdleRepeating(callable); +} + +/***************************************************************************** +* LLLater: callbacks at some future time +*****************************************************************************/ +class LLLater: public LLSingleton +{ + LLSINGLETON(LLLater); + + using token_t = U32; + + // Define a struct for our priority queue entries, instead of using + // a tuple, because we need to define the comparison operator. + struct func_at + { + nullary_func_t mFunc; + LLDate::timestamp mTime; + token_t mToken; + + func_at(const nullary_func_t& func, LLDate::timestamp tm, token_t token): + mFunc(func), + mTime(tm), + mToken(token) + {} + + friend bool operator<(const func_at& lhs, const func_at& rhs) + { + // use greater-than because we want fibonacci_heap to select the + // EARLIEST time as the top() + return lhs.mTime > rhs.mTime; + } + }; + + // Accept default stable: when two funcs have the same timestamp, + // we don't care in what order they're called. + // Specify constant_time_size: we don't need to optimize the size() + // method, iow we don't need to store and maintain a count of entries. + typedef boost::heap::fibonacci_heap> + queue_t; + +public: + // If tasks that come ready during a given tick() take longer than this, + // defer any subsequent ready tasks to a future tick() call. + static constexpr F32 TIMESLICE{ 0.005f }; + class handle_t + { + private: + friend class LLLater; + token_t token; + public: + handle_t(token_t token=0): token(token) {} + bool operator==(const handle_t& rhs) const { return this->token == rhs.token; } + explicit operator bool() const { return bool(token); } + bool operator!() const { return ! bool(*this); } + }; + + // Call a given callable once at specified timestamp. + handle_t doAtTime(nullary_func_t callable, LLDate::timestamp time); + + // Call a given callable once after specified interval. + handle_t doAfterInterval(nullary_func_t callable, F32 seconds); + + // Call a given callable every specified number of seconds, until it returns true. + handle_t doPeriodically(bool_func_t callable, F32 seconds); + + // test whether specified handle is still live + bool isRunning(handle_t timer); + + // Cancel a future timer set by doAtTime(), doAfterInterval(), doPeriodically(). + // Return true iff the handle corresponds to a live timer. + bool cancel(const handle_t& timer); + // If we're canceling a non-const handle_t, also clear it so we need not + // cancel again. + bool cancel(handle_t& timer); + + // Store a handle_t returned by doAtTime(), doAfterInterval() or + // doPeriodically() in a temp_handle_t to cancel() automatically on + // destruction of the temp_handle_t. + class temp_handle_t + { + public: + temp_handle_t() {} + temp_handle_t(const handle_t& hdl): mHandle(hdl) {} + temp_handle_t(const temp_handle_t&) = delete; + temp_handle_t(temp_handle_t&&) = default; + temp_handle_t& operator=(const handle_t& hdl) + { + // initializing a new temp_handle_t, then swapping it into *this, + // takes care of destroying any previous mHandle + temp_handle_t replacement(hdl); + swap(replacement); + return *this; + } + temp_handle_t& operator=(const temp_handle_t&) = delete; + temp_handle_t& operator=(temp_handle_t&&) = default; + ~temp_handle_t() + { + cancel(); + } + + // temp_handle_t should be usable wherever handle_t is + operator handle_t() const { return mHandle; } + // If we're dealing with a non-const temp_handle_t, pass a reference + // to our handle_t member (e.g. to LLLater::cancel()). + operator handle_t&() { return mHandle; } + + // For those in the know, provide a cancel() method of our own that + // avoids LLLater::instance() lookup when mHandle isn't live. + bool cancel() + { + if (! mHandle) + { + return false; + } + else + { + return LLLater::instance().cancel(mHandle); + } + } + + void swap(temp_handle_t& other) noexcept + { + std::swap(this->mHandle, other.mHandle); + } + + private: + handle_t mHandle; + }; + +private: + bool tick(); + + // NOTE: We don't lock our data members because it doesn't make sense to + // register cross-thread callbacks. If we start wanting to use them on + // threads other than the main thread, it would make more sense to make + // our data members thread_local than to lock them. + + // the heap aka priority queue + queue_t mQueue; + // handles we've returned that haven't yet canceled + std::unordered_map mHandles; + token_t mToken{ 0 }; + // While mQueue is non-empty, register for regular callbacks. + LLCallbackList::temp_handle_t mLive; +}; + +/*-------------------- legacy names in global namespace --------------------*/ // Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds); +inline +LLLater::handle_t doAfterInterval(nullary_func_t callable, F32 seconds) +{ + return LLLater::instance().doAfterInterval(callable, seconds); +} // Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds); - -extern LLCallbackList gIdleCallbacks; +inline +LLLater::handle_t doPeriodically(bool_func_t callable, F32 seconds) +{ + return LLLater::instance().doPeriodically(callable, seconds); +} #endif -- cgit v1.2.3 From 48e1979abaecc03af96e7e752e65c645083a4268 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 2 May 2024 23:57:29 -0400 Subject: Introduce LLLater::getRemaining(handle). Some timer use cases need to know not only whether the timer is active, but how much time remains before it (next) fires. Introduce LLLater::mDoneTimes to track, for each handle, the timestamp at which it's expected to fire. We can't just look up the target timestamp in mQueue's func_at entry because there's no documented way to navigate from a handle_type to a node iterator or pointer. Nor can we store it in mHandles because of order dependency: we need the mDoneTimes iterator so we can bind it into the Periodic functor for doPeriodically(), but we need the mQueue handle to store in mHandles. If we could find the mQueue node from the new handle, we could update the func_at entry after emplace() -- but if we could find the mQueue node from a handle, we wouldn't need to store the target timestamp separately anyway. Split LLLater::doAtTime() into internal doAtTime1() and doAtTime2(): the first creates an mDoneTimes entry and returns an iterator, the second finishes creating new mQueue and mHandles entries based on that mDoneTimes entry. This lets doPeriodically()'s Periodic bind the mDoneTimes iterator. Then instead of continually incrementing an internal data member, it increments the mDoneTimes entry to set the next upcoming timestamp. That lets getRemaining() report the next upcoming timestamp rather than only the original one. Add LLEventTimer::isRunning() and getRemaining(), forwarding to its LLLater handle. Fix various LLEventTimer subclass references to mEventTimer.stop(), etc. Fix non-inline LLEventTimer subclass tick() overrides for bool, not BOOL. Remove LLAppViewer::idle() call to LLEventTimer::updateClass(). Since LLApp::stepFrame() already calls LLCallbackList::callFunctions(), assume we've already handled that every tick. --- indra/llcommon/llcallbacklist.h | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index 522a9b838b..17adb7f431 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -114,13 +114,13 @@ class LLLater: public LLSingleton struct func_at { nullary_func_t mFunc; - LLDate::timestamp mTime; token_t mToken; + LLDate::timestamp mTime; - func_at(const nullary_func_t& func, LLDate::timestamp tm, token_t token): + func_at(const nullary_func_t& func, token_t token, LLDate::timestamp tm): mFunc(func), - mTime(tm), - mToken(token) + mToken(token), + mTime(tm) {} friend bool operator<(const func_at& lhs, const func_at& rhs) @@ -165,7 +165,9 @@ public: handle_t doPeriodically(bool_func_t callable, F32 seconds); // test whether specified handle is still live - bool isRunning(handle_t timer); + bool isRunning(handle_t timer) const; + // check remaining time + F32 getRemaining(handle_t timer) const; // Cancel a future timer set by doAtTime(), doAfterInterval(), doPeriodically(). // Return true iff the handle corresponds to a live timer. @@ -239,10 +241,19 @@ private: // the heap aka priority queue queue_t mQueue; // handles we've returned that haven't yet canceled - std::unordered_map mHandles; + using HandleMap = std::unordered_map; + HandleMap mHandles; + using DoneMap = std::unordered_map; + DoneMap mDoneTimes; token_t mToken{ 0 }; // While mQueue is non-empty, register for regular callbacks. LLCallbackList::temp_handle_t mLive; + + struct Periodic; + + // internal implementation for doAtTime() + DoneMap::iterator doAtTime1(LLDate::timestamp time); + handle_t doAtTime2(nullary_func_t callable, DoneMap::iterator iter); }; /*-------------------- legacy names in global namespace --------------------*/ -- cgit v1.2.3 From 9f620efa9dd60c5de6b7ea807d53bba922294726 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 3 May 2024 08:52:32 -0400 Subject: Make LLLater store target time in mHandles; ditch 2nd unordered_map. Instead of maintaining a whole separate unordered_map to look up target times, make room in the HandleMap entry for the target time. There's still circularity, but the split into doAtTime1() and doAtTime2() resolves it: since doAtTime2() accepts the mHandles iterator created by doAtTime1(), doAtTime2() can simply store the new mQueue handle_type into the appropriate slot. Also sprinkle in a few more override keywords for consistency. --- indra/llcommon/llcallbacklist.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index 17adb7f431..3ff1aad04e 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -241,10 +241,10 @@ private: // the heap aka priority queue queue_t mQueue; // handles we've returned that haven't yet canceled - using HandleMap = std::unordered_map; + using HandleMap = std::unordered_map< + token_t, + std::pair>; HandleMap mHandles; - using DoneMap = std::unordered_map; - DoneMap mDoneTimes; token_t mToken{ 0 }; // While mQueue is non-empty, register for regular callbacks. LLCallbackList::temp_handle_t mLive; @@ -252,8 +252,8 @@ private: struct Periodic; // internal implementation for doAtTime() - DoneMap::iterator doAtTime1(LLDate::timestamp time); - handle_t doAtTime2(nullary_func_t callable, DoneMap::iterator iter); + HandleMap::iterator doAtTime1(LLDate::timestamp time); + handle_t doAtTime2(nullary_func_t callable, HandleMap::iterator iter); }; /*-------------------- legacy names in global namespace --------------------*/ -- cgit v1.2.3 From d4f384b4ec55758a7f2ca4338894ea6cacc98eec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 May 2024 16:13:03 -0400 Subject: Refactor LLLater -> LL::Timers to accommodate nonpositive repeats. In the previous design, the tick() method ran each task exactly once. doPeriodically() was implemented by posting a functor that would, after calling the specified callable, repost itself at (timestamp + interval). The trouble with that design is that it required (interval > 0). A nonpositive interval would result in looping over any timers with nonpositive repetition intervals without ever returning from the tick() method. To avoid that, doPeriodically() contained an llassert(interval > 0). Unfortunately the viewer failed that constraint immediately at login, leading to the suspicion that eliminating every such usage might require a protracted search. Lifting that restriction required a redesign. Now the priority queue stores a callable returning bool, and the tick() method itself contains the logic to repost a recurring task -- but defers doing so until after it stops looping over ready tasks, ensuring that a task with a nonpositive interval will at least wait until the following tick() call. This simplifies not only doPeriodically(), but also doAtTime(). The previous split of doAtTime() into doAtTime1() and doAtTime2() was only to accommodate the needs of the Periodic functor class. Ditch Periodic. Per feedback from NickyD, rename doAtTime() to scheduleAt(), which wraps its passed nullary callable into a callable that unconditionally returns true (so tick() will run it only once). Rename the doAfterInterval() method to scheduleAfter(), which similarly wraps its nullary callable. However, the legacy doAfterInterval() free function remains. scheduleAfter() also loses its llassert(seconds > 0). Rename the doPeriodically() method to scheduleRepeating(). However, the legacy doPeriodically() free function remains. Add internal scheduleAtRepeating(), whose role is to accept both a specific timestamp and a repetition interval (which might be ignored, depending on the callable). scheduleAtRepeating() now contains the real logic to add a task. Rename getRemaining() to timeUntilCall(), hopefully resolving the question of "remaining what?" Expand the std::pair metadata stored in Timers's auxiliary unordered_map to a Metadata struct containing the repetition interval plus two bools to mediate deferred cancel() processing. Rename HandleMap to MetaMap, mHandles to mMeta. Defend against the case when cancel(handle) is reached during the call to that handle's callable. Meta::mRunning is set for the duration of that call. When cancel() sees mRunning, instead of immediately deleting map entries, it sets mCancel. Upon return from a task's callable, tick() notices mCancel and behaves as if the callable returned true to stop the series of calls. To guarantee that mRunning doesn't inadvertently remain set even in the case of an exception, introduce local RAII class TempSet whose constructor accepts a non-const variable reference and a desired value. The constructor captures the current value and sets the desired value; the destructor restores the previous value. Defend against exception in a task's callable, and stop calling that task. Use LOG_UNHANDLED_EXCEPTION() to report it. --- indra/llcommon/llcallbacklist.h | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index 3ff1aad04e..f9b15867ef 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -101,11 +101,14 @@ LLCallbackList::handle_t doOnIdleRepeating(bool_func_t callable) } /***************************************************************************** -* LLLater: callbacks at some future time +* LL::Timers: callbacks at some future time *****************************************************************************/ -class LLLater: public LLSingleton +namespace LL { - LLSINGLETON(LLLater); + +class Timers: public LLSingleton +{ + LLSINGLETON(Timers); using token_t = U32; @@ -113,11 +116,14 @@ class LLLater: public LLSingleton // a tuple, because we need to define the comparison operator. struct func_at { - nullary_func_t mFunc; + // callback to run when this timer fires + bool_func_t mFunc; + // key to look up metadata in mHandles token_t mToken; + // time at which this timer is supposed to fire LLDate::timestamp mTime; - func_at(const nullary_func_t& func, token_t token, LLDate::timestamp tm): + func_at(const bool_func_t& func, token_t token, LLDate::timestamp tm): mFunc(func), mToken(token), mTime(tm) @@ -146,7 +152,7 @@ public: class handle_t { private: - friend class LLLater; + friend class Timers; token_t token; public: handle_t(token_t token=0): token(token) {} @@ -156,33 +162,33 @@ public: }; // Call a given callable once at specified timestamp. - handle_t doAtTime(nullary_func_t callable, LLDate::timestamp time); + handle_t scheduleAt(nullary_func_t callable, LLDate::timestamp time); // Call a given callable once after specified interval. - handle_t doAfterInterval(nullary_func_t callable, F32 seconds); + handle_t scheduleAfter(nullary_func_t callable, F32 seconds); // Call a given callable every specified number of seconds, until it returns true. - handle_t doPeriodically(bool_func_t callable, F32 seconds); + handle_t scheduleRepeating(bool_func_t callable, F32 seconds); // test whether specified handle is still live bool isRunning(handle_t timer) const; // check remaining time - F32 getRemaining(handle_t timer) const; + F32 timeUntilCall(handle_t timer) const; - // Cancel a future timer set by doAtTime(), doAfterInterval(), doPeriodically(). - // Return true iff the handle corresponds to a live timer. + // Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleRepeating(). + // Return true if and only if the handle corresponds to a live timer. bool cancel(const handle_t& timer); // If we're canceling a non-const handle_t, also clear it so we need not // cancel again. bool cancel(handle_t& timer); - // Store a handle_t returned by doAtTime(), doAfterInterval() or - // doPeriodically() in a temp_handle_t to cancel() automatically on + // Store a handle_t returned by scheduleAt(), scheduleAfter() or + // scheduleRepeating() in a temp_handle_t to cancel() automatically on // destruction of the temp_handle_t. class temp_handle_t { public: - temp_handle_t() {} + temp_handle_t() = default; temp_handle_t(const handle_t& hdl): mHandle(hdl) {} temp_handle_t(const temp_handle_t&) = delete; temp_handle_t(temp_handle_t&&) = default; @@ -204,11 +210,11 @@ public: // temp_handle_t should be usable wherever handle_t is operator handle_t() const { return mHandle; } // If we're dealing with a non-const temp_handle_t, pass a reference - // to our handle_t member (e.g. to LLLater::cancel()). + // to our handle_t member (e.g. to Timers::cancel()). operator handle_t&() { return mHandle; } // For those in the know, provide a cancel() method of our own that - // avoids LLLater::instance() lookup when mHandle isn't live. + // avoids Timers::instance() lookup when mHandle isn't live. bool cancel() { if (! mHandle) @@ -217,7 +223,7 @@ public: } else { - return LLLater::instance().cancel(mHandle); + return Timers::instance().cancel(mHandle); } } @@ -231,44 +237,64 @@ public: }; private: + handle_t scheduleAtRepeating(bool_func_t callable, LLDate::timestamp time, F32 interval); + LLDate::timestamp now() const { return LLDate::now().secondsSinceEpoch(); } + // wrap a nullary_func_t with a bool_func_t that will only execute once + bool_func_t once(nullary_func_t callable) + { + return [callable] + { + callable(); + return true; + }; + } bool tick(); // NOTE: We don't lock our data members because it doesn't make sense to - // register cross-thread callbacks. If we start wanting to use them on + // register cross-thread callbacks. If we start wanting to use Timers on // threads other than the main thread, it would make more sense to make // our data members thread_local than to lock them. // the heap aka priority queue queue_t mQueue; - // handles we've returned that haven't yet canceled - using HandleMap = std::unordered_map< - token_t, - std::pair>; - HandleMap mHandles; + + // metadata about a given task + struct Metadata + { + // handle to mQueue entry + queue_t::handle_type mHandle; + // time at which this timer is supposed to fire + LLDate::timestamp mTime; + // interval at which this timer is supposed to fire repeatedly + F32 mInterval{ 0 }; + // mFunc is currently running: don't delete this entry + bool mRunning{ false }; + // cancel() was called while mFunc was running: deferred cancel + bool mCancel{ false }; + }; + + using MetaMap = std::unordered_map; + MetaMap mMeta; token_t mToken{ 0 }; // While mQueue is non-empty, register for regular callbacks. LLCallbackList::temp_handle_t mLive; - - struct Periodic; - - // internal implementation for doAtTime() - HandleMap::iterator doAtTime1(LLDate::timestamp time); - handle_t doAtTime2(nullary_func_t callable, HandleMap::iterator iter); }; +} // namespace LL + /*-------------------- legacy names in global namespace --------------------*/ // Call a given callable once after specified interval. inline -LLLater::handle_t doAfterInterval(nullary_func_t callable, F32 seconds) +LL::Timers::handle_t doAfterInterval(nullary_func_t callable, F32 seconds) { - return LLLater::instance().doAfterInterval(callable, seconds); + return LL::Timers::instance().scheduleAfter(callable, seconds); } // Call a given callable every specified number of seconds, until it returns true. inline -LLLater::handle_t doPeriodically(bool_func_t callable, F32 seconds) +LL::Timers::handle_t doPeriodically(bool_func_t callable, F32 seconds) { - return LLLater::instance().doPeriodically(callable, seconds); + return LL::Timers::instance().scheduleRepeating(callable, seconds); } #endif -- cgit v1.2.3 From 5060ccb1530ab576458aeff0d0b2b5dd24bc5880 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 May 2024 16:05:46 -0400 Subject: Add "Timers" LLEventAPI, actually a LazyEventAPI, for LL::Timers. Rename LL::Timers::scheduleRepeating() to scheduleEvery(). --- indra/llcommon/llcallbacklist.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index f9b15867ef..2fb27d5ea8 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -168,14 +168,14 @@ public: handle_t scheduleAfter(nullary_func_t callable, F32 seconds); // Call a given callable every specified number of seconds, until it returns true. - handle_t scheduleRepeating(bool_func_t callable, F32 seconds); + handle_t scheduleEvery(bool_func_t callable, F32 seconds); // test whether specified handle is still live bool isRunning(handle_t timer) const; // check remaining time F32 timeUntilCall(handle_t timer) const; - // Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleRepeating(). + // Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleEvery(). // Return true if and only if the handle corresponds to a live timer. bool cancel(const handle_t& timer); // If we're canceling a non-const handle_t, also clear it so we need not @@ -183,7 +183,7 @@ public: bool cancel(handle_t& timer); // Store a handle_t returned by scheduleAt(), scheduleAfter() or - // scheduleRepeating() in a temp_handle_t to cancel() automatically on + // scheduleEvery() in a temp_handle_t to cancel() automatically on // destruction of the temp_handle_t. class temp_handle_t { @@ -237,7 +237,7 @@ public: }; private: - handle_t scheduleAtRepeating(bool_func_t callable, LLDate::timestamp time, F32 interval); + handle_t scheduleAtEvery(bool_func_t callable, LLDate::timestamp time, F32 interval); LLDate::timestamp now() const { return LLDate::now().secondsSinceEpoch(); } // wrap a nullary_func_t with a bool_func_t that will only execute once bool_func_t once(nullary_func_t callable) @@ -294,7 +294,7 @@ LL::Timers::handle_t doAfterInterval(nullary_func_t callable, F32 seconds) inline LL::Timers::handle_t doPeriodically(bool_func_t callable, F32 seconds) { - return LL::Timers::instance().scheduleRepeating(callable, seconds); + return LL::Timers::instance().scheduleEvery(callable, seconds); } #endif -- cgit v1.2.3 From 7137647e90d8c11197513f542f04fb39b483d663 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 May 2024 12:19:54 -0400 Subject: Manual whitespace cleanup (fix_whitespace.py). --- indra/llcommon/llcallbacklist.h | 68 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 34 deletions(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index 2fb27d5ea8..b245b3db94 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -1,25 +1,25 @@ -/** +/** * @file llcallbacklist.h * @brief A simple list of callback functions to call. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -41,43 +41,43 @@ *****************************************************************************/ class LLCallbackList: public LLSingleton { - LLSINGLETON(LLCallbackList); + LLSINGLETON(LLCallbackList); public: - typedef void (*callback_t)(void*); + typedef void (*callback_t)(void*); - typedef boost::signals2::signal callback_list_t; - typedef callback_list_t::slot_type callable_t; - typedef boost::signals2::connection handle_t; - typedef boost::signals2::scoped_connection temp_handle_t; - typedef std::function bool_func_t; + typedef boost::signals2::signal callback_list_t; + typedef callback_list_t::slot_type callable_t; + typedef boost::signals2::connection handle_t; + typedef boost::signals2::scoped_connection temp_handle_t; + typedef std::function bool_func_t; - ~LLCallbackList(); + ~LLCallbackList(); - handle_t addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) - handle_t addFunction( const callable_t& func ); - bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair - bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found - void deleteFunction( const handle_t& handle ); - void callFunctions(); // calls all functions - void deleteAllFunctions(); + handle_t addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) + handle_t addFunction( const callable_t& func ); + bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair + bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found + void deleteFunction( const handle_t& handle ); + void callFunctions(); // calls all functions + void deleteAllFunctions(); - handle_t doOnIdleOneTime( const callable_t& func ); - handle_t doOnIdleRepeating( const bool_func_t& func ); - bool isRunning(const handle_t& handle) const { return handle.connected(); }; + handle_t doOnIdleOneTime( const callable_t& func ); + handle_t doOnIdleRepeating( const bool_func_t& func ); + bool isRunning(const handle_t& handle) const { return handle.connected(); }; - static void test(); + static void test(); protected: - callback_list_t mCallbackList; - - // "Additional specializations for std::pair and the standard container - // types, as well as utility functions to compose hashes are available in - // boost::hash." - // https://en.cppreference.com/w/cpp/utility/hash - typedef std::pair< callback_t,void* > callback_pair_t; - typedef std::unordered_map> lookup_table; - lookup_table mLookup; + callback_list_t mCallbackList; + + // "Additional specializations for std::pair and the standard container + // types, as well as utility functions to compose hashes are available in + // boost::hash." + // https://en.cppreference.com/w/cpp/utility/hash + typedef std::pair< callback_t,void* > callback_pair_t; + typedef std::unordered_map> lookup_table; + lookup_table mLookup; }; /*-------------------- legacy names in global namespace --------------------*/ -- cgit v1.2.3 From ce9886e3d374df3017eeaeeb9e30f28343712896 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 3 Jun 2024 16:50:22 -0400 Subject: Allow consumer code to query or adjust the per-tick timeslice. --- indra/llcommon/llcallbacklist.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/llcallbacklist.h') diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index b245b3db94..fb4696188a 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -147,7 +147,11 @@ class Timers: public LLSingleton public: // If tasks that come ready during a given tick() take longer than this, // defer any subsequent ready tasks to a future tick() call. - static constexpr F32 TIMESLICE{ 0.005f }; + static constexpr F32 DEFAULT_TIMESLICE{ 0.005f }; + // Setting timeslice to be less than MINIMUM_TIMESLICE could lock up + // Timers processing, causing it to believe it's exceeded the allowable + // time every tick before processing ANY queue items. + static constexpr F32 MINIMUM_TIMESLICE{ 0.001f }; class handle_t { @@ -182,6 +186,9 @@ public: // cancel again. bool cancel(handle_t& timer); + F32 getTimeslice() const { return mTimeslice; } + void setTimeslice(F32 timeslice); + // Store a handle_t returned by scheduleAt(), scheduleAfter() or // scheduleEvery() in a temp_handle_t to cancel() automatically on // destruction of the temp_handle_t. @@ -278,6 +285,7 @@ private: token_t mToken{ 0 }; // While mQueue is non-empty, register for regular callbacks. LLCallbackList::temp_handle_t mLive; + F32 mTimeslice{ DEFAULT_TIMESLICE }; }; } // namespace LL -- cgit v1.2.3