fix redraw at top-left of map
[Tsunagari.git] / src / resourcer.cpp
blob2c8398ade5a1dff94b4d01a7b0846a3943737c6f
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 // FIXME: TiledImages aren't held on to while in map
70 reclaim<TiledImageMap, boost::shared_ptr<TiledImage> >(tiles);
71 reclaim<SampleRefMap, SampleRef>(samples);
72 reclaim<XMLMap, XMLDocRef>(xmls);
75 template<class Map, class MapValue>
76 void Resourcer::reclaim(Map& map)
78 int now = GameWindow::getWindow().time();
79 std::vector<std::string> dead;
80 BOOST_FOREACH(typename Map::value_type& i, map) {
81 const std::string& name = i.first;
82 CachedItem<MapValue>& cache = i.second;
83 long extUses = cache.resource.use_count() - 1;
84 if (extUses == 0) {
85 if (!cache.lastUsed) {
86 cache.lastUsed = now;
87 Log::dbg("Resourcer", name + " unused");
89 else if (now < cache.lastUsed) {
90 // Handle time overflow
91 cache.lastUsed = now;
93 else if (now > cache.lastUsed + conf->cache_ttl*1000) {
94 dead.push_back(name);
95 Log::dbg("Resourcer", "Removing " + name);
98 // XXX: Redundant? We're working around this because it won't
99 // catch XML documents.
100 else if (cache.lastUsed) {
101 cache.lastUsed = 0;
102 Log::dbg("Resourcer", name + " used again");
105 BOOST_FOREACH(std::string& name, dead)
106 map.erase(name);
109 ImageRef Resourcer::getImage(const std::string& name)
111 if (conf->cache_enabled) {
112 ImageRefMap::iterator entry = images.find(name);
113 if (entry != images.end()) {
114 if (entry->second.lastUsed) {
115 Log::dbg("Resourcer", name + " used again");
116 entry->second.lastUsed = 0;
118 return entry->second.resource;
122 BufferPtr buffer(read(name));
123 if (!buffer)
124 return ImageRef();
125 Gosu::Bitmap bitmap;
126 Gosu::loadImageFile(bitmap, buffer->frontReader());
127 ImageRef result(new Gosu::Image(window->graphics(), bitmap, false));
129 if (conf->cache_enabled) {
130 CachedItem<ImageRef> data;
131 data.resource = result;
132 data.lastUsed = 0;
133 images[name] = data;
135 return result;
138 bool Resourcer::getTiledImage(TiledImage& img, const std::string& name,
139 unsigned w, unsigned h, bool tileable)
141 if (conf->cache_enabled) {
142 TiledImageMap::iterator entry = tiles.find(name);
143 if (entry != tiles.end()) {
144 int now = GameWindow::getWindow().time();
145 Log::dbg("Resourcer", name + " used again");
146 // We set lastUsed to now because it won't be used by
147 // the time reclaim() gets to it.
148 entry->second.lastUsed = now;
149 img = *entry->second.resource.get();
150 return true;
154 BufferPtr buffer(read(name));
155 if (!buffer)
156 return false;
157 Gosu::Bitmap bitmap;
158 Gosu::loadImageFile(bitmap, buffer->frontReader());
159 boost::shared_ptr<TiledImage> result(new TiledImage);
160 Gosu::imagesFromTiledBitmap(window->graphics(), bitmap, w, h,
161 tileable, *result.get());
162 img = *result.get();
164 if (conf->cache_enabled) {
165 CachedItem<boost::shared_ptr<TiledImage> > data;
166 data.resource = result;
167 data.lastUsed = 0;
168 tiles[name] = data;
170 return true;
173 /* FIXME
174 * We use Gosu::Sample for music because Gosu::Song's SDL implementation
175 * doesn't support loading from a memory buffer at the moment.
177 SampleRef Resourcer::getSample(const std::string& name)
179 if (conf->cache_enabled) {
180 SampleRefMap::iterator entry = samples.find(name);
181 if (entry != samples.end()) {
182 if (entry->second.lastUsed) {
183 Log::dbg("Resourcer", name + " used again");
184 entry->second.lastUsed = 0;
186 return entry->second.resource;
190 BufferPtr buffer(read(name));
191 if (!buffer)
192 return SampleRef();
193 SampleRef result(new Gosu::Sample(buffer->frontReader()));
195 if (conf->cache_enabled) {
196 CachedItem<SampleRef> data;
197 data.resource = result;
198 data.lastUsed = 0;
199 samples[name] = data;
201 return result;
204 XMLDocRef Resourcer::getXMLDoc(const std::string& name,
205 const std::string& dtdFile)
207 if (conf->cache_enabled) {
208 XMLMap::iterator entry = xmls.find(name);
209 if (entry != xmls.end()) {
210 int now = GameWindow::getWindow().time();
211 Log::dbg("Resourcer", name + " used again");
212 // We set lastUsed to now because it won't be used by
213 // the time reclaim() gets to it.
214 entry->second.lastUsed = now;
215 return entry->second.resource;
219 XMLDocRef result(readXMLDocFromDisk(name, dtdFile), xmlFreeDoc);
220 if (conf->cache_enabled) {
221 CachedItem<XMLDocRef> data;
222 data.resource = result;
223 data.lastUsed = 0;
224 xmls[name] = data;
226 return result;
229 // use RAII to ensure doc is freed
230 // boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
231 xmlDoc* Resourcer::readXMLDocFromDisk(const std::string& name,
232 const std::string& dtdFile)
234 const std::string docStr = readStringFromDisk(name);
235 if (docStr.empty())
236 return NULL;
238 xmlParserCtxt* pc = xmlNewParserCtxt();
239 const std::string pathname = path(name);
240 pc->vctxt.userData = (void*)&pathname;
241 pc->vctxt.error = xmlErrorCb;
243 // Parse the XML. Hand over our error callback fn.
244 xmlDoc* doc = xmlCtxtReadMemory(pc, docStr.c_str(),
245 (int)docStr.size(), NULL, NULL,
246 XML_PARSE_NOBLANKS |
247 XML_PARSE_NONET);
248 xmlFreeParserCtxt(pc);
249 if (!doc) {
250 Log::err(pathname, "Could not parse file");
251 return NULL;
254 // Load up a Document Type Definition for validating the document.
255 std::string dtdPath = conf->dtdDir + "/" + dtdFile;
256 xmlDtd* dtd = xmlParseDTD(NULL, (const xmlChar*)dtdPath.c_str());
257 if (!dtd) {
258 Log::err(dtdPath, "file not found");
259 return NULL;
262 // Assert the document is sane here and now so we don't have to have a
263 // billion if-else statements while traversing the document tree.
264 xmlValidCtxt* vc = xmlNewValidCtxt();
265 int valid = xmlValidateDtd(vc, doc, dtd);
266 xmlFreeValidCtxt(vc);
267 xmlFreeDtd(dtd);
269 if (!valid) {
270 Log::err(pathname, "XML document does not follow DTD");
271 return NULL;
274 return doc;
277 std::string Resourcer::readStringFromDisk(const std::string& name)
279 struct zip_stat stat;
280 zip_file* zf;
281 int size;
282 char* buf;
283 std::string str;
285 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
286 Log::err(path(name), "file missing");
287 return "";
290 size = (int)stat.size;
291 buf = new char[size + 1];
292 buf[size] = '\0';
294 zf = zip_fopen(z, name.c_str(), 0x0);
295 if (!zf) {
296 Log::err(path(name),
297 std::string("opening : ") + zip_strerror(z));
298 return "";
301 if (zip_fread(zf, buf, size) != size) {
302 Log::err(path(name), "reading didn't complete");
303 zip_fclose(zf);
304 return "";
307 str = buf;
308 delete[] buf;
310 zip_fclose(zf);
311 return str;
314 Gosu::Buffer* Resourcer::read(const std::string& name)
316 struct zip_stat stat;
317 zip_file* zf;
318 int size;
320 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
321 Log::err(path(name), "file missing");
322 return NULL;
325 size = (int)stat.size;
327 if (!(zf = zip_fopen(z, name.c_str(), 0x0))) {
328 Log::err(path(name),
329 std::string("opening : ") + zip_strerror(z));
330 return NULL;
333 Gosu::Buffer* buffer = new Gosu::Buffer;
334 buffer->resize(size);
335 if (zip_fread(zf, buffer->data(), size) != size) {
336 Log::err(path(name), "reading didn't complete");
337 zip_fclose(zf);
338 delete buffer;
339 return NULL;
342 zip_fclose(zf);
343 return buffer;
346 std::string Resourcer::path(const std::string& entryName) const
348 return conf->world + "/" + entryName;