use command line cache_ttl
[Tsunagari.git] / src / resourcer.cpp
blobf9a114146db75131f0cd753034bde81c38c66660
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** resourcer.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <errno.h>
8 #include <stdlib.h>
10 #include <boost/foreach.hpp>
11 #include <boost/scoped_ptr.hpp>
12 #include <boost/shared_ptr.hpp>
13 #include <Gosu/Audio.hpp>
14 #include <Gosu/Bitmap.hpp>
15 #include <Gosu/Image.hpp>
16 #include <Gosu/IO.hpp>
17 #include <zip.h>
19 #include "common.h"
20 #include "config.h"
21 #include "log.h"
22 #include "resourcer.h"
23 #include "window.h"
25 #ifndef LIBXML_TREE_ENABLED
26 #error Tree must be enabled in libxml2
27 #endif
29 static void xmlErrorCb(void* pstrFilename, const char* msg, ...)
31 const std::string* filename = (const std::string*)pstrFilename;
32 char buf[512];
33 va_list ap;
35 va_start(ap, msg);
36 snprintf(buf, sizeof(buf)-1, msg, va_arg(ap, char*));
37 Log::err(*filename, buf);
38 va_end(ap);
41 Resourcer::Resourcer(GameWindow* window, ClientValues* conf)
42 : window(window), z(NULL), conf(conf)
46 Resourcer::~Resourcer()
48 if (z && zip_close(z))
49 Log::err(conf->world,
50 std::string("closing : ") + zip_strerror(z));
53 bool Resourcer::init()
55 int err;
56 z = zip_open(conf->world.c_str(), 0x0, &err);
57 if (!z) {
58 char buf[512];
59 zip_error_to_str(buf, sizeof(buf), err, errno);
60 Log::err(conf->world, buf);
63 return z;
66 void Resourcer::garbageCollect()
68 reclaim<ImageRefMap, ImageRef>(images);
69 reclaim<SampleRefMap, SampleRef>(samples);
70 reclaim<XMLMap, XMLDocRef>(xmls);
73 template<class Map, class MapValue>
74 void Resourcer::reclaim(Map& map)
76 int now = GameWindow::getWindow().time();
77 std::vector<std::string> dead;
78 BOOST_FOREACH(typename Map::value_type& i, map) {
79 const std::string& name = i.first;
80 CachedItem<MapValue>& cache = i.second;
81 long extUses = cache.resource.use_count() - 1;
82 if (extUses == 0) {
83 if (!cache.lastUsed) {
84 cache.lastUsed = now;
85 Log::dbg("Resourcer", name + " unused");
87 else if (now < cache.lastUsed) {
88 // Handle time overflow
89 cache.lastUsed = now;
91 else if (now > cache.lastUsed + conf->cache_ttl*1000) {
92 dead.push_back(name);
93 Log::dbg("Resourcer", "Removing " + name);
96 else if (cache.lastUsed) {
97 cache.lastUsed = 0;
98 Log::dbg("Resourcer", name + " used again");
101 BOOST_FOREACH(std::string name, dead)
102 map.erase(name);
105 ImageRef Resourcer::getImage(const std::string& name)
107 if (conf->cache_enabled) {
108 ImageRefMap::iterator entry = images.find(name);
109 if (entry != images.end())
110 return entry->second.resource;
113 BufferPtr buffer(read(name));
114 if (!buffer)
115 return ImageRef();
116 Gosu::Bitmap bitmap;
117 Gosu::loadImageFile(bitmap, buffer->frontReader());
118 ImageRef result(new Gosu::Image(window->graphics(), bitmap, false));
120 if (conf->cache_enabled) {
121 CachedItem<ImageRef> data;
122 data.resource = result;
123 data.lastUsed = 0;
124 images[name] = data;
126 return result;
129 bool Resourcer::getTiledImage(TiledImage& img, const std::string& name,
130 unsigned w, unsigned h, bool tileable)
132 if (conf->cache_enabled) {
133 TiledImageMap::iterator entry = tiles.find(name);
134 if (entry != tiles.end()) {
135 img = entry->second;
136 return true;
140 BufferPtr buffer(read(name));
141 if (!buffer)
142 return false;
143 Gosu::Bitmap bitmap;
144 Gosu::loadImageFile(bitmap, buffer->frontReader());
145 Gosu::imagesFromTiledBitmap(window->graphics(), bitmap, w, h,
146 tileable, img);
148 if (conf->cache_enabled)
149 tiles[name] = img;
150 return true;
153 /* FIXME
154 * We use Gosu::Sample for music because Gosu::Song's SDL implementation
155 * doesn't support loading from a memory buffer at the moment.
157 SampleRef Resourcer::getSample(const std::string& name)
159 if (conf->cache_enabled) {
160 SampleRefMap::iterator entry = samples.find(name);
161 if (entry != samples.end())
162 return entry->second.resource;
165 BufferPtr buffer(read(name));
166 if (!buffer)
167 return SampleRef();
168 SampleRef result(new Gosu::Sample(buffer->frontReader()));
170 if (conf->cache_enabled) {
171 CachedItem<SampleRef> data;
172 data.resource = result;
173 data.lastUsed = 0;
174 samples[name] = data;
176 return result;
179 XMLDocRef Resourcer::getXMLDoc(const std::string& name,
180 const std::string& dtdPath)
182 if (conf->cache_enabled) {
183 XMLMap::iterator entry = xmls.find(name);
184 if (entry != xmls.end()) {
185 int now = GameWindow::getWindow().time();
186 Log::dbg("Resourcer", name + " used again");
187 entry->second.lastUsed = now;
188 return entry->second.resource;
192 XMLDocRef result(readXMLDocFromDisk(name, dtdPath), xmlFreeDoc);
193 if (conf->cache_enabled) {
194 CachedItem<XMLDocRef> data;
195 data.resource = result;
196 data.lastUsed = 0;
197 xmls[name] = data;
199 return result;
202 // use RAII to ensure doc is freed
203 // boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
204 xmlDoc* Resourcer::readXMLDocFromDisk(const std::string& name,
205 const std::string& dtdPath)
207 const std::string docStr = readStringFromDisk(name);
208 if (docStr.empty())
209 return NULL;
211 xmlParserCtxt* pc = xmlNewParserCtxt();
212 const std::string pathname = path(name);
213 pc->vctxt.userData = (void*)&pathname;
214 pc->vctxt.error = xmlErrorCb;
216 // Parse the XML. Hand over our error callback fn.
217 xmlDoc* doc = xmlCtxtReadMemory(pc, docStr.c_str(),
218 (int)docStr.size(), NULL, NULL,
219 XML_PARSE_NOBLANKS |
220 XML_PARSE_NONET);
221 xmlFreeParserCtxt(pc);
222 if (!doc) {
223 Log::err(pathname, "Could not parse file");
224 return NULL;
227 // Load up a Document Type Definition for validating the document.
228 xmlDtd* dtd = xmlParseDTD(NULL, (const xmlChar*)dtdPath.c_str());
229 if (!dtd) {
230 Log::err(dtdPath, "file not found");
231 return NULL;
234 // Assert the document is sane here and now so we don't have to have a
235 // billion if-else statements while traversing the document tree.
236 xmlValidCtxt* vc = xmlNewValidCtxt();
237 int valid = xmlValidateDtd(vc, doc, dtd);
238 xmlFreeValidCtxt(vc);
239 xmlFreeDtd(dtd);
241 if (!valid) {
242 Log::err(pathname, "XML document does not follow DTD");
243 return NULL;
246 return doc;
249 std::string Resourcer::readStringFromDisk(const std::string& name)
251 struct zip_stat stat;
252 zip_file* zf;
253 int size;
254 char* buf;
255 std::string str;
257 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
258 Log::err(path(name), "file missing");
259 return "";
262 size = (int)stat.size;
263 buf = new char[size + 1];
264 buf[size] = '\0';
266 zf = zip_fopen(z, name.c_str(), 0x0);
267 if (!zf) {
268 Log::err(path(name),
269 std::string("opening : ") + zip_strerror(z));
270 return "";
273 if (zip_fread(zf, buf, size) != size) {
274 Log::err(path(name), "reading didn't complete");
275 zip_fclose(zf);
276 return "";
279 str = buf;
280 delete[] buf;
282 zip_fclose(zf);
283 return str;
286 Gosu::Buffer* Resourcer::read(const std::string& name)
288 struct zip_stat stat;
289 zip_file* zf;
290 int size;
292 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
293 Log::err(path(name), "file missing");
294 return NULL;
297 size = (int)stat.size;
299 if (!(zf = zip_fopen(z, name.c_str(), 0x0))) {
300 Log::err(path(name),
301 std::string("opening : ") + zip_strerror(z));
302 return NULL;
305 Gosu::Buffer* buffer = new Gosu::Buffer;
306 buffer->resize(size);
307 if (zip_fread(zf, buffer->data(), size) != size) {
308 Log::err(path(name), "reading didn't complete");
309 zip_fclose(zf);
310 delete buffer;
311 return NULL;
314 zip_fclose(zf);
315 return buffer;
318 std::string Resourcer::path(const std::string& entry_name) const
320 return conf->world + "/" + entry_name;