File indexing completed on 2024-05-19 05:42:09

0001 // ct_lvtldr_physicalloader.cpp                                       -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtldr_physicalloader.h>
0021 
0022 #include <ct_lvtldr_graphloader.h>
0023 #include <ct_lvtldr_lakosiannode.h>
0024 #include <ct_lvtldr_nodestorage.h>
0025 
0026 #include <ct_lvtshr_stringhelpers.h>
0027 
0028 #include <cassert>
0029 #include <optional>
0030 #include <unordered_set>
0031 
0032 // TODO: Have a way to enable debugs from application
0033 //       While removing the dependency from ldr to QtGui and QtWidgets, we needed to avoid touching the Preferences
0034 //       module directly, since they depend on those things. We need a way to enable/disable debug messages from this
0035 //       module without touching the Preferences global directly to avoid unwanted dependencies.
0036 //       Old code for reference:
0037 //       Preferences::self()->debug()->enableDebugOutput() || Preferences::self()->debug()->storeDebugOutput();
0038 //
0039 static const bool showDebug = false;
0040 
0041 namespace {
0042 
0043 using namespace Codethink::lvtldr;
0044 
0045 struct VisitLog {
0046     // logs which calls to visitVertex we have already processed
0047     LakosianNode *node;
0048 
0049     // needed for std::find.
0050     explicit VisitLog(LakosianNode *node): node(node)
0051     {
0052     }
0053 };
0054 
0055 struct VisitLogHash {
0056     std::size_t operator()(const VisitLog& log) const
0057     {
0058         return std::hash<LakosianNode *>{}(log.node);
0059         // one log per LakosianNode
0060     }
0061 };
0062 
0063 inline bool operator==(const VisitLog& lhs, const VisitLog& rhs)
0064 {
0065     // only check nodes - see VisitLogHash
0066     return lhs.node == rhs.node;
0067 }
0068 
0069 } // namespace
0070 
0071 namespace Codethink::lvtldr {
0072 
0073 struct PhysicalLoader::Private {
0074     NodeStorage& nodeStorage;
0075 
0076     lvtshr::DiagramType type = lvtshr::DiagramType::NoneType;
0077     std::string qualifiedName;
0078     LakosianNode *mainNode = nullptr;
0079 
0080     bool extDeps = false;
0081 
0082     GraphLoader loader;
0083     std::unordered_set<VisitLog, VisitLogHash> visited;
0084 
0085     IGraphLoader *graphLoader = nullptr;
0086 
0087     explicit Private(NodeStorage& nodeStorage): nodeStorage(nodeStorage)
0088     {
0089     }
0090 };
0091 
0092 PhysicalLoader::PhysicalLoader(NodeStorage& nodeStorage): d(std::make_unique<Private>(nodeStorage))
0093 {
0094 }
0095 
0096 PhysicalLoader::~PhysicalLoader() noexcept = default;
0097 
0098 void PhysicalLoader::addVertex(LakosianNode *node, bool withParent, lvtshr::LoaderInfo info)
0099 {
0100     assert(node);
0101 
0102     d->loader.addVertex(node, withParent, info);
0103 
0104     if (showDebug) {
0105         qDebug() << "lvtldr::PhysicalLoader::addVertex: " << node->qualifiedName() << ": " << withParent;
0106     }
0107 }
0108 
0109 void PhysicalLoader::setGraph(IGraphLoader *graph)
0110 {
0111     d->loader.setGraph(graph);
0112     d->graphLoader = graph;
0113 }
0114 
0115 void PhysicalLoader::clear()
0116 {
0117     d->visited.clear();
0118     d->loader.clear();
0119 }
0120 
0121 void PhysicalLoader::setMainNode(LakosianNode *node)
0122 {
0123     d->mainNode = node;
0124     if (d->mainNode) {
0125         d->type = d->mainNode->type();
0126         d->qualifiedName = d->mainNode->qualifiedName();
0127     }
0128 }
0129 
0130 void PhysicalLoader::setExtDeps(bool extDeps)
0131 {
0132     d->extDeps = extDeps;
0133 }
0134 
0135 cpp::result<void, GraphLoadError> PhysicalLoader::load(LakosianNode *node, lvtldr::NodeLoadFlags flags)
0136 {
0137     // Each call to load() will *not* clean the previous graph - this will be used as a cache during the scene
0138     // visualization. When the user selects a *completely* new graph then, we clean the view.
0139 
0140     if (!node) {
0141         return cpp::fail(GraphLoadError{"Trying to load a null node"});
0142     }
0143 
0144     visitVertex(node, 0, flags);
0145 
0146     d->loader.loadReverseEdges();
0147     d->loader.loadForwardEdges();
0148 
0149     d->loader.load();
0150 
0151     return {};
0152 }
0153 
0154 void PhysicalLoader::unvisitVertex(LakosianNode *node)
0155 {
0156     d->loader.unload(node);
0157 
0158     auto it = d->visited.find(VisitLog{node});
0159     if (it == std::end(d->visited)) {
0160         return;
0161     }
0162 
0163     d->visited.erase(it);
0164 }
0165 
0166 bool PhysicalLoader::isNodeFullyLoaded(LakosianNode *node, lvtldr::NodeLoadFlags flags) const
0167 {
0168     if (flags.loadChildren) {
0169         for (LakosianNode *child : node->children()) {
0170             if (d->visited.find(VisitLog{child}) == d->visited.end()) {
0171                 return false;
0172             }
0173         }
0174     }
0175 
0176     if (flags.traverseProviders) {
0177         for (const LakosianEdge& edge : node->providers()) {
0178             if (d->visited.find(VisitLog{edge.other()}) == d->visited.end()) {
0179                 return false;
0180             }
0181         }
0182     }
0183 
0184     if (flags.traverseClients) {
0185         for (const LakosianEdge& edge : node->clients()) {
0186             if (d->visited.find(VisitLog{edge.other()}) == d->visited.end()) {
0187                 return false;
0188             }
0189         }
0190     }
0191 
0192     return true;
0193 }
0194 
0195 void PhysicalLoader::visitVertex(LakosianNode *node, const unsigned distance, lvtldr::NodeLoadFlags flags)
0196 {
0197     constexpr unsigned MAX_DISTANCE = 99;
0198     if (distance == MAX_DISTANCE) {
0199         return;
0200     }
0201     assert(node);
0202 
0203     auto [it, inserted] = d->visited.emplace(node);
0204     if (!inserted) {
0205         if (isNodeFullyLoaded(node, flags)) {
0206             if (showDebug) {
0207                 qDebug() << "Node " << node->qualifiedName() << " is fully loaded, exiting";
0208             }
0209             return;
0210         }
0211     }
0212 
0213     if (showDebug) {
0214         qDebug() << "Loading node " << node->qualifiedName();
0215     }
0216 
0217     bool loadParent = node->parent();
0218     if (loadParent) {
0219         // 99: distance parameter so high that we won't even think about loading
0220         // edges
0221         const bool hasParent = d->visited.find(VisitLog{node->parent()}) != std::end(d->visited);
0222         if (!hasParent) {
0223             auto visitFlags = d->graphLoader->loadFlagsFor(node);
0224             visitVertex(node->parent(), MAX_DISTANCE, visitFlags);
0225         }
0226     }
0227 
0228     const bool loadChildren = flags.loadChildren;
0229     const lvtshr::LoaderInfo loaderInfo(loadChildren || node->children().empty(), loadParent || !node->parent(), false);
0230 
0231     addVertex(node, loadParent, loaderInfo);
0232 
0233     // load dependencies
0234     if (flags.traverseProviders) {
0235         for (const LakosianEdge& edge : node->providers()) {
0236             auto *other = edge.other();
0237             if (flags.traverseProvidersOnlyLocal && other->parent() != node->parent()) {
0238                 continue;
0239             }
0240 
0241             auto visitFlags = d->graphLoader->loadFlagsFor(other);
0242             visitVertex(edge.other(), distance + 1, visitFlags);
0243         }
0244     }
0245 
0246     if (flags.traverseClients) {
0247         for (const LakosianEdge& edge : node->clients()) {
0248             auto *other = edge.other();
0249             if (flags.traverseClientsOnlyLocal && other->parent() != node->parent()) {
0250                 continue;
0251             }
0252 
0253             auto visitFlags = d->graphLoader->loadFlagsFor(other);
0254             visitVertex(edge.other(), distance + 1, visitFlags);
0255         }
0256     }
0257 
0258     // load children
0259     if (loadChildren) {
0260         for (LakosianNode *child : node->children()) {
0261             auto visitFlags = d->graphLoader->loadFlagsFor(child);
0262             visitVertex(child, distance, visitFlags);
0263         }
0264     }
0265 }
0266 
0267 } // namespace Codethink::lvtldr