aboutsummaryrefslogtreecommitdiff
path: root/engines/sword25/kernel/resmanager.cpp
blob: dd7a38b7533d493b3b7ec99da37a1b1f5bb7eea7 (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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/* 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/kernel/resmanager.h"

#include "sword25/kernel/resource.h"
#include "sword25/kernel/resservice.h"
#include "sword25/kernel/string.h"
#include "sword25/package/packagemanager.h"

namespace Sword25 {

#define BS_LOG_PREFIX "RESOURCEMANAGER"

BS_ResourceManager::~BS_ResourceManager() {
	// Clear all unlocked resources
	EmptyCache();

	// All remaining resources are not released, so print warnings and release
	Common::List<BS_Resource *>::iterator Iter = m_Resources.begin();
	for (; Iter != m_Resources.end(); ++Iter) {
		BS_LOG_WARNINGLN("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);
	}
}

/**
 * Returns a resource by it's ordinal index. Returns NULL if any error occurs
 * Note: This method is not optimised for speed and should be used only for debugging purposes
 * @param Ord       Ordinal number of the resource. Must be between 0 and GetResourceCount() - 1.
 */
BS_Resource *BS_ResourceManager::GetResourceByOrdinal(int Ord) const {
	// �berpr�fen ob der Index Ord innerhald der Listengrenzen liegt.
	if (Ord < 0 || Ord >= GetResourceCount()) {
		BS_LOG_ERRORLN("Resource ordinal (%d) out of bounds (0 - %d).", Ord, GetResourceCount() - 1);
		return NULL;
	}

	// Liste durchlaufen und die Resource mit dem gew�nschten Index zur�ckgeben.
	int CurOrd = 0;
	Common::List<BS_Resource *>::const_iterator Iter = m_Resources.begin();
	for (; Iter != m_Resources.end(); ++Iter, ++CurOrd) {
		if (CurOrd == Ord)
			return (*Iter);
	}

	// Die Ausf�hrung sollte nie an diesem Punkt ankommen.
	BS_LOG_EXTERRORLN("Execution reached unexpected point.");
	return NULL;
}

/**
 * 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 BS_ResourceManager::RegisterResourceService(BS_ResourceService *pService) {
	if (!pService) {
		BS_LOG_ERRORLN("Can't register NULL resource service.");
		return false;
	}

	m_ResourceServices.push_back(pService);

	return true;
}

/**
 * Deletes resources as necessary until the specified memory limit is not being exceeded.
 */
void BS_ResourceManager::DeleteResourcesIfNecessary() {
	// If enough memory is available, or no resources are loaded, then the function can immediately end
	if (m_KernelPtr->GetUsedMemory() < m_MaxMemoryUsage || m_Resources.empty()) 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 who have been
	// not been accessed for the longest
	Common::List<BS_Resource *>::iterator Iter = m_Resources.end();
	do {
		--Iter;

		// The resource may be released only if it isn't locked
		if ((*Iter)->GetLockCount() == 0) Iter = DeleteResource(*Iter);
	} while (Iter != m_Resources.begin() && m_KernelPtr->GetUsedMemory() > m_MaxMemoryUsage);
}

/**
 * Releases all resources that are not locked.
 **/
void BS_ResourceManager::EmptyCache() {
	// Scan through the resource list
	Common::List<BS_Resource *>::iterator Iter = m_Resources.begin();
	while (Iter != m_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
 */
BS_Resource *BS_ResourceManager::RequestResource(const Common::String &FileName) {
	// Get the absolute path to the file
	Common::String UniqueFileName = GetUniqueFileName(FileName);
	if (UniqueFileName == "")
		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
	{
		BS_Resource *pResource = GetResource(UniqueFileName);
		if (pResource) {
			MoveToFront(pResource);
			(pResource)->AddReference();
			return pResource;
		}
	}

	// The resource was not found, therefore, must not be loaded yet
	if (m_LogCacheMiss) BS_LOG_WARNINGLN("\"%s\" was not precached.", UniqueFileName.c_str());

	BS_Resource *pResource;
	if ((pResource = LoadResource(UniqueFileName))) {
		pResource->AddReference();
		return pResource;
	}

	return NULL;
}

/**
 * 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 BS_ResourceManager::PrecacheResource(const Common::String &FileName, bool ForceReload) {
	// Get the absolute path to the file
	Common::String UniqueFileName = GetUniqueFileName(FileName);
	if (UniqueFileName == "")
		return false;

	BS_Resource *ResourcePtr = GetResource(UniqueFileName);

	if (ForceReload && ResourcePtr) {
		if (ResourcePtr->GetLockCount()) {
			BS_LOG_ERRORLN("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) {
		BS_LOG_ERRORLN("Could not precache \"%s\",", FileName.c_str());
		return false;
	}

	return true;
}

/**
 * Moves a resource to the top of the resource list
 * @param pResource     The resource
 */
void BS_ResourceManager::MoveToFront(BS_Resource *pResource) {
	// Erase the resource from it's current position
	m_Resources.erase(pResource->_Iterator);
	// Re-add the resource at the front of the list
	m_Resources.push_front(pResource);
	// Reset the resource iterator to the repositioned item
	pResource->_Iterator = m_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
 */
BS_Resource *BS_ResourceManager::LoadResource(const Common::String &FileName) {
	// ResourceService finden, der die Resource laden kann.
	for (unsigned int i = 0; i < m_ResourceServices.size(); ++i) {
		if (m_ResourceServices[i]->CanLoadResource(FileName)) {
			// If more memory is desired, memory must be released
			DeleteResourcesIfNecessary();

			// Load the resource
			BS_Resource *pResource;
			if (!(pResource = m_ResourceServices[i]->LoadResource(FileName))) {
				BS_LOG_ERRORLN("Responsible service could not load resource \"%s\".", FileName.c_str());
				return NULL;
			}

			// Add the resource to the front of the list
			m_Resources.push_front(pResource);
			pResource->_Iterator = m_Resources.begin();

			// Also store the resource in the hash table for quick lookup
			m_ResourceHashTable[pResource->GetFileNameHash() % HASH_TABLE_BUCKETS].push_front(pResource);

			return pResource;
		}
	}

	BS_LOG_ERRORLN("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 BS_ResourceManager::GetUniqueFileName(const Common::String &FileName) const {
	// Get a pointer to the package manager
	BS_PackageManager *pPackage = (BS_PackageManager *)m_KernelPtr->GetService("package");
	if (!pPackage) {
		BS_LOG_ERRORLN("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 == "")
		BS_LOG_ERRORLN("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<BS_Resource *>::iterator BS_ResourceManager::DeleteResource(BS_Resource *pResource) {
	// Remove the resource from the hash table
	m_ResourceHashTable[pResource->GetFileNameHash() % HASH_TABLE_BUCKETS].remove(pResource);

	BS_Resource *pDummy = pResource;

	// Delete the resource from the resource list
	Common::List<BS_Resource *>::iterator Result = m_Resources.erase(pResource->_Iterator);

	// Delete the resource
	delete(pDummy);

	// 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
 * Gibt einen Pointer auf die angeforderte Resource zur�ck, oder NULL, wenn die Resourcen nicht geladen ist.
 */
BS_Resource *BS_ResourceManager::GetResource(const Common::String &UniqueFileName) const {
	// Determine whether the resource is already loaded
	const Common::List<BS_Resource *>& HashBucket = m_ResourceHashTable[
	            BS_String::GetHash(UniqueFileName) % HASH_TABLE_BUCKETS];
	{
		Common::List<BS_Resource *>::const_iterator Iter = HashBucket.begin();
		for (; Iter != HashBucket.end(); ++Iter) {
			// Wenn die Resource gefunden wurde wird sie zur�ckgegeben.
			if ((*Iter)->GetFileName() == UniqueFileName)
				return *Iter;
		}
	}

	// Resource wurde nicht gefunden, ist also nicht geladen
	return NULL;
}

/**
 * Writes the names of all currently locked resources to the log file
 */
void BS_ResourceManager::DumpLockedResources() {
	for (Common::List<BS_Resource *>::iterator Iter = m_Resources.begin(); Iter != m_Resources.end(); ++Iter) {
		if ((*Iter)->GetLockCount() > 0) {
			BS_LOGLN("%s", (*Iter)->GetFileName().c_str());
		}
	}
}

/**
 * Specifies the maximum amount of memory the engine is allowed to use.
 * If this value is exceeded, resources will be unloaded to make room. This value is meant
 * as a guideline, and not as a fixed boundary. It is not guaranteed not to be exceeded;
 * the whole game engine may still use more memory than any amount specified.
 */
void BS_ResourceManager::SetMaxMemoryUsage(unsigned int MaxMemoryUsage) {
	m_MaxMemoryUsage = MaxMemoryUsage;
	DeleteResourcesIfNecessary();
}

} // End of namespace Sword25