Let me introduce first the problem. I will present a simplification of my QT6 project app (in C++) to not make it too difficult to read, as the original code has lots of unnecesary things for this problem in particular, so maybe something is missing but I tried to put everything needed here.
I have a an abstract class, let us call it Event
class EventList; // "Shortcut" for std::vector<Event*>
class Event
{
protected:
uint16_t _code;
std::shared_ptr<std::vector<EventList*>> _children_lists;
// More things...
public:
// More things...
static Event* create(VALUE obj);
inline virtual uint16_t code() const { return _code; }
};
that represents an Event, that can be of very different types depending on some parameter of the argument VALUE obj of the create method, each one of this being a derived class, e.g.:
class Event_1 : public Event
{
public:
Event_1(...);
static Event_1* create(VALUE obj);
inline uint16_t code() const override { return 1; }
// More things...
}
Now, I want to create a QT6 Widget to represent each of this Event classes in a GUI. Key things to have in mind:
- Each
Eventmay have 0 or any number of childs of any type associated to it, and I want to display the children widgets "inside" the parent widget (like a tree structure) as this example:- Event #1 (Type 1)
- Child #1.1 of Event #1 (Type 1)
- Child #1.1.1 of Child #1.1 (Type 2)
- Child #1.1.2 of Child #1.1 (Type 3)
- Child #1.2 of Event #1 (Type 2)
- etc
- Child #1.1 of Event #1 (Type 1)
- Event #1 (Type 1)
- Each derived class
Event_Xshould have a different widget (Widget_X)
My approach is to have an abstract Widget class "equivalent" to the Event one and a subclass for each Ecvent_X that will be Widget_X
class WidgetViewer; // Stores and draws a vector of widgets, used for children.
class Widget : public QWidget
{
Q_OBJECT;
public:
Widget(QWidget* parent);
public slots:
virtual void setEvent(Event*) = 0;
// More things...
protected:
WidgetViewer * addChildren(EventList* list);
virtual uint16_t code() const = 0; // Similar to the prev. one
};
class Widget_1 : public Widget
{
Widget_1(QWidget* parent); // Constructor that will call Widget::Widget(parent)
void setEvent(Event* ev) override
{
Event_1* event = dynamic_cast<Event_1*>(ev);
// Do things
}
// More things...
};
This classes constructors typically initializate one or more smaller widgets, like labels, this is an example of one of them:
Widget_1::Widget_1(QWidget *parent)
: WidgetViewer(parent),
label(new QLabel(this))
{
// Custom functions to customize a header widget every Widget_X shares.
setHeaderText("Show Text");
setHeaderColor("#b6d7a8");
setBackgroundColor("#d9ead3");
setBorderColor("#a2be96");
label->setWordWrap(true);
contents->layout()->addWidget(label);
}
Then, I have a factory method to create the correct instance of one of the Widget_X from the Event class based on code,
class WidgetFactory
{
public:
static Widget* createWidget(WidgetViewer* parent, Event* event);
// More things...
};
The class WidgetViewer is the responsable for creating the Widgets itself, using this function:
Widget* WidgetViewer::createWidget(Event* event)
{
QElapsedTimer timer;
timer.start();
Widget* widget = WidgetFactory::createWidget(this, event);
qDebug() << "Took " << timer.elapsed() << "ms to create event widget for event " << event->code();
timer.restart();
widget->setEvent(event);
qDebug() << "Took " << timer.elapsed() << "ms to set event event for event widget " << _id;
return widget;
}
And for each Event in a EventList, this process in done recursively for each children. It is not visible in this 'simplified' version, but most of the implementations of Widget_X::setEvent pure abstract function call the following function over children of Widget_X:
void WidgetViewer::setList(WidgetList* _list)
{
for(auto it = list->begin(); it < list->end(); it++)
{
Event* event = (*it);
if(event == nullptr)
continue;
widgets.push_back(createWidget(event));
ui->verticalLayout->addWidget(widgets.last());
}
}
creating therefore the recursion.
This is my approach to do it. The problem is that this lists can be very long, and creating a new Widget class each time takes so much time than expected (sometimes up to 2-3 secs, in which my app is stopped). I have tried some things to speed it up, but I would like to know if there is a better way, as the result is not very satisfactory.
The first attempt I tried to solve the slowness issue was to use multithreading, however, since GUI operations cannot be made on other threads than the main one, I dind't win anything but problems inherited by the threading stuff.
My second attempt was (and is my current best) to create a WidgetPool so that there are Widget_X of any X type already stored in memory, so there is no need of creating them via constructor, what takes a long time (up to 10ms for some of them). I did this by creating a new class,
class WidgetPool
{
public:
static WidgetPool& instance()
{
static WidgetPool instance;
return instance;
}
Widget* getWidget(WidgetViewer* parent, Event* event); // Gets a widget from the pool, if there is no one, uses the WidgetFactory to create one.
void returnWidget(Widget* widget); // Releases a widget to the pool again
private:
WidgetPool();
~WidgetPool();
WidgetPool(const WidgetPool&) = delete;
WidgetPool& operator=(const WidgetPool&) = delete;
QMap<uint16_t, QVector<Widget*>> widget_pool;
};
and modifying WidgetViewer::createWidget as follows:
Widget* WidgetViewer::createWidget(Event* event)
{
QElapsedTimer timer;
timer.start();
Widget* widget = WidgetPool::instance().getWidget(this, event);
qDebug() << "Took " << timer.elapsed() << "ms to create command widget for command " << event->code();
timer.restart();
widget->setEvent(event);
qDebug() << "Took " << timer.elapsed() << "ms to set event command for command widget " << _id;
return widget;
}
In this way, the creation of the WidgetViwer is faster, but the RAM usage is significantly bigger (from 400-500 MB to roughly 1 GB), and it is not that faster, there is still some time before it fully loades big EventLists.
Another solution I have thought of is to have stored in memory "patterns" for each Widget_X and then copy them instead of needing to use new Widget_X, to then just call setEvent as above, but I don't know if this will be any better than just creating a fully new instance with new.
So, if anyone knows/can think of a better approach than these ones, or how to improve one of these, it is greatly appreciated.
Thanks.
WidgetViewercreates a list of widgets. Generally an operation likepush_backis O(n) on a list, and you have this inside a loop.