QGraphicsItem is not visible after adding to scene - c++

I'm working on a diagram visualisation tool and I ran into an issue where my QGraphicsScene does not display a shared_ptr<DiagramItem> when a raw pointer obtained via .get() is passed to scene->addItem().
Subsequent check via scene->items() shows that my DiagramItem is not a part of the scene. My guess is that it got freed as the refcounter on the shared_ptr will be zero after leaving the scope of the testing function...
But that was the testing case. In my actual code I'm using a shared_ptr that I got from elsewhere and is definitely present in memory with a non-zero refcounter. I get the raw pointer of that and pass it to scene->addItem(). It is also not displayed, but this time it is present in scene->items(). So why is it not being drawn?
If I switch from using shared_ptr<DiagramItem> to DiagramItem* then the issue disappears and everything is displayed properly. But due to limitations from the rest of the project, I cannot easily abandon smart pointers here, nor do I want to.
Did I run into some kind of memory limitation or am I doing something wrong?
I already tried calling show() and update() on the item and increasing the scene size in case the item doesn't fit (it does). I also tried breakpointing the paint() method, but that one doesn't get called at all.
I found a possibly related question here where similar behaviour occurs due to the object going out of scope and being deallocated, but that doesn't seem to be the case with my actual DiagramItem.
class DiagramItem : public QGraphicsItem
{
...
}
//Create scene
auto scene = new QGraphicsScene(nullptr);
//Item is created OR obtained from elsewhere
auto item1 = std::make_shared<DiagramItem>(nullptr, QString("aaa"), true);
auto item2 = GetDiagramItem(...);
//Raw pointers get passed to addItem
scene->addItem(item1.get());
scene->addItem(item2.get());
//Item1 is not present at all (directly created DiagramItem)
//Item2 is present but invisible (DiagramItem passed from elsewhere)
//myItem gets Item2
auto myItem = scene->items()[0];
...

Related

Segfault after deleting elements from vector

