-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reuse common code between the audiofilecaches
- Loading branch information
Showing
10 changed files
with
441 additions
and
555 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
/** | ||
* @file | ||
* | ||
* @author feliwir | ||
* | ||
* @brief Base class for caching loaded audio samples to reduce file IO. | ||
* | ||
* @copyright Thyme 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. | ||
* A full copy of the GNU General Public License can be found in | ||
* LICENSE | ||
*/ | ||
#include "audiofilecache.h" | ||
#include "audioeventrts.h" | ||
#include "audiomanager.h" | ||
#include "filesystem.h" | ||
|
||
#include <captainslog.h> | ||
|
||
using namespace Thyme; | ||
|
||
/** | ||
* Opens an audio file. Reads from the cache if available or loads from file if not. | ||
*/ | ||
AudioDataHandle AudioFileCache::Open_File(const Utf8String &filename, const AudioEventInfo *event_info) | ||
{ | ||
ScopedMutexClass lock(&m_mutex); | ||
|
||
captainslog_trace("AudioFileCache: opening file %s", filename.Str()); | ||
|
||
// Try to find existing data for this file to avoid loading it if unneeded. | ||
auto it = m_cacheMap.find(filename); | ||
|
||
if (it != m_cacheMap.end()) { | ||
++(it->second.ref_count); | ||
|
||
return static_cast<AudioDataHandle>(it->second.wave_data); | ||
} | ||
|
||
// Load the file from disk | ||
File *file = g_theFileSystem->Open_File(filename.Str(), File::READ | File::BINARY | File::BUFFERED); | ||
|
||
if (file == nullptr) { | ||
if (filename.Is_Not_Empty()) { | ||
captainslog_warn("Missing audio file '%s', could not cache.", filename.Str()); | ||
} | ||
|
||
return nullptr; | ||
} | ||
|
||
OpenAudioFile open_audio; | ||
if (!Load_File(file, open_audio)) { | ||
captainslog_warn("Failed to load audio file '%s', could not cache.", filename.Str()); | ||
return nullptr; | ||
} | ||
|
||
file->Close(); | ||
|
||
open_audio.audio_event_info = event_info; | ||
open_audio.ref_count = 1; | ||
m_currentSize += open_audio.data_size; | ||
|
||
// m_maxSize prevents using overly large amounts of memory, so if we are over it, unload some other samples. | ||
if (m_currentSize > m_maxSize && !Free_Space_For_Sample(open_audio)) { | ||
captainslog_warn("Cannot play audio file since cache is full: %s", filename.Str()); | ||
m_currentSize -= open_audio.data_size; | ||
Release_Open_Audio(&open_audio); | ||
|
||
return nullptr; | ||
} | ||
|
||
m_cacheMap[filename] = open_audio; | ||
|
||
return static_cast<AudioDataHandle>(open_audio.wave_data); | ||
} | ||
|
||
/** | ||
* Opens an audio file for an event. Reads from the cache if available or loads from file if not. | ||
*/ | ||
AudioDataHandle AudioFileCache::Open_File(AudioEventRTS *audio_event) | ||
{ | ||
Utf8String filename; | ||
|
||
// What part of an event are we playing? | ||
switch (audio_event->Get_Next_Play_Portion()) { | ||
case 0: | ||
filename = audio_event->Get_Attack_Name(); | ||
break; | ||
case 1: | ||
filename = audio_event->Get_File_Name(); | ||
break; | ||
case 2: | ||
filename = audio_event->Get_Decay_Name(); | ||
break; | ||
case 3: | ||
default: | ||
return nullptr; | ||
} | ||
|
||
return Open_File(filename, audio_event->Get_Event_Info()); | ||
} | ||
|
||
/** | ||
* Closes a file, reducing the references to it. Does not actually free the cache. | ||
*/ | ||
void AudioFileCache::Close_File(AudioDataHandle file) | ||
{ | ||
if (file == nullptr) { | ||
return; | ||
} | ||
|
||
ScopedMutexClass lock(&m_mutex); | ||
|
||
for (auto it = m_cacheMap.begin(); it != m_cacheMap.end(); ++it) { | ||
if (static_cast<AudioDataHandle>(it->second.wave_data) == file) { | ||
--(it->second.ref_count); | ||
|
||
break; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Sets the maximum amount of memory in bytes that the cache should use. | ||
*/ | ||
void AudioFileCache::Set_Max_Size(unsigned size) | ||
{ | ||
ScopedMutexClass lock(&m_mutex); | ||
m_maxSize = size; | ||
} | ||
|
||
/** | ||
* Attempts to free space by releasing files with no references | ||
*/ | ||
unsigned AudioFileCache::Free_Space(unsigned required) | ||
{ | ||
std::list<Utf8String> to_free; | ||
unsigned freed = 0; | ||
|
||
// First check for samples that don't have any references. | ||
for (const auto &cached : m_cacheMap) { | ||
if (cached.second.ref_count == 0) { | ||
to_free.push_back(cached.first); | ||
freed += cached.second.data_size; | ||
|
||
// If required is "0" we free as much as possible | ||
if (required && freed >= required) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
for (const auto &file : to_free) { | ||
auto to_remove = m_cacheMap.find(file); | ||
|
||
if (to_remove != m_cacheMap.end()) { | ||
Release_Open_Audio(&to_remove->second); | ||
m_currentSize -= to_remove->second.data_size; | ||
m_cacheMap.erase(to_remove); | ||
} | ||
} | ||
|
||
return freed; | ||
} | ||
|
||
/** | ||
* Attempts to free space for a file by releasing files with no references and lower priority sounds. | ||
*/ | ||
bool AudioFileCache::Free_Space_For_Sample(const OpenAudioFile &file) | ||
{ | ||
captainslog_assert(m_currentSize >= m_maxSize); // Assumed to be called only when we need more than allowed. | ||
std::list<Utf8String> to_free; | ||
unsigned required = m_currentSize - m_maxSize; | ||
unsigned freed = 0; | ||
|
||
// First check for samples that don't have any references. | ||
freed = Free_Space(required); | ||
|
||
// If we still don't have enough potential space freed up, look for lower priority sounds to remove. | ||
if (freed < required) { | ||
for (const auto &cached : m_cacheMap) { | ||
if (cached.second.ref_count != 0 | ||
&& cached.second.audio_event_info->Get_Priority() < file.audio_event_info->Get_Priority()) { | ||
to_free.push_back(cached.first); | ||
freed += cached.second.data_size; | ||
|
||
if (freed >= required) { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// If we have enough space to free, do the actual freeing, otherwise we didn't succeed, no point bothering. | ||
if (freed < required) { | ||
return false; | ||
} | ||
|
||
for (const auto &file : to_free) { | ||
auto to_remove = m_cacheMap.find(file); | ||
|
||
if (to_remove != m_cacheMap.end()) { | ||
Release_Open_Audio(&to_remove->second); | ||
m_currentSize -= to_remove->second.data_size; | ||
m_cacheMap.erase(to_remove); | ||
} | ||
} | ||
|
||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/** | ||
* @file | ||
* | ||
* @author feliwir | ||
* | ||
* @brief Base class for caching loaded audio samples to reduce file IO. | ||
* | ||
* @copyright Thyme 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. | ||
* A full copy of the GNU General Public License can be found in | ||
* LICENSE | ||
*/ | ||
#pragma once | ||
|
||
#include "always.h" | ||
#include "asciistring.h" | ||
#include "audiomanager.h" | ||
#include "file.h" | ||
#include "mutex.h" | ||
#include "rtsutils.h" | ||
|
||
#ifdef THYME_USE_STLPORT | ||
#include <hash_map> | ||
#else | ||
#include <unordered_map> | ||
#endif | ||
|
||
class AudioEventInfo; | ||
class AudioEventRTS; | ||
|
||
struct OpenAudioFile | ||
{ | ||
AudioDataHandle wave_data = nullptr; | ||
int ref_count = 0; | ||
int data_size = 0; | ||
const AudioEventInfo *audio_event_info = nullptr; | ||
void *opaque = nullptr; | ||
}; | ||
|
||
#ifdef THYME_USE_STLPORT | ||
typedef std::hash_map<const Utf8String, OpenAudioFile, rts::hash<Utf8String>, std::equal_to<Utf8String>> audiocachemap_t; | ||
#else | ||
typedef std::unordered_map<const Utf8String, OpenAudioFile, rts::hash<Utf8String>, std::equal_to<Utf8String>> | ||
audiocachemap_t; | ||
#endif | ||
|
||
namespace Thyme | ||
{ | ||
|
||
class AudioFileCache | ||
{ | ||
public: | ||
AudioFileCache() : m_maxSize(0), m_currentSize(0), m_mutex("AudioFileCacheMutex") {} | ||
AudioDataHandle Open_File(AudioEventRTS *file); | ||
AudioDataHandle Open_File(const Utf8String &filename, const AudioEventInfo *event_info = nullptr); | ||
|
||
void Close_File(AudioDataHandle file); | ||
void Set_Max_Size(unsigned size); | ||
inline unsigned Get_Max_Size() const { return m_maxSize; } | ||
inline unsigned Get_Current_Size() const { return m_currentSize; } | ||
|
||
// #FEATURE: We can maybe call this during loading to free any old sounds we won't need ingame and decrease computation | ||
// ingame | ||
unsigned Free_Space(unsigned required = 0); | ||
|
||
protected: | ||
bool Free_Space_For_Sample(const OpenAudioFile &open_audio); | ||
|
||
virtual bool Load_File(File *file, OpenAudioFile &audio_file) = 0; | ||
virtual void Release_Open_Audio(OpenAudioFile *open_audio) = 0; | ||
|
||
protected: | ||
audiocachemap_t m_cacheMap; | ||
unsigned m_currentSize; | ||
unsigned m_maxSize; | ||
SimpleMutexClass m_mutex; | ||
}; | ||
} // namespace Thyme |
Oops, something went wrong.