aboutsummaryrefslogtreecommitdiff
path: root/engines/sword25/kernel/resmanager.cpp
blob: 8b446e69d17557f8198fe1ca8dbfca411e5454c6 (plain)
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