I took the “simple_event” example project and added the following code in “simple_event.cpp”:
Between the includes and the start of FMOD_Main
:
// ...
#include "common.h"
// ^^^ Already there
// vvv New stuff
#include <iostream>
#include <mutex>
#include <string>
#include <vector>
std::mutex mTotalBytesMutex;
unsigned int mTotalBytes = 0;
constexpr int gHeaderSize = sizeof(unsigned int);
void* F_CALL allocate(unsigned int size, FMOD_MEMORY_TYPE type, const char* sourcestr) {
void* fullMemory = new char[gHeaderSize + size];
unsigned int* header = static_cast<unsigned int*>(fullMemory);
void* data = static_cast<char*>(fullMemory) + gHeaderSize;
*header = size;
{
std::lock_guard<std::mutex> lock(mTotalBytesMutex);
mTotalBytes += size;
Sleep(100);
}
return data;
}
void F_CALL deallocate(void* ptr, FMOD_MEMORY_TYPE type, const char* sourcestr) {
void* fullMemory = static_cast<char*>(ptr) - gHeaderSize;
unsigned int* header = static_cast<unsigned int*>(fullMemory);
{
std::lock_guard<std::mutex> lock(mTotalBytesMutex);
mTotalBytes -= *header;
delete[] fullMemory;
Sleep(100);
}
}
void* F_CALL reallocate(void* ptr, unsigned int size, FMOD_MEMORY_TYPE type, const char* sourcestr) {
void* fullMemory = static_cast<char*>(ptr) - gHeaderSize;
unsigned int* header = static_cast<unsigned int*>(fullMemory);
void* memory = allocate(size, type, sourcestr);
memcpy(memory, ptr, size < *header ? size : *header);
deallocate(ptr, type, sourcestr);
return memory;
}
FMOD::Studio::System* gStudioSystem = nullptr;
FMOD::System* gCoreSystem = nullptr;
void test(int argc, const char** argv) {
if (argc == 1) {
std::cout << "Parent" << std::endl;
struct Task {
HANDLE mProcess;
HANDLE mThread;
};
std::vector<Task> tasks;
constexpr int taskCount = 2;
for (int i = 0; i < taskCount; ++i) {
char commandLine[256];
sprintf_s(commandLine, "\"%s\" %d", argv[0], i);
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInfo;
memset(&startupInfo, 0, sizeof(startupInfo));
memset(&processInfo, 0, sizeof(processInfo));
startupInfo.cb = sizeof(startupInfo);
if (CreateProcess(
NULL, // Program to execute
commandLine, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&startupInfo, // Pointer to STARTUPINFO structure
&processInfo) // Pointer to PROCESS_INFORMATION structure
) {
Task task;
task.mProcess = processInfo.hProcess;
task.mThread = processInfo.hThread;
tasks.emplace_back(std::move(task));
}
else {
std::cout << "Could not start task: " << i << std::endl;
}
}
while (!tasks.empty()) {
for (int i = static_cast<int>(tasks.size()) - 1; i >= 0; --i) {
DWORD exitCode;
if (GetExitCodeProcess(tasks[i].mProcess, &exitCode)) {
if (exitCode == STILL_ACTIVE) {
continue;
}
}
std::cout << "Task finished (" << tasks.size() - 1 << " remaining): " << std::endl;
CloseHandle(tasks[i].mProcess);
CloseHandle(tasks[i].mThread);
tasks.erase(tasks.begin() + i);
}
}
}
else if (argc == 2) {
std::cout << "Child[" << argv[1] << "] start" << std::endl;
FMOD::Memory_Initialize(nullptr, 0, allocate, reallocate, deallocate, FMOD_MEMORY_ALL);
std::cout << "Child[" << argv[1] << "] memory" << std::endl;
FMOD::Studio::System::create(&gStudioSystem);
std::cout << "Child[" << argv[1] << "] create" << std::endl;
gStudioSystem->getCoreSystem(&gCoreSystem);
std::cout << "Child[" << argv[1] << "] initialize" << std::endl;
gStudioSystem->initialize(32, FMOD_STUDIO_INIT_NORMAL | FMOD_STUDIO_INIT_LIVEUPDATE, FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_PROFILE_ENABLE, nullptr);
std::cout << "Child[" << argv[1] << "] load master" << std::endl;
FMOD::Studio::Bank* masterBank = NULL;
gStudioSystem->loadBankFile(Common_MediaPath("Master.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &masterBank);
std::cout << "Child[" << argv[1] << "] load strings" << std::endl;
FMOD::Studio::Bank* stringsBank = NULL;
gStudioSystem->loadBankFile(Common_MediaPath("Master.strings.bank"), FMOD_STUDIO_LOAD_BANK_NORMAL, &stringsBank);
std::cout << "Child[" << argv[1] << "] finish" << std::endl;
}
}
extern int Common_Private_Argc;
extern char** Common_Private_Argv;
// ^^^ New stuff
// vvv Already there
int FMOD_Main()
// ...
AND at the start of FMOD_Main
:
// ...
int FMOD_Main()
{
// ^^^ Already there
// vvv New stuff
test(Common_Private_Argc, const_cast<const char**>(Common_Private_Argv));
return 0;
// ^^^ New stuff
// vvv Already there
void *extraDriverData = NULL;
// ...
With those modifications the output is not deterministic, but one run looked like the following:
I was able to make the abort happen every single time by adding the Sleep
calls in the allocate
and deallocate
callbacks (which is also the reason the loading of the banks takes so long). I’ve noticed that without the Sleep
calls in the allocate
and deallocate
callbacks, the abort occurs more frequently in banks with more data in them, probably because more allocate
and deallocate
are called more times. I’ve also noticed that without the Sleep
calls, the abort occurs more frequently if you increase the number of processes (the constexpr int taskCount = 1
will control that in the code I’ve provided).