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

Custom FileSystem callbacks #67

Merged
merged 9 commits into from
Jan 28, 2021
Merged
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
2 changes: 2 additions & 0 deletions Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ LOCAL_LDLIBS := -llog
LOCAL_SRC_FILES := \
src/godot_fmod.cpp \
src/gdlibrary.cpp \
src/callback/file_callbacks.cpp \
src/callback/event_callbacks.cpp \

LOCAL_SHARED_LIBRARIES := \
fmod-core-prebuilt \
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ include_directories("../godot-cpp/godot_headers")
link_directories("../libs/fmod/${OS}/core/lib/${ARCH}")
link_directories("../libs/fmod/${OS}/studio/lib/${ARCH}")
include_directories(../libs/fmod/${OS}/core/inc ../libs/fmod/${OS}/studio/inc)
add_library(GodotFmod SHARED src/godot_fmod.cpp src/godot_fmod.h src/gdlibrary.cpp src/helpers/current_function.h src/helpers/containers.h src/callback/callbacks.h)
add_library(GodotFmod SHARED src/godot_fmod.cpp src/godot_fmod.h src/gdlibrary.cpp src/helpers/current_function.h src/helpers/containers.h src/callback/event_callbacks.h src/callback/file_callbacks.h src/callback/file_callbacks.cpp src/callback/event_callbacks.cpp)
if (${OS} EQUAL "osx")
target_link_libraries(GodotFmod libgodot-cpp.osx.64.a libfmod.dylib libfmodstudio.dylib)
endif ()
Expand Down
2 changes: 1 addition & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ elif platform == "android":

sources = []
add_sources(sources, "./src", 'cpp')

add_sources(sources, "./src/callback", 'cpp')

###############
#BUILD LIB#####
Expand Down
2 changes: 1 addition & 1 deletion demo/test/unit/test_event.gd
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ class TestEvent:

func test_assert_timeline_position():
var desired_value: int = 10
Fmod.set_event_timeline_position(id, desired_value)
Fmod.set_event_paused(id, true)
Fmod.set_event_timeline_position(id, desired_value)
yield(yield_for(2), YIELD)
assert_eq(Fmod.get_event_timeline_position(id), 10, "Event timeline should be at " + str(desired_value))

Expand Down
47 changes: 47 additions & 0 deletions src/callback/event_callbacks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <fmod_studio.hpp>
#include "event_callbacks.h"
#include "../godot_fmod.h"

namespace Callbacks{
std::mutex callback_mut;

FMOD_RESULT F_CALLBACK eventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters) {
auto *instance = (FMOD::Studio::EventInstance *) event;
auto instanceId = (uint64_t) instance;
godot::EventInfo *eventInfo;
std::lock_guard<std::mutex> lk(callback_mut);
instance->getUserData((void **) &eventInfo);
if (eventInfo) {
Callbacks::CallbackInfo *callbackInfo = &eventInfo->callbackInfo;

if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER) {
auto *props = (FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES *) parameters;
callbackInfo->markerCallbackInfo["event_id"] = instanceId;
callbackInfo->markerCallbackInfo["name"] = props->name;
callbackInfo->markerCallbackInfo["position"] = props->position;
callbackInfo->markerSignalEmitted = false;
} else if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT) {
auto *props = (FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES *) parameters;
callbackInfo->beatCallbackInfo["event_id"] = instanceId;
callbackInfo->beatCallbackInfo["beat"] = props->beat;
callbackInfo->beatCallbackInfo["bar"] = props->bar;
callbackInfo->beatCallbackInfo["tempo"] = props->tempo;
callbackInfo->beatCallbackInfo["time_signature_upper"] = props->timesignatureupper;
callbackInfo->beatCallbackInfo["time_signature_lower"] = props->timesignaturelower;
callbackInfo->beatCallbackInfo["position"] = props->position;
callbackInfo->beatSignalEmitted = false;
} else if (type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED ||
type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED) {
auto *sound = (FMOD::Sound *) parameters;
char n[256];
sound->getName(n, 256);
godot::String name(n);
godot::String mType = type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED ? "played" : "stopped";
callbackInfo->soundCallbackInfo["name"] = name;
callbackInfo->soundCallbackInfo["type"] = mType;
callbackInfo->soundSignalEmitted = false;
}
}
return FMOD_OK;
}
}
4 changes: 2 additions & 2 deletions src/callback/callbacks.h → src/callback/event_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <fmod_common.h>
#include <fmod_studio_common.h>
#include <core/Dictionary.hpp>
#include <Mutex.hpp>
#include <mutex>