I'm currently working on a project, more precisely a tangram game.
I have a problem with a segfault and I don't understand why.
Given that I have a whole project, I will try to simplify the problem :
I have a GameManager class that contains in particular a Menu object (and other things, but I don't think that is important. The gameManager is used to inititialize this object and manage it.
The Menu contains a vector of Button(each button has a lambda to perform an action when the user click on it).
std::vector<std::unique_ptr<Button>> buttons;
To illustrate how it works I will take an example : if the user clicks on the "Load" button, the gameManager delete the current Buttons contained in the menu and add new buttons in that menu.
void GameManager::initMainMenuButtons() {
...
menu -> addButton(std::unique_ptr<Button>(new Button(x1, y1, x2, y2, "Create",
[this]{
std::cout << "Create level" << std::endl;
menu->clear()
initCreateLevelButtons();
actionManager->setMenu(menu);
}
)));
...
}
In that code sample, I have a method initMainMenuButtons that adds several buttons in the menu , like "Load" or "Quit".
When the user clicks on "Create", I want to change the interface (adding and deleting buttons). So, to delete the buttons, i call the method clear()
void Menu::clear() {
buttons.clear();
decorationPieces.clear(); // not interesting
}
I'm using unique_ptr, thus, I don't have to delete the buttons mannualy.
So far, no problem : the vector of buttons seems to be empty (size is 0).
Next, the method initCreateLevelButtons() is called. This method is very similar to initMainMenu : it adds buttons in the menu, nothing else. During this call, the buttons seems to be correctly added in the vector, I printed the content of the vector at the end and the vector contains the correct buttons.
And there, the problem appears : after the call of initCreateLevelButtons(), there is a segfault when i want to use the menu, so, actionManager->setMenu(menu); doesn't work. I tried to print the menu std::cout << menu << std::endl, and test if this pointer is nullptr, but it doesn't work either. I don't understand why the menu seems to be correct at the last line of initCreateLevelButtons() and becomes invalid just after.
If I doesn't clear the vector of buttons (the menu->clear instruction), the program works, but, the last buttons are still here).
I tried to use raw pointers and I notices that the program is able to clear the vector as long as the buttons are not deleted (If I add a loop to delete the buttons, the problem arises), so, I conclued that the probleme is the buttons deleting. I don't understanf why, I'm stuck.
I don't know if I explained it weel, because, as I have already said, the code is part of a whole project, it's hard to introduce classes without introduce other things.
if you want details or the complete code of methods, I can provide them.
menu sustains lifetime of some button
button sustain lifetime of lambda
when you click button lambda clears menu
menu destructor clears button, button clears lambda
lambda continues execution when it in fact has been already destroyed -> undefined behavior ends with a crash
Now question is: Do you own Button class?
If yes then the easiest way to fix it, is to invoke copy of lambda in the button.
When you call menu->clear() it calls buttons.clear().
When you call buttons.clear() it destroys all the elements of buttons.
When you destroy the unique_ptr for the "Create" button, it destroys the "Create" button.
I assume button's callback is a std::function. When the button is destroyed, so is the std::function.
When the std::function is destroyed, your callback lambda object ([this]{...}) is destroyed.
The this pointer inside a lambda is stored in the lambda object. So now the memory that held the this pointer has been deallocated.
Since actionManager is a member variable of GameManager, actionManager->setMenu(menu) is really this->actionManager->setMenu(menu) which crashes because it uses a dangling pointer.
One workaround is to put the button code in a function of GameManager (since the GameManager is not destroyed), and call that from the lambda. Then, it's okay if you destroy the button while inside that function. It's okay to destroy an object whose code is currently running, as long as you are careful to not access the object after it's destroyed! This is also okay with std::function. I.e.:
[this]{
// move the rest of the code to the CreateLevel function
this->CreateLevel();
// At this point the lambda has been destroyed, but it's not a problem
// because we don't do anything.
}

Crash after QGraphicsScene::removeItem() with custom item class

I am populating a QGraphicsScene with instances of a custom item class (inherting QGraphicsPathItem). At some point during runtime, I try to remove an item (plus its children) from the scene by calling:
delete pItem;
This automatically calls QGraphicsScene::removeItem(), however it also leads to a crash in the class QGraphicsSceneFindItemBspTreeVisitor during the next repaint.
TL;DR: The solution is to ensure that QGraphicsItem::prepareGeometryChange() gets called before the item's removal from the scene.
The problem is that during the item removal from the scene, the scene internal index was not properly updated, resulting in the crash upon the next attempt of drawing the scene.
Since in my case, I use a custom subclass from QGraphicsPathItem, I simply put the call to QGraphicsItem::prepareGeometryChange() into its destructor since I am not manually removing the item from the scene (via QGraphicsScene::removeItem()), but instead I simply call delete pItem; which in return triggers the item's destructor as well as removeItem() later on.
I ran into the same issue using PySide2.
Disabling BSP indexing (as mentioned here) does work for me and is most likely the actual solution to the problem. But is a sub-optimal one, because the scene that I am working with can get arbitrarily large. I also tried to call prepareGeometryChange before removing the item, and while that did seem to work for a while, the error re-appeared just a few weeks later.
What worked for me (so far) is manually removing all child items before removing the item itself...
To that end, I am overwriting the QGraphicsScene::removeItem method in Python:
class GraphicsScene(QtWidgets.QGraphicsScene):
def removeItem(self, item: QtWidgets.QGraphicsItem) -> None:
for child_item in item.childItems():
super().removeItem(child_item)
super().removeItem(item)
Note that this will not quite work the same in C++ because QGraphicsScene::removeItem is not a virtual method, so you will probably have to add your own method removeItemSafely or whatever.
Disclaimer: Other methods have worked for me as well ... until they didn't. I have not seen a crash in QGraphicsSceneFindItemBspTreeVisitor::visit since introducing this workaround, but that does not mean that this is actually the solution. Use at your own risk.
I had this issue and it was a real pain to fix it. Besides the crash, I was also having "guost" items appearing on the screen.
I was changing the boundingRect size 2x inside a custom updateGeometry() method that updates the boundingbox and shape caches of the item.
I was initializing the boundig rectangle as QRectf():
boundingBox = QRectF();
... then doing some processing (and taking the opportunity to do some clean ups in unneeded objects from the scene).
And finally setting the value of the boundingRect to its new size:
boundingBox = polygon.boundingRect();
Calling prepareGeometryChange() in the beggining, alone, didn't solve the issue since I was changing it's size twice.
The solution was to remove the first attribution.
It seems the issue lasting for long time today and there are open bugs also.
But it seems to have a workaround, which I could find it useful and after hours of debugging and reading and investigations I have found it here:
https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem/17
Some other tips and tricks regarding Graphics Scene here:
https://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063/6

what happen if i call "delele" a CCLayer object with reference count greater than 0

I used one scene and many layers for my game.
when user go to another game screen I remove current layer from scene , delete current layer, set current layer = NULL, then create a new layer, add it to the scene
void UIManager::openScreen(int screenId){
m_currentScreen = screenId;
CCLayer *newLayer;
if(screenId == MENU_SCREEN){
newLayer = new MenuLayer();
}else{
...
}
if(m_currentLayer != NULL){
m_scene->removeChild(m_currentLayer, true);
delete m_currentLayer;
m_currentLayer = NULL;
}
m_scene->addChild(newLayer);
m_currentLayer = newLayer;
}
On some layers, i call some CCHttpRequest with callback:
setResponseCallback(CCObject* pTarget, SEL_CallFuncND pSelector)
And i use "this" to pass to "pTarget", it means first parameter for this callback is my layer which defined a SEL_CallFuncND selector.
The problem is when user switch between screens(layers) to quick, but some slow CCHttpRequest still not completed, and the response callback will be called after UIManager delete the layer then my game crash :(. I don't want to lock the screen and force use wait the http request complete. User should can abort loading a screen and switch to the next screen they want.
So should i call "delete m_currentLayer" instead of m_currentLayer->release()?
As i know, release will decrease the reference count, i just want to make sure "noone" use the m_currentLayer rather than m_scene so i used "delete". But i'm not sure it is correct way.
If i use release function in this case, i worry some places in code use the layer and increase the retain count of the layer, is this can make a leak memory issue?
if m_currentLayer->retainCount() = 4 and i call "delete m_currentLayer", then what will happen with m_currentLayer?
I'm confusing with these issues, please someone give me an advide.
Thank you very much!
You should not delete your nodes manually. It can cause a bunch of errors. Reference
counting convention is to call retain when you need to be sure that object still exists and call release when you do not need this object.
Use retain and release only inside object, that really needs other object to exist. In any other case you can use assign property(do not retain object and check it is not NULL) before doing something with it. If some object(let's call it a), that is not yours(CCNode, for example) retains the other object(let's call it b), deleting b manually can cause bad access error, because a will be sure that b still exists. And can call something like b->doSmth()
Your m_currentLayer will be deleted, but it can cause errors because retain count 4 means that 4 objects can cause bad access error described above.

QGraphicsScene, error by accessing pointer in custom class

I have a pretty complex problem... In Qt I have a custom class (named FotoGebouw) that inherits from QGraphicsItem, it also contains a pointer to another custom class (named Gebouw). If I want to acces the selected items from the scene, in other words the "FotoGebouw"items, I first have to cast them to QGraphicsItems. But this way, I seem to lose the pointer (called linkGebouw) that they were pointing to.
Does anyone know a way to get the FotoGebouw items that are selected from the scene, while I can still get the
QList<QGraphicsItem *>bordSceneGebouwen=bordscene->selectedItems();
FotoGebouw *teVerplaatsenFoto=dynamic_cast<FotoGebouw *>(bordSceneGebouwen[0]);
Gebouw *teVerplaatsen=teVerplaatsenFoto->linkGebouw;

C++/Qt - multiple inheritance with QGraphicsItem doesn't work as expected

I recently met a strange problem of my little program and it would be great if you help me to get the reason of this behavior.
My task is quiet simple - I want to use Qt Graphics Framework to show some objects and I want Box2D to calculate bodies position. So my class hierarchy looks like the following:
I have 1 base abstract class B2DObject. It contains some Box2D staff + some common parameters for its successors (names, some flags, etc.). It also has couple of pure virtual functions that will be reimplemented in successor classes.
Then I implement some classes that represent basic shapes: circles, rectangles, polygons, etc. I am doing it in the following way:
class ExtendedPolygon : public B2DObject, public QGraphicsPolygonItem { ... };
class ExtendedCircle : public B2DObject, public QGraphicsEllipseItem { ... };
etc.
(for those who are not familiar with Qt, QGraphics***Item is inherited from QGraphicsItem).
Also I inherited QGraphicsScene and reimplemented its mousePressEvent. In this function I request an object placed at some point on the screen using QGraphicsScene::itemAt function (which returns QGraphicsItem*), convert it to B2DObject* and try to get some internal field from this object:
void TestScene::mousePressEvent (QGraphicsSceneMouseEvent *event)
{
QGraphicsItem* item = itemAt (event->scenePos ());
if (item)
{
B2DObject* obj = reinterpret_cast < B2DObject* > (item);
QString objName = obj->Name(); // just for example,
// getting other internal fields has
// the same effect (described below)
// use retrieved field somehow (e.g. print in the screen)
}
// give the event to the ancestor
}
Unfortunately, dynamic_cast will not work here because these classes are completely unrelated.
Then I create necessary objects and add it to my scene:
ExtendedPolygon* polygon = new ExtendedPolygon (parameters);
polygon->setName (QString ("Object 1"));
...
TestScene scene;
scene.addItem (polygon);
(for those who are not familiar with Qt, here is the prototype of the last function:
void QGraphicsScene::addItem(QGraphicsItem *item);
I guess it just stores all items in internal index storage and calls QGraphicsItem::paint (...) when item needs to be repainted. I suppose QGraphicsScene doesn't make any significant changes to this item).
So my problems start when I run the program and click on an item on the screen. TestScene::mousePressEvent is called (see a piece of code above).
Mouse click position is retrieved, item is found. Casting works fine: in the debugger window (I'm using Qt Creator) I see that obj points to ExtendedPolygon (address is the same as when I add the item to the scene and in the debugger window I can see all the fields). But when I get some field, I receive garbage in any case (and it does not matter, what I'm trying to get - a QString or a pointer to some other structure).
So first of all, I would like to get any advice about my multiple inheritance. In 95% of cases I try to avoid it, but here it is very effective in the programming point of view. So I would appreciate it if you provide me with your point of view about the architecture of the classes hierarchy - does it even suppose to work as I expect it?
If on this level everything is quite fine, then it would be great if someone gets any idea why doesn't it work.
I have some ideas about workaround, but I really would like to solve this problem (just in order not to repeat the same error anymore).
Looks like I've found the root cause of my problem. It was just lack of knowledge regarding how multiple inheritance really works on data layer.
Let's assume that we have 2 basic classes, A and B. Each of them provides some internal data fields and some interfaces.
Then we create a derived class AABB, inheriting both A and B:
class AABB : public A, public B {...}
AABB could add some additional data fields and reimplement some of the interfaces, but it is not necessary.
Let's create and object of class AABB:
AABB* obj = new AABB ();
For example, obj points at address 0x8416e0. At this address starts data from ancestor class A. Data from ancestor class B starts with some offset (it should bw equal to sizeof (A)), for example, at 0x841700.
If we have some function f (B* b), and if we pass a pointer at AABB object to that function (like this: f (obj), obj is created above), actually not obj start address is passed, but rather a pointer at a start of B data section of AABB object.
Thus this misunderstanding of multiple inheritance inner works has led me to the problem I've got.
I guess Qobjects and multiple inheritance has been already treated. As an example: QObject Multiple Inheritance