I'm making a timer program the counts the time passed since the program started. In the background I'm also checking for keyboard input (enter/return to exit, click on the window); this is done in a separate thread I've run as detached.
It seems the second thread cannot receive the input from the main thread. When I use the keyboard or mouse, nothing happens. Also, nothing appears on the screen, just white.
std::mutex g_mutex;
std::condition_variable cv;
// check for input from the user using the window object
// sets stopProgram to true if the user wishes to exit
void poll(sf::RenderWindow& window, bool& stopProgram) {
std::unique_lock<std::mutex> lk(g_mutex);
// wait for main thread to open window
cv.wait(lk, [&] { return !stopProgram && window.isOpen(); });
sf::Event event;
while (true) {
if (window.pollEvent(event)) {
// if user wants to exit program
if (event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed &&
(event.key.code == sf::Keyboard::Return || event.key.code == sf::Keyboard::Escape))) {
window.close();
// main thread will explicitly exit the main loop
stopProgram = true;
break;
}
}
}
}
int main()
{
int hour = 0, minute = 0, second = 0;
auto text = textObject();
bool stopProgram = false;
// run a background thread that checks for input while the main program runs
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
std::once_flag flag;
std::lock_guard<std::mutex> lk(g_mutex);
while (window.isOpen()) {
// notify once window opens
std::call_once(flag, [&] { cv.notify_one(); });
// set timestamp
text->setString(makeTimeStamp(hour, minute, second));
// if the background thread set stopProgram, end the program
if (stopProgram) break;
window.clear(sf::Color::White);
window.draw(*text);
window.display();
// update time
second = (second + 1) % MAX_SEC;
if (second == 0) minute = (minute + 1) % MAX_MIN;
if (second == 0 && minute == 0) hour = (hour + 1) % MAX_HOUR;
// sleep one second
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Is my use of multithreading correct? If so, can only the main thread receive input and that's why it's not working?
Update: If I get rid of the while (true) and use while (window.pollEvent(event)) and move the lock_guard to just before if (stopProgram) then the text (timestamp) appears on the screen, but I still cannot process input.
main thread launches poll thread.
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
main thread acquires g_mutex and never ever releases it.
std::lock_guard<std::mutex> lk(g_mutex);
poll thread waits for g_mutex to be released:
std::unique_lock<std::mutex> lk(g_mutex);
but the main thread never releases it, so the poll thread never does anything.
To fix it. Change the beginning of the main() function:
int main()
{
int hour = 0, minute = 0, second = 0;
auto text = textObject();
volatile bool stopProgram = false;
// run a background thread that checks for input while the main program runs
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
while (!window.isOpen()) { /* busy loop */ }
{
std::lock_guard<std::mutex> lk(g_mutex);
cv.notify_all();
}
while (window.isOpen()) {
...
This SFML API makes things more difficult than other windowing frameworks that I've used. It would be sooo useful if there were a thread-safe window.pushCustomEvent() function.
Related
I'm trying to detect keyPress only once, but it goes for random amount of presses. I'm using keyrelease, and it work normally if I have breakpoint.
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
} else {
characterScreen.handleEvent(event, window);
}
}
bool keyReleased = event.type == sf::Event::KeyPressed;
bool rightArrowKey = event.key.code == sf::Keyboard::Right;
bool leftArrowKey = event.key.code == sf::Keyboard::Left;
if (keyReleased&&leftArrowKey)
{
if (selectedCharacter == 0)
{
selectedCharacter = characterList.size() - 1;
}
else
{
selectedCharacter--;
}
changeCharacters(characterList[selectedCharacter], font, characterScreen);
std::cout << "left pressed" << std::endl;
keyReleased = false;
std::cout << keyReleased << std::endl;
}
else if (keyReleased && rightArrowKey)
{
if (selectedCharacter == characterList.size()-1)
{
selectedCharacter = 0;
}
else
{
selectedCharacter++;
}
changeCharacters(characterList[selectedCharacter], font, characterScreen);
}
window.clear(sf::Color::White);
characterScreen.update();
characterScreen.render(window);
window.display();
}
I've tryed window.setKeyRepeatEnabled(false); and keypressed, still nothing. Tryed different keyboard aswell.
When using (C)SMFL, we generally have a main loop condition rendering the main frame (the windows) and checking events, it can be an infinite loop (such as while (1)) or a more recomanded one like your while (window.isOpen()).
It means that the windows and all events listener are refreshed / updated each loop's turn, so if you press a key on your keyboard, you will trigger your keyboard's event listener, fine, and you will continue your code until re-enter in your main loop, and re-refreshing the windows and re-updating your event listener.
What the point of explaining that ? it just means by only pressing once a key, your computer will process maybe 10, 100 or maybe 1000 iteration of your main loop (depending on it's performances and more). So it's "normal" to works fine when you use convenient features like breakpoints, but in reality you can't.
You have a lot of possibilities, bad ones (like "use delay / pause" who's not recommended since it will halt your window's refresh and can be interpreted by a crash by your OS) or good ones like tampon variables, who'll cancel the entrance of some conditions by their state:
Example:
bool tmp_var = false;
while (window.isOpen()) {
// Warn, you called your variable keyReleased but it check keyPressed
if (event.type == sf::Event::KeyPressed && tmp_var == false) {
// Check wich key is pressed and perform required actions
tmp_var = true;
} else if (event.type == sf::Event::KeyReleased) {
tmp_var = false;
}
}
Note that the code can be not correct, I wanted to point out the reasoning
I am using sfml library for my graphics. During paused I want to avoid drawing on the screen. I want to intercept a spacebar press using sf::Event::MouseButtonPressed. In the debug mode, the event is successfully intercepted but failed in normal execution, why? The bool variable 'paused' don't seemed to update.
while (window.isOpen()) {
Event event;
while (window.pollEvent(event)) {
/*..*/
if (event.type = Event::KeyPressed) {
if (event.key.code == Keyboard::Space) {
if (!paused) {
paused = true;// UDATE HERE
}
else
if (paused)
{
paused = false; // UDATE HERE
}
}
}
}
if (!paused) //// UDATE DONT SEEM TO AFFECT HERE
{
/* draw here */
}
}
Edited: Even tried by declaring the variable paused as atomic, but still don't work.
Use the Event::KeyReleased instead of the Event::KeyPressed as the key press gets called every loop that a key is down leading to many enters while the Release event is only called once per click.
Consider a simple program: (require -std=c++11 -lpthread -lreadline -lSDL2 when compile with g++ with pthread)
#include <iostream>
#include <thread>
#include <SDL2/SDL.h>
#include <cstdlib>
extern "C" {
#include <readline/readline.h>
}
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
auto window = SDL_CreateWindow("title", 0, 0, 200, 200, SDL_WINDOW_SHOWN);
std::thread console([](){
while (true) {
char *console_input_c_str = readline("> ");
if (console_input_c_str == NULL)
break;
std::cout << "line: " << console_input_c_str << '\n';
std::free(console_input_c_str);
}
});
while (true) {
SDL_Event event;
SDL_WaitEvent(&event);
std::cerr << "received event type "<<event.type<<'\n';
if(event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_CLOSE)
break;
}
SDL_DestroyWindow(window);
SDL_Quit();
console.join();
}
The main thread creates a window window with SDL2, enter a SDL event loop, while the thread console repeatedly read from the console using readline. When the window is exited, it waits for the console thread to finish then exit.
The program works fine; however, how can I make the console thread to stop when the window is exited?
It's fine if the program only use a single thread. It's also fine if exiting the console readline quits the program.
Exit readline is easy, but there is a problem with all the answers there - pthread_kill or reset -Q is not portable.
Terminating a thread is easy (using std::terminate or .detach()), but the console is left in a bad state because readline don't terminate normally. To reset the console it's possible to use reset -Q, but that's not portable.
Using readline's alternative (async) interface doesn't work because SDL don't listen for events on the console. Using select is also not portable.
Actually, readline is not completely blocking, it calls rl_event_hook every 1/10 seconds (by default). It's possible to check whether the program is stopped inside the event hook, and if so do a longjmp or throw to get out of the function (in this example int is used, although it's possible to define your own exception class):
std::atomic<bool> stopped (false);
rl_event_hook = [](){
if (stopped)
throw 0;
return 0;
};
try {
/* something calls readline() */
} catch (int) {
rl_cleanup_after_signal();
}
return 0;
or by setting rl_done = 1: (this won't work when rl_event_hook is nullptr. Also note that rl_event_hook is not called when a key is held, it's necessary to override rl_getc_function to change the behavior. It's not recommended to modify rl_done from the other thread)
std::atomic<bool> stopped (false);
rl_event_hook = [](){
if (stopped)
rl_done = true;
return 0;
};
/* something calls readline() */
if (stopped) return 0;
/* process */
return 0;
and in the main thread use:
stopped = true;
console.join();
I am using SFML and C++ and I am getting an odd problem,
Here is my main game update method
while (renderService.Window.isOpen())
{
//Poll events
sf::Event event;
while (renderService.Window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
renderService.Window.close();
running = false;
}
MouseMovment(event);
MouseClick(event);
Update();
Draw();
}
and here is my MouseClick method
void Game::MouseClick(sf::Event event)
{
sf::Vector2i position = sf::Mouse::getPosition(renderService.Window);
if (event.mouseButton.button == sf::Mouse::Left && event.type == sf::Event::MouseButtonReleased)
{
std::cout << "Mouse released" << std::endl;
}
}
now here is the weird part, in my console sometimes my cout will be spammed like 10/20 times, but sometimes it will work perfectly, am I calling the event wrongly?
You're doing it wrong, suppose that a MouseButtonReleased event is fired and your polling function grabs it (follows the numbers in the comments):
while (renderService.Window.isOpen()) // 4) The loop starts again
{
//Poll events
sf::Event event;
while (renderService.Window.pollEvent(event)) // 1) Grabs the event // 5) No more events
{
if (event.type == sf::Event::Closed) // 2) Nope, it's not this one
renderService.Window.close();
running = false;
}
MouseMovment(event);
MouseClick(event); // 3) Yes, handle it // 6) Uses the unmodified event variable - undefined behavior
Update();
Draw();
}
you should rather do something like:
sf::Event event;
// while there are pending events...
while (window.pollEvent(event))
{
// check the type of the event...
switch (event.type)
{
// window closed
case sf::Event::Closed:
...
break;
// mouse button released
case sf::Event::MouseButtonReleased:
{
if (event.mouseButton.button == sf::Mouse::Left)
...
} break;
// we don't process other types of events
default:
break;
}
}
First of all I'd like to say that I'm not sure if the title is appropriate but it's the only logical explanation I could find.
What I'm trying to do is move my window by clicking a sprite (sort of like simulating a titlebar).
All working fine until I let go of the mouse button the rate new mouse events are emitted is way lower and with huge 1.5~2s pauses between them.
Is it possible that sf::Mouse::isBUttonPressed is filling the queue or is it another issue?
Edit: The window class has a sf::Event object and passes it to each object's event handler.
The sprite class has an event handler of this form:
bool object::handleEvents(sf::Event& event)
{
switch(event.type)
{
case sf::Event::MouseMoved:
case sf::Event::MouseButtonPressed:
case sf::Event::MouseButtonReleased:
{
auto mouse_pos = sf::Mouse::getPosition(*this->parent);
if(this->isPointInside(mouse_pos))
{
if(event.type == sf::Event::MouseMoved)
{
this->hovering = true;
if(this->callback["onHover"])
this->callback["onHover"](this, nullptr);
return true;
}
else if(event.type == sf::Event::MouseButtonPressed)
{
this->clicked = true;
this->focused = true;
if(event.mouseButton.button == sf::Mouse::Left)
if(this->callback["onLClick"])
this->callback["onLClick"](this, ref(mouse_pos));
if(event.mouseButton.button == sf::Mouse::Right)
if(this->callback["onRClick"])
this->callback["onRClick"](this, ref(mouse_pos));
return true;
}
else if(event.type == sf::Event::MouseButtonReleased && this->clicked)
{
this->clicked = false;
if(event.mouseButton.button == sf::Mouse::Left)
if(this->callback["onLClickReleased"])
this->callback["onLClickReleased"](this, ref(mouse_pos));
if(event.mouseButton.button == sf::Mouse::Right)
if(this->callback["onRClickReleased"])
this->callback["onRClickReleased"](this, ref(mouse_pos));
return true;
}
}
else
{
if(this->hovering)
{
if(this->callback["onHoverLost"])
this->callback["onHoverLost"](this, nullptr);
this->hovering = false;
}
}
}break;
default: ;
}
return false;
}
and the code responsible for moving the window:
titlebar->callback["onLClick"] = [&](object* obj, void* data)
{
sf::Vector2i* relpos = (sf::Vector2i*)(data);
while(sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
sf::Vector2i abspos = sf::Mouse::getPosition();
window.setPosition(sf::Vector2i((abspos.x - relpos->x),(abspos.y - relpos->y)));
}
titlebar->clicked = false;
};
The sf::Mouse class as well as sf::Keyboard and sf::Joystick aren't connected to the event system, but are completely independent, thus it's impossible that isButtonPressed would've any influence on the events.
The real problem in your code is the 'infinite' loop when the left mouse button is pressed. If the left mouse button is pressed, everything that happens in your application is, that the window gets moved around. There won't be any event dispatches (= processing) and every event that happens within that time, will get pilled in the event queue. Thus when you return to process the events, you'll have a longer queue than usual and will start dispatching with the oldest event.
So if you now move your window around for 2 seconds, you'll get a filled queue worth 2 seconds which can delay the further processing.
To solve this problem, you'll most probably have to dispatch all the events while you're moving the window.