summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2016-10-12 23:01:48 -0400
committerNat Goodspeed <nat@lindenlab.com>2016-10-12 23:01:48 -0400
commit763509589dc9933d04a26e8109f90591eef1d012 (patch)
treeb74614fa4933e02ecee3f0f6466dd4799cead0e7
parent704c53b3c506c1274981b3e1ca5a22c16e4fbbb4 (diff)
MAINT-5232: Add LLHeteroMap to contain objects of unrelated classes.
-rw-r--r--indra/llcommon/CMakeLists.txt3
-rw-r--r--indra/llcommon/llheteromap.cpp32
-rw-r--r--indra/llcommon/llheteromap.h95
-rw-r--r--indra/llcommon/tests/llheteromap_test.cpp158
4 files changed, 288 insertions, 0 deletions
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