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 // 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;
85 if (!cache
.lastUsed
) {
87 Log::dbg("Resourcer", name
+ " unused");
89 else if (now
< cache
.lastUsed
) {
90 // Handle time overflow
93 else if (now
> cache
.lastUsed
+ conf
->cache_ttl
*1000) {
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
) {
102 Log::dbg("Resourcer", name
+ " used again");
105 BOOST_FOREACH(std::string
& name
, dead
)
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
));
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
;
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();
154 BufferPtr
buffer(read(name
));
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());
164 if (conf
->cache_enabled
) {
165 CachedItem
<boost::shared_ptr
<TiledImage
> > data
;
166 data
.resource
= result
;
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
));
193 SampleRef
result(new Gosu::Sample(buffer
->frontReader()));
195 if (conf
->cache_enabled
) {
196 CachedItem
<SampleRef
> data
;
197 data
.resource
= result
;
199 samples
[name
] = data
;
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
;
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
);
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
,
248 xmlFreeParserCtxt(pc
);
250 Log::err(pathname
, "Could not parse file");
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());
258 Log::err(dtdPath
, "file not found");
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
);
270 Log::err(pathname
, "XML document does not follow DTD");
277 std::string
Resourcer::readStringFromDisk(const std::string
& name
)
279 struct zip_stat stat
;
285 if (zip_stat(z
, name
.c_str(), 0x0, &stat
)) {
286 Log::err(path(name
), "file missing");
290 size
= (int)stat
.size
;
291 buf
= new char[size
+ 1];
294 zf
= zip_fopen(z
, name
.c_str(), 0x0);
297 std::string("opening : ") + zip_strerror(z
));
301 if (zip_fread(zf
, buf
, size
) != size
) {
302 Log::err(path(name
), "reading didn't complete");
314 Gosu::Buffer
* Resourcer::read(const std::string
& name
)
316 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
;
327 if (!(zf
= zip_fopen(z
, name
.c_str(), 0x0))) {
329 std::string("opening : ") + zip_strerror(z
));
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");
346 std::string
Resourcer::path(const std::string
& entryName
) const
348 return conf
->world
+ "/" + entryName
;