Skip to content

Commit

Permalink
Reuse common code between the audiofilecaches
Browse files Browse the repository at this point in the history
  • Loading branch information
feliwir committed Nov 25, 2022
1 parent 3e39884 commit 3966ca8
Show file tree
Hide file tree
Showing 10 changed files with 441 additions and 555 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- name: Build Thyme
run: |
cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTS=ON -DBUILD_TOOLS=ON -DLOGGING=ON -DSTANDALONE=ON -DUSE_CRASHPAD=ON -B build
cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_IGNORE_PATH=C:/Strawberry/c/lib -DBUILD_TESTS=ON -DBUILD_TOOLS=ON -DLOGGING=ON -DSTANDALONE=ON -DUSE_CRASHPAD=ON -B build
cmake --build build --config RelWithDebInfo
- name: Test Thyme
Expand Down
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 @@ -286,6 +286,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

0 comments on commit 3966ca8

Please sign in to comment.