namespace Callbacks {
struct CallbackInfo {
Expand All @@ -16,7 +16,7 @@ namespace Callbacks {
godot::Dictionary soundCallbackInfo;
};

extern godot::Mutex *mut;
extern std::mutex callback_mut;

FMOD_RESULT F_CALLBACK eventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters);
}
Expand Down
167 changes: 167 additions & 0 deletions src/callback/file_callbacks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "file_callbacks.h"
#include <OS.hpp>

namespace Callbacks{

GodotFileRunner* GodotFileRunner::get_singleton() {
static GodotFileRunner singleton;
return &singleton;
}

void GodotFileRunner::queueReadRequest(FMOD_ASYNCREADINFO* request, ReadPriority priority) {
//High priority requests have to be processed first.
if(priority == ReadPriority::HIGH) {
//lock so we can't add and remove elements from the queue at the same time.
std::lock_guard<std::mutex> lk(read_mut);
requests.push_front_value(request);
}
else{
//lock so we can't add and remove elements from the queue at the same time.
std::lock_guard<std::mutex> lk(read_mut);
requests.push_back_value(request);
}
read_cv.notify_one();
}

void GodotFileRunner::cancelReadRequest(FMOD_ASYNCREADINFO* request) {
//lock so we can't add and remove elements from the queue at the same time.
{
std::lock_guard<std::mutex> lk(read_mut);
requests.erase(request);
}

//We lock and check if the current request is the one being canceled.
//In this case, we wait until it's done.
{
std::unique_lock<std::mutex> lk(cancel_mut);
if(request == current_request){
cancel_cv.wait(lk);
}
}
}

void GodotFileRunner::run() {
while(!stop) {

//waiting for the container to have one request
{
std::unique_lock<std::mutex> lk(read_mut);
read_cv.wait(lk, [this]{return !requests.empty() || stop;});
}

while(!requests.empty()) {
//lock so we can't add and remove elements from the queue at the same time.
//also store the current request so it cannot be cancel during process.
{
std::lock_guard<std::mutex> lk(read_mut);
current_request = requests.pop_front_value();
}

//We get the Godot File object from the handle
GodotFileHandle* handle {reinterpret_cast<GodotFileHandle*>(current_request->handle)};
godot::Ref<godot::File> file {handle->file};

//update the position of the cursor
file->seek(current_request->offset);

//We read and store the requested data in an array.
godot::PoolByteArray buffer {file->get_buffer(current_request->sizebytes)};
int size {buffer.size()};
const uint8_t* data {buffer.read().ptr()};

//We copy the data to FMOD buffer
memcpy(current_request->buffer, data, size * sizeof(uint8_t));
current_request->bytesread = size;

//Don't forget the return an error if end of the file is reached
FMOD_RESULT result;
if(file->eof_reached()) {
result = FMOD_RESULT::FMOD_ERR_FILE_EOF;
}
else {
result = FMOD_RESULT::FMOD_OK;
}
current_request->done(current_request, result);

//Request no longer processed
{
std::lock_guard<std::mutex> lk(cancel_mut);
current_request = nullptr;
}
cancel_cv.notify_one();
}
}
}

void GodotFileRunner::start() {
stop = false;
fileThread = std::thread(&GodotFileRunner::run, this);
}

void GodotFileRunner::finish(){
stop = true;
//we need to notify the loop one last time, otherwise it will stay stuck in the wait method.
read_cv.notify_one();
fileThread.join();

}


FMOD_RESULT F_CALLBACK godotFileOpen(
const char *name,
unsigned int *filesize,
void **handle,
void *userdata
) {
godot::Ref<godot::File> file;
file.instance();

GodotFileHandle* fileHandle {new GodotFileHandle{file}};

godot::Error err = file->open(name, godot::File::ModeFlags::READ);
*filesize = file->get_len();
*handle = reinterpret_cast<void *>(fileHandle);

if(err == godot::Error::OK) {
return FMOD_RESULT::FMOD_OK;
}
return FMOD_RESULT::FMOD_ERR_FILE_BAD;
}

FMOD_RESULT F_CALLBACK godotFileClose(
void *handle,
void *userdata
) {
godot::Ref<godot::File> file {reinterpret_cast<GodotFileHandle*>(handle)->file};
file->close();
delete reinterpret_cast<GodotFileHandle*>(handle);
return FMOD_RESULT::FMOD_OK;
}

FMOD_RESULT F_CALLBACK godotSyncRead(
FMOD_ASYNCREADINFO *info,
void *userdata
) {
GodotFileRunner* runner {GodotFileRunner::get_singleton()};
int priority {info->priority};

GodotFileRunner::ReadPriority priorityRank;
if(priority == 100){
priorityRank = GodotFileRunner::ReadPriority::HIGH;
}
else {
priorityRank = GodotFileRunner::ReadPriority::NORMAL;
}
runner->queueReadRequest(info, priorityRank);
return FMOD_RESULT::FMOD_OK;
}

FMOD_RESULT F_CALLBACK godotSyncCancel(
FMOD_ASYNCREADINFO *info,
void *userdata
) {
GodotFileRunner* runner {GodotFileRunner::get_singleton()};
runner->cancelReadRequest(info);
return FMOD_RESULT::FMOD_OK;
}
}
77 changes: 77 additions & 0 deletions src/callback/file_callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef GODOTFMOD_FILE_CALLBACKS_H
#define GODOTFMOD_FILE_CALLBACKS_H

