//------------------------------------------------------------------------ // Project : SDK Base // Version : 1.0 // // Category : Helpers // Filename : base/source/updatehandler.cpp // Created by : Steinberg, 2008 // Description : // //----------------------------------------------------------------------------- // This file is part of a Steinberg SDK. It is subject to the license terms // in the LICENSE file found in the top-level directory of this distribution // and at www.steinberg.net/sdklicenses. // No part of the SDK, including this file, may be copied, modified, propagated, // or distributed except according to the terms contained in the LICENSE file. //----------------------------------------------------------------------------- #include "base/source/updatehandler.h" #include "base/source/classfactoryhelpers.h" #include "base/source/fstring.h" #if SMTG_CPP11_STDLIBSUPPORT #include #else #include #endif #include #include #include #define NON_EXISTING_DEPENDENCY_CHECK 0 // not yet #define CLASS_NAME_TRACKED DEVELOPMENT using Steinberg::Base::Thread::FGuard; namespace Steinberg { DEF_CLASS_IID (IUpdateManager) namespace Update { const uint32 kHashSize = (1 << 8); // must be power of 2 (16 bytes * 256 == 4096) const uint32 kMapSize = 1024 * 10; //------------------------------------------------------------------------ inline uint32 hashPointer (void* p) { return (uint32)((uint64 (p) >> 12) & (kHashSize - 1)); } //------------------------------------------------------------------------ inline IPtr getUnknownBase (FUnknown* unknown) { FUnknown* result = nullptr; if (unknown) { if (unknown->queryInterface (FObject::iid, (void**)&result) != kResultTrue) unknown->queryInterface (FUnknown::iid, (void**)&result); } return owned (result); } #if CLASS_NAME_TRACKED //------------------------------------------------------------------------ struct Dependency { Dependency (FUnknown* o, IDependent* d) : obj (o), dep (d), objClass (nullptr), depClass (nullptr) { } inline bool operator== (const Dependency& d) const { return obj == d.obj; } inline bool operator!= (const Dependency& d) const { return obj != d.obj; } inline bool operator< (const Dependency& d) const { return obj < d.obj; } inline bool operator> (const Dependency& d) const { return obj > d.obj; } FUnknown* obj; IDependent* dep; FClassID objClass; FClassID depClass; }; #endif //------------------------------------------------------------------------ struct DeferedChange { DeferedChange (FUnknown* o, int32 m = 0) : obj (o), msg (m) {} ~DeferedChange () {} DeferedChange (const DeferedChange& r) : obj (r.obj), msg (r.msg) {} inline bool operator== (const DeferedChange& d) const { return obj == d.obj; } inline bool operator!= (const DeferedChange& d) const { return obj != d.obj; } FUnknown* obj; int32 msg; }; //------------------------------------------------------------------------ struct UpdateData { UpdateData (FUnknown* o, IDependent** d, uint32 c) : obj (o), dependents (d), count (c) { } FUnknown* obj; IDependent** dependents; uint32 count; bool operator== (const UpdateData& d) const { return d.obj == obj && d.dependents == dependents; } }; //------------------------------------------------------------------------ using DeferedChangeList = std::deque; using DeferedChangeListIterConst = DeferedChangeList::const_iterator; using DeferedChangeListIter = DeferedChangeList::iterator; using UpdateDataList = std::deque; using UpdateDataListIterConst = UpdateDataList::const_iterator; #if CLASS_NAME_TRACKED using DependentList = std::vector; #else typedef std::vector DependentList; #endif using DependentListIter = DependentList::iterator; using DependentListIterConst = DependentList::const_iterator; #if SMTG_CPP11_STDLIBSUPPORT using DependentMap = std::unordered_map; #else typedef std::map DependentMap; #endif using DependentMapIter = DependentMap::iterator; using DependentMapIterConst = DependentMap::const_iterator; struct Table { DependentMap depMap[kHashSize]; DeferedChangeList defered; UpdateDataList updateData; }; //------------------------------------------------------------------------ void updateDone (FUnknown* unknown, int32 message) { if (message != IDependent::kDestroyed) { FObject* obj = FObject::unknownToObject (unknown); if (obj) obj->updateDone (message); } } } // namespace Update //------------------------------------------------------------------------ static int32 countEntries (Update::DependentMap& map) { int32 total = 0; Update::DependentMapIterConst iterMap = map.begin (); while (iterMap != map.end ()) { const Update::DependentList& list = iterMap->second; Update::DependentListIterConst iterList = list.begin (); while (iterList != list.end ()) { total++; ++iterList; } ++iterMap; } return total; } //------------------------------------------------------------------------ UpdateHandler::UpdateHandler () { table = NEW Update::Table; if (FObject::getUpdateHandler () == nullptr) FObject::setUpdateHandler (this); } //------------------------------------------------------------------------ UpdateHandler::~UpdateHandler () { if (FObject::getUpdateHandler () == this) FObject::setUpdateHandler (nullptr); delete table; table = nullptr; } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::addDependent (FUnknown* u, IDependent* _dependent) { IPtr unknown = Update::getUnknownBase (u); if (!unknown || !_dependent) return kResultFalse; FGuard guard (lock); #if CLASS_NAME_TRACKED Update::Dependency dependent (unknown, _dependent); FObject* obj = FObject::unknownToObject (unknown); if (obj) dependent.objClass = obj->isA (); obj = FObject::unknownToObject (_dependent); if (obj) dependent.depClass = obj->isA (); #else IDependent* dependent = _dependent; #endif Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIter it = map.find (unknown); if (it == map.end ()) { Update::DependentList list; list.push_back (dependent); map[unknown] = list; } else { (*it).second.push_back (dependent); } return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* dependent) { size_t eraseCount; return removeDependent (u, dependent, eraseCount); } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* dependent, size_t& eraseCount) { eraseCount = 0; IPtr unknown = Update::getUnknownBase (u); if (unknown == nullptr && dependent == nullptr) return kResultFalse; FGuard guard (lock); Update::UpdateDataListIterConst iter = table->updateData.begin (); while (iter != table->updateData.end ()) { if ((*iter).obj == unknown || unknown == nullptr) { for (uint32 count = 0; count < (*iter).count; count++) { if ((*iter).dependents[count] == dependent) (*iter).dependents[count] = nullptr; } } ++iter; } // Remove all objects for the given dependent if (unknown == nullptr) { for (uint32 j = 0; j < Update::kHashSize; j++) { Update::DependentMap& map = table->depMap[j]; Update::DependentMapIter iterMap = map.begin (); while (iterMap != map.end ()) { Update::DependentList& list = (*iterMap).second; Update::DependentListIter iterList = list.begin (); bool listIsEmpty = false; while (iterList != list.end ()) { #if CLASS_NAME_TRACKED if ((*iterList).dep == dependent) #else if ((*iterList) == dependent) #endif { eraseCount = list.size (); if (list.size () == 1u) { listIsEmpty = true; break; } iterList = list.erase (iterList); } else { ++iterList; } } if (listIsEmpty) iterMap = map.erase (iterMap); else ++iterMap; } } } else { bool mustFlush = true; Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIter iterList = map.find (unknown); #if NON_EXISTING_DEPENDENCY_CHECK SMTG_ASSERT (iterList != map.end () && "ERROR: Trying to remove a non existing dependency!") #endif if (iterList != map.end ()) { if (dependent == nullptr) // Remove all dependents of object { eraseCount = iterList->second.size (); map.erase (iterList); } else // Remove one dependent { Update::DependentList& dependentlist = (*iterList).second; Update::DependentListIter iterDependentlist = dependentlist.begin (); while (iterDependentlist != dependentlist.end ()) { #if CLASS_NAME_TRACKED if ((*iterDependentlist).dep == dependent) #else if ((*iterDependentlist) == dependent) #endif { iterDependentlist = dependentlist.erase (iterDependentlist); eraseCount++; if (dependentlist.empty ()) { map.erase (iterList); break; } } else { ++iterDependentlist; mustFlush = false; } } } } if (mustFlush) cancelUpdates (unknown); } return kResultTrue; } //------------------------------------------------------------------------ tresult UpdateHandler::doTriggerUpdates (FUnknown* u, int32 message, bool suppressUpdateDone) { IPtr unknown = Update::getUnknownBase (u); if (!unknown) return kResultFalse; // to avoid crashes due to stack overflow, we reduce the amount of memory reserved for the // dependents IDependent* smallDependents[Update::kMapSize / 10]; // 8kB for x64 IDependent** dependents = smallDependents; int32 maxDependents = Update::kMapSize / 10; int32 count = 0; { FGuard guard (lock); // scope for lock Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIterConst iterList = map.find (unknown); if (iterList != map.end ()) { const Update::DependentList& dependentlist = (*iterList).second; Update::DependentListIterConst iterDependentlist = dependentlist.begin (); while (iterDependentlist != dependentlist.end ()) { #if CLASS_NAME_TRACKED dependents[count] = (*iterDependentlist).dep; #else dependents[count] = *iterDependentlist; #endif count++; if (count >= maxDependents) { if (dependents == smallDependents) { dependents = NEW IDependent*[Update::kMapSize]; memcpy (dependents, smallDependents, count * sizeof (dependents[0])); maxDependents = Update::kMapSize; } else { SMTG_WARNING ("Dependency overflow") break; } } ++iterDependentlist; } } // push update data on the stack if (count > 0) { Update::UpdateData data (unknown, dependents, count); table->updateData.push_back (data); } } // end scope int32 i = 0; while (i < count) { if (dependents[i]) dependents[i]->update (unknown, message); i++; } if (dependents != smallDependents) delete[] dependents; // remove update data from the stack if (count > 0) { FGuard guard (lock); table->updateData.pop_back (); } // send update done message if (suppressUpdateDone == false) Update::updateDone (unknown, message); return count > 0 ? kResultTrue : kResultFalse; // Object was found and has been updated } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::triggerUpdates (FUnknown* u, int32 message) { return doTriggerUpdates (u, message, false); } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::deferUpdates (FUnknown* u, int32 message) { IPtr unknown = Update::getUnknownBase (u); if (!unknown) return kResultFalse; FGuard guard (lock); Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIterConst iterList = map.find (unknown); bool hasDependency = (iterList != map.end ()); if (hasDependency == false) { Update::updateDone (unknown, message); return kResultTrue; } bool found = false; Update::DeferedChangeListIterConst iter = table->defered.begin (); while (iter != table->defered.end ()) { if ((*iter).obj == unknown && (*iter).msg == message) { found = true; break; } ++iter; } if (!found) { Update::DeferedChange change (unknown, message); table->defered.push_back (change); } return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::triggerDeferedUpdates (FUnknown* unknown) { Update::DeferedChangeList deferedAgain; if (!unknown) { while (table->defered.empty () == false) { // Remove first from queue lock.lock (); FUnknown* obj = table->defered.front ().obj; int32 msg = table->defered.front ().msg; table->defered.pop_front (); // check if this object is currently being updated. in this case, defer update again... bool canSignal = true; Update::UpdateDataListIterConst iter = table->updateData.begin (); while (iter != table->updateData.end ()) { if ((*iter).obj == obj) { canSignal = false; break; } ++iter; } lock.unlock (); if (canSignal) { triggerUpdates (obj, msg); } else { Update::DeferedChange change (obj, msg); deferedAgain.push_back (change); } } } else { IPtr object = Update::getUnknownBase (unknown); Update::DeferedChange tmp (object); while (true) { lock.lock (); Update::DeferedChangeListIter it = std::find (table->defered.begin (), table->defered.end (), tmp); if (it == table->defered.end ()) { lock.unlock (); return kResultTrue; } if ((*it).obj != nullptr) { int32 msg = (*it).msg; table->defered.erase (it); // check if this object is currently being updated. in this case, defer update // again... bool canSignal = true; Update::UpdateDataListIterConst iter = table->updateData.begin (); while (iter != table->updateData.end ()) { if ((*iter).obj == object) { canSignal = false; break; } ++iter; } lock.unlock (); if (canSignal) { triggerUpdates (object, msg); } else { Update::DeferedChange change (object, msg); deferedAgain.push_back (change); } } } } if (deferedAgain.empty () == false) { FGuard guard (lock); Update::DeferedChangeListIterConst iter = deferedAgain.begin (); while (iter != deferedAgain.end ()) { table->defered.push_back (*iter); ++iter; } } return kResultTrue; } //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::cancelUpdates (FUnknown* u) { IPtr unknown = Update::getUnknownBase (u); if (!unknown) return kResultFalse; FGuard guard (lock); Update::DeferedChange change (unknown, 0); while (true) { auto iter = std::find (table->defered.begin (), table->defered.end (), change); if (iter != table->defered.end ()) table->defered.erase (iter); else break; } return kResultTrue; } //------------------------------------------------------------------------ size_t UpdateHandler::countDependencies (FUnknown* object) { FGuard guard (lock); uint32 res = 0; IPtr unknown = Update::getUnknownBase (object); if (unknown) { Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIter iterList = map.find (unknown); if (iterList != map.end ()) return iterList->second.size (); } else { for (uint32 j = 0; j < Update::kHashSize; j++) { Update::DependentMap& map = table->depMap[j]; res += countEntries (map); } } return res; } #if DEVELOPMENT //------------------------------------------------------------------------ bool UpdateHandler::checkDeferred (FUnknown* object) { IPtr unknown = Update::getUnknownBase (object); FGuard guard (lock); Update::DeferedChange tmp (unknown); Update::DeferedChangeListIterConst it = std::find (table->defered.begin (), table->defered.end (), tmp); if (it != table->defered.end ()) return true; return false; } //------------------------------------------------------------------------ bool UpdateHandler::hasDependencies (FUnknown* u) { IPtr unknown = Update::getUnknownBase (u); if (!unknown) return false; FGuard guard (lock); Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIterConst iterList = map.find (unknown); bool hasDependency = (iterList != map.end ()); return hasDependency; } //------------------------------------------------------------------------ void UpdateHandler::printForObject (FObject* obj) const { IPtr unknown = Update::getUnknownBase (obj); if (!unknown) return; FUnknownPtr dep (obj); bool header = false; Update::DependentMap& map = table->depMap[Update::hashPointer (unknown)]; Update::DependentMapIterConst iterList = map.begin (); while (iterList != map.end ()) { const Update::DependentList& dependentlist = (*iterList).second; Update::DependentListIterConst iterDependentlist = dependentlist.begin (); while (iterDependentlist != dependentlist.end ()) { #if CLASS_NAME_TRACKED if ((*iterList).first == unknown || (*iterDependentlist).dep == dep.getInterface ()) { if (!header) { FDebugPrint ("Dependencies for object %8" FORMAT_INT64A " %s\n", (uint64)obj, obj->isA ()); header = true; } FDebugPrint ("%s %8" FORMAT_INT64A "\n <- %s %8" FORMAT_INT64A "\n", (*iterDependentlist).depClass, (uint64) (*iterDependentlist).dep, (*iterDependentlist).objClass, (uint64) ((*iterList).first)); } #else if ((*iterList).first == unknown || (*iterDependentlist) == dep.getInterface ()) { if (!header) { FDebugPrint ("Dependencies for object %8" FORMAT_INT64A " %s\n", (uint64)obj, obj->isA ()); header = true; } FDebugPrint ("%8" FORMAT_INT64A "\n <- %8" FORMAT_INT64A "\n", (uint64) (*iterDependentlist), (uint64) ((*iterList).first)); } #endif ++iterDependentlist; } ++iterList; } } #endif //------------------------------------------------------------------------ } // namespace Steinberg