wx stalling after modal dialog exits Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

wx stalling after modal dialog exits

Post by Disch »

I'm making an NES emulator with wxWidgets (it's working out great so far!) I'm working on my input config dialog, where I let the user set their buttons and stuff. Everything works -- but I've noticed an odd delay that happens after you set a button.

Here's an image of my input config dialog to give the idea:
Image

The idea is the user clicks on the button they want to change, then I pop up a new modal dialog which waits and listens for keyboard/joystick input, and exits once it detects a key press. Additionally, there's that "Set All Buttons" button which steps through each button and opens that listening dialog repeatedly for each button.

This is all working... however there's an ugly delay that happens and I can't figure out why. It's especially noticable when you press the Set All Buttons button -- once the first dialog closes, nothing happens (the next dialog doesn't open) until you either move the mouse... press a key... or do something else that sends a message to the dialog.

It's very inconvienient, since the user will have their hands on their controller or keyboard at this point in the program... and pressing keys to get past the delay will interfere with the input for the next button -- and having to move your hand over to the mouse between each button is annoying and eliminates the conveinience of using the Set All Buttons button.

Between the first dialog closing and the next opening, the only thing I'm doing is changing the label of the buttons to reflect the key the user just assigned to that button. I'm not doing anything that should cause this kind of hangup. Here is relevent code (truncated to avoid my post from being huge):

Code: Select all


// called on "Set All Buttons" press
void dlg_configinput::OnCont_SetAll(wxCommandEvent& event)
{
	int sel = mControllerSelection->GetSelection();
	if((sel < 0) || (sel > 3))		return;

	sel = (sel * (BTN_P2_A - BTN_P1_A)) + BTN_P1_A;

	if(dlg_inputlistner::Go(this,&pBtns[sel+4],wxT("Button for:  Up")))			LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+5],wxT("Button for:  Down")))		LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+6],wxT("Button for:  Left")))		LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+7],wxT("Button for:  Right")))		LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+2],wxT("Button for:  Select")))		LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+3],wxT("Button for:  Start")))		LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+1],wxT("Button for:  B")))			LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+0],wxT("Button for:  A")))			LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+9],wxT("Button for:  Turbo B")))	LoadControllerNames();
	if(dlg_inputlistner::Go(this,&pBtns[sel+8],wxT("Button for:  Turbo A")))	LoadControllerNames();
}

// called to refresh the labels of the controller buttons
void dlg_configinput::LoadControllerNames()
{
	int oft = mControllerSelection->GetSelection();
	if((oft < 0) || (oft > 3))
		return;
	else
	{
		oft *= (BTN_P2_A - BTN_P1_A);
		oft += BTN_P1_A;

		mCont_A->SetLabel(		InputManager::GetButtonName(pBtns[oft+0]) );
		mCont_B->SetLabel(		InputManager::GetButtonName(pBtns[oft+1]) );
		mCont_Select->SetLabel(	InputManager::GetButtonName(pBtns[oft+2]) );
		mCont_Start->SetLabel(	InputManager::GetButtonName(pBtns[oft+3]) );
		mCont_Up->SetLabel(		InputManager::GetButtonName(pBtns[oft+4]) );
		mCont_Down->SetLabel(	InputManager::GetButtonName(pBtns[oft+5]) );
		mCont_Left->SetLabel(	InputManager::GetButtonName(pBtns[oft+6]) );
		mCont_Right->SetLabel(	InputManager::GetButtonName(pBtns[oft+7]) );
		mCont_TurboA->SetLabel(	InputManager::GetButtonName(pBtns[oft+8]) );
		mCont_TurboB->SetLabel(	InputManager::GetButtonName(pBtns[oft+9]) );
	}
}

// called to open up the new dialog that waits and listens for keypresses
// returns nonzero if the user selected a new button
int dlg_inputlistner::Go(wxWindow* parent,BUTTON* btn,const wxString& message)
{
	dlg_inputlistner* dlg = new dlg_inputlistner(parent,message);
	dlg->pBtn = btn;

	int ret = dlg->ShowModal();
	delete dlg;

	return (ret == wxID_OK);
}
I thought maybe the label changing done in LoadControllerNames was causing the delay, so I tried removing it. But that did not help at all.

I suspect the delay is happening after ShowModal exits, but before I change the labels. The reason why is because the labels changes don't become visible on screen until after I move the mouse.

Does anyone have any ideas what could be happening? How can I get around this? I'm using wxWidgets 2.8.7 on MSVS 2002 (yeah yeah, I know) and I'm running Windows 2000 SP4

