diff options
| -rwxr-xr-x | indra/llcommon/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/llcommon/llpounceable.h | 217 | ||||
| -rw-r--r-- | indra/llcommon/tests/llpounceable_test.cpp | 200 | 
3 files changed, 419 insertions, 0 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 1459b9ada2..d2d507d676 100755 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -178,6 +178,7 @@ set(llcommon_HEADER_FILES      llmortician.h      llnametable.h      llpointer.h +    llpounceable.h      llpredicate.h      llpreprocessor.h      llpriqueuemap.h @@ -310,6 +311,7 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}") +  LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")    # *TODO - reenable these once tcmalloc libs no longer break the build.    #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h new file mode 100644 index 0000000000..94d508d917 --- /dev/null +++ b/indra/llcommon/llpounceable.h @@ -0,0 +1,217 @@ +/** + * @file   llpounceable.h + * @author Nat Goodspeed + * @date   2015-05-22 + * @brief  LLPounceable is tangentially related to a future: it's a holder for + *         a value that may or may not exist yet. Unlike a future, though, + *         LLPounceable freely allows reading the held value. (If the held + *         type T does not have a distinguished "empty" value, consider using + *         LLPounceable<boost::optional<T>>.) + * + *         LLPounceable::callWhenReady() is this template's claim to fame. It + *         allows its caller to "pounce" on the held value as soon as it + *         becomes non-empty. Call callWhenReady() with any C++ callable + *         accepting T. If the held value is already non-empty, callWhenReady() + *         will immediately call the callable with the held value. If the held + *         value is empty, though, callWhenReady() will enqueue the callable + *         for later. As soon as LLPounceable is assigned a non-empty held + *         value, it will flush the queue of deferred callables. + * + *         Consider a global LLMessageSystem* gMessageSystem. Message system + *         initialization happens at a very specific point during viewer + *         initialization. Other subsystems want to register callbacks on the + *         LLMessageSystem instance as soon as it's initialized, but their own + *         initialization may precede that. If we define gMessageSystem to be + *         an LLPounceable<LLMessageSystem*>, a subsystem can use + *         callWhenReady() to either register immediately (if gMessageSystem + *         is already up and runnning) or register as soon as gMessageSystem + *         is set with a new, initialized instance. + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLPOUNCEABLE_H) +#define LL_LLPOUNCEABLE_H + +#include "llsingleton.h" +#include <boost/call_traits.hpp> +#include <boost/type_traits/remove_pointer.hpp> +#include <boost/utility/value_init.hpp> +#include <boost/unordered_map.hpp> +#include <boost/function.hpp> +#include <queue> + +// Forward declare the user template, since we want to be able to point to it +// in some of its implementation classes. +template <typename T, class TAG> +class LLPounceable; + +template <typename T, typename TAG> +struct LLPounceableTraits +{ +    // Call callWhenReady() with any callable accepting T. +    typedef boost::function<void (typename boost::call_traits<T>::param_type)> func_t; +    // Our actual queue is a simple queue of such callables. +    typedef std::queue<func_t> queue_t; +    // owner pointer type +    typedef LLPounceable<T, TAG>* owner_ptr; +}; + +// Tag types distinguish the two different implementations of LLPounceable's +// queue. +struct LLPounceableQueue {}; +struct LLPounceableStatic {}; + +// generic LLPounceableQueueImpl deliberately omitted: only the above tags are +// legal +template <typename T, class TAG> +class LLPounceableQueueImpl; + +// The implementation selected by LLPounceableStatic uses an LLSingleton +// because we can't count on a data member queue being initialized at the time +// we start getting callWhenReady() calls. This is that LLSingleton. +template <typename T> +class LLPounceableQueueSingleton: +    public LLSingleton<LLPounceableQueueSingleton<T> > +{ +private: +    typedef LLPounceableTraits<T, LLPounceableStatic> traits; +    typedef typename traits::owner_ptr owner_ptr; +    typedef typename traits::queue_t queue_t; + +    // For a given held type T, every LLPounceable<T, LLPounceableStatic> +    // instance will call on the SAME LLPounceableQueueSingleton instance -- +    // given how class statics work. We must keep a separate queue for each +    // LLPounceable instance. Use a hash map for that. +    typedef boost::unordered_map<owner_ptr, queue_t> map_t; + +public: +    // Disambiguate queues belonging to different LLPounceables. +    queue_t& get(owner_ptr owner) +    { +        // operator[] has find-or-create semantics -- just what we want! +        return mMap[owner]; +    } + +private: +    map_t mMap; +}; + +// LLPounceableQueueImpl that uses the above LLSingleton +template <typename T> +class LLPounceableQueueImpl<T, LLPounceableStatic> +{ +public: +    typedef LLPounceableTraits<T, LLPounceableStatic> traits; +    typedef typename traits::owner_ptr owner_ptr; +    typedef typename traits::queue_t queue_t; + +    queue_t& get(owner_ptr owner) const +    { +        // this Impl contains nothing; it delegates to the Singleton +        return LLPounceableQueueSingleton<T>::instance().get(owner); +    } +}; + +// The implementation selected by LLPounceableQueue directly contains the +// queue of interest, suitable for an LLPounceable we can trust to be fully +// initialized when it starts getting callWhenReady() calls. +template <typename T> +class LLPounceableQueueImpl<T, LLPounceableQueue> +{ +public: +    typedef LLPounceableTraits<T, LLPounceableQueue> traits; +    typedef typename traits::owner_ptr owner_ptr; +    typedef typename traits::queue_t queue_t; + +    queue_t& get(owner_ptr) +    { +        return mQueue; +    } + +private: +    queue_t mQueue; +}; + +// LLPounceable<T> is for an LLPounceable instance on the heap or the stack. +// LLPounceable<T, LLPounceableStatic> is for a static LLPounceable instance. +template <typename T, class TAG=LLPounceableQueue> +class LLPounceable +{ +private: +    typedef LLPounceableTraits<T, TAG> traits; +    typedef typename traits::owner_ptr owner_ptr; +    typedef typename traits::queue_t queue_t; + +public: +    typedef typename traits::func_t func_t; + +    // By default, both the initial value and the distinguished empty value +    // are a default-constructed T instance. However you can explicitly +    // specify each. +    LLPounceable(typename boost::call_traits<T>::value_type init =boost::value_initialized<T>(), +                 typename boost::call_traits<T>::param_type empty=boost::value_initialized<T>()): +        mHeld(init), +        mEmpty(empty) +    {} + +    // make read access to mHeld as cheap and transparent as possible +    operator T () const { return mHeld; } +    typename boost::remove_pointer<T>::type operator*() const { return *mHeld; } +    typename boost::call_traits<T>::value_type operator->() const { return mHeld; } +    // uncomment 'explicit' as soon as we allow C++11 compilation +    /*explicit*/ operator bool() const { return bool(mHeld); } +    bool operator!() const { return ! mHeld; } + +    // support both assignment (dumb ptr idiom) and reset() (smart ptr) +    void operator=(typename boost::call_traits<T>::param_type value) +    { +        reset(value); +    } + +    void reset(typename boost::call_traits<T>::param_type value) +    { +        mHeld = value; +        // If this new value is non-empty, flush anything pending in the queue. +        if (mHeld != mEmpty) +        { +            queue_t& queue(get_queue()); +            while (! queue.empty()) +            { +                queue.front()(mHeld); +                queue.pop(); +            } +        } +    } + +    // our claim to fame +    void callWhenReady(const func_t& func) +    { +        if (mHeld != mEmpty) +        { +            // If the held value is already non-empty, immediately call func() +            func(mHeld); +        } +        else +        { +            // held value still empty, queue func() for later +            get_queue().push(func); +        } +    } + +private: +    queue_t& get_queue() { return mQueue.get(this); } + +    // Store both the current and the empty value. +    // MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of +    // testing for "empty." For some types we want operator!(); for others we +    // want to compare to a distinguished value. +    typename boost::call_traits<T>::value_type mHeld, mEmpty; +    // This might either contain the queue (LLPounceableQueue) or delegate to +    // an LLSingleton (LLPounceableStatic). +    LLPounceableQueueImpl<T, TAG> mQueue; +}; + +#endif /* ! defined(LL_LLPOUNCEABLE_H) */ diff --git a/indra/llcommon/tests/llpounceable_test.cpp b/indra/llcommon/tests/llpounceable_test.cpp new file mode 100644 index 0000000000..1f8cdca145 --- /dev/null +++ b/indra/llcommon/tests/llpounceable_test.cpp @@ -0,0 +1,200 @@ +/** + * @file   llpounceable_test.cpp + * @author Nat Goodspeed + * @date   2015-05-22 + * @brief  Test for llpounceable. + *  + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Copyright (c) 2015, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llpounceable.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "../test/lltut.h" + +struct Data +{ +    Data(const std::string& data): +        mData(data) +    {} +    const std::string mData; +}; + +void setter(Data** dest, Data* ptr) +{ +    *dest = ptr; +} + +static Data* static_check = 0; + +// Set up an extern pointer to an LLPounceableStatic so the linker will fill +// in the forward reference from below, before runtime. +extern LLPounceable<Data*, LLPounceableStatic> gForward; + +struct EnqueueCall +{ +    EnqueueCall() +    { +        // Intentionally use a forward reference to an LLPounceableStatic that +        // we believe is NOT YET CONSTRUCTED. This models the scenario in +        // which a constructor in another translation unit runs before +        // constructors in this one. We very specifically want callWhenReady() +        // to work even in that case: we need the LLPounceableQueueImpl to be +        // initialized even if the LLPounceable itself is not. +        gForward.callWhenReady(boost::bind(setter, &static_check, _1)); +    } +} nqcall; +// When this declaration is processed, we should enqueue the +// setter(&static_check, _1) call for when gForward is set non-NULL. Needless +// to remark, we want this call not to crash. + +// Now declare gForward. Its constructor should not run until after nqcall's. +LLPounceable<Data*, LLPounceableStatic> gForward; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llpounceable_data +    { +    }; +    typedef test_group<llpounceable_data> llpounceable_group; +    typedef llpounceable_group::object object; +    llpounceable_group llpounceablegrp("llpounceable"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("LLPounceableStatic out-of-order test"); +        // LLPounceable<T, LLPounceableStatic>::callWhenReady() must work even +        // before LLPounceable's constructor runs. That's the whole point of +        // implementing it with an LLSingleton queue. This models (say) +        // LLPounceableStatic<LLMessageSystem*, LLPounceableStatic>. +        ensure("static_check should still be null", ! static_check); +        Data myData("test<1>"); +        gForward = &myData;         // should run setter +        ensure_equals("static_check should be &myData", static_check, &myData); +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("LLPounceableQueue different queues"); +        // We expect that LLPounceable<T, LLPounceableQueue> should have +        // different queues because that specialization stores the queue +        // directly in the LLPounceable instance. +        Data *aptr = 0, *bptr = 0; +        LLPounceable<Data*> a, b; +        a.callWhenReady(boost::bind(setter, &aptr, _1)); +        b.callWhenReady(boost::bind(setter, &bptr, _1)); +        ensure("aptr should be null", ! aptr); +        ensure("bptr should be null", ! bptr); +        Data adata("a"), bdata("b"); +        a = &adata; +        ensure_equals("aptr should be &adata", aptr, &adata); +        // but we haven't yet set b +        ensure("bptr should still be null", !bptr); +        b = &bdata; +        ensure_equals("bptr should be &bdata", bptr, &bdata); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("LLPounceableStatic different queues"); +        // LLPounceable<T, LLPounceableStatic> should also have a distinct +        // queue for each instance, but that engages an additional map lookup +        // because there's only one LLSingleton for each T. +        Data *aptr = 0, *bptr = 0; +        LLPounceable<Data*, LLPounceableStatic> a, b; +        a.callWhenReady(boost::bind(setter, &aptr, _1)); +        b.callWhenReady(boost::bind(setter, &bptr, _1)); +        ensure("aptr should be null", ! aptr); +        ensure("bptr should be null", ! bptr); +        Data adata("a"), bdata("b"); +        a = &adata; +        ensure_equals("aptr should be &adata", aptr, &adata); +        // but we haven't yet set b +        ensure("bptr should still be null", !bptr); +        b = &bdata; +        ensure_equals("bptr should be &bdata", bptr, &bdata); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("LLPounceable<T> looks like T"); +        // We want LLPounceable<T, TAG> to be drop-in replaceable for a plain +        // T for read constructs. In particular, it should behave like a dumb +        // pointer -- and with zero abstraction cost for such usage. +        Data* aptr = 0; +        Data a("a"); +        // should be able to initialize a pounceable (when its constructor +        // runs) +        LLPounceable<Data*> pounceable = &a; +        // should be able to pass LLPounceable<T> to function accepting T +        setter(&aptr, pounceable); +        ensure_equals("aptr should be &a", aptr, &a); +        // should be able to dereference with * +        ensure_equals("deref with *", (*pounceable).mData, "a"); +        // should be able to dereference with -> +        ensure_equals("deref with ->", pounceable->mData, "a"); +        // bool operations +        ensure("test with operator bool()", pounceable); +        ensure("test with operator !()", ! (! pounceable)); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("Multiple callWhenReady() queue items"); +        Data *p1 = 0, *p2 = 0, *p3 = 0; +        Data a("a"); +        LLPounceable<Data*> pounceable; +        // queue up a couple setter() calls for later +        pounceable.callWhenReady(boost::bind(setter, &p1, _1)); +        pounceable.callWhenReady(boost::bind(setter, &p2, _1)); +        // should still be pending +        ensure("p1 should be null", !p1); +        ensure("p2 should be null", !p2); +        ensure("p3 should be null", !p3); +        pounceable = 0; +        // assigning a new empty value shouldn't flush the queue +        ensure("p1 should still be null", !p1); +        ensure("p2 should still be null", !p2); +        ensure("p3 should still be null", !p3); +        // using whichever syntax +        pounceable.reset(0); +        // try to make ensure messages distinct... tough to pin down which +        // ensure() failed if multiple ensure() calls in the same test<n> have +        // the same message! +        ensure("p1 should again be null", !p1); +        ensure("p2 should again be null", !p2); +        ensure("p3 should again be null", !p3); +        pounceable.reset(&a);       // should flush queue +        ensure_equals("p1 should be &a", p1, &a); +        ensure_equals("p2 should be &a", p2, &a); +        ensure("p3 still not set", !p3); +        // immediate call +        pounceable.callWhenReady(boost::bind(setter, &p3, _1)); +        ensure_equals("p3 should be &a", p3, &a); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("compile-fail test, uncomment to check"); +        // The following declaration should fail: only LLPounceableQueue and +        // LLPounceableStatic should work as tags. +//      LLPounceable<Data*, int> pounceable; +    } +} // namespace tut | 
