Skip to content

Commit 8cf845f

Browse files
committed
Reuse common code between the audiofilecaches
1 parent 3e39884 commit 8cf845f

10 files changed

+441
-555
lines changed

.github/workflows/windows.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
4646
- name: Build Thyme
4747
run: |
48-
cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTS=ON -DBUILD_TOOLS=ON -DLOGGING=ON -DSTANDALONE=ON -DUSE_CRASHPAD=ON -B build
48+
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
4949
cmake --build build --config RelWithDebInfo
5050
5151
- name: Test Thyme

deps/miles/miles.h

+3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ int32_t __stdcall AIL_stream_loop_count(HSTREAM stream);
133133
int32_t __stdcall AIL_3D_sample_playback_rate(H3DSAMPLE sample);
134134
void __stdcall AIL_set_3D_sample_playback_rate(H3DSAMPLE sample, int32_t playback_rate);
135135

136+
#define WAVE_FORMAT_PCM 1
137+
#define WAVE_FORMAT_IMA_ADPCM 0x0011
138+
136139
#ifdef __cplusplus
137140
} // extern "C"
138141
#endif

src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ set(GAMEENGINE_SRC
286286
platform/win32gameengine.cpp
287287
platform/win32localfile.cpp
288288
platform/win32localfilesystem.cpp
289+
platform/audio/audiofilecache.cpp
289290
platform/w3dengine/client/w3dbibbuffer.cpp
290291
platform/w3dengine/client/w3dbridgebuffer.cpp
291292
platform/w3dengine/client/w3ddebugdisplay.cpp

src/platform/audio/audiofilecache.cpp

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/**
2+
* @file
3+
*
4+
* @author feliwir
5+
*
6+
* @brief Base class for caching loaded audio samples to reduce file IO.
7+
*
8+
* @copyright Thyme is free software: you can redistribute it and/or
9+
* modify it under the terms of the GNU General Public License
10+
* as published by the Free Software Foundation, either version
11+
* 2 of the License, or (at your option) any later version.
12+
* A full copy of the GNU General Public License can be found in
13+
* LICENSE
14+
*/
15+
#include "audiofilecache.h"
16+
#include "audioeventrts.h"
17+
#include "audiomanager.h"
18+
#include "filesystem.h"
19+
20+
#include <captainslog.h>
21+
22+
using namespace Thyme;
23+
24+
/**
25+
* Opens an audio file. Reads from the cache if available or loads from file if not.
26+
*/
27+
AudioDataHandle AudioFileCache::Open_File(const Utf8String &filename, const AudioEventInfo *event_info)
28+
{
29+
ScopedMutexClass lock(&m_mutex);
30+
31+
captainslog_trace("AudioFileCache: opening file %s", filename.Str());
32+
33+
// Try to find existing data for this file to avoid loading it if unneeded.
34+
auto it = m_cacheMap.find(filename);
35+
36+
if (it != m_cacheMap.end()) {
37+
++(it->second.ref_count);
38+
39+
return static_cast<AudioDataHandle>(it->second.wave_data);
40+
}
41+
42+
// Load the file from disk
43+
File *file = g_theFileSystem->Open_File(filename.Str(), File::READ | File::BINARY | File::BUFFERED);
44+
45+
if (file == nullptr) {
46+
if (filename.Is_Not_Empty()) {
47+
captainslog_warn("Missing audio file '%s', could not cache.", filename.Str());
48+
}
49+
50+
return nullptr;
51+
}
52+
53+
OpenAudioFile open_audio;
54+
if (!Load_File(file, open_audio)) {
55+
captainslog_warn("Failed to load audio file '%s', could not cache.", filename.Str());
56+
return nullptr;
57+
}
58+
59+
file->Close();
60+
61+
open_audio.audio_event_info = event_info;
62+
open_audio.ref_count = 1;
63+
m_currentSize += open_audio.data_size;
64+
65+
// m_maxSize prevents using overly large amounts of memory, so if we are over it, unload some other samples.
66+
if (m_currentSize > m_maxSize && !Free_Space_For_Sample(open_audio)) {
67+
captainslog_warn("Cannot play audio file since cache is full: %s", filename.Str());
68+
m_currentSize -= open_audio.data_size;
69+
Release_Open_Audio(&open_audio);
70+
71+
return nullptr;
72+
}
73+
74+
m_cacheMap[filename] = open_audio;
75+
76+
return static_cast<AudioDataHandle>(open_audio.wave_data);
77+
}
78+
79+
/**
80+
* Opens an audio file for an event. Reads from the cache if available or loads from file if not.
81+
*/
82+
AudioDataHandle AudioFileCache::Open_File(AudioEventRTS *audio_event)
83+
{
84+
Utf8String filename;
85+
86+
// What part of an event are we playing?
87+
switch (audio_event->Get_Next_Play_Portion()) {
88+
case 0:
89+
filename = audio_event->Get_Attack_Name();
90+
break;
91+
case 1:
92+
filename = audio_event->Get_File_Name();
93+
break;
94+
case 2:
95+
filename = audio_event->Get_Decay_Name();
96+
break;
97+
case 3:
98+
default:
99+
return nullptr;
100+
}
101+
102+
return Open_File(filename, audio_event->Get_Event_Info());
103+
}
104+
105+
/**
106+
* Closes a file, reducing the references to it. Does not actually free the cache.
107+
*/
108+
void AudioFileCache::Close_File(AudioDataHandle file)
109+
{
110+
if (file == nullptr) {
111+
return;
112+
}
113+
114+
ScopedMutexClass lock(&m_mutex);
115+
116+
for (auto it = m_cacheMap.begin(); it != m_cacheMap.end(); ++it) {
117+
if (static_cast<AudioDataHandle>(it->second.wave_data) == file) {
118+
--(it->second.ref_count);
119+
120+
break;
121+
}
122+
}
123+
}
124+
125+
/**
126+
* Sets the maximum amount of memory in bytes that the cache should use.
127+
*/
128+
void AudioFileCache::Set_Max_Size(unsigned size)
129+
{
130+
ScopedMutexClass lock(&m_mutex);
131+
m_maxSize = size;
132+
}
133+
134+
/**
135+
* Attempts to free space by releasing files with no references
136+
*/
137+
unsigned AudioFileCache::Free_Space(unsigned required)
138+
{
139+
std::list<Utf8String> to_free;
140+
unsigned freed = 0;
141+
142+
// First check for samples that don't have any references.
143+
for (const auto &cached : m_cacheMap) {
144+
if (cached.second.ref_count == 0) {
145+
to_free.push_back(cached.first);
146+
freed += cached.second.data_size;
147+
148+
// If required is "0" we free as much as possible
149+
if (required && freed >= required) {
150+
break;
151+
}
152+
}
153+
}
154+
155+
for (const auto &file : to_free) {
156+
auto to_remove = m_cacheMap.find(file);
157+
158+
if (to_remove != m_cacheMap.end()) {
159+
Release_Open_Audio(&to_remove->second);
160+
m_currentSize -= to_remove->second.data_size;
161+
m_cacheMap.erase(to_remove);
162+
}
163+
}
164+
165+
return freed;
166+
}
167+
168+
/**
169+
* Attempts to free space for a file by releasing files with no references and lower priority sounds.
170+
*/
171+
bool AudioFileCache::Free_Space_For_Sample(const OpenAudioFile &file)
172+
{
173+
captainslog_assert(m_currentSize >= m_maxSize); // Assumed to be called only when we need more than allowed.
174+
std::list<Utf8String> to_free;
175+
unsigned required = m_currentSize - m_maxSize;
176+
unsigned freed = 0;
177+
178+
// First check for samples that don't have any references.
179+
freed = Free_Space(required);
180+
181+
// If we still don't have enough potential space freed up, look for lower priority sounds to remove.
182+
if (freed < required) {
183+
for (const auto &cached : m_cacheMap) {
184+
if (cached.second.ref_count != 0
185+
&& cached.second.audio_event_info->Get_Priority() < file.audio_event_info->Get_Priority()) {
186+
to_free.push_back(cached.first);
187+
freed += cached.second.data_size;
188+
189+
if (freed >= required) {
190+
break;
191+
}
192+
}
193+
}
194+
}
195+
196+
// If we have enough space to free, do the actual freeing, otherwise we didn't succeed, no point bothering.
197+
if (freed < required) {
198+
return false;
199+
}
200+
201+
for (const auto &file : to_free) {
202+
auto to_remove = m_cacheMap.find(file);
203+
204+
if (to_remove != m_cacheMap.end()) {
205+
Release_Open_Audio(&to_remove->second);
206+
m_currentSize -= to_remove->second.data_size;
207+
m_cacheMap.erase(to_remove);
208+
}
209+
}
210+
211+
return true;
212+
}

src/platform/audio/audiofilecache.h

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @file
3+
*
4+
* @author feliwir
5+
*
6+
* @brief Base class for caching loaded audio samples to reduce file IO.
7+
*
8+
* @copyright Thyme is free software: you can redistribute it and/or
9+
* modify it under the terms of the GNU General Public License
10+
* as published by the Free Software Foundation, either version
11+
* 2 of the License, or (at your option) any later version.
12+
* A full copy of the GNU General Public License can be found in
13+
* LICENSE
14+
*/
15+
#pragma once
16+
17+
#include "always.h"
18+
#include "asciistring.h"
19+
#include "audiomanager.h"
20+
#include "file.h"
21+
#include "mutex.h"
22+
#include "rtsutils.h"
23+
24+
#ifdef THYME_USE_STLPORT
25+
#include <hash_map>
26+
#else
27+
#include <unordered_map>
28+
#endif
29+
30+
class AudioEventInfo;
31+
class AudioEventRTS;
32+
33+
struct OpenAudioFile
34+
{
35+
AudioDataHandle wave_data = nullptr;
36+
int ref_count = 0;
37+
int data_size = 0;
38+
const AudioEventInfo *audio_event_info = nullptr;
39+
void *opaque = nullptr;
40+
};
41+
42+
#ifdef THYME_USE_STLPORT
43+
typedef std::hash_map<const Utf8String, OpenAudioFile, rts::hash<Utf8String>, std::equal_to<Utf8String>> audiocachemap_t;
44+
#else
45+
typedef std::unordered_map<const Utf8String, OpenAudioFile, rts::hash<Utf8String>, std::equal_to<Utf8String>>
46+
audiocachemap_t;
47+
#endif
48+
49+
namespace Thyme
50+
{
51+
52+
class AudioFileCache
53+
{
54+
public:
55+
AudioFileCache() : m_maxSize(0), m_currentSize(0), m_mutex("AudioFileCacheMutex") {}
56+
AudioDataHandle Open_File(AudioEventRTS *file);
57+
AudioDataHandle Open_File(const Utf8String &filename, const AudioEventInfo *event_info = nullptr);
58+
59+
void Close_File(AudioDataHandle file);
60+
void Set_Max_Size(unsigned size);
61+
inline unsigned Get_Max_Size() const { return m_maxSize; }
62+
inline unsigned Get_Current_Size() const { return m_currentSize; }
63+
64+
// #FEATURE: We can maybe call this during loading to free any old sounds we won't need ingame and decrease computation
65+
// ingame
66+
unsigned Free_Space(unsigned required = 0);
67+
68+
protected:
69+
bool Free_Space_For_Sample(const OpenAudioFile &open_audio);
70+
71+
virtual bool Load_File(File *file, OpenAudioFile &audio_file) = 0;
72+
virtual void Release_Open_Audio(OpenAudioFile *open_audio) = 0;
73+
74+
protected:
75+
audiocachemap_t m_cacheMap;
76+
unsigned m_currentSize;
77+
unsigned m_maxSize;
78+
SimpleMutexClass m_mutex;
79+
};
80+
} // namespace Thyme

0 commit comments

Comments
 (0)