From 1d258eab6b0ec3740d634f014af5dbff882e0069 Mon Sep 17 00:00:00 2001 From: Simon Robertshaw Date: Tue, 31 Jul 2012 19:49:08 +0100 Subject: [PATCH] ThumbnailBroker for background retrieval and rendering --- src/client/Client.cpp | 5 + src/client/Client.h | 2 + src/client/ThumbnailBroker.cpp | 372 +++++++++++++++++++++++++++++++++ src/client/ThumbnailBroker.h | 58 +++++ src/client/ThumbnailListener.h | 11 + src/interface/SaveButton.cpp | 45 +++- src/interface/SaveButton.h | 6 +- src/search/Thumbnail.cpp | 27 +++ src/search/Thumbnail.h | 3 + 9 files changed, 523 insertions(+), 6 deletions(-) create mode 100644 src/client/ThumbnailBroker.cpp create mode 100644 src/client/ThumbnailBroker.h create mode 100644 src/client/ThumbnailListener.h diff --git a/src/client/Client.cpp b/src/client/Client.cpp index f5ea91d70..7f26a4c52 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -21,10 +21,12 @@ #include "graphics/Graphics.h" #include "Misc.h" +#include "simulation/SaveRenderer.h" #include "interface/Point.h" #include "client/SaveInfo.h" #include "ClientListener.h" #include "Update.h" +#include "ThumbnailBroker.h" extern "C" { @@ -232,6 +234,9 @@ std::vector Client::ReadFile(std::string filename) void Client::Tick() { + //Check thumbnail queue + ThumbnailBroker::Ref().FlushThumbQueue(); + //Check status on version check request if(versionCheckRequest && http_async_req_status(versionCheckRequest)) { diff --git a/src/client/Client.h b/src/client/Client.h index d7e1afb8a..ccf62f359 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -42,6 +42,7 @@ public: UpdateInfo(int time, std::string file, BuildType type) : Major(0), Minor(0), Build(0), Time(time), File(file), Type(type) {} }; +class ThumbnailListener; class ClientListener; class Client: public Singleton { private: @@ -74,6 +75,7 @@ private: //Config file handle json::Object configDocument; public: + vector listeners; UpdateInfo GetUpdateInfo(); diff --git a/src/client/ThumbnailBroker.cpp b/src/client/ThumbnailBroker.cpp new file mode 100644 index 000000000..86a38d8ae --- /dev/null +++ b/src/client/ThumbnailBroker.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include "ThumbnailBroker.h" +#include "ThumbnailListener.h" +#include "Client.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 + 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 if(!thumbnail) + { + //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; + + //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 + if(!requested && CheckThumbnailListener(req.SubRequests.front().CompletedListener)) + { + 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(); + std::list::iterator end = currentRequests.end(); + 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 + { + thumbnail = new Thumbnail(req.ID.SaveID, req.ID.SaveID, thumbData, ui::Point(128, 128)); + free(thumbData); + } + + 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::remove(validListeners.begin(), validListeners.end(), tListener); + pthread_mutex_unlock(&listenersMutex); +} \ No newline at end of file diff --git a/src/client/ThumbnailBroker.h b/src/client/ThumbnailBroker.h new file mode 100644 index 000000000..8fec6c1e9 --- /dev/null +++ b/src/client/ThumbnailBroker.h @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include +#include +#include +#undef GetUserName //God dammit microsoft! + +#include "Singleton.h" + +class GameSave; +class Thumbnail; +class ThumbnailListener; +class ThumbnailBroker: public Singleton +{ +private: + class ThumbnailID; + class ThumbnailRequest; + class ThumbnailSpec; + class ThumbRenderRequest; + + //Thumbnail retreival + /*int thumbnailCacheNextID; + Thumbnail * thumbnailCache[THUMB_CACHE_SIZE]; + void * activeThumbRequests[IMGCONNS]; + int activeThumbRequestTimes[IMGCONNS]; + int activeThumbRequestCompleteTimes[IMGCONNS]; + std::string activeThumbRequestIDs[IMGCONNS];*/ + + pthread_mutex_t thumbnailQueueMutex; + pthread_mutex_t listenersMutex; + pthread_t thumbnailQueueThread; + bool thumbnailQueueRunning; + std::deque thumbnailRequests; + std::deque renderRequests; + + std::deque > thumbnailComplete; + std::list currentRequests; + std::deque > thumbnailCache; + + std::vector validListeners; + + static void * thumbnailQueueProcessHelper(void * ref); + void thumbnailQueueProcessTH(); + +public: + ThumbnailBroker(); + virtual ~ThumbnailBroker(); + + void FlushThumbQueue(); + void RenderThumbnail(GameSave * gameSave, int width, int height, ThumbnailListener * tListener); + void RetrieveThumbnail(int saveID, int saveDate, int width, int height, ThumbnailListener * tListener); + void RetrieveThumbnail(int saveID, int width, int height, ThumbnailListener * tListener); + + bool CheckThumbnailListener(ThumbnailListener * tListener); + void AttachThumbnailListener(ThumbnailListener * tListener); + void DetachThumbnailListener(ThumbnailListener * tListener); +}; \ No newline at end of file diff --git a/src/client/ThumbnailListener.h b/src/client/ThumbnailListener.h new file mode 100644 index 000000000..c46b4a4d1 --- /dev/null +++ b/src/client/ThumbnailListener.h @@ -0,0 +1,11 @@ +#pragma once + +class Thumbnail; +class ThumbnailListener +{ +public: + ThumbnailListener() {} + virtual ~ThumbnailListener() {} + + virtual void OnThumbnailReady(Thumbnail * thumb) {} +}; diff --git a/src/interface/SaveButton.cpp b/src/interface/SaveButton.cpp index b312d3ac4..18e382dc0 100644 --- a/src/interface/SaveButton.cpp +++ b/src/interface/SaveButton.cpp @@ -4,7 +4,7 @@ #include "client/SaveInfo.h" #include "graphics/Graphics.h" #include "Engine.h" -#include "client/Client.h" +#include "client/ThumbnailBroker.h" #include "simulation/SaveRenderer.h" namespace ui { @@ -62,7 +62,8 @@ SaveButton::SaveButton(Point position, Point size, SaveFile * file): voteColour(255, 0, 0), selectable(false), selected(false), - wantsDraw(false) + wantsDraw(false), + waitingForThumb(false) { if(file) { @@ -78,6 +79,8 @@ SaveButton::SaveButton(Point position, Point size, SaveFile * file): SaveButton::~SaveButton() { + ThumbnailBroker::Ref().DetachThumbnailListener(this); + if(thumbnail) delete thumbnail; if(actionCallback) @@ -88,11 +91,43 @@ SaveButton::~SaveButton() delete file; } +void SaveButton::OnThumbnailReady(Thumbnail * thumb) +{ + if(thumb) + { + if(thumbnail) + delete thumbnail; + thumbnail = thumb; + waitingForThumb = false; + } +} + void SaveButton::Tick(float dt) { - Thumbnail * tempThumb; + if(!thumbnail && !waitingForThumb) + { + if(save) + { + if(save->GetGameSave()) + { + waitingForThumb = true; + ThumbnailBroker::Ref().RenderThumbnail(save->GetGameSave(), Size.X-3, Size.Y-25, this); + } + else if(save->GetID()) + { + waitingForThumb = true; + ThumbnailBroker::Ref().RetrieveThumbnail(save->GetID() , Size.X-3, Size.Y-25, this); + } + } + else if(file && file->GetGameSave()) + { + waitingForThumb = true; + ThumbnailBroker::Ref().RenderThumbnail(file->GetGameSave(), Size.X-3, Size.Y-25, this); + } + } + /*Thumbnail * tempThumb; float scaleFactorY = 1.0f, scaleFactorX = 1.0f; - if(!thumbnail/* && wantsDraw*/) + if(!thumbnail) { if(save) { @@ -148,7 +183,7 @@ void SaveButton::Tick(float dt) free(thumbData); } } - } + }*/ } void SaveButton::Draw(const Point& screenPos) diff --git a/src/interface/SaveButton.h b/src/interface/SaveButton.h index faa85d717..a5902cd1d 100644 --- a/src/interface/SaveButton.h +++ b/src/interface/SaveButton.h @@ -6,6 +6,7 @@ #include "Component.h" #include "client/SaveFile.h" #include "client/SaveInfo.h" +#include "client/ThumbnailListener.h" #include "graphics/Graphics.h" #include "search/Thumbnail.h" #include "interface/Colour.h" @@ -21,13 +22,14 @@ public: virtual ~SaveButtonAction() {} }; -class SaveButton : public Component +class SaveButton : public Component, public ThumbnailListener { SaveFile * file; SaveInfo * save; Thumbnail * thumbnail; std::string name; bool wantsDraw; + bool waitingForThumb; public: SaveButton(Point position, Point size, SaveInfo * save); SaveButton(Point position, Point size, SaveFile * file); @@ -42,6 +44,8 @@ public: virtual void Draw(const Point& screenPos); virtual void Tick(float dt); + virtual void OnThumbnailReady(Thumbnail * thumb); + void SetSelected(bool selected_) { selected = selected_; } bool GetSelected() { return selected; } void SetSelectable(bool selectable_) { selectable = selectable_; } diff --git a/src/search/Thumbnail.cpp b/src/search/Thumbnail.cpp index 6a97a08ce..60c6b2b56 100644 --- a/src/search/Thumbnail.cpp +++ b/src/search/Thumbnail.cpp @@ -42,6 +42,33 @@ Thumbnail::Thumbnail(int _id, int _datestamp, pixel * _data, ui::Point _size): } } +void Thumbnail::Resize(int width, int height) +{ + Resize(ui::Point(width, height)); +} + +void Thumbnail::Resize(ui::Point newSize) +{ + float scaleFactorX = 1.0f, scaleFactorY = 1.0f; + if(Size.Y > newSize.Y) + { + scaleFactorY = float(newSize.Y)/((float)Size.Y); + } + if(Size.X > newSize.X) + { + scaleFactorX = float(newSize.X)/((float)Size.X); + } + if(scaleFactorY < 1.0f || scaleFactorX < 1.0f) + { + float scaleFactor = scaleFactorY < scaleFactorX ? scaleFactorY : scaleFactorX; + pixel * thumbData = Data; + Data = Graphics::resample_img(thumbData, Size.X, Size.Y, Size.X * scaleFactor, Size.Y * scaleFactor); + Size.X *= scaleFactor; + Size.Y *= scaleFactor; + free(thumbData); + } +} + Thumbnail::~Thumbnail() { if(Data) diff --git a/src/search/Thumbnail.h b/src/search/Thumbnail.h index d951a961f..74ebbad4f 100644 --- a/src/search/Thumbnail.h +++ b/src/search/Thumbnail.h @@ -14,6 +14,9 @@ public: ~Thumbnail(); + void Resize(int Width, int Height); + void Resize(ui::Point newSize); + int ID, Datestamp; ui::Point Size; pixel * Data;