Re-using RenderWindow

Started by ingar, 30 April 2019, 15:50:39

ingar

Hi,

I have a TGUI app where a screen (dialog) starts another screen (dialog) with a button.
The second screen creates an sf::RenderWindow and attaches this window to a tgui::Gui.
This works, but to construct a new sf::RenderWindow everytime takes time.

Q: Is there a way a new tgui:Gui can reuse a window used by another tgui:Gui?
     The problem, as I can see, is that the render windows also gets destroyed when the Gui is destroyed,
     so that the old Gui crashes when it continues trying to use the destroyed window.
     Perhaps sf::RenderWindow has a copy constructor that doesn't take that long time to execute,
     or there is a way of detaching a window from the Gui?

Ingar


texus

tgui::Gui just keeps a pointer to the RenderWindow, it will not destroy the window when the Gui gets destroyed. You only have to make sure not to call functions on the Gui object after the window it uses has been destroyed.

Why do you need a second sf::RenderWindow? Can't you just continue to use the existing window? If you really need a second window then you also need a second Gui that renders to that window.

ingar

Hi again,

Thanks. I will try to re-use the RenderWindow.

My application has a main window, and a button for starting a "Setup" window. So what is then the best procedure?

A: Creating a new RenderWindow and a new Gui, as I am doing now
B: Re-using the RenderWindow with a new Gui.
    The change in my code will then be to let the "Back" (or "OK") button just set a "Finish" flag,
    instead of, as now, closing the RenderWindow.
C: Other method?

Ingar




Quote from: texus on 30 April 2019, 15:57:19
tgui::Gui just keeps a pointer to the RenderWindow, it will not destroy the window when the Gui gets destroyed. You only have to make sure not to call functions on the Gui object after the window it uses has been destroyed.

Why do you need a second sf::RenderWindow? Can't you just continue to use the existing window? If you really need a second window then you also need a second Gui that renders to that window.

ingar

Hi again,

I tried it. The second window just set a flag, and the event loop was finished for the second Gui.

But, unfortunately, the RenderWindow stopped responding to events. After the Gui was closed, three more
events came out of RenderWindow (I will figure out what events), and after that, pollEvent always returned false.
I guess, the Gui somehow is doing something so that RenderWindow does not return events.

Ingar



Quote from: ingar on 02 May 2019, 14:53:31
Hi again,

Thanks. I will try to re-use the RenderWindow.

My application has a main window, and a button for starting a "Setup" window. So what is then the best procedure?

A: Creating a new RenderWindow and a new Gui, as I am doing now
B: Re-using the RenderWindow with a new Gui.
    The change in my code will then be to let the "Back" (or "OK") button just set a "Finish" flag,
    instead of, as now, closing the RenderWindow.
C: Other method?

Ingar




Quote from: texus on 30 April 2019, 15:57:19
tgui::Gui just keeps a pointer to the RenderWindow, it will not destroy the window when the Gui gets destroyed. You only have to make sure not to call functions on the Gui object after the window it uses has been destroyed.

Why do you need a second sf::RenderWindow? Can't you just continue to use the existing window? If you really need a second window then you also need a second Gui that renders to that window.

texus

Is there anything that prevents you from just keeping the window and gui and just calling gui.removeAllWidgets() and adding the widgets for the next screen?
Multiple screens could be prepared upfront by creating a Group widget per screen (and adding the widgets to the group instead of to the gui). Then showing another screen is as simple as calling group1->setVisible(false); group2->setVisible(true);

Or maybe use a tgui::ChildWindow instead of an actual window?

texus

If you really want then I could give you an example code that uses 2 windows (e.g. if you want to show the setup window while the main window is still displayed in the background), but creating a new sf::RenderWindow is always going to take some time (creating a new window is simply slow operation, it isn't related to SFML). Maybe if you make the window invisible instead of closing it, the second time you open the settings could be faster, but the first time will always be relatively slow.
I recommend doing it with just a single window and having only a single main loop.

ingar

Hi again,

Thanks for the hints. :)
For now I just keep the multiple RenderWindows. I will try out Group widgets later. On my Raspberry by creating RenderWindow is not that slow, it's when I am running via an X-Windows (Xming) window on another computer I can see a 2 second delay.
But, it would be interesting to know why the RenderWindows stops returning events AFTER the second Gui has been terminated.

My first app only contains two screens, but I am planning another bigger app that uses may sub-windows. In this case, I need to figure out what happens. Perhaps, by patching SFML, I can figure out why pollEvent always fails.

Another thing: I have now made an App that creates a new app with two windows. If I send it to you, you can decide if you want to publish it as a sample source.

Ingar

Quote from: texus on 02 May 2019, 18:10:58
Is there anything that prevents you from just keeping the window and gui and just calling gui.removeAllWidgets() and adding the widgets for the next screen?
Multiple screens could be prepared upfront by creating a Group widget per screen (and adding the widgets to the group instead of to the gui). Then showing another screen is as simple as calling group1->setVisible(false); group2->setVisible(true);

Or maybe use a tgui::ChildWindow instead of an actual window?

ingar

Hi again!

VERY GOOD NEWS. I tried the tgui:Group and it worked beautifully.

So now I only create ONE RenderWindow. In my screen class  I declare tgui::Group::Ptr and load all my
widgets into the group from a stringstream (gui-builder was used to create the screen). And then I did gui.add(...).

When I enter the second screen, I did what you said: Doing setVisible(false), and when the second screen closes I do
setVisible(true). I do not have to do a setVisible(false) on the second group, because (I guess) the Group is destroyed
when my second screen class is destroyed, or perhaps when the second Gui is destroyed. What is correct? I am worried to create memory/resource leaks.

I am bit confused by the tgui::Ptr's. They cannot be C++ class pointers, since I cannot do new/delete on them. But still,
I have to use -> operator, not dot(.).

Ingar

texus

QuoteI am bit confused by the tgui::Ptr's. They cannot be C++ class pointers, since I cannot do new/delete on them. But still, I have to use -> operator, not dot(.).
tgui::Widget::Ptr is simply a typedef for std::shared_ptr<tgui::Widget>. So they are pointers, but memory is managed automatically so you don't have to delete them.
In example code I always use e.g. tgui::Button::create() to create the widget, but std::make_shared<tgui::Button>() would work just as well (although the create function may take extra optional parameters and it is shorter to write).

QuoteI am worried to create memory/resource leaks.
Removing widgets from the Gui and releasing memory are 2 different things (although in many cases they happen at the same time). Because widgets are stored as shared_ptr objects, the memory is only released when all pointers to it are gone. The gui object has pointers to the widgets it contains (and the group has pointers to the child widgets inside the group) and you may store pointers to widgets in your own code. If you remove the group from the gui or destroy the entire gui, the widgets will automatically be destroyed with it unless you were still storing these widgets somewhere in your own code.

If you put one group in one gui then you indeed don't have to do setVisible(false) as you are destroying the group when destroying the gui. My suggestion was however to only have a single Gui object to which both groups are added. Then you have to hide one when showing the other or you will be seeing the widgets from both groups at the same time.
You can of course keep using a Gui per screen. The Gui was intended to be constructed only once per window, but currently creating a new Gui object probably takes less time than creating a widget, so there shouldn't be a problem with recreating it every time.