/* Ousía Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel 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 3 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, see . */ #include #include "Managed.hpp" #include "Manager.hpp" //#define MANAGER_DEBUG_PRINT #if defined(MANAGER_DEBUG_PRINT) || defined(MANAGER_GRAPHVIZ_EXPORT) #include #include #include "core/common/Rtti.hpp" #include "core/model/Node.hpp" #endif namespace ousia { /* Private Class ScopedIncrement */ /** * The ScopedIncrement class is used by the Manager to safely increment a * variable when a scope is entered and to decrement it when the scope is left. */ class ScopedIncrement { private: /** * Reference to the variable that should be incremented. */ int &i; public: /** * Constructor of ScopedIncrement. Increments the given variable. * * @param i is the variable that should be incremented. */ ScopedIncrement(int &i) : i(i) { i++; } /** * Destructor of ScopedIncrement. Decrements the referenced variable. */ ~ScopedIncrement() { i--; } }; /* Class Manager::ObjectDescriptor */ bool Manager::ObjectDescriptor::hasInRef() const { return rootRefCount > 0 || !refIn.empty(); } void Manager::ObjectDescriptor::incrDegree(RefDir dir, Managed *o) { // If the given Managed is null it refers to an input rooted reference if (o == nullptr) { rootRefCount++; return; } // Fetch a reference to either the input or the output reference map auto &m = dir == RefDir::IN ? refIn : refOut; // Insert a new entry or increment the corresponding reference counter auto it = m.find(o); if (it == m.end()) { m.emplace(std::make_pair(o, 1)); } else { it->second++; } } bool Manager::ObjectDescriptor::decrDegree(RefDir dir, Managed *o, bool all) { // If the given Managed is null it refers to an input rooted reference if (o == nullptr) { if (rootRefCount > 0) { if (all) { rootRefCount = 0; } else { rootRefCount--; } return true; } return false; } // Fetch a reference to either the input or the output reference map auto &m = dir == RefDir::IN ? refIn : refOut; // Decrement corresponding reference counter, delete the entry if the // reference counter reaches zero auto it = m.find(o); if (it != m.end()) { it->second--; if (it->second == 0 || all) { m.erase(it); } return true; } return false; } /* Class Manager */ Manager::~Manager() { // Perform a final sweep sweep(); // All objects should have been deleted! assert(objects.empty()); // Free all objects managed by the Managed manager (we'll get here if // assertions // are disabled) if (!objects.empty()) { ScopedIncrement incr{deletionRecursionDepth}; for (auto &e : objects) { delete e.first; } } } /* Class Manager: Garbage collection */ Manager::ObjectDescriptor *Manager::getDescriptor(Managed *o) { if (o) { auto it = objects.find(o); if (it != objects.end()) { return &(it->second); } } return nullptr; } void Manager::manage(Managed *o) { #ifdef MANAGER_DEBUG_PRINT std::cout << "manage " << o << std::endl; #endif objects.emplace(o, ObjectDescriptor{nextUid}); uids.emplace(nextUid, o); nextUid++; } void Manager::addRef(Managed *tar, Managed *src) { #ifdef MANAGER_DEBUG_PRINT std::cout << "addRef " << tar << " <- " << src << std::endl; #endif // Make sure the source and target manager are the same if (src) { assert(&tar->getManager() == &src->getManager()); } // Fetch the Managed descriptors for the two objects ObjectDescriptor *dTar = getDescriptor(tar); ObjectDescriptor *dSrc = getDescriptor(src); // Store the tar <- src reference assert(dTar); dTar->incrDegree(RefDir::IN, src); if (src) { // Store the src -> tar reference assert(dSrc); dSrc->incrDegree(RefDir::OUT, tar); } else { // We have just added a root reference, remove the element from the // list of marked objects marked.erase(tar); } } void Manager::deleteRef(Managed *tar, Managed *src, bool all) { #ifdef MANAGER_DEBUG_PRINT std::cout << "deleteRef " << tar << " <- " << src << std::endl; #endif // Fetch the Managed descriptors for the two objects ObjectDescriptor *dTar = getDescriptor(tar); ObjectDescriptor *dSrc = getDescriptor(src); // Decrement the output degree of the source Managed first if (dSrc) { dSrc->decrDegree(RefDir::OUT, tar, all); } // Decrement the input degree of the input Managed if (dTar && dTar->decrDegree(RefDir::IN, src, all)) { // If the Managed has a zero in degree, it can be safely deleted, // otherwise if it has no root reference, add it to the "marked" set // which is subject to tracing garbage collection if (!dTar->hasInRef()) { deleteObject(tar, dTar); } else if (dTar->rootRefCount == 0) { // Insert the Managed into the list of objects to be inspected by // garbage // collection marked.insert(tar); } } // Call the tracing garbage collector if the marked size is larger than the // actual value if (marked.size() >= threshold) { sweep(); } } void Manager::deleteObject(Managed *o, ObjectDescriptor *descr) { #ifdef MANAGER_DEBUG_PRINT std::cout << "deleteObject " << o << std::endl; #endif // Abort if the Managed already is on the "deleted" list if (deleted.count(o)) { return; } // Increment the recursion depth counter. The "deleteRef" function called // below may descend further into this function and the actual deletion // should be done in a single step. { ScopedIncrement incr{deletionRecursionDepth}; // Add the Managed to the "deleted" set deleted.insert(o); // Make sure all input references are deleted while (!descr->refIn.empty()) { deleteRef(o, descr->refIn.begin()->first, true); } // Add the Managed object to the orderedDeleted list -- this should // happen after all input references have been removed orderedDeleted.push_back(o); // Remove all output references of this Managed while (!descr->refOut.empty()) { deleteRef(descr->refOut.begin()->first, o, true); } // Remove the uid, data and event store entry uids.erase(descr->uid); store.erase(o); events.erase(o); // Remove the Managed from the "marked" set marked.erase(o); } purgeDeleted(); } void Manager::purgeDeleted() { // Perform the actual deletion if the recursion level is zero if (deletionRecursionDepth == 0 && !deleted.empty()) { // Increment the recursion depth so this function does not get called // again while deleting objects ScopedIncrement incr{deletionRecursionDepth}; for (size_t i = 0; i < orderedDeleted.size(); i++) { Managed *m = orderedDeleted[i]; deleted.erase(m); marked.erase(m); objects.erase(m); delete m; } orderedDeleted.clear(); assert(deleted.empty()); } } void Manager::sweep() { // Only execute sweep on the highest recursion level if (deletionRecursionDepth > 0) { return; } // Set containing objects which are reachable from a rooted Managed std::unordered_set reachable; // Deletion of objects may cause other objects to be added to the "marked" // list // so we repeat this process until objects are no longer deleted while (!marked.empty()) { // Repeat until all objects in the "marked" list have been visited while (!marked.empty()) { // Increment the deletionRecursionDepth counter to prevent deletion // of objects while sweep is running ScopedIncrement incr{deletionRecursionDepth}; // Fetch the next Managed in the "marked" list and remove it Managed *curManaged = *(marked.begin()); // Perform a breadth-first search starting from the current Managed bool isReachable = false; std::unordered_set visited{{curManaged}}; std::queue queue{{curManaged}}; while (!queue.empty() && !isReachable) { // Pop the next element from the queue, remove the element from // the marked list as we obviously have evaluated it curManaged = queue.front(); queue.pop(); marked.erase(curManaged); // Fetch the Managed descriptor ObjectDescriptor *descr = getDescriptor(curManaged); if (!descr) { continue; } // If this Managed is rooted, the complete visited subgraph is // rooted if (descr->rootRefCount > 0) { isReachable = true; break; } // Iterate over all objects leading to the current one for (auto &src : descr->refIn) { Managed *srcManaged = src.first; // Abort if the Managed already in the reachable list, // otherwise add the Managed to the queue if it was not // visited if (reachable.find(srcManaged) != reachable.end()) { isReachable = true; break; } else if (visited.find(srcManaged) == visited.end()) { visited.insert(srcManaged); queue.push(srcManaged); } } } // Insert the objects into the list of to be deleted objects or // reachable objects depending on the "isReachable" flag if (isReachable) { for (auto o : visited) { reachable.insert(o); } } else { for (auto o : visited) { deleteObject(o, getDescriptor(o)); } } } // Now purge all objects marked for deletion purgeDeleted(); } } /* Class Managed: Unique IDs */ ManagedUid Manager::getUid(Managed *o) { const auto it = objects.find(o); if (it != objects.end()) { return it->second.uid; } return 0; } Managed *Manager::getManaged(ManagedUid uid) { const auto it = uids.find(uid); if (it != uids.end()) { return it->second; } return nullptr; } /* Class Manager: Attached data */ void Manager::storeData(Managed *ref, const std::string &key, Managed *data) { // Add the new reference from the reference object to the data object addRef(data, ref); // Make sure a data map for the given reference object exists auto &map = store.emplace(ref, std::map{}).first->second; // Insert the given data for the key, decrement the references if auto it = map.find(key); if (it == map.end()) { map.insert(it, std::make_pair(key, data)); } else { // Do nothing if the same data is stored if (it->second == data) { return; } // Delete the reference from "ref" to the previously stored element. deleteRef(it->second, ref); // Insert a new reference and add the element to the map map.insert(map.erase(it), std::make_pair(key, data)); } } Managed *Manager::readData(Managed *ref, const std::string &key) const { // Try to find the reference element in the store auto storeIt = store.find(ref); if (storeIt != store.end()) { // Try to find the key in the map for the element auto &map = storeIt->second; auto mapIt = map.find(key); if (mapIt != map.end()) { return mapIt->second; } } return nullptr; } std::map Manager::readData(Managed *ref) const { // Try to find the map for the given reference element and return it auto storeIt = store.find(ref); if (storeIt != store.end()) { return storeIt->second; } return std::map{}; } bool Manager::deleteData(Managed *ref, const std::string &key) { // Find the reference element in the store auto storeIt = store.find(ref); if (storeIt != store.end()) { // Delete the key from the data map auto &map = storeIt->second; auto mapIt = map.find(key); if (mapIt != map.end()) { // Delete the reference from "ref" to the previously stored element deleteRef(mapIt->second, ref); // Remove the element from the list map.erase(mapIt); return true; } } return false; } /* Class Manager: Event handling */ EventId Manager::registerEvent(Managed *ref, EventType type, EventHandler handler, Managed *owner, void *data) { // Create a event handler descriptor and store it along with the auto &vec = events.emplace(ref, std::vector{}) .first->second; const EventHandlerDescriptor descr(type, handler, getUid(owner), data); for (size_t i = 0; i < vec.size(); i++) { if (!vec[i].handler) { vec[i] = descr; return i; } } vec.push_back(descr); return vec.size() - 1; } bool Manager::unregisterEvent(Managed *ref, EventId id) { auto eventsIt = events.find(ref); if (eventsIt != events.end()) { auto &vec = eventsIt->second; if (id < vec.size() && vec[id].handler) { // Remove the handler from the list by resetting handler and owner // to nullptr EventHandlerDescriptor &descr = vec[id]; descr.handler = nullptr; descr.ownerUid = 0; return true; } } return false; } bool Manager::unregisterEvent(Managed *ref, EventType type, EventHandler handler, Managed *owner, void *data) { auto eventsIt = events.find(ref); if (eventsIt != events.end()) { const ManagedUid ownerUid = getUid(owner); auto &vec = eventsIt->second; for (EventHandlerDescriptor &descr : vec) { if (descr.type == type && descr.handler == handler && descr.ownerUid == ownerUid && descr.data == data) { // Remove the handler from the list by resetting handler and // owner to nullptr descr.handler = nullptr; descr.ownerUid = 0; return true; } } } return false; } bool Manager::triggerEvent(Managed *ref, Event &ev) { bool hasHandler = false; auto eventsIt = events.find(ref); if (eventsIt != events.end()) { std::vector descrs = eventsIt->second; for (auto it = descrs.begin(); it != descrs.end();) { const EventHandlerDescriptor &descr = *it; if (descr.type == ev.type && descr.handler) { // Resolve the given owner uid to a managed pointer -- erase the // event handler if the owner no longer exists Managed *owner = nullptr; if (descr.ownerUid != 0) { owner = getManaged(descr.ownerUid); if (!owner) { it = descrs.erase(it); continue; } } // Call the event handler ev.sender = ref; descr.handler(ev, owner, descr.data); hasHandler = true; } it++; } } return hasHandler; } #ifdef MANAGER_GRAPHVIZ_EXPORT // Note: This is just an ugly hack used for development. Do not complain. You // have been warned. enum class EdgeType { NORMAL, DATA, AGGREGATE }; void Manager::exportGraphviz(const char *filename) { std::fstream fs(filename, std::ios_base::out); if (!fs.good()) { throw "Error while opening output file."; } fs << "digraph G {" << std::endl; fs << "\tnode [shape=plaintext,fontsize=10]" << std::endl; size_t idx = 0; for (auto &object : objects) { // References to the object descriptor and the object Managed *objectPtr = object.first; ObjectDescriptor &objectDescr = object.second; // Read flags, data and events bool isMarked = marked.count(objectPtr) > 0; bool isDeleted = deleted.count(objectPtr) > 0; std::map storeData = store.count(objectPtr) > 0 ? store[objectPtr] : std::map{}; std::vector eventData = events.count(objectPtr) > 0 ? events[objectPtr] : std::vector{}; // Read type information and Node name (if available) const RttiBase &type = objectPtr->type(); const std::string &typeName = type.name; std::string name = ""; if (type.isa(RttiTypes::Node)) { name = dynamic_cast(objectPtr)->getName(); } // Print the node uintptr_t p = reinterpret_cast(objectPtr); fs << "\tn" << std::hex << std::noshowbase << p << " [" << std::endl; // Print the label fs << "\t\tlabel=<" << "" << "" << ""; // Print any name if (!name.empty()) { fs << ""; } // Print any available data element { for (auto &d : storeData) { fs << ""; } } // Print any available event { std::unordered_set eventTypes; for (auto &d : eventData) { if (d.handler) { eventTypes.insert(d.getEventTypeName()); } } for (const char *name : eventTypes) { fs << ""; } } fs << "
" << std::hex << std::showbase << p << "
" << typeName << "
" << name << "
" << d.first << "
" << name << "
>" << std::endl; // Color deleted and marked objects in other colors if (isDeleted) { fs << ",color=firebrick4"; } else if (isMarked) { fs << ",color=gray40"; } fs << "\t]" << std::endl; // Create edges to all outgoing nodes for (const auto &e : objectDescr.refOut) { // Copy the number of references -- repeat until all references have // been drawn int edgeCount = e.second; while (edgeCount > 0) { // Get the type of the target element uintptr_t pTar = reinterpret_cast(e.first); const RttiBase &typeTar = e.first->type(); // Get some information about the edge std::string port = ""; EdgeType et = EdgeType::NORMAL; if (et == EdgeType::NORMAL) { for (auto it = storeData.begin(); it != storeData.end(); it++) { if (it->second == e.first) { et = EdgeType::DATA; port = std::string(":data_") + it->first; storeData.erase(it); break; } } } if (et == EdgeType::NORMAL && type.aggregatedOf(typeTar)) { et = EdgeType::AGGREGATE; } // Print the edge fs << "\tn" << std::hex << std::noshowbase << p << port << " -> n" << std::hex << std::noshowbase << pTar << " ["; int c = edgeCount; switch (et) { /* case EdgeType::EVENT: fs << "weight=5,penwidth=1,color=darkolivegreen4,"; c = 1; break;*/ case EdgeType::DATA: fs << "weight=5,penwidth=1,color=orangered2,"; c = 1; break; case EdgeType::AGGREGATE: fs << "weight=100,color=dodgerblue4,arrowhead=diamond," "penwidth=2,"; c = edgeCount; break; default: fs << "weight=0,penwidth=0.5,"; c = edgeCount; break; } edgeCount -= c; fs << "headlabel=\"" << std::dec << c << "\"]" << std::endl; } } // Create edges for all events for (auto &d : eventData) { Managed *owner = getManaged(d.ownerUid); if (!owner) { continue; } uintptr_t pTar = reinterpret_cast(owner); fs << "\tn" << std::hex << std::noshowbase << p << ":ev_" << d.getEventTypeName() << " -> n" << std::hex << std::noshowbase << pTar << " [weight=0,penwidth=1,color=darkolivegreen4,style=dashed]" << std::endl; } // Display root edges if (objectDescr.rootRefCount > 0) { fs << "\tr" << std::hex << std::noshowbase << p << " [shape=\"point\",width=0.1]" << std::endl; fs << "\tr" << std::hex << std::noshowbase << p << " -> n" << std::hex << std::noshowbase << p << " [weight=1000,headlabel=" "\"" << std::dec << objectDescr.rootRefCount << "\"]" << std::endl; } idx++; } fs << "}" << std::endl; } #endif }