#include <fmod_common.h>
#include <fmod_studio_common.h>
#include <File.hpp>
#include "../helpers/containers.h"
#include <thread>
#include <condition_variable>

namespace Callbacks {
struct GodotFileHandle {
godot::Ref<godot::File> file;
};

class GodotFileRunner {
public:
static GodotFileRunner* get_singleton();

enum ReadPriority {
NORMAL,
HIGH
};

~GodotFileRunner() = default;

private:

std::thread fileThread;

std::condition_variable read_cv;
std::mutex read_mut;

std::condition_variable cancel_cv;
std::mutex cancel_mut;

bool stop = false;
FMOD_ASYNCREADINFO* current_request = nullptr;
godot::Vector<FMOD_ASYNCREADINFO*> requests = godot::Vector<FMOD_ASYNCREADINFO*>();

GodotFileRunner() = default;
GodotFileRunner(const GodotFileRunner&) = delete;
GodotFileRunner& operator=(const GodotFileRunner&) = delete;

void run();
public:
void queueReadRequest(FMOD_ASYNCREADINFO* request, ReadPriority priority);
void cancelReadRequest(FMOD_ASYNCREADINFO* request);
void start();
void finish();

};

FMOD_RESULT F_CALLBACK godotFileOpen(
const char *name,
unsigned int *filesize,
void **handle,
void *userdata
);

FMOD_RESULT F_CALLBACK godotFileClose(
void *handle,
void *userdata
);

FMOD_RESULT F_CALLBACK godotSyncRead(
FMOD_ASYNCREADINFO *info,
void *userdata
);

FMOD_RESULT F_CALLBACK godotSyncCancel(
FMOD_ASYNCREADINFO *info,
void *userdata
);
}

#endif //GODOTFMOD_FILE_CALLBACKS_H
3 changes: 3 additions & 0 deletions src/gdlibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ using namespace godot;

extern "C" void GDN_EXPORT fmod_gdnative_init(godot_gdnative_init_options *o){
Godot::gdnative_init(o);
//Just to initialize the singleton();
Callbacks::GodotFileRunner::get_singleton();
}

extern "C" void GDN_EXPORT fmod_gdnative_terminate(godot_gdnative_terminate_options *o){
delete Callbacks::GodotFileRunner::get_singleton();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved too quickly, singleton is now on stack

Godot::gdnative_terminate(o);
}

Expand Down
Loading