Simplified loading of news: no more threading in main_menu.

git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@7129 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2010-12-21 06:13:40 +00:00
parent 83a9993f68
commit 13241636d4
4 changed files with 178 additions and 114 deletions

View File

@ -21,6 +21,7 @@
#include <curl/curl.h>
#include <stdio.h>
#include <string>
#if defined(WIN32) && !defined(__CYGWIN__)
# define isnan _isnan
#else
@ -33,40 +34,151 @@
#include "states_screens/addons_screen.hpp"
#include "states_screens/main_menu_screen.hpp"
#if defined(WIN32) && !defined(__CYGWIN__)
// Use Sleep, which takes time in msecs. It must be defined after the
// includes, since otherwise irrlicht's sleep function is changed.
# define sleep(s) Sleep(1000*(s))
#else
# include <unistd.h>
#endif
pthread_mutex_t download_mutex;
NetworkHttp * network_http = 0;
// ------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------
/** Create a thread that handles all network functions independent of the
* main program.
*/
NetworkHttp::NetworkHttp()
{
pthread_t thread;
pthread_create(&thread, NULL, &NetworkHttp::checkNewServer, this);
}
pthread_mutex_init(&m_mutex_news, NULL);
m_news_message = "";
pthread_mutex_init(&m_mutex_command, NULL);
pthread_cond_init(&m_cond_command, NULL);
m_command = HC_SLEEP;
m_abort = false;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&m_thread_id, &attr, &NetworkHttp::mainLoop, this);
pthread_attr_destroy(&attr);
} // NetworkHttp
// ---------------------------------------------------------------------------
void * NetworkHttp::checkNewServer(void * obj)
/** The actual main loop, which is started as a separate thread from the
* constructor. After testing for a new server, fetching news, the list
* of packages to download, it will wait for commands to be issued.
* \param obj: A pointer to this object, passed on by pthread_create
*/
void *NetworkHttp::mainLoop(void *obj)
{
NetworkHttp * pthis = (NetworkHttp *)obj;
std::string newserver = pthis->downloadToStr("redirect");
NetworkHttp *me=(NetworkHttp*)obj;
me->checkNewServer();
const std::string tmp_str = me->downloadToStr("news");
pthread_mutex_lock(&(me->m_mutex_news));
{
printf("tmp %s\n", tmp_str.c_str());
// Only lock the actual assignment, not the downloading!
me->m_news_message = tmp_str;
}
pthread_mutex_unlock(&(me->m_mutex_news));
// Allow this thread to be cancelled anytime
// FIXME: this mechanism will later not be necessary anymore!
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// Wait in the main loop till a command is received
// (atm only QUIT is used).
pthread_mutex_lock(&me->m_mutex_command);
while(1)
{
pthread_cond_wait(&me->m_cond_command, &me->m_mutex_command);
switch(me->m_command)
{
case HC_QUIT:
pthread_exit(NULL);
break;
case HC_SLEEP: break;
case HC_NEWS:
const std::string tmp_str = me->downloadToStr("news");
pthread_mutex_lock(&(me->m_mutex_news));
{
me->m_news_message = tmp_str;
}
pthread_mutex_unlock(&(me->m_mutex_news));
break;
} // switch(m_command)
} // while !m_abort
return NULL;
} // mainLoop
// ---------------------------------------------------------------------------
/** Aborts the thread running here, and returns then.
*/
NetworkHttp::~NetworkHttp()
{
m_abort=1; // Doesn't really need a mutex
pthread_mutex_lock(&m_mutex_command);
{
m_command=HC_QUIT;
pthread_cond_signal(&m_cond_command);
}
pthread_mutex_unlock(&m_mutex_command);
void *result;
pthread_join(m_thread_id, &result);
pthread_mutex_destroy(&m_mutex_news);
pthread_mutex_destroy(&m_mutex_command);
pthread_cond_destroy(&m_cond_command);
} // ~NetworkHttp
// ---------------------------------------------------------------------------
/** Checks if a redirect is received, causing a new server to be used for
* downloading addons.
*/
void NetworkHttp::checkNewServer()
{
std::string newserver = downloadToStr("redirect");
if (newserver != "")
{
newserver.replace(newserver.find("\n"), 1, "");
std::cout << "[Addons] Current server: " << UserConfigParams::m_server_addons.toString() << std::endl;
if(UserConfigParams::m_verbosity>=4)
{
std::cout << "[Addons] Current server: "
<< UserConfigParams::m_server_addons.toString()
<< std::endl
<< "[Addons] New server: " << newserver << std::endl;
}
UserConfigParams::m_server_addons = newserver;
std::cout << "[Addons] New server: " << newserver << std::endl;
user_config->saveConfig();
}
else
else if(UserConfigParams::m_verbosity>=4)
{
std::cout << "[Addons] No new server." << std::endl;
}
return NULL;
}
} // checkNewServer
// ------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------
/** Returns the last loaded news message (using mutex to make sure a valid
* value is available).
*/
const std::string NetworkHttp::getNewsMessage() const
{
pthread_mutex_lock(&m_mutex_news);
const std::string tmp_str = m_news_message;
pthread_mutex_unlock(&m_mutex_news);
return tmp_str;
} // getNewsMessage
// ----------------------------------------------------------------------------
size_t NetworkHttp::writeStr(char ptr [], size_t size, size_t nb_char, std::string * stream)
{
@ -113,9 +225,9 @@ bool download(std::string file, const std::string &save, int * progress_data)
curl_easy_setopt(session, CURLOPT_URL, full_url.c_str());
FILE * fout;
if(save != "")
fout = fopen(std::string(file_manager->getConfigDir() + "/" + save).c_str(), "w");
fout = fopen((file_manager->getAddonsDir() + "/" + save).c_str(), "w");
else
fout = fopen(std::string(file_manager->getConfigDir() + "/" + file).c_str(), "w");
fout = fopen((file_manager->getAddonsDir() + "/" + file).c_str(), "w");
//from and out

View File

@ -24,14 +24,50 @@
#include <string>
class NetworkHttp
{
private:
public:
NetworkHttp();
static void * checkNewServer(void * obj);
static size_t writeStr(char str [], size_t size, size_t nb_char,
std::string * stream);
std::string downloadToStr(std::string url);
public:
/** List of 'http commands' for this object:
* HC_SLEEP: No command, sleep
* HC_QUIT: Stop loop and terminate thread.
* HC_NEWS: Update the news
*/
enum HttpCommands {HC_SLEEP,
HC_QUIT,
HC_NEWS } ;
private:
/** The news message from the server. This is guarded by m_mutex_news. */
std::string m_news_message;
/** A mutex for accessing m_news_message. Exclude this so that
* getter can be declared const. */
mutable pthread_mutex_t m_mutex_news;
/** Which command to execute next. Access to this variable is guarded
* by m_mutex_command and m_cond_command. */
HttpCommands m_command;
/** A mutex for accessing m_commands. */
pthread_mutex_t m_mutex_command;
/** A conditional variable to wake up the main loop. */
pthread_cond_t m_cond_command;
/** Thread id of the thread running in this object. */
pthread_t m_thread_id;
/** Signals that the main loop is to be aborted. */
bool m_abort;
static void *mainLoop(void *obj);
void checkNewServer();
public:
NetworkHttp();
~NetworkHttp();
static size_t writeStr(char str [], size_t size, size_t nb_char,
std::string * stream);
std::string downloadToStr(std::string url);
const std::string
getNewsMessage() const;
};
extern NetworkHttp *network_http;

View File

@ -63,30 +63,7 @@ MainMenuScreen::MainMenuScreen() : Screen("main.stkgui")
{
}
#endif
#ifdef ADDONS_MANAGER
void MainMenuScreen::changeNewsText(std::string action, std::string content)
{
if(action == "news")
{
pthread_testcancel(); // check if thread was cancelled
// enter the mutex; this will prevent the shutdown procedure
// from continuing until this critical section is over
pthread_mutex_lock(&(this->m_mutex_news_text));
m_news_text = content;
pthread_mutex_unlock(&(this->m_mutex_news_text));
}
if(action == "offline")
{
pthread_testcancel(); // check if thread was cancelled
pthread_mutex_lock(&(this->m_mutex_news_text));
m_news_text = "offline";
pthread_mutex_unlock(&(this->m_mutex_news_text));
}
}
#endif
// ------------------------------------------------------------------------------------------------------
void MainMenuScreen::loadedFromFile()
@ -117,13 +94,6 @@ void MainMenuScreen::init()
// the key bindings for the first player the default again.
input_manager->getDeviceList()->clearLatestUsedDevice();
#ifdef ADDONS_MANAGER
// FIXME: this is wrong, init may be called several times in the object's
// lifespan, and the pthread docs state that initing several times the same
// mutex can cause undefined behavior
pthread_mutex_init(&(this->m_mutex_news_text), NULL);
pthread_create(&m_thread_news_text, NULL, &MainMenuScreen::downloadNews, this);
#endif
}
#ifdef ADDONS_MANAGER
@ -132,14 +102,13 @@ void MainMenuScreen::onUpdate(float delta, irr::video::IVideoDriver* driver)
{
//FIXME:very bad for performance
LabelWidget* w = this->getWidget<LabelWidget>("info_addons");
pthread_mutex_lock(&(this->m_mutex_news_text));
if(m_news_text == "offline")
const std::string &news_text = network_http->getNewsMessage();
if(news_text == "")
{
w->setText("Can't access stkaddons server...");
}
else
w->setText(m_news_text.c_str());
pthread_mutex_unlock(&(this->m_mutex_news_text));
w->setText(news_text.c_str());
}
#endif
// ------------------------------------------------------------------------------------------------------
@ -237,48 +206,4 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, cons
StateManager::get()->pushScreen(AddonsScreen::getInstance());
}
#endif
}
#ifdef ADDONS_MANAGER
// ------------------------------------------------------------------------------------------------------
void * MainMenuScreen::downloadNews( void * pthis)
{
// enable thread to be cancelled
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
// FIXME: this code is wrong, "pt" might have been deleted by the
// time the download is done (by having switched to another screen,
// or exiting the game, etc...)
//MainMenuScreen * pt = (MainMenuScreen*)pthis;
std::string news = network_http->downloadToStr("news");
if (news != "")
{
pthread_testcancel(); // check if thread was cancelled
MainMenuScreen * pt = (MainMenuScreen*)pthis;
pt->changeNewsText("news", news);
}
else
{
pthread_testcancel(); // check if thread was cancelled
MainMenuScreen * pt = (MainMenuScreen*)pthis;
pt->changeNewsText("offline");
}
return NULL;
}
#endif
// ------------------------------------------------------------------------------------------------------
#ifdef ADDONS_MANAGER
void MainMenuScreen::tearDown()
{
fprintf(stdout, "canceling the thread\n");
// grab the mutex before returning to make sure the screen is not deleted while the thread is
// changing the text, which would result in weird results (tearDown does not automatically mean
// the screen is going to be destroyed but let's play on the safe side)
pthread_mutex_lock(&(this->m_mutex_news_text));
pthread_cancel(m_thread_news_text);
pthread_mutex_unlock(&(this->m_mutex_news_text));
}
#endif
}

View File

@ -34,13 +34,8 @@ class MainMenuScreen : public GUIEngine::Screen, public GUIEngine::ScreenSinglet
public:
#ifdef ADDONS_MANAGER
void changeNewsText(std::string action, std::string content="");
pthread_mutex_t m_mutex_news_text;
pthread_t m_thread_news_text;
std::string m_news_text;
virtual void onUpdate(float delta, irr::video::IVideoDriver* driver);
virtual void tearDown();
#endif
/** \brief implement callback from parent class GUIEngine::Screen */
@ -50,11 +45,7 @@ public:
virtual void eventCallback(GUIEngine::Widget* widget, const std::string& name, const int playerID);
/** \brief implement callback from parent class GUIEngine::Screen */
virtual void init();
/** This function is used to download a text from the server to show the news. */
static void * downloadNews(void *);
virtual void init();
};
#endif