Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse common code between the audiofilecaches #803

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deps/miles/miles.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ int32_t __stdcall AIL_stream_loop_count(HSTREAM stream);
int32_t __stdcall AIL_3D_sample_playback_rate(H3DSAMPLE sample);
void __stdcall AIL_set_3D_sample_playback_rate(H3DSAMPLE sample, int32_t playback_rate);

#define WAVE_FORMAT_PCM 1
#define WAVE_FORMAT_IMA_ADPCM 0x0011

#ifdef __cplusplus
} // extern "C"
#endif
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ set(GAMEENGINE_SRC
platform/win32gameengine.cpp
platform/win32localfile.cpp
platform/win32localfilesystem.cpp
platform/audio/audiofilecache.cpp
platform/w3dengine/client/w3dbibbuffer.cpp
platform/w3dengine/client/w3dbridgebuffer.cpp
platform/w3dengine/client/w3ddebugdisplay.cpp
Expand Down
212 changes: 212 additions & 0 deletions src/platform/audio/audiofilecache.cpp
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;
}
80 changes: 80 additions & 0 deletions src/platform/audio/audiofilecache.h
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
Loading