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

0001 // ct_lvtqtc_alg_level_layout.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_lakosiannode.h>
0021 #include <ct_lvtqtc_alg_level_layout.h>
0022 #include <ct_lvtqtc_lakosentity.h>
0023 #include <set>
0024 #include <unordered_set>
0025 
0026 namespace Codethink::lvtqtc {
0027 
0028 // clang-format off
0029 template<LakosEntity::LevelizationLayoutType LTS>
0030 struct LayoutTypeStrategy
0031 {
0032 };
0033 
0034 template<>
0035 struct LayoutTypeStrategy<LakosEntity::LevelizationLayoutType::Vertical>
0036 {
0037     static inline double getPosOnReferenceDirection(LakosEntity *entity) { return entity->pos().y(); }
0038     static inline double getPosOnOrthoDirection(LakosEntity *entity) { return entity->pos().x(); }
0039     static inline void setPosOnReferenceDirection(LakosEntity *entity, double pos) { entity->setPos(entity->pos().x(), pos); }
0040     static inline void setPosOnOrthoDirection(LakosEntity *entity, double pos) { entity->setPos(pos, entity->pos().y()); }
0041     static inline double rectSize(LakosEntity *entity) { return entity->rect().height(); }
0042     static inline double rectOrthoSize(LakosEntity *entity) { return entity->rect().width(); }
0043 };
0044 
0045 template<>
0046 struct LayoutTypeStrategy<LakosEntity::LevelizationLayoutType::Horizontal>
0047 {
0048     static inline double getPosOnReferenceDirection(LakosEntity *entity) { return entity->pos().x(); }
0049     static inline double getPosOnOrthoDirection(LakosEntity *entity) { return entity->pos().y(); }
0050     static inline void setPosOnReferenceDirection(LakosEntity *entity, double pos) { entity->setPos(pos, entity->pos().y()); }
0051     static inline void setPosOnOrthoDirection(LakosEntity *entity, double pos) { entity->setPos(entity->pos().x(), pos); }
0052     static inline double rectSize(LakosEntity *entity) { return entity->rect().width(); }
0053     static inline double rectOrthoSize(LakosEntity *entity) { return entity->rect().height(); }
0054 };
0055 // clang-format on
0056 
0057 std::unordered_map<LakosEntity *, int> computeLevelForEntities(std::vector<LakosEntity *> const& entities,
0058                                                                std::optional<const LakosEntity *> commonParentEntity)
0059 {
0060     auto entityToLevel = std::unordered_map<LakosEntity *, int>{};
0061 
0062     // Keep track of history for cycle detection
0063     auto entitiesVisitHistory = std::unordered_map<LakosEntity *, std::unordered_set<LakosEntity *>>{};
0064     for (auto *entity : entities) {
0065         entitiesVisitHistory[entity].insert(entity);
0066     }
0067 
0068     auto copyAllDependentNodes =
0069         [&commonParentEntity, &entitiesVisitHistory](LakosEntity *entity, std::set<LakosEntity *>& processEntities) {
0070             for (auto const& edge : entity->targetCollection()) {
0071                 auto *const toNode = edge->from();
0072                 auto *const parentNode = toNode->internalNode()->parent();
0073                 if (commonParentEntity) {
0074                     // Only consider packages/components and dependencies within the common parent entity
0075                     auto parentEntityName = (*commonParentEntity)->name();
0076                     if (!parentNode || parentNode->name() != parentEntityName) {
0077                         continue;
0078                     }
0079                 } else {
0080                     // If no common parent is provided, only consider top level entities (Ignore those with parent node)
0081                     if (parentNode) {
0082                         continue;
0083                     }
0084                 }
0085 
0086                 // Avoid creating cycles
0087                 auto const& history = entitiesVisitHistory[entity];
0088                 if (std::find(history.cbegin(), history.cend(), toNode) != history.cend()) {
0089                     continue;
0090                 }
0091                 entitiesVisitHistory[toNode].insert(entitiesVisitHistory[entity].begin(),
0092                                                     entitiesVisitHistory[entity].end());
0093 
0094                 processEntities.insert(toNode);
0095             }
0096         };
0097 
0098     auto hasDependenciesWithinThisParent = [&commonParentEntity](LakosEntity *entity) {
0099         auto const& deps = entity->edgesCollection();
0100 
0101         if (commonParentEntity) {
0102             // Only consider packages/components and dependencies within the common parent entity
0103             auto parentEntityName = (*commonParentEntity)->name();
0104             return std::any_of(deps.cbegin(), deps.cend(), [&parentEntityName](auto& dep) {
0105                 auto const *depParent = dep->to()->internalNode()->parent();
0106                 return depParent && depParent->name() == parentEntityName;
0107             });
0108         }
0109 
0110         // If no common parent is provided, only consider top level entities (With no parent)
0111         return std::any_of(deps.cbegin(), deps.cend(), [](auto& dep) {
0112             auto const *depParent = dep->to()->internalNode()->parent();
0113             return !depParent;
0114         });
0115     };
0116 
0117     auto entitiesOnNextLevel = std::set<LakosEntity *>{};
0118 
0119     // Finds the "first level", where the entities don't have any dependency.
0120     // Also prepares "processEntities" with the entities that will be on the next level.
0121     auto currentLevel = 0;
0122     for (auto *entity : entities) {
0123         entityToLevel[entity] = currentLevel;
0124         if (!hasDependenciesWithinThisParent(entity)) {
0125             copyAllDependentNodes(entity, entitiesOnNextLevel);
0126         }
0127     }
0128 
0129     // While there are entities in the current level, process the entities while finding the entities for the next
0130     // level, until eventually we have processed all the levels.
0131     auto entitiesOnCurrentLevel = entitiesOnNextLevel;
0132     currentLevel += 1;
0133     while (!entitiesOnCurrentLevel.empty()) {
0134         entitiesOnNextLevel.clear();
0135         for (auto const& entity : entitiesOnCurrentLevel) {
0136             entityToLevel[entity] = currentLevel;
0137             copyAllDependentNodes(entity, entitiesOnNextLevel);
0138         }
0139 
0140         currentLevel += 1;
0141         entitiesOnCurrentLevel = entitiesOnNextLevel;
0142     }
0143     return entityToLevel;
0144 }
0145 
0146 template<LakosEntity::LevelizationLayoutType LT>
0147 void limitNumberOfEntitiesPerLevel(std::unordered_map<LakosEntity *, int> const& entityToLevel,
0148                                    RunLevelizationLayoutConfig const& config)
0149 {
0150     using LTS = LayoutTypeStrategy<LT>;
0151 
0152     auto entitiesFromLevel = std::map<int, std::vector<LakosEntity *>>{};
0153     for (auto const& [e, level] : entityToLevel) {
0154         entitiesFromLevel[level].push_back(e);
0155     }
0156     for (auto& [level, entities] : entitiesFromLevel) {
0157         std::sort(entities.begin(), entities.end(), [](auto *e0, auto *e1) {
0158             return LTS::getPosOnOrthoDirection(e0) < LTS::getPosOnOrthoDirection(e1);
0159         });
0160     }
0161 
0162     auto globalOffset = 0.;
0163     for (auto& [level, entities] : entitiesFromLevel) {
0164         for (auto& e : entities) {
0165             auto currentPos = LTS::getPosOnReferenceDirection(e);
0166             LTS::setPosOnReferenceDirection(e, currentPos + config.direction * globalOffset);
0167         }
0168 
0169         auto localOffset = 0.;
0170         auto localNumberOfEntities = 0;
0171         auto maxSizeOnCurrentLevel = 0.;
0172         for (auto& e : entities) {
0173             if (localNumberOfEntities == config.maxEntitiesPerLevel) {
0174                 localOffset += maxSizeOnCurrentLevel + config.spaceBetweenSublevels;
0175                 localNumberOfEntities = 0;
0176                 maxSizeOnCurrentLevel = 0.;
0177             }
0178 
0179             auto currentReferencePos = LTS::getPosOnReferenceDirection(e);
0180             LTS::setPosOnReferenceDirection(e, currentReferencePos + config.direction * localOffset);
0181             maxSizeOnCurrentLevel = std::max(maxSizeOnCurrentLevel, LTS::rectSize(e));
0182             localNumberOfEntities += 1;
0183         }
0184         globalOffset += localOffset;
0185     }
0186 }
0187 
0188 template<LakosEntity::LevelizationLayoutType LT>
0189 void centralizeLayout(std::unordered_map<LakosEntity *, int> const& entityToLevel,
0190                       RunLevelizationLayoutConfig const& config)
0191 {
0192     using LTS = LayoutTypeStrategy<LT>;
0193 
0194     // Warning: A "line" is not the same as a "level". One level may be composed by multiple lines.
0195     auto lineToLineTotalWidth = std::unordered_map<int, double>{};
0196     auto maxSize = 0.;
0197     for (auto& [e, _] : entityToLevel) {
0198         // The use of "integer" for a "line position" is only to avoid having to deal with real numbers, and
0199         // thus being able to make easy buckets for the lines.
0200         auto lineRepr = (int) LTS::getPosOnReferenceDirection(e);
0201 
0202         lineToLineTotalWidth[lineRepr] = lineToLineTotalWidth[lineRepr] == 0.0
0203             ? LTS::rectSize(e)
0204             : lineToLineTotalWidth[lineRepr] + LTS::rectOrthoSize(e) + config.spaceBetweenEntities;
0205 
0206         maxSize = std::max(lineToLineTotalWidth[lineRepr], maxSize);
0207     }
0208 
0209     auto lineCurrentPos = std::map<int, double>{};
0210     for (auto& [e, _] : entityToLevel) {
0211         auto lineRepr = (int) LTS::getPosOnReferenceDirection(e);
0212         auto currentPos = lineCurrentPos[lineRepr];
0213         LTS::setPosOnOrthoDirection(e, currentPos + (maxSize - lineToLineTotalWidth[lineRepr]) / 2.);
0214         lineCurrentPos[lineRepr] += LTS::rectOrthoSize(e) + config.spaceBetweenEntities;
0215     }
0216 }
0217 
0218 template<LakosEntity::LevelizationLayoutType LT>
0219 void prepareEntityPositionForEachLevel(std::unordered_map<LakosEntity *, int> const& entityToLevel,
0220                                        RunLevelizationLayoutConfig const& config)
0221 {
0222     using LTS = LayoutTypeStrategy<LT>;
0223 
0224     auto sizeForLvl = std::map<int, double>{};
0225     for (auto const& [e, level] : entityToLevel) {
0226         sizeForLvl[level] = std::max(sizeForLvl[level], LTS::rectSize(e));
0227     }
0228 
0229     auto posPerLevel = std::map<int, double>{};
0230     for (auto const& [level, _] : sizeForLvl) {
0231         // clang-format off
0232         posPerLevel[level] = (
0233             level == 0 ? 0.0 : posPerLevel[level - 1] + config.direction * (sizeForLvl[level - 1] + config.spaceBetweenLevels)
0234         );
0235         // clang-format on
0236     }
0237 
0238     for (auto [e, level] : entityToLevel) {
0239         LTS::setPosOnOrthoDirection(e, 0.0);
0240         LTS::setPosOnReferenceDirection(e, posPerLevel[level]);
0241     }
0242 }
0243 
0244 void runLevelizationLayout(std::unordered_map<LakosEntity *, int> const& entityToLevel,
0245                            RunLevelizationLayoutConfig const& config)
0246 {
0247     if (config.type == LakosEntity::LevelizationLayoutType::Vertical) {
0248         static auto const LT = LakosEntity::LevelizationLayoutType::Vertical;
0249         prepareEntityPositionForEachLevel<LT>(entityToLevel, config);
0250         limitNumberOfEntitiesPerLevel<LT>(entityToLevel, config);
0251         centralizeLayout<LT>(entityToLevel, config);
0252     } else {
0253         static auto const LT = LakosEntity::LevelizationLayoutType::Horizontal;
0254         prepareEntityPositionForEachLevel<LT>(entityToLevel, config);
0255         limitNumberOfEntitiesPerLevel<LT>(entityToLevel, config);
0256         centralizeLayout<LT>(entityToLevel, config);
0257     }
0258 }
0259 
0260 } // namespace Codethink::lvtqtc