diff --git a/gui/treemodel.cc b/gui/treemodel.cc index d46d7b6b..eaf62928 100644 --- a/gui/treemodel.cc +++ b/gui/treemodel.cc @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,9 +25,109 @@ NEXTPNR_NAMESPACE_BEGIN namespace TreeModel { + +// converts 'aa123bb432' -> ['aa', '123', 'bb', '432'] +std::vector IdStringList::alphaNumSplit(const QString &str) +{ + std::vector res; + QString current_part; + + bool number = true; + for (const auto c : str) { + if (current_part.size() == 0 && res.size() == 0) { + current_part.push_back(c); + number = c.isNumber(); + continue; + } + + if (number != c.isNumber()) { + number = c.isNumber(); + res.push_back(current_part); + current_part.clear(); + } + + current_part.push_back(c); + } + + res.push_back(current_part); + + return res; +} + +void IdStringList::updateElements(Context *ctx, std::vector elements) +{ + // For any elements that are not yet in managed_, created them. + std::unordered_set element_set; + for (auto elem : elements) { + element_set.insert(elem); + auto existing = managed_.find(elem); + if (existing == managed_.end()) { + auto item = new IdStringItem(ctx, elem, this, child_type_); + managed_.emplace(elem, std::unique_ptr(item)); + } + } + + children_.clear(); + // For any elements that are in managed_ but not in new, delete them. + for (auto &pair : managed_) { + if (element_set.count(pair.first) != 0) { + children_.push_back(pair.second.get()); + continue; + } + managed_.erase(pair.first); + } + + // Sort new children + qSort(children_.begin(), children_.end(), [&](const Item *a, const Item *b){ + auto parts_a = alphaNumSplit(a->name()); + auto parts_b = alphaNumSplit(b->name()); + + // Short-circuit for different part count. + if (parts_a.size() != parts_b.size()) { + return parts_a.size() < parts_b.size(); + } + + for (size_t i = 0; i < parts_a.size(); i++) { + auto &part_a = parts_a.at(i); + auto &part_b = parts_b.at(i); + + bool a_is_number, b_is_number; + int a_number = part_a.toInt(&a_is_number); + int b_number = part_b.toInt(&b_is_number); + + // If both parts are numbers, compare numerically. + // If they're equal, continue to next part. + if (a_is_number && b_is_number) { + if (a_number != b_number) { + return a_number < b_number; + } else { + continue; + } + } + + // For different alpha/nonalpha types, make numeric parts appear + // first. + if (a_is_number != b_is_number) { + return a_is_number; + } + + // If both parts are numbers, compare lexically. + // If they're equal, continue to next part. + if (part_a == part_b) { + continue; + } + return part_a < part_b; + } + + // Same string. + return true; + }); +} + + Model::Model(QObject *parent) : QAbstractItemModel(parent), - root_(new Item("Elements", nullptr, ElementType::NONE)) {} + root_(new Item("Elements", nullptr)) {} Model::~Model() {} diff --git a/gui/treemodel.h b/gui/treemodel.h index ff4e7254..4f708768 100644 --- a/gui/treemodel.h +++ b/gui/treemodel.h @@ -2,6 +2,7 @@ * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2018 Serge Bazanski * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -40,13 +41,24 @@ enum class ElementType namespace TreeModel { +// Item is a leaf or non-leaf item in the TreeModel hierarchy. It does not +// manage any memory. +// It has a list of children, and when created it registers itself as a child +// of its parent. +// It has some PNR-specific members, like type (if any), idstring (if ay). +// They should be overwritten by deriving classes to make them relate to an +// object somewhere in the arch universe. +// It also has provisions for lazy loading of data, via the canFetchMore and +// fetchMore methods. class Item { protected: + // Human-friendly name of this item. QString name_; + // Parent or nullptr if root. Item *parent_; + // Children that are loaded into memory. QList children_; - ElementType type_; void addChild(Item *child) { @@ -54,8 +66,8 @@ class Item } public: - Item(QString name, Item *parent, ElementType type) : - name_(name), parent_(parent), type_(type) + Item(QString name, Item *parent) : + name_(name), parent_(parent) { // Register in parent if exists. if (parent_ != nullptr) { @@ -63,93 +75,121 @@ class Item } }; - int count() const - { - return children_.count(); - } + // Number of children. + int count() const { return children_.count(); } - QString name() const - { - return name_; - } + // Name getter. + QString name() const { return name_; } - Item *child(int index) - { - return children_.at(index); - } + // Child getter. + Item *child(int index) { return children_.at(index); } + // Parent getter. + const Item *parent() const { return parent_; } + Item *parent() { return parent_; } + + // indexOf gets index of child in children array. int indexOf(const Item *child) const { // Dropping the const for indexOf to work. return children_.indexOf((Item *)child, 0); } + int indexOf(Item *child) { return children_.indexOf(child, 0); } - int indexOf(Item *child) - { - return children_.indexOf(child, 0); - } - - const Item *parent() const - { - return parent_; - } - - Item *parent() - { - return parent_; - } - - ElementType type() const - { - return type_; - } - - virtual bool canFetchMore() const - { - return false; - } + // Arch id and type that correspond to this element. + virtual IdString id() const { return IdString(); } + virtual ElementType type() const { return ElementType::NONE; } + // Lazy loading methods. + virtual bool canFetchMore() const { return false; } virtual void fetchMore() {} - virtual IdString id() const - { - return IdString(); - } - ~Item() {} }; +// IdString is an Item that corresponds to a real element in Arch. class IdStringItem : public Item { private: IdString id_; + ElementType type_; public: IdStringItem(Context *ctx, IdString str, Item *parent, ElementType type) : - Item(QString(str.c_str(ctx)), parent, type), id_(str) {} + Item(QString(str.c_str(ctx)), parent), id_(str), type_(type) {} virtual IdString id() const override { return id_; } + + virtual ElementType type() const override + { + return type_; + } }; +// IdString list is a static list of IdStrings which can be set/updates from +// a vector of IdStrings. It will render each IdStrings as a child, with the +// list sorted in a smart way. +class IdStringList : public Item +{ + private: + // Children that we manage the memory for, stored for quick lookup from + // IdString to child. + std::unordered_map> managed_; + // Type of children that the list creates. + ElementType child_type_; + + public: + // Create an IdStringList at given partent that will contain elements of + // the given type. + IdStringList(QString name, Item *parent, ElementType type) : + Item(name, parent), child_type_(type) {} + + // Split a name into alpha/non-alpha parts, which is then used for sorting + // of children. + static std::vector alphaNumSplit(const QString &str); + + // getById finds a child for the given IdString. + IdStringItem *getById(IdString id) const + { + return managed_.at(id).get(); + } + + // (Re-)create children from a list of IdStrings. + void updateElements(Context *ctx, std::vector elements); +}; + + +// ElementList is a dynamic list of ElementT (BelId,WireId,...) that are +// automatically generated based on an overall map of elements. +// ElementList is emitted from ElementXYRoot, and contains the actual +// Bels/Wires/Pips underneath it. template class ElementList : public Item { public: + // A map from tile (X,Y) to list of ElementTs in that tile. using ElementMap = std::map, std::vector>; + // A method that converts an ElementT to an IdString. using ElementGetter = std::function; private: Context *ctx_; + // ElementMap given to use by our constructor. const ElementMap *map_; + // The X, Y that this list handles. int x_, y_; ElementGetter getter_; + // Children that we manage the memory for, stored for quick lookup from + // IdString to child. std::unordered_map> managed_; + // Type of children that he list creates. ElementType child_type_; - // scope valid until map gets mutated... + // Gets elements that this list should create from the map. This pointer is + // short-lived (as it will change when the map mutates. const std::vector *elements() const { return &map_->at(std::pair(x_, y_)); @@ -157,10 +197,12 @@ class ElementList : public Item public: ElementList(Context *ctx, QString name, Item *parent, ElementMap *map, int x, int y, ElementGetter getter, ElementType type) : - Item(name, parent, ElementType::NONE), ctx_(ctx), map_(map), x_(x), y_(y), getter_(getter), child_type_(type) + Item(name, parent), ctx_(ctx), map_(map), x_(x), y_(y), getter_(getter), child_type_(type) { } + // Lazy loading of elements. + virtual bool canFetchMore() const override { return (size_t)children_.size() < elements()->size(); @@ -189,11 +231,7 @@ class ElementList : public Item fetchMore(100); } - virtual IdString id() const override - { - return IdString(); - } - + // getById finds a child for the given IdString. boost::optional getById(IdString id) { // Search requires us to load all our elements... @@ -207,161 +245,69 @@ class ElementList : public Item } }; -class IdStringList : public Item -{ - private: - std::unordered_map> managed_; - ElementType child_type_; - public: - IdStringList(QString name, Item *parent, ElementType type) : - Item(name, parent, ElementType::NONE), child_type_(type) {} - using Item::Item; - - static std::vector alphaNumSplit(const QString &str) - { - std::vector res; - - QString current_part; - bool number = true; - for (const auto c : str) { - if (current_part.size() == 0 && res.size() == 0) { - current_part.push_back(c); - number = c.isNumber(); - continue; - } - - if (number != c.isNumber()) { - number = c.isNumber(); - res.push_back(current_part); - current_part.clear(); - } - - current_part.push_back(c); - } - - res.push_back(current_part); - - return res; - } - - IdStringItem *getById(IdString id) const - { - return managed_.at(id).get(); - } - - void updateElements(Context *ctx, std::vector elements) - { - // for any elements that are not yet in managed_, created them. - std::unordered_set element_set; - for (auto elem : elements) { - element_set.insert(elem); - auto existing = managed_.find(elem); - if (existing == managed_.end()) { - auto item = new IdStringItem(ctx, elem, this, child_type_); - managed_.emplace(elem, std::unique_ptr(item)); - } - } - - children_.clear(); - // for any elements that are in managed_ but not in new, delete them. - for (auto &pair : managed_) { - if (element_set.count(pair.first) != 0) { - children_.push_back(pair.second.get()); - continue; - } - managed_.erase(pair.first); - } - - // sort new children - qSort(children_.begin(), children_.end(), [&](const Item *a, const Item *b){ - auto parts_a = alphaNumSplit(a->name()); - auto parts_b = alphaNumSplit(b->name()); - - if (parts_a.size() != parts_b.size()) { - return parts_a.size() < parts_b.size(); - } - - for (size_t i = 0; i < parts_a.size(); i++) { - auto &part_a = parts_a.at(i); - auto &part_b = parts_b.at(i); - - - bool a_is_number, b_is_number; - int a_number = part_a.toInt(&a_is_number); - int b_number = part_b.toInt(&b_is_number); - - if (a_is_number && b_is_number) { - if (a_number != b_number) { - return a_number < b_number; - } else { - continue; - } - } - - if (a_is_number != b_is_number) { - return a_is_number; - } - - // both strings - - if (part_a == part_b) { - continue; - } - - return part_a < part_b; - } - - // both equal - return true; - }); - } -}; - +// ElementXYRoot is the root of an ElementT multi-level lazy loading list. +// It can take any of {BelId,WireId,PipId} and create a tree that +// hierarchizes them by X and Y tile positions, when given a map from X,Y to +// list of ElementTs in that tile. template class ElementXYRoot : public Item { public: + // A map from tile (X,Y) to list of ElementTs in that tile. using ElementMap = std::map, std::vector>; + // A method that converts an ElementT to an IdString. using ElementGetter = std::function; private: Context *ctx_; + // X-index children that we manage the memory for. std::vector> managed_labels_; + // Y-index children (ElementLists) that we manage the memory for. std::vector>> managed_lists_; + // Source of truth for elements to display. ElementMap map_; ElementGetter getter_; + // Type of children that he list creates in X->Y->... ElementType child_type_; public: ElementXYRoot(Context *ctx, QString name, Item *parent, ElementMap map, ElementGetter getter, ElementType type) : - Item(name, parent, ElementType::NONE), ctx_(ctx), map_(map), getter_(getter), child_type_(type) + Item(name, parent), ctx_(ctx), map_(map), getter_(getter), child_type_(type) { + // Create all X and Y label Items/ElementLists. + + // Y coordinates at which an element exists for a given X - taken out + // of loop to limit heap allocation/deallocation. std::vector y_present; for (int i = 0; i < ctx->getGridDimX(); i++) { y_present.clear(); - // first find all the elements in all Y coordinates in this X + // First find all the elements in all Y coordinates in this X. for (int j = 0; j < ctx->getGridDimY(); j++) { if (map_.count(std::pair(i, j)) == 0) continue; y_present.push_back(j); } - // no bels in any X coordinate? do not add X tree item. + // No elements in any X coordinate? Do not add X tree item. if (y_present.size() == 0) continue; - // create X item for tree - auto item = new Item(QString("X%1").arg(i), this, child_type_); + // Create X list Item. + auto item = new Item(QString("X%1").arg(i), this); managed_labels_.push_back(std::move(std::unique_ptr(item))); + for (auto j : y_present) { + // Create Y list ElementList. auto item2 = new ElementList(ctx_, QString("Y%1").arg(j), item, &map_, i, j, getter_, child_type_); + // Pre-populate list with one element, other Qt will never ask for more. item2->fetchMore(1); managed_lists_.push_back(std::move(std::unique_ptr>(item2))); } } } + // getById finds a child for the given IdString. boost::optional getById(IdString id) { // For now, scan linearly all ElementLists. @@ -429,6 +375,8 @@ class Model : public QAbstractItemModel bool canFetchMore(const QModelIndex &parent) const Q_DECL_OVERRIDE; private: + + // Tree elements that we manage the memory for. std::unique_ptr root_; std::unique_ptr bel_root_; std::unique_ptr wire_root_;