#include #include #include #include "ThumbnailBroker.h" #include "ThumbnailListener.h" #include "Client.h" #include "HTTP.h" #include "GameSave.h" #include "search/Thumbnail.h" #include "simulation/SaveRenderer.h" //Asynchronous Thumbnail render & request processing class ThumbnailBroker::ThumbnailSpec { public: int Width, Height; ThumbnailListener * CompletedListener; ThumbnailSpec(int width, int height, ThumbnailListener * completedListener) : Width(width), Height(height), CompletedListener(completedListener) {} }; class ThumbnailBroker::ThumbnailID { public: int SaveID, SaveDate; bool operator ==(const ThumbnailID & second) { return SaveID == second.SaveID && SaveDate == second.SaveDate; } ThumbnailID(int saveID, int saveDate) : SaveID(saveID), SaveDate(saveDate) {} ThumbnailID() : SaveID(0), SaveDate(0) {} }; class ThumbnailBroker::ThumbnailRequest { public: bool Complete; void * HTTPContext; int RequestTime; ThumbnailID ID; std::vector SubRequests; ThumbnailRequest(int saveID, int saveDate, int width, int height, ThumbnailListener * completedListener) : ID(saveID, saveDate), Complete(false), HTTPContext(NULL), RequestTime(0) { SubRequests.push_back(ThumbnailSpec(width, height, completedListener)); } ThumbnailRequest() : Complete(false), HTTPContext(NULL), RequestTime(0) {} }; class ThumbnailBroker::ThumbRenderRequest { public: int Width, Height; GameSave * Save; ThumbnailListener * CompletedListener; ThumbRenderRequest(GameSave * save, int width, int height, ThumbnailListener * completedListener) : Save(save), Width(width), Height(height), CompletedListener(completedListener) {} ThumbRenderRequest() : Save(0), Width(0), Height(0), CompletedListener(NULL) {} }; ThumbnailBroker::ThumbnailBroker() { thumbnailQueueRunning = false; thumbnailQueueMutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_init (&thumbnailQueueMutex, NULL); listenersMutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_init (&listenersMutex, NULL); } ThumbnailBroker::~ThumbnailBroker() { } void ThumbnailBroker::RenderThumbnail(GameSave * gameSave, int width, int height, ThumbnailListener * tListener) { AttachThumbnailListener(tListener); pthread_mutex_lock(&thumbnailQueueMutex); bool running = thumbnailQueueRunning; thumbnailQueueRunning = true; renderRequests.push_back(ThumbRenderRequest(new GameSave(*gameSave), width, height, tListener)); pthread_mutex_unlock(&thumbnailQueueMutex); if(!running) { #ifdef DEBUG std::cout << typeid(*this).name() << " Starting background thread for new " << __FUNCTION__ << " request" << std::endl; #endif pthread_create(&thumbnailQueueThread, 0, &ThumbnailBroker::thumbnailQueueProcessHelper, this); } } void ThumbnailBroker::RetrieveThumbnail(int saveID, int saveDate, int width, int height, ThumbnailListener * tListener) { AttachThumbnailListener(tListener); pthread_mutex_lock(&thumbnailQueueMutex); bool running = thumbnailQueueRunning; thumbnailQueueRunning = true; thumbnailRequests.push_back(ThumbnailRequest(saveID, saveDate, width, height, tListener)); pthread_mutex_unlock(&thumbnailQueueMutex); if(!running) { #ifdef DEBUG std::cout << typeid(*this).name() << " Starting background thread for new " << __FUNCTION__ << " request" << std::endl; #endif pthread_create(&thumbnailQueueThread, 0, &ThumbnailBroker::thumbnailQueueProcessHelper, this); } } void * ThumbnailBroker::thumbnailQueueProcessHelper(void * ref) { ((ThumbnailBroker*)ref)->thumbnailQueueProcessTH(); return NULL; } void ThumbnailBroker::FlushThumbQueue() { pthread_mutex_lock(&thumbnailQueueMutex); while(thumbnailComplete.size()) { if(CheckThumbnailListener(thumbnailComplete.front().first)) { thumbnailComplete.front().first->OnThumbnailReady(thumbnailComplete.front().second); } else { #ifdef DEBUG std::cout << typeid(*this).name() << " Listener lost, discarding request" << std::endl; #endif delete thumbnailComplete.front().second; } thumbnailComplete.pop_front(); } pthread_mutex_unlock(&thumbnailQueueMutex); } void ThumbnailBroker::thumbnailQueueProcessTH() { time_t lastAction = time(NULL); while(true) { //Shutdown after 2 seconds of idle if(time(NULL) - lastAction > 2) { pthread_mutex_lock(&thumbnailQueueMutex); thumbnailQueueRunning = false; pthread_mutex_unlock(&thumbnailQueueMutex); break; } //Renderer pthread_mutex_lock(&thumbnailQueueMutex); if(renderRequests.size()) { lastAction = time(NULL); ThumbRenderRequest req; req = renderRequests.front(); renderRequests.pop_front(); pthread_mutex_unlock(&thumbnailQueueMutex); #ifdef DEBUG std::cout << typeid(*this).name() << " Processing render request" << std::endl; #endif Thumbnail * thumbnail = SaveRenderer::Ref().Render(req.Save); delete req.Save; if(thumbnail) { thumbnail->Resize(req.Width, req.Height); pthread_mutex_lock(&thumbnailQueueMutex); thumbnailComplete.push_back(std::pair(req.CompletedListener, thumbnail)); pthread_mutex_unlock(&thumbnailQueueMutex); } } else { pthread_mutex_unlock(&thumbnailQueueMutex); } //Renderer pthread_mutex_lock(&thumbnailQueueMutex); if(thumbnailRequests.size()) { lastAction = time(NULL); Thumbnail * thumbnail = NULL; ThumbnailRequest req; req = thumbnailRequests.front(); //Check the cache for(std::deque >::iterator iter = thumbnailCache.begin(), end = thumbnailCache.end(); iter != end; ++iter) { if((*iter).first == req.ID) { thumbnail = (*iter).second; #ifdef DEBUG std::cout << typeid(*this).name() << " " << req.ID.SaveID << ":" << req.ID.SaveDate << " found in cache" << std::endl; #endif } } if(thumbnail) { //Got thumbnail from cache thumbnailRequests.pop_front(); pthread_mutex_unlock(&thumbnailQueueMutex); for(std::vector::iterator specIter = req.SubRequests.begin(), specEnd = req.SubRequests.end(); specIter != specEnd; ++specIter) { Thumbnail * tempThumbnail = new Thumbnail(*thumbnail); tempThumbnail->Resize((*specIter).Width, (*specIter).Height); pthread_mutex_lock(&thumbnailQueueMutex); thumbnailComplete.push_back(std::pair((*specIter).CompletedListener, tempThumbnail)); pthread_mutex_unlock(&thumbnailQueueMutex); } } else { //Check for ongoing requests bool requested = false; for(std::list::iterator iter = currentRequests.begin(), end = currentRequests.end(); iter != end; ++iter) { if((*iter).ID == req.ID) { requested = true; #ifdef DEBUG std::cout << typeid(*this).name() << " Request for " << req.ID.SaveID << ":" << req.ID.SaveDate << " found, appending." << std::endl; #endif //Add the current listener to the item already being requested (*iter).SubRequests.push_back(req.SubRequests.front()); } } if(requested) { //Already requested thumbnailRequests.pop_front(); pthread_mutex_unlock(&thumbnailQueueMutex); } else if(currentRequests.size() < IMGCONNS) //If it's not already being requested and we still have more space for a new connection, request it { thumbnailRequests.pop_front(); pthread_mutex_unlock(&thumbnailQueueMutex); //If it's not already being requested, request it std::stringstream urlStream; urlStream << "http://" << STATICSERVER << "/" << req.ID.SaveID; if(req.ID.SaveDate) { urlStream << "_" << req.ID.SaveDate; } urlStream << "_small.pti"; #ifdef DEBUG std::cout << typeid(*this).name() << " Creating new request for " << req.ID.SaveID << ":" << req.ID.SaveDate << std::endl; #endif req.HTTPContext = http_async_req_start(NULL, (char *)urlStream.str().c_str(), NULL, 0, 1); req.RequestTime = time(NULL); currentRequests.push_back(req); } else { //Already full of requests pthread_mutex_unlock(&thumbnailQueueMutex); } } } else { pthread_mutex_unlock(&thumbnailQueueMutex); } std::list::iterator iter = currentRequests.begin(); while (iter != currentRequests.end()) { lastAction = time(NULL); ThumbnailRequest req = *iter; Thumbnail * thumbnail = NULL; if(http_async_req_status(req.HTTPContext)) { pixel * thumbData; char * data; int status, data_size, imgw, imgh; data = http_async_req_stop(req.HTTPContext, &status, &data_size); free(req.HTTPContext); if (status == 200 && data) { thumbData = Graphics::ptif_unpack(data, data_size, &imgw, &imgh); free(data); if(thumbData) { thumbnail = new Thumbnail(req.ID.SaveID, req.ID.SaveID, thumbData, ui::Point(imgw, imgh)); free(thumbData); } else { //Error thumbnail VideoBuffer errorThumb(128, 128); errorThumb.SetCharacter(64, 64, 'x', 255, 255, 255, 255); thumbnail = new Thumbnail(req.ID.SaveID, req.ID.SaveID, errorThumb.Buffer, ui::Point(errorThumb.Width, errorThumb.Height)); } if(thumbnailCache.size() >= THUMB_CACHE_SIZE) { delete thumbnailCache.front().second; thumbnailCache.pop_front(); } thumbnailCache.push_back(std::pair(req.ID, thumbnail)); for(std::vector::iterator specIter = req.SubRequests.begin(), specEnd = req.SubRequests.end(); specIter != specEnd; ++specIter) { Thumbnail * tempThumbnail = new Thumbnail(*thumbnail); tempThumbnail->Resize((*specIter).Width, (*specIter).Height); pthread_mutex_lock(&thumbnailQueueMutex); thumbnailComplete.push_back(std::pair((*specIter).CompletedListener, tempThumbnail)); pthread_mutex_unlock(&thumbnailQueueMutex); } } else { #ifdef DEBUG std::cout << typeid(*this).name() << " Request for " << req.ID.SaveID << ":" << req.ID.SaveDate << " failed with status " << status << std::endl; #endif if(data) free(data); } iter = currentRequests.erase(iter); } else { ++iter; } } } } void ThumbnailBroker::RetrieveThumbnail(int saveID, int width, int height, ThumbnailListener * tListener) { RetrieveThumbnail(saveID, 0, width, height, tListener); } bool ThumbnailBroker::CheckThumbnailListener(ThumbnailListener * tListener) { pthread_mutex_lock(&listenersMutex); int count = std::count(validListeners.begin(), validListeners.end(), tListener); pthread_mutex_unlock(&listenersMutex); return count; } void ThumbnailBroker::AttachThumbnailListener(ThumbnailListener * tListener) { pthread_mutex_lock(&listenersMutex); validListeners.push_back(tListener); pthread_mutex_unlock(&listenersMutex); } void ThumbnailBroker::DetachThumbnailListener(ThumbnailListener * tListener) { pthread_mutex_lock(&listenersMutex); std::vector::iterator iter = validListeners.begin(); while (iter != validListeners.end()) { if(*iter == tListener) iter = validListeners.erase(iter); else ++iter; } pthread_mutex_unlock(&listenersMutex); }