scripts load from world
[Tsunagari.git] / src / resourcer.cpp
blob7a25ebbc5740997809476a5aa7f9b1e788b3f17b
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 typedef boost::scoped_ptr<Gosu::Buffer> BufferPtr;
31 static void xmlErrorCb(void* pstrFilename, const char* msg, ...)
33 const std::string* filename = (const std::string*)pstrFilename;
34 char buf[512];
35 va_list ap;
37 va_start(ap, msg);
38 snprintf(buf, sizeof(buf)-1, msg, va_arg(ap, char*));
39 Log::err(*filename, buf);
40 va_end(ap);
43 Resourcer::Resourcer(GameWindow* window, ClientValues* conf)
44 : window(window), z(NULL), conf(conf)
48 Resourcer::~Resourcer()
50 if (z && zip_close(z))
51 Log::err(conf->world,
52 std::string("closing : ") + zip_strerror(z));
55 bool Resourcer::init()
57 int err;
58 z = zip_open(conf->world.c_str(), 0x0, &err);
59 if (!z) {
60 char buf[512];
61 zip_error_to_str(buf, sizeof(buf), err, errno);
62 Log::err(conf->world, buf);
65 return z;
68 void Resourcer::garbageCollect()
70 reclaim<ImageRefMap, ImageRef>(images);
71 // FIXME: TiledImages aren't held on to while in map
72 reclaim<TiledImageMap, boost::shared_ptr<TiledImage> >(tiles);
73 reclaim<SampleRefMap, SampleRef>(samples);
74 reclaim<XMLMap, XMLDocRef>(xmls);
77 template<class Map, class MapValue>
78 void Resourcer::reclaim(Map& map)
80 int now = GameWindow::getWindow().time();
81 std::vector<std::string> dead;
82 BOOST_FOREACH(typename Map::value_type& i, map) {
83 const std::string& name = i.first;
84 CachedItem<MapValue>& cache = i.second;
85 long extUses = cache.resource.use_count() - 1;
86 if (extUses == 0) {
87 if (!cache.lastUsed) {
88 cache.lastUsed = now;
89 Log::dbg("Resourcer", name + " unused");
91 else if (now < cache.lastUsed) {
92 // Handle time overflow
93 cache.lastUsed = now;
95 else if (now > cache.lastUsed + conf->cache_ttl*1000) {
96 dead.push_back(name);
97 Log::dbg("Resourcer", "Removing " + name);
100 // XXX: Redundant? We're working around this because it won't
101 // catch XML documents.
102 else if (cache.lastUsed) {
103 cache.lastUsed = 0;
104 Log::dbg("Resourcer", name + " used again");
107 BOOST_FOREACH(std::string& name, dead)
108 map.erase(name);
111 ImageRef Resourcer::getImage(const std::string& name)
113 if (conf->cache_enabled) {
114 ImageRefMap::iterator entry = images.find(name);
115 if (entry != images.end()) {
116 if (entry->second.lastUsed) {
117 Log::dbg("Resourcer", name + " used again");
118 entry->second.lastUsed = 0;
120 return entry->second.resource;
124 BufferPtr buffer(read(name));
125 if (!buffer)
126 return ImageRef();
127 Gosu::Bitmap bitmap;
128 Gosu::loadImageFile(bitmap, buffer->frontReader());
129 ImageRef result(new Gosu::Image(window->graphics(), bitmap, false));
131 if (conf->cache_enabled) {
132 CachedItem<ImageRef> data;
133 data.resource = result;
134 data.lastUsed = 0;
135 images[name] = data;
137 return result;
140 bool Resourcer::getTiledImage(TiledImage& img, const std::string& name,
141 unsigned w, unsigned h, bool tileable)
143 if (conf->cache_enabled) {
144 TiledImageMap::iterator entry = tiles.find(name);
145 if (entry != tiles.end()) {
146 int now = GameWindow::getWindow().time();
147 Log::dbg("Resourcer", name + " used again");
148 // We set lastUsed to now because it won't be used by
149 // the time reclaim() gets to it.
150 entry->second.lastUsed = now;
151 img = *entry->second.resource.get();
152 return true;
156 BufferPtr buffer(read(name));
157 if (!buffer)
158 return false;
159 Gosu::Bitmap bitmap;
160 Gosu::loadImageFile(bitmap, buffer->frontReader());
161 boost::shared_ptr<TiledImage> result(new TiledImage);
162 Gosu::imagesFromTiledBitmap(window->graphics(), bitmap, w, h,
163 tileable, *result.get());
164 img = *result.get();
166 if (conf->cache_enabled) {
167 CachedItem<boost::shared_ptr<TiledImage> > data;
168 data.resource = result;
169 data.lastUsed = 0;
170 tiles[name] = data;
172 return true;
175 /* FIXME
176 * We use Gosu::Sample for music because Gosu::Song's SDL implementation
177 * doesn't support loading from a memory buffer at the moment.
179 SampleRef Resourcer::getSample(const std::string& name)
181 if (conf->cache_enabled) {
182 SampleRefMap::iterator entry = samples.find(name);
183 if (entry != samples.end()) {
184 if (entry->second.lastUsed) {
185 Log::dbg("Resourcer", name + " used again");
186 entry->second.lastUsed = 0;
188 return entry->second.resource;
192 BufferPtr buffer(read(name));
193 if (!buffer)
194 return SampleRef();
195 SampleRef result(new Gosu::Sample(buffer->frontReader()));
197 if (conf->cache_enabled) {
198 CachedItem<SampleRef> data;
199 data.resource = result;
200 data.lastUsed = 0;
201 samples[name] = data;
203 return result;
206 XMLDocRef Resourcer::getXMLDoc(const std::string& name,
207 const std::string& dtdFile)
209 if (conf->cache_enabled) {
210 XMLMap::iterator entry = xmls.find(name);
211 if (entry != xmls.end()) {
212 int now = GameWindow::getWindow().time();
213 Log::dbg("Resourcer", name + " used again");
214 // We set lastUsed to now because it won't be used by
215 // the time reclaim() gets to it.
216 entry->second.lastUsed = now;
217 return entry->second.resource;
221 XMLDocRef result(readXMLDocFromDisk(name, dtdFile), xmlFreeDoc);
222 if (conf->cache_enabled) {
223 CachedItem<XMLDocRef> data;
224 data.resource = result;
225 data.lastUsed = 0;
226 xmls[name] = data;
228 return result;
231 static void parseError(const std::string& name, lua_State* L)
233 const char* err = lua_tostring(L, -1);
234 const char* afterFile = strchr(err, ':') + 1; // +1 for ':'
235 const char* afterLine = strchr(afterFile, ':') + 2; // +2 for ': '
236 char line[512];
237 memcpy(line, afterFile, afterLine - afterFile - 2);
238 Log::err(name + ":" + line, std::string("parsing error: ") + afterLine);
241 bool Resourcer::getLuaScript(const std::string& name, lua_State* L)
243 const std::string script = readStringFromDisk(name);
244 if (script.empty()) // error logged
245 return false;
247 int status = luaL_loadbuffer(L, script.data(), script.size(),
248 name.c_str());
251 switch (status) {
252 case LUA_ERRSYNTAX:
253 parseError(name, L);
254 return false;
255 case LUA_ERRMEM:
256 // Should we even bother with this?
257 Log::err(name, "out of memory while parsing");
258 return false;
261 return true;
264 // use RAII to ensure doc is freed
265 // boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
266 xmlDoc* Resourcer::readXMLDocFromDisk(const std::string& name,
267 const std::string& dtdFile)
269 const std::string docStr = readStringFromDisk(name);
270 if (docStr.empty())
271 return NULL;
273 xmlParserCtxt* pc = xmlNewParserCtxt();
274 const std::string pathname = path(name);
275 pc->vctxt.userData = (void*)&pathname;
276 pc->vctxt.error = xmlErrorCb;
278 // Parse the XML. Hand over our error callback fn.
279 xmlDoc* doc = xmlCtxtReadMemory(pc, docStr.c_str(),
280 (int)docStr.size(), NULL, NULL,
281 XML_PARSE_NOBLANKS |
282 XML_PARSE_NONET);
283 xmlFreeParserCtxt(pc);
284 if (!doc) {
285 Log::err(pathname, "Could not parse file");
286 return NULL;
289 // Load up a Document Type Definition for validating the document.
290 std::string dtdPath = std::string(DTD_DIRECTORY) + "/" + dtdFile;
291 xmlDtd* dtd = xmlParseDTD(NULL, (const xmlChar*)dtdPath.c_str());
292 if (!dtd) {
293 Log::err(dtdPath, "file not found");
294 return NULL;
297 // Assert the document is sane here and now so we don't have to have a
298 // billion if-else statements while traversing the document tree.
299 xmlValidCtxt* vc = xmlNewValidCtxt();
300 int valid = xmlValidateDtd(vc, doc, dtd);
301 xmlFreeValidCtxt(vc);
302 xmlFreeDtd(dtd);
304 if (!valid) {
305 Log::err(pathname, "XML document does not follow DTD");
306 return NULL;
309 return doc;
312 std::string Resourcer::readStringFromDisk(const std::string& name)
314 struct zip_stat stat;
315 zip_file* zf;
316 int size;
317 char* buf;
318 std::string str;
320 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
321 Log::err(path(name), "file missing");
322 return "";
325 size = (int)stat.size;
326 buf = new char[size + 1];
327 buf[size] = '\0';
329 zf = zip_fopen(z, name.c_str(), 0x0);
330 if (!zf) {
331 Log::err(path(name),
332 std::string("opening : ") + zip_strerror(z));
333 return "";
336 if (zip_fread(zf, buf, size) != size) {
337 Log::err(path(name), "reading didn't complete");
338 zip_fclose(zf);
339 return "";
342 str = buf;
343 delete[] buf;
345 zip_fclose(zf);
346 return str;
349 Gosu::Buffer* Resourcer::read(const std::string& name)
351 struct zip_stat stat;
352 zip_file* zf;
353 int size;
355 if (zip_stat(z, name.c_str(), 0x0, &stat)) {
356 Log::err(path(name), "file missing");
357 return NULL;
360 size = (int)stat.size;
362 if (!(zf = zip_fopen(z, name.c_str(), 0x0))) {
363 Log::err(path(name),
364 std::string("opening : ") + zip_strerror(z));
365 return NULL;
368 Gosu::Buffer* buffer = new Gosu::Buffer;
369 buffer->resize(size);
370 if (zip_fread(zf, buffer->data(), size) != size) {
371 Log::err(path(name), "reading didn't complete");
372 zip_fclose(zf);
373 delete buffer;
374 return NULL;
377 zip_fclose(zf);
378 return buffer;
381 std::string Resourcer::path(const std::string& entryName) const
383 return conf->world + "/" + entryName;