Thanks in advance!
tan
wxWorld Domination!
wxWorld Domination!
Posts: 1471
Joined: Tue Nov 14, 2006 7:58 am
Location: Saint-Petersburg, Russia

Post by tan »

Hi,
try to insert ::wxYield() after every call of the dlg_inputlistner::Go(...):

Code: Select all

     if(dlg_inputlistner::Go(this,&pBtns[sel+4],wxT("Button for:  Up")))   LoadControllerNames();
     ::wxYield();   
     ...
OS: Windows XP Pro
Compiler: MSVC++ 7.1
wxWidgets: 2.8.10
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

wxYield does not appear to help. I tried putting it in a few places (after the ShowModal call, after deleteing my dialog object in Go(), etc) and nowhere seemed to make a difference.

To try and narrow down the problem I put in wxMessageBoxes in the same places to see where the delay is occuring. It seems to be that ShowModal() is not returning immediately (a message box placed immediately after it does not show until you move the mouse or press a key, etc)

In the likely event that I'm doing something I shouldn't be in my input listener dialog that is causing this weirdness... here is its code:

Code: Select all

#include <vector>
#include <wx/wx.h>
#include "nescore/nes_types.h"
#include "input.h"
#include "dlg_inputlistener.h"

// dialog has "Skip" and "Unassign" buttons
//   when "Skip" is pressed, assigned key does not change
//   when "Unassign" is pressed, key assignment is removed
//   I drop OK and CANCEL presses because I want control over
//   how the dialog exits -- I don't want it to exit when the 
//   user presses enter or escape or whatever unless I detect
//   enter/escape as an input key
//
//   The dialog does not contain any OK or CANCEL button
//   it does not even have a border or system menu

BEGIN_EVENT_TABLE(dlg_inputlistner,wxDialog)
	EVT_BUTTON(ID_Unassign,dlg_inputlistner::OnUnassign)
	EVT_BUTTON(ID_Skip,dlg_inputlistner::OnSkip)
	EVT_BUTTON(wxID_OK,dlg_inputlistner::OnDrop)
	EVT_BUTTON(wxID_CANCEL,dlg_inputlistner::OnDrop)
	EVT_IDLE(dlg_inputlistner::OnIdle)
END_EVENT_TABLE()

dlg_inputlistner::dlg_inputlistner()
{
	// just so you can't create any of these classes!
	//   must use static function Go()
}

dlg_inputlistner::dlg_inputlistner(wxWindow* parent,const wxString& message)
	: wxDialog(parent,-1,wxEmptyString,wxDefaultPosition,wxDefaultSize,0)
{
	wxPanel* panel = new wxPanel(this);
	lblCaught = new wxStaticText(panel,-1,wxT(" "));

	wxBoxSizer* pVBar = new wxBoxSizer(wxVERTICAL);
	pVBar->Add(new wxStaticText(panel,-1,wxT("Press the desired key")));

	if(!message.IsEmpty())
		pVBar->Add(new wxStaticText(panel,-1,message),0,wxTOP,8);
	pVBar->Add(lblCaught,0,wxTOP,8);

	wxBoxSizer* pHBar = new wxBoxSizer(wxHORIZONTAL);
	pHBar->Add(new wxButton(panel,ID_Unassign,wxT("Unassign")),1,wxRIGHT,8);
	pHBar->Add(new wxButton(panel,ID_Skip,wxT("Skip")),1);

	pVBar->Add(pHBar,0,wxTOP,16);

	wxBoxSizer* pHost = new wxBoxSizer(wxVERTICAL);
	pHost->Add(pVBar,0,wxALL,32);

	panel->SetSizer(pHost);
	pHost->SetSizeHints(panel);

	wxBoxSizer* pPanelSizor = new wxBoxSizer(wxVERTICAL);
	pPanelSizor->Add(panel);

	pPanelSizor->SetSizeHints(this);
	SetSizer(pPanelSizor);

	nBtnCaught = 0;

	// build ignore list
	BUTTON btn;
	while(Listen(btn))
	{
		mIgnoreList.push_back(btn);
	}
}

// InputManager::Listen simply scans through a bunch of keys and joysticks
// looking for any button that is pressed.  Keypresses are detected with wxGetKeyState.  I do this instead of catching
// messages because I need the realtime state of keys for
// when the emulator is running

int dlg_inputlistner::Listen(BUTTON& btn)
{
	if(mIgnoreList.empty())		return InputManager::Listen(btn,0,0);

	return InputManager::Listen(btn,&mIgnoreList[0],(int)mIgnoreList.size());
}

