/* ENTRY-ID.hpp - plain symbolic and hash ID used for accounting Copyright (C) Lumiera.org 2010, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file entry-id.hpp ** Bare symbolic and hash ID used for accounting of asset like entries. ** This ID can be used to register instances with an accounting table, without all the ** overhead of creating individual assets for each entry. The datafields in the symbolic part ** of the ID are similar to the asset identity tuple; the idea is to promote individual entries ** to full fledged assets on demand. Alongside with the symbolic identity, which can be reduced ** to just a Symbol and (compile time) type information, we store the derived hash value as LUID. ** ** @note as of 3/2010 this is an experimental setup and exists somewhat in parallel ** to the assets. We're still in the process of finding out what's really required ** to keep track of all the various kinds of objects. ///////////////////TICKET #739 ** @todo as of 11/2018 the basic design seems adequate, but the actual solution looks fishy. ** Even more so, since we now use subclasses of BareEntryID ** - as identity tag within lib::diff::GenNode ** - as identity tag for all [tangible UI elements](\ref stage::model::Tangible) ** There are various quirks and hacks to make all of this happen, and especially ** the hashed-in type information feels gratuitous at places, when all we actually ** need is a distinct identity plus a human readable symbol. ** ** @see asset::Asset::Ident ** @see entry-id-test.cpp ** */ #ifndef LIB_IDI_ENTRY_ID_H #define LIB_IDI_ENTRY_ID_H #include "lib/error.hpp" #include "lib/hash-indexed.hpp" #include "lib/idi/genfunc.hpp" #include "lib/util.hpp" #include #include #include namespace lib { /** * Identification Schemes. * Collection of commonly used mechanisms to build identification records, * unique identifiers, registration numbers and hashes. These are used as glue * and thin abstraction to link various subsystems or to allow interoperation * of registration facilities */ namespace idi { namespace error = lumiera::error; using std::string; using std::ostream; using lib::idi::generateSymbolicID; using lib::idi::getTypeHash; using lib::idi::typeSymbol; using lib::hash::LuidH; using lib::HashVal; namespace { /** lousy old tinkerer's trick: * hash values with poor distribution can be improved * by spreading the input with something close to the golden ratio. * Additionally, the scaling factor (for hashing) should be prime. * 2^32 * (√5-1)/2 = 2654435769.49723 */ const size_t KNUTH_MAGIC = 2654435761; /** build up a hash value, packaged as LUID. * @param sym symbolic ID-string to be hashed * @param seed (optional) hash value to combine with the sym. * @note This is a half baked preliminary solution. The issue here * is that LUID has a fixed size of 128bit, whereas the hash values * of the std library (and boost) have the smaller and platform dependent * type of \c size_t. This hack here assumes that size_t corresponds to void*, * which is correct for i386 and AMD64. LUID provides a hook for embedding a * void* (setting the trailing bits to zero). Finally we reinterpret the * char[] of the LUID as a LuidH class, which is ugly, but granted to work. * @todo several unsolved design problems. How to deal with std hash values in * conjunction with LUID. How to create a LuidH instance, if not generating * a new random value. How to make EntryID and asset::Ident interchangeable, /////////TICKET #739 * which would require both to yield the same hash values.... * @warning there is a weakness in boost::hash for strings of running numbers, * causing collisions already for a small set with less than 100000 entries. * To mitigate the problem, we hash in the trailing digits, and * spread them by the #KNUTH_MAGIC /////////TICKET #865 * @warning this code isn't portable and breaks if sizeof(size_t) < sizeof(void*) * @see HashGenerator_test#verify_Knuth_workaround */ inline LuidH buildHash (string const& sym, HashVal seed =0) { size_t l = sym.length(); if (l > 1) boost::hash_combine(seed, KNUTH_MAGIC * sym[l-1]); if (l > 2) boost::hash_combine(seed, KNUTH_MAGIC * sym[l-2]); if (l > 3) boost::hash_combine(seed, KNUTH_MAGIC * sym[l-3]); if (l > 4) boost::hash_combine(seed, KNUTH_MAGIC * sym[l-4]); ////////////////////////TICKET #865 boost::hash_combine(seed, sym); lumiera_uid tmpLUID; lumiera_uid_set_ptr (&tmpLUID, reinterpret_cast (seed)); return reinterpret_cast (tmpLUID); } } template struct EntryID; /** * type erased baseclass * for building a combined hash and symbolic ID. */ class BareEntryID : public boost::equality_comparable { string symbol_; LuidH hash_; protected: /** * Not to be created stand-alone. * derived classes feed down the specific type information * encoded into a hash seed. Thus even the same symbolicID * generates differing hash-IDs for different type parameters */ BareEntryID (string const& symbolID, HashVal seed) : symbol_(symbolID) , hash_(buildHash (symbol_, seed)) { } /** store the symbol but use a random hash part */ explicit BareEntryID (string const& symbolID) : symbol_(symbolID) , hash_{} // random { } public: /* default copyable and assignable */ bool isValid() const { return bool(hash_); } string const& getSym() const { return symbol_; } LuidH const& getHash() const { return hash_; } operator string() const; /** using BareEntryID derived objects as keys within std::unordered_map */ struct UseEmbeddedHash : public std::unary_function { size_t operator() (BareEntryID const& obj) const { return obj.getHash(); } }; template EntryID const& recast() const; }; /** * typed symbolic and hash ID for asset-like position accounting. * Allows for creating an entry with symbolic id and distinct type, * combined with an derived hash value, without the overhead in storage * and instance management imposed by using a full-fledged Asset. * * Similar to an Asset, an identification tuple is available (generated on the fly), * as is an unique LUID and total ordering. The type information is attached as * template parameter, but included into the hash calculation. All instantiations of the * EntryID template share a common baseclass, usable for type erased common registration. * @todo currently storing the symbolic-ID as string. It should be a lib::Symbol, * but this isn't possible unless we use a symbol table. //////TICKET #158 * @warning there is a weakness in boost::hash applied to strings. EntryID by default * generates symbolic IDs from a type prefix plus a running number, which causes * collisions already with less than 100000 entries. * @todo As a temporary workaround, we hash the symbolic ID twice, but we should look * into using a better hash function ////////////TICKET #865 * * @see mobject::session::Fork */ template struct EntryID : BareEntryID , boost::totally_ordered< EntryID > { /** case-1: auto generated symbolic ID */ EntryID() : BareEntryID (generateSymbolicID(), getTypeHash()) { } /** case-2: explicitly specify a symbolic ID to use. * The type information TY will be included automatically * into the generated hash-ID. This hash is reproducible. */ explicit EntryID (string const& symbolID) : BareEntryID (util::sanitise(symbolID), getTypeHash()) { } explicit EntryID (const char* symbolID) : BareEntryID (util::sanitise(symbolID), getTypeHash()) { } /** case-2b: rely on an internal, already sanitised symbol. * The symbol string will be passed through as-is, while * the type information from TY will be hashed in. */ explicit EntryID (Symbol const& internalSymbol) : BareEntryID (string(internalSymbol), getTypeHash()) { } /** @return true if the upcast would yield exactly the same * tuple (symbol,type) as was used on original definition * of an ID, based on the given BareEntryID. Implemented * by re-calculating the hash. */ static bool canRecast (BareEntryID const& bID) { return bID.getHash() == buildHash (bID.getSym(), getTypeHash()); } static EntryID const& recast (BareEntryID const& bID) { if (!canRecast(bID)) throw error::Logic ("unable to recast EntryID: desired type " "doesn't match original definition" , error::LUMIERA_ERROR_WRONG_TYPE); return static_cast (bID); } explicit operator string() const; friend bool operator< (EntryID const& i1, EntryID const& i2) { return i1.getSym() < i2.getSym(); } }; inline bool operator== (BareEntryID const& i1, BareEntryID const& i2) { return i1.getHash() == i2.getHash(); } /** * Entry-ID with a symbolic tag but just a plain random hash part. * @remarks use this flavour when it is _not relevant_ to tag with * some type information nor to reproduce the hash value. */ struct RandID : BareEntryID { RandID (string const& symbolID) : BareEntryID{util::sanitise (symbolID)} { } RandID (const char* symbolID) : BareEntryID{util::sanitise (symbolID)} { } RandID (Symbol const& internalSymbol) : BareEntryID{string{internalSymbol}} { } }; /** try to upcast this BareEntryID to a fully typed EntryID. * Effectively, this is the attempt to reverse a type erasure; * thus the caller needs to know the type information (as provided * by the template parameter), because this information can't be * recovered from the stored data. * @throws error::Logic if the given type parameter isn't exactly * the same as was used on creation of the original EntryID, * prior to type erasing it into a BareEntryID. Implemented * by re-calculating the hash from typeinfo + symbolicID; * Exception if it doesn't match the stored hash. */ template EntryID const& BareEntryID::recast() const { return EntryID::recast(*this); } inline BareEntryID::operator string() const { return "bID-"+lib::idi::format::instance_hex_format(symbol_, hash_); } template inline EntryID::operator string() const { return "ID<"+typeSymbol()+">-"+EntryID::getSym(); } }} // namespace lib::idi #endif /*LIB_IDI_ENTRY_ID_H*/