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