summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorSimon Robertshaw <simon@hardwired.org.uk>2012-07-31 18:49:08 (GMT)
committer Simon Robertshaw <simon@hardwired.org.uk>2012-07-31 18:49:08 (GMT)
commit1d258eab6b0ec3740d634f014af5dbff882e0069 (patch)
tree956d446f144415d4f188dcca17c7dffba08851c2 /src/client
parent303b546ceb134df48763730cbfd8ce7b6df008a2 (diff)
downloadpowder-1d258eab6b0ec3740d634f014af5dbff882e0069.zip
powder-1d258eab6b0ec3740d634f014af5dbff882e0069.tar.gz
ThumbnailBroker for background retrieval and rendering
Diffstat (limited to 'src/client')
-rw-r--r--src/client/Client.cpp5
-rw-r--r--src/client/Client.h2
-rw-r--r--src/client/ThumbnailBroker.cpp372
-rw-r--r--src/client/ThumbnailBroker.h58
-rw-r--r--src/client/ThumbnailListener.h11
5 files changed, 448 insertions, 0 deletions
diff --git a/src/client/Client.cpp b/src/client/Client.cpp
index f5ea91d..7f26a4c 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<unsigned char> 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 d7e1afb..ccf62f3 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<Client> {
private:
@@ -74,6 +75,7 @@ private:
//Config file handle
json::Object configDocument;
public:
+
vector<ClientListener*> listeners;
UpdateInfo GetUpdateInfo();
diff --git a/src/client/ThumbnailBroker.cpp b/src/client/ThumbnailBroker.cpp
new file mode 100644
index 0000000..86a38d8
--- /dev/null
+++ b/src/client/ThumbnailBroker.cpp
@@ -0,0 +1,372 @@
+#include <algorithm>
+#include <iostream>
+#include <typeinfo>
+#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<ThumbnailSpec> 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<ThumbnailListener*, Thumbnail*>(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<std::pair<ThumbnailID, Thumbnail*> >::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<ThumbnailSpec>::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<ThumbnailListener*, Thumbnail*>((*specIter).CompletedListener, tempThumbnail));
+ pthread_mutex_unlock(&thumbnailQueueMutex);
+ }
+ }
+ else if(!thumbnail)
+ {
+ //Check for ongoing requests
+ bool requested = false;
+ for(std::list<ThumbnailRequest>::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<ThumbnailRequest>::iterator iter = currentRequests.begin();
+ std::list<ThumbnailRequest>::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<ThumbnailID, Thumbnail*>(req.ID, thumbnail));
+
+ for(std::vector<ThumbnailSpec>::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<ThumbnailListener*, Thumbnail*>((*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 0000000..8fec6c1
--- /dev/null
+++ b/src/client/ThumbnailBroker.h
@@ -0,0 +1,58 @@
+#pragma once
+#include <queue>
+#include <list>
+#include <utility>
+#include <deque>
+#include <pthread.h>
+#undef GetUserName //God dammit microsoft!
+
+#include "Singleton.h"
+
+class GameSave;
+class Thumbnail;
+class ThumbnailListener;
+class ThumbnailBroker: public Singleton<ThumbnailBroker>
+{
+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<ThumbnailRequest> thumbnailRequests;
+ std::deque<ThumbRenderRequest> renderRequests;
+
+ std::deque<std::pair<ThumbnailListener*, Thumbnail*> > thumbnailComplete;
+ std::list<ThumbnailRequest> currentRequests;
+ std::deque<std::pair<ThumbnailID, Thumbnail*> > thumbnailCache;
+
+ std::vector<ThumbnailListener*> 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 0000000..c46b4a4
--- /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) {}
+};