1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
/*
* This code is based on Broken Sword 2.5 engine
*
* Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
*
* Licensed under GNU GPL v2
*
*/
#include "sword25/sword25.h" // for kDebugResource
#include "sword25/kernel/resmanager.h"
#include "sword25/kernel/resource.h"
#include "sword25/kernel/resservice.h"
#include "sword25/package/packagemanager.h"
namespace Sword25 {
// Sets the amount of resources that are simultaneously loaded.
// This needs to be a relatively high number, as all the animation
// frames in each scene are loaded as separate resources.
// Also, George's walk states are all loaded here (150 files)
#define SWORD25_RESOURCECACHE_MIN 400
// The maximum number of loaded resources. If more than these resources
// are loaded, the resource manager will start purging resources till it
// hits the minimum limit above
#define SWORD25_RESOURCECACHE_MAX 500
ResourceManager::~ResourceManager() {
// Clear all unlocked resources
emptyCache();
// All remaining resources are not released, so print warnings and release
Common::List<Resource *>::iterator iter = _resources.begin();
for (; iter != _resources.end(); ++iter) {
warning("Resource \"%s\" was not released.", (*iter)->getFileName().c_str());
// Set the lock count to zero
while ((*iter)->getLockCount() > 0) {
(*iter)->release();
};
// Delete the resource
delete(*iter);
}
}
/**
* Registers a RegisterResourceService. This method is the constructor of
* BS_ResourceService, and thus helps all resource services in the ResourceManager list
* @param pService Which service
*/
bool ResourceManager::registerResourceService(ResourceService *pService) {
if (!pService) {
error("Can't register NULL resource service.");
return false;
}
_resourceServices.push_back(pService);
return true;
}
/**
* Deletes resources as necessary until the specified memory limit is not being exceeded.
*/
void ResourceManager::deleteResourcesIfNecessary() {
// If enough memory is available, or no resources are loaded, then the function can immediately end
if (_resources.size() < SWORD25_RESOURCECACHE_MAX)
return;
// Keep deleting resources until the memory usage of the process falls below the set maximum limit.
// The list is processed backwards in order to first release those resources that have been
// not been accessed for the longest
Common::List<Resource *>::iterator iter = _resources.end();
do {
--iter;
// The resource may be released only if it isn't locked
if ((*iter)->getLockCount() == 0)
iter = deleteResource(*iter);
} while (iter != _resources.begin() && _resources.size() >= SWORD25_RESOURCECACHE_MIN);
// Are we still above the minimum? If yes, then start releasing locked resources
// FIXME: This code shouldn't be needed at all, but it seems like there is a bug
// in the resource lock code, and resources are not unlocked when changing rooms.
// Only image/animation resources are unlocked forcibly, thus this shouldn't have
// any impact on the game itself.
if (_resources.size() <= SWORD25_RESOURCECACHE_MIN)
return;
iter = _resources.end();
do {
--iter;
// Only unlock image/animation resources
if ((*iter)->getFileName().hasSuffix(".swf") ||
(*iter)->getFileName().hasSuffix(".png")) {
warning("Forcibly unlocking %s", (*iter)->getFileName().c_str());
// Forcibly unlock the resource
while ((*iter)->getLockCount() > 0)
(*iter)->release();
iter = deleteResource(*iter);
}
} while (iter != _resources.begin() && _resources.size() >= SWORD25_RESOURCECACHE_MIN);
}
/**
* Releases all resources that are not locked.
*/
void ResourceManager::emptyCache() {
// Scan through the resource list
Common::List<Resource *>::iterator iter = _resources.begin();
while (iter != _resources.end()) {
if ((*iter)->getLockCount() == 0) {
// Delete the resource
iter = deleteResource(*iter);
} else
++iter;
}
}
/**
* Returns a requested resource. If any error occurs, returns NULL
* @param FileName Filename of resource
*/
Resource *ResourceManager::requestResource(const Common::String &fileName) {
// Get the absolute path to the file
Common::String uniqueFileName = getUniqueFileName(fileName);
if (uniqueFileName.empty())
return NULL;
// Determine whether the resource is already loaded
// If the resource is found, it will be placed at the head of the resource list and returned
Resource *pResource = getResource(uniqueFileName);
if (!pResource)
pResource = loadResource(uniqueFileName);
if (pResource) {
moveToFront(pResource);
(pResource)->addReference();
return pResource;
}
return NULL;
}
#ifdef PRECACHE_RESOURCES
/**
* Loads a resource into the cache
* @param FileName The filename of the resource to be cached
* @param ForceReload Indicates whether the file should be reloaded if it's already in the cache.
* This is useful for files that may have changed in the interim
*/
bool ResourceManager::precacheResource(const Common::String &fileName, bool forceReload) {
// Get the absolute path to the file
Common::String uniqueFileName = getUniqueFileName(fileName);
if (uniqueFileName.empty())
return false;
Resource *resourcePtr = getResource(uniqueFileName);
if (forceReload && resourcePtr) {
if (resourcePtr->getLockCount()) {
error("Could not force precaching of \"%s\". The resource is locked.", fileName.c_str());
return false;
} else {
deleteResource(resourcePtr);
resourcePtr = 0;
}
}
if (!resourcePtr && loadResource(uniqueFileName) == NULL) {
// This isn't fatal - e.g. it can happen when loading saved games
debugC(kDebugResource, "Could not precache \"%s\",", fileName.c_str());
return false;
}
return true;
}
#endif
/**
* Moves a resource to the top of the resource list
* @param pResource The resource
*/
void ResourceManager::moveToFront(Resource *pResource) {
// Erase the resource from it's current position
_resources.erase(pResource->_iterator);
// Re-add the resource at the front of the list
_resources.push_front(pResource);
// Reset the resource iterator to the repositioned item
pResource->_iterator = _resources.begin();
}
/**
* Loads a resource and updates the m_UsedMemory total
*
* The resource must not already be loaded
* @param FileName The unique filename of the resource to be loaded
*/
Resource *ResourceManager::loadResource(const Common::String &fileName) {
// ResourceService finden, der die Resource laden kann.
for (uint i = 0; i < _resourceServices.size(); ++i) {
if (_resourceServices[i]->canLoadResource(fileName)) {
// If more memory is desired, memory must be released
deleteResourcesIfNecessary();
// Load the resource
Resource *pResource = _resourceServices[i]->loadResource(fileName);
if (!pResource) {
error("Responsible service could not load resource \"%s\".", fileName.c_str());
return NULL;
}
// Add the resource to the front of the list
_resources.push_front(pResource);
pResource->_iterator = _resources.begin();
// Also store the resource in the hash table for quick lookup
_resourceHashMap[pResource->getFileName()] = pResource;
return pResource;
}
}
// This isn't fatal - e.g. it can happen when loading saved games
debugC(kDebugResource, "Could not find a service that can load \"%s\".", fileName.c_str());
return NULL;
}
/**
* Returns the full path of a given resource filename.
* It will return an empty string if a path could not be created.
*/
Common::String ResourceManager::getUniqueFileName(const Common::String &fileName) const {
// Get a pointer to the package manager
PackageManager *pPackage = (PackageManager *)_kernelPtr->getPackage();
if (!pPackage) {
error("Could not get package manager.");
return Common::String();
}
// Absoluten Pfad der Datei bekommen und somit die Eindeutigkeit des Dateinamens sicherstellen
Common::String uniquefileName = pPackage->getAbsolutePath(fileName);
if (uniquefileName.empty())
error("Could not create absolute file name for \"%s\".", fileName.c_str());
return uniquefileName;
}
/**
* Deletes a resource, removes it from the lists, and updates m_UsedMemory
*/
Common::List<Resource *>::iterator ResourceManager::deleteResource(Resource *pResource) {
// Remove the resource from the hash table
_resourceHashMap.erase(pResource->_fileName);
// Delete the resource from the resource list
Common::List<Resource *>::iterator result = _resources.erase(pResource->_iterator);
// Delete the resource
delete pResource;
// Return the iterator
return result;
}
/**
* Returns a pointer to a loaded resource. If any error occurs, NULL will be returned.
* @param UniquefileName The absolute path and filename
*/
Resource *ResourceManager::getResource(const Common::String &uniquefileName) const {
// Determine whether the resource is already loaded
ResMap::iterator it = _resourceHashMap.find(uniquefileName);
if (it != _resourceHashMap.end())
return it->_value;
// Resource was not found, i.e. has not yet been loaded.
return NULL;
}
/**
* Writes the names of all currently locked resources to the log file
*/
void ResourceManager::dumpLockedResources() {
for (Common::List<Resource *>::iterator iter = _resources.begin(); iter != _resources.end(); ++iter) {
if ((*iter)->getLockCount() > 0) {
debugC(kDebugResource, "%s", (*iter)->getFileName().c_str());
}
}
}
} // End of namespace Sword25
|