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 static void xmlErrorCb(void* pstrFilename
, const char* msg
, ...)
31 const std::string
* filename
= (const std::string
*)pstrFilename
;
36 snprintf(buf
, sizeof(buf
)-1, msg
, va_arg(ap
, char*));
37 Log::err(*filename
, buf
);
41 Resourcer::Resourcer(GameWindow
* window
, ClientValues
* conf
)
42 : window(window
), z(NULL
), conf(conf
)
46 Resourcer::~Resourcer()
48 if (z
&& zip_close(z
))
50 std::string("closing : ") + zip_strerror(z
));
53 bool Resourcer::init()
56 z
= zip_open(conf
->world
.c_str(), 0x0, &err
);
59 zip_error_to_str(buf
, sizeof(buf
), err
, errno
);
60 Log::err(conf
->world
, buf
);
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;
83 if (!cache
.lastUsed
) {
85 Log::dbg("Resourcer", name
+ " unused");
87 else if (now
< cache
.lastUsed
) {
88 // Handle time overflow
91 else if (now
> cache
.lastUsed
+ conf
->cache_ttl
*1000) {
93 Log::dbg("Resourcer", "Removing " + name
);
96 else if (cache
.lastUsed
) {
98 Log::dbg("Resourcer", name
+ " used again");
101 BOOST_FOREACH(std::string name
, dead
)
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
));
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
;
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()) {
140 BufferPtr
buffer(read(name
));
144 Gosu::loadImageFile(bitmap
, buffer
->frontReader());
145 Gosu::imagesFromTiledBitmap(window
->graphics(), bitmap
, w
, h
,
148 if (conf
->cache_enabled
)
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
));
168 SampleRef
result(new Gosu::Sample(buffer
->frontReader()));
170 if (conf
->cache_enabled
) {
171 CachedItem
<SampleRef
> data
;
172 data
.resource
= result
;
174 samples
[name
] = data
;
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
;
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
);
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
,
221 xmlFreeParserCtxt(pc
);
223 Log::err(pathname
, "Could not parse file");
227 // Load up a Document Type Definition for validating the document.
228 xmlDtd
* dtd
= xmlParseDTD(NULL
, (const xmlChar
*)dtdPath
.c_str());
230 Log::err(dtdPath
, "file not found");
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
);
242 Log::err(pathname
, "XML document does not follow DTD");
249 std::string
Resourcer::readStringFromDisk(const std::string
& name
)
251 struct zip_stat stat
;
257 if (zip_stat(z
, name
.c_str(), 0x0, &stat
)) {
258 Log::err(path(name
), "file missing");
262 size
= (int)stat
.size
;
263 buf
= new char[size
+ 1];
266 zf
= zip_fopen(z
, name
.c_str(), 0x0);
269 std::string("opening : ") + zip_strerror(z
));
273 if (zip_fread(zf
, buf
, size
) != size
) {
274 Log::err(path(name
), "reading didn't complete");
286 Gosu::Buffer
* Resourcer::read(const std::string
& name
)
288 struct zip_stat stat
;
292 if (zip_stat(z
, name
.c_str(), 0x0, &stat
)) {
293 Log::err(path(name
), "file missing");
297 size
= (int)stat
.size
;
299 if (!(zf
= zip_fopen(z
, name
.c_str(), 0x0))) {
301 std::string("opening : ") + zip_strerror(z
));
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");
318 std::string
Resourcer::path(const std::string
& entry_name
) const
320 return conf
->world
+ "/" + entry_name
;