// every few milliseconds, I "Listen" for input.  Once a key is
// pressed and released, this function records that key and
// assigns it to this button, then exits the dialog

void dlg_inputlistner::OnIdle(wxIdleEvent& event)
{
	event.Skip();

	::wxMilliSleep(5);

	if(nBtnCaught)
	{
		BUTTON tmp;
		if(!Listen(tmp))
		{
			EndModal(wxID_OK);
			return;
		}
	}
	else if(Listen(*pBtn))
	{
		nBtnCaught = 1;
		lblCaught->SetLabel(InputManager::GetButtonName(*pBtn));
	}

	if(!event.MoreRequested())
		event.RequestMore();
}

// "Unassign" pressed -- removes key assignment, exits dialog
//   with wxID_OK indicating assignment has changed
void dlg_inputlistner::OnUnassign(wxCommandEvent& event)
{
	InputManager::Unassign(*pBtn);
	EndModal(wxID_OK);
}

// "Skip" pressed -- exits dialog with wxID_CANCEL
//   indicating assignment did not change
void dlg_inputlistner::OnSkip(wxCommandEvent& event)
{
	EndModal(wxID_CANCEL);
}

// OK or CANCEL pressed somehow -- simply drop it so they
//   do nothing (don't want them to close the dialog on me)
void dlg_inputlistner::OnDrop(wxCommandEvent& event)
{
	event.Skip(false);
	event.StopPropagation();
}

int dlg_inputlistner::Go(wxWindow* parent,BUTTON* btn,const wxString& message)
{
	dlg_inputlistner* dlg = new dlg_inputlistner(parent,message);
	dlg->pBtn = btn;

	int ret = dlg->ShowModal();
	 // ShowModal does not return until I move the mouse!
	delete dlg;

	return (ret == wxID_OK);
}
mc2r
wxWorld Domination!
wxWorld Domination!
Posts: 1195
Joined: Thu Feb 22, 2007 4:47 pm
Location: Denver, Co
Contact:

Post by mc2r »

I am not sure if this is your problem. But I'd try moving the creation of the dialog outside of your static go member function.

ie...

Code: Select all

int dlg_inputlistner::Go(BUTTON* btn)
{
        dlg->pBtn = btn;

        int ret = dlg->ShowModal();
         // ShowModal does not return until I move the mouse!
        delete dlg;

        return (ret == wxID_OK);
}

dlg_inputlistner* listner = new dlg_inputlistner(this,wxT("Button for:  Up"));
if(listner->Go(&pBtns[sel+4]))   LoadControllerNames();
-Max
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

That would require a good bit of shifting code around -- and I fail to see what difference it would make.

The fact that Go() is a member of dlg_inputlistner shouldn't matter. Since it's static -- it may as well be a global function... I just made it a static member for organizational sake.
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

Can you reproduce that in a compilable minimal sample?

Anyway I find your strategy a bit weird, in my own app I have a somewhat similar case, and I do not "listen" for events on idle, I simply set an event handler to one specific object, set the keyboard focus on that object, and wait for an event. (that works on wxGTK, didn't test windows)
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

Auria wrote:Can you reproduce that in a compilable minimal sample?
Actually... no!

I tried but my smaller sample worked fine. After tinkering around commenting out parts of the program that I suspected might be troublesome -- I've come to the realization that SDL Video was interfereing somehow.

I use SDL Video for fullscreen display (since wx offers no form of fullscreen display that I saw). But I still have to init it when running windowed because you need to init the video to get audio, joysticks and other stuff working.

Anyway... after quitting SDL's Video when running windowed, this delay disappears. Since these dialogs cannot be opened when you're running fullscreen, this does not cause a problem.

Thanks!
Anyway I find your strategy a bit weird, in my own app I have a somewhat similar case, and I do not "listen" for events on idle, I simply set an event handler to one specific object, set the keyboard focus on that object, and wait for an event. (that works on wxGTK, didn't test windows)
I need to get realtime pressed/released states, though, when running a game in the emulator... so wxGetKeyState is much easier. And when I run fullscreen, I'm using SDL for video, so I can't catch and process key events since there's no wx window to receives them. Plus it's not just keyboard events -- but I'm also interested in joystick and other input device states -- so keeping everything unified in one InputManager object that's independent of a wx window is just so much cleaner.

etc etc.

I suppose I could use events for keyboard input for input config and the other methods for in-game. However that would require duplicating a lot of code and would cause more headaches as I add support for additional input devices.

So anyway yeah .. I know it might seem strange, but I'm quite confident it really was the best way to go.
Post Reply