1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
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>
22 #include "resourcer.h"
25 #ifndef LIBXML_TREE_ENABLED
26 #error Tree must be enabled in libxml2
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
;
38 snprintf(buf
, sizeof(buf
)-1, msg
, va_arg(ap
, char*));
39 Log::err(*filename
, buf
);
43 Resourcer::Resourcer(GameWindow
* window
, ClientValues
* conf
)
44 : window(window
), z(NULL
), conf(conf
)
48 Resourcer::~Resourcer()
50 if (z
&& zip_close(z
))
52 std::string("closing : ") + zip_strerror(z
));
55 bool Resourcer::init()
58 z
= zip_open(conf
->world
.c_str(), 0x0, &err
);
61 zip_error_to_str(buf
, sizeof(buf
), err
, errno
);
62 Log::err(conf
->world
, buf
);
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;
87 if (!cache
.lastUsed
) {
89 Log::dbg("Resourcer", name
+ " unused");
91 else if (now
< cache
.lastUsed
) {
92 // Handle time overflow
95 else if (now
> cache
.lastUsed
+ conf
->cache_ttl
*1000) {
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
) {
104 Log::dbg("Resourcer", name
+ " used again");
107 BOOST_FOREACH(std::string
& name
, dead
)
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
));
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
;
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();
156 BufferPtr
buffer(read(name
));
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());
166 if (conf
->cache_enabled
) {
167 CachedItem
<boost::shared_ptr
<TiledImage
> > data
;
168 data
.resource
= result
;
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
));
195 SampleRef
result(new Gosu::Sample(buffer
->frontReader()));
197 if (conf
->cache_enabled
) {
198 CachedItem
<SampleRef
> data
;
199 data
.resource
= result
;
201 samples
[name
] = data
;
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
;
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 ': '
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
247 int status
= luaL_loadbuffer(L
, script
.data(), script
.size(),
256 // Should we even bother with this?
257 Log::err(name
, "out of memory while parsing");
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
);
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
,
283 xmlFreeParserCtxt(pc
);
285 Log::err(pathname
, "Could not parse file");
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());
293 Log::err(dtdPath
, "file not found");
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
);
305 Log::err(pathname
, "XML document does not follow DTD");
312 std::string
Resourcer::readStringFromDisk(const std::string
& name
)
314 struct zip_stat stat
;
320 if (zip_stat(z
, name
.c_str(), 0x0, &stat
)) {
321 Log::err(path(name
), "file missing");
325 size
= (int)stat
.size
;
326 buf
= new char[size
+ 1];
329 zf
= zip_fopen(z
, name
.c_str(), 0x0);
332 std::string("opening : ") + zip_strerror(z
));
336 if (zip_fread(zf
, buf
, size
) != size
) {
337 Log::err(path(name
), "reading didn't complete");
349 Gosu::Buffer
* Resourcer::read(const std::string
& name
)
351 struct zip_stat stat
;
355 if (zip_stat(z
, name
.c_str(), 0x0, &stat
)) {
356 Log::err(path(name
), "file missing");
360 size
= (int)stat
.size
;
362 if (!(zf
= zip_fopen(z
, name
.c_str(), 0x0))) {
364 std::string("opening : ") + zip_strerror(z
));
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");
381 std::string
Resourcer::path(const std::string
& entryName
) const
383 return conf
->world
+ "/" + entryName
;