From 763509589dc9933d04a26e8109f90591eef1d012 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Wed, 12 Oct 2016 23:01:48 -0400
Subject: MAINT-5232: Add LLHeteroMap to contain objects of unrelated classes.

---
 indra/llcommon/CMakeLists.txt             |   3 +
 indra/llcommon/llheteromap.cpp            |  32 ++++++
 indra/llcommon/llheteromap.h              |  95 ++++++++++++++++++
 indra/llcommon/tests/llheteromap_test.cpp | 158 ++++++++++++++++++++++++++++++
 4 files changed, 288 insertions(+)
 create mode 100644 indra/llcommon/llheteromap.cpp
 create mode 100644 indra/llcommon/llheteromap.h
 create mode 100644 indra/llcommon/tests/llheteromap_test.cpp

(limited to 'indra/llcommon')

diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 99514fdbf7..d9ab7c1b38 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -68,6 +68,7 @@ set(llcommon_SOURCE_FILES
     llformat.cpp
     llframetimer.cpp
     llheartbeat.cpp
+    llheteromap.cpp
     llinitparam.cpp
     llinitdestroyclass.cpp
     llinstancetracker.cpp
@@ -173,6 +174,7 @@ set(llcommon_HEADER_FILES
     llhandle.h
     llhash.h
     llheartbeat.h
+    llheteromap.h
     llindexedvector.h
     llinitdestroyclass.h
     llinitparam.h
@@ -337,6 +339,7 @@ if (LL_TESTS)
   LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
   LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
   LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
+  LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
 
 ## llexception_test.cpp isn't a regression test, and doesn't need to be run
 ## every build. It's to help a developer make implementation choices about
diff --git a/indra/llcommon/llheteromap.cpp b/indra/llcommon/llheteromap.cpp
new file mode 100644
index 0000000000..7c19196e0c
--- /dev/null
+++ b/indra/llcommon/llheteromap.cpp
@@ -0,0 +1,32 @@
+/**
+ * @file   llheteromap.cpp
+ * @author Nat Goodspeed
+ * @date   2016-10-12
+ * @brief  Implementation for llheteromap.
+ * 
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llheteromap.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+LLHeteroMap::~LLHeteroMap()
+{
+    // For each entry in our map, we must call its deleter, which is the only
+    // record we have of its original type.
+    for (TypeMap::iterator mi(mMap.begin()), me(mMap.end()); mi != me; ++mi)
+    {
+        // mi->second is the std::pair; mi->second.first is the void*;
+        // mi->second.second points to the deleter function
+        (mi->second.second)(mi->second.first);
+        mi->second.first = NULL;
+    }
+}
diff --git a/indra/llcommon/llheteromap.h b/indra/llcommon/llheteromap.h
new file mode 100644
index 0000000000..9d6f303d08
--- /dev/null
+++ b/indra/llcommon/llheteromap.h
@@ -0,0 +1,95 @@
+/**
+ * @file   llheteromap.h
+ * @author Nat Goodspeed
+ * @date   2016-10-12
+ * @brief  Map capable of storing objects of diverse types, looked up by type.
+ * 
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLHETEROMAP_H)
+#define LL_LLHETEROMAP_H
+
+#include <typeinfo>
+#include <utility>                  // std::pair
+#include <map>
+
+/**
+ * LLHeteroMap addresses an odd requirement. Usually when you want to put
+ * objects of different classes into a runtime collection of any kind, you
+ * derive them all from a common base class and store pointers to that common
+ * base class.
+ *
+ * LLInitParam::BaseBlock uses disturbing raw-pointer arithmetic to find data
+ * members in its subclasses. It seems that no BaseBlock subclass can be
+ * stored in a polymorphic class of any kind: the presence of a vtbl pointer
+ * in the layout silently throws off the reinterpret_cast arithmetic. Bad
+ * Things result. (Many thanks to Nicky D for this analysis!)
+ *
+ * LLHeteroMap collects objects WITHOUT a common base class, retrieves them by
+ * object type and destroys them when the LLHeteroMap is destroyed.
+ */
+class LLHeteroMap
+{
+public:
+    ~LLHeteroMap();
+
+    /// find or create
+    template <class T>
+    T& obtain()
+    {
+        // Look up map entry by typeid(T). We don't simply use mMap[typeid(T)]
+        // because that requires default-constructing T on every lookup. For
+        // some kinds of T, that could be expensive.
+        TypeMap::iterator found = mMap.find(&typeid(T));
+        if (found == mMap.end())
+        {
+            // Didn't find typeid(T). Create an entry. Because we're storing
+            // only a void* in the map, discarding type information, make sure
+            // we capture that type information in our deleter.
+            void* ptr = new T();
+            void (*dlfn)(void*) = &deleter<T>;
+            std::pair<TypeMap::iterator, bool> inserted =
+                mMap.insert(TypeMap::value_type(&typeid(T),
+                    TypeMap::mapped_type(ptr, dlfn)));
+            // Okay, now that we have an entry, claim we found it.
+            found = inserted.first;
+        }
+        // found->second is the std::pair; second.first is the void*
+        // pointer to the object in question. Cast it to correct type and
+        // dereference it.
+        return *(static_cast<T*>(found->second.first));
+    }
+
+private:
+    template <class T>
+    static
+    void deleter(void* p)
+    {
+        delete static_cast<T*>(p);
+    }
+
+    // Comparing two std::type_info* values is tricky, because the standard
+    // does not guarantee that there will be only one type_info instance for a
+    // given type. In other words, &typeid(A) in one part of the program may
+    // not always equal &typeid(A) in some other part. Use special comparator.
+    struct type_info_ptr_comp
+    {
+        bool operator()(const std::type_info* lhs, const std::type_info* rhs)
+        {
+            return lhs->before(*rhs);
+        }
+    };
+
+    // What we actually store is a map from std::type_info (permitting lookup
+    // by object type) to a void* pointer to the object PLUS its deleter.
+    typedef std::map<
+        const std::type_info*, std::pair<void*, void (*)(void*)>,
+        type_info_ptr_comp>
+    TypeMap;
+    TypeMap mMap;
+};
+
+#endif /* ! defined(LL_LLHETEROMAP_H) */
diff --git a/indra/llcommon/tests/llheteromap_test.cpp b/indra/llcommon/tests/llheteromap_test.cpp
new file mode 100644
index 0000000000..4f38933e50
--- /dev/null
+++ b/indra/llcommon/tests/llheteromap_test.cpp
@@ -0,0 +1,158 @@
+/**
+ * @file   llheteromap_test.cpp
+ * @author Nat Goodspeed
+ * @date   2016-10-12
+ * @brief  Test for llheteromap.
+ * 
+ * $LicenseInfo:firstyear=2016&license=viewerlgpl$
+ * Copyright (c) 2016, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llheteromap.h"
+// STL headers
+#include <set>
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+
+static std::string clog;
+static std::set<std::string> dlog;
+
+std::ostream& operator<<(std::ostream& out, const std::set<std::string>& strset)
+{
+    out << '{';
+    const char* delim = "";
+    for (std::set<std::string>::const_iterator si(strset.begin()), se(strset.end());
+         si != se; ++si)
+    {
+        out << delim << '"' << *si << '"';
+        delim = ", ";
+    }
+    out << '}';
+    return out;
+}
+
+struct Chalk
+{
+    int dummy;
+    std::string name;
+
+    Chalk():
+        dummy(0)
+    {
+        clog.append("a");
+    }
+
+    ~Chalk()
+    {
+        dlog.insert("a");
+    }
+
+private:
+    Chalk(const Chalk&);            // no implementation
+};
+
+struct Cheese
+{
+    std::string name;
+
+    Cheese()
+    {
+        clog.append("e");
+    }
+
+    ~Cheese()
+    {
+        dlog.insert("e");
+    }
+
+private:
+    Cheese(const Cheese&);          // no implementation
+};
+
+struct Chowdah
+{
+    char displace[17];
+    std::string name;
+
+    Chowdah()
+    {
+        displace[0] = '\0';
+        clog.append("o");
+    }
+
+    ~Chowdah()
+    {
+        dlog.insert("o");
+    }
+
+private:
+    Chowdah(const Chowdah&);        // no implementation
+};
+
+/*****************************************************************************
+*   TUT
+*****************************************************************************/
+namespace tut
+{
+    struct llheteromap_data
+    {
+        llheteromap_data()
+        {
+            clog.erase();
+            dlog.clear();
+        }
+    };
+    typedef test_group<llheteromap_data> llheteromap_group;
+    typedef llheteromap_group::object object;
+    llheteromap_group llheteromapgrp("llheteromap");
+
+    template<> template<>
+    void object::test<1>()
+    {
+        set_test_name("create, get, delete");
+
+        {
+            LLHeteroMap map;
+
+            {
+                // create each instance
+                Chalk& chalk = map.obtain<Chalk>();
+                chalk.name = "Chalk";
+
+                Cheese& cheese = map.obtain<Cheese>();
+                cheese.name = "Cheese";
+
+                Chowdah& chowdah = map.obtain<Chowdah>();
+                chowdah.name = "Chowdah";
+            } // refs go out of scope
+
+            {
+                // verify each instance
+                Chalk& chalk = map.obtain<Chalk>();
+                ensure_equals(chalk.name, "Chalk");
+
+                Cheese& cheese = map.obtain<Cheese>();
+                ensure_equals(cheese.name, "Cheese");
+
+                Chowdah& chowdah = map.obtain<Chowdah>();
+                ensure_equals(chowdah.name, "Chowdah");
+            }
+        } // destroy map
+
+        // Chalk, Cheese and Chowdah should have been created in specific order
+        ensure_equals(clog, "aeo");
+
+        // We don't care what order they're destroyed in, as long as each is
+        // appropriately destroyed.
+        std::set<std::string> dtorset;
+        for (const char* cp = "aeo"; *cp; ++cp)
+            dtorset.insert(std::string(1, *cp));
+        ensure_equals(dlog, dtorset);
+    }
+} // namespace tut
-- 
cgit v1.2.3