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

Add callback API, update PR labeler #3300

Merged
merged 3 commits into from
Nov 29, 2024
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
33 changes: 32 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Building:
- .github/**
- CMakeLists.txt
- builds/**
- '!builds/android/app/**'
- Makefile.am
- configure.ac

Expand Down Expand Up @@ -37,6 +36,22 @@ Audio:
- changed-files:
- any-glob-to-any-file: [ src/**/*audio* ]

Battle:
- changed-files:
- any-glob-to-any-file:
- src/**/scene_battle*
- src/**/window_battle*
- src/**/game_battle.*
- src/**/game_battlealgorithm.*

Bitmaps:
- changed-files:
- any-glob-to-any-file:
- src/**/bitmap.*
- src/**/bitmap_*
- src/**/sprite.*
- src/**/sprite_*

FileFinder:
- changed-files:
- any-glob-to-any-file: [ src/**/filefinder*, src/**/filesystem* ]
Expand All @@ -51,10 +66,26 @@ Fonts:
- src/**/*font*
- src/generated/bitmapfont_*

Input:
- changed-files:
- any-glob-to-any-file: [ src/**/*input* ]

Messages:
- changed-files:
- any-glob-to-any-file: [ src/**/*message* ]

MIDI:
- changed-files:
- any-glob-to-any-file: [ src/**/*midi* ]

Settings:
- changed-files:
- any-glob-to-any-file: [ src/**/*config* ]

Translation:
- changed-files:
- any-glob-to-any-file: [ src/**/translation* ]

# platforms

3DS:
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ add_library(${PROJECT_NAME} OBJECT
src/bitmap_hslrgb.h
src/cache.cpp
src/cache.h
src/callback.h
src/cmdline_parser.cpp
src/cmdline_parser.h
src/color.h
Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ libeasyrpg_player_a_SOURCES = \
src/bitmap_hslrgb.h \
src/cache.cpp \
src/cache.h \
src/callback.h \
src/cmdline_parser.cpp \
src/cmdline_parser.h \
src/color.h \
Expand Down
157 changes: 157 additions & 0 deletions src/callback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* This file is part of EasyRPG Player.
*
* EasyRPG Player 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 3 of the License, or
* (at your option) any later version.
*
* EasyRPG Player is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef EP_CALLBACK_H
#define EP_CALLBACK_H

#include <lcf/scope_guard.h>
#include <vector>
#include <functional>
#include <utility>
#include <algorithm>

#if 0
Where to place a callback:
When it is related to something global put the callback where it is suitable.
When it is related to an instance of a class/struct add it as a public variable.

Adding a new callback:

Assuming you want to add a notification when a variable is changed.

In the header of Game_Variables (game_variables.h) add a public field

```cpp
Callback<int /* variable_id */, int /* value */> OnVariableChanged;
```

The /* names */ are for increated readability.
Functions that take two integer arguments can be bound to this callback.

Signal the callback at a suitable location.
Here this would be in SetOp in game_variables.cpp:

```cpp
OnVariableChanged.Call(variable_id, v);
```

This invocation will trigger all functions attached with Bind.

Attaching to the callback:

```cpp
// Attaching with a scope guard (the callback will automatically Unbind when the
// guard is not in scope anymore).
// This prevents stale handlers that crash.
auto guard = Main_Data::game_variables->OnVariableChanged.Bind([](int var_id, int val) {
Output::Warning("{} = {}", var_id, val);
});

// In case of a class the scope guard should be a field.
// It is automatically unbound when the instance is destroyed.
class MyClass {
public:
MyClass() {
guard = Main_Data::game_variables->OnVariableChanged.Bind([](int var_id, int val) {
Output::Warning("{} = {}", var_id, val);
});

// Alternative: Binding a method instead of a lambda
guard = Main_Data::game_variables->OnVariableChanged.Bind(&MyClass::VariableChanged, this);
}

void VariableChanged(int var_id, int val) {
Output::Warning("{} = {}", var_id, val);
}

private:
BindingScopeGuard guard;
}
```

Not recommended:

If you want to (for whatever reason) manually manage the lifetime of the binding
use BindUnmanaged.

This works the same as described above but the function returns an integer.

And you must that integer to Unbind(id) to remove the handler.
#endif

using BindingScopeGuard = lcf::ScopeGuard<std::function<void()>>;

template</*typename Ret, */typename ...Args>
class Callback {
public:
using Ret = void;
using Fn = std::function<Ret(Args...)>;

template<typename T>
int BindUnmanaged(Ret (T::*func)(Args...), T* that, Args... args) {
Fn f = std::bind(std::mem_fn(func), that, std::placeholders::_1, args...);
return BindUnmanaged(f);
}

int BindUnmanaged(Fn func) {
listeners.push_back({next_id, func});
return next_id++;
}

template<typename T>
BindingScopeGuard Bind(Ret (T::*func)(Args...), T* that, Args... args) {
int id = BindUnmanaged(func, that, args...);

return lcf::ScopeGuard<std::function<void()>>([this, id=id]() {
Unbind(id);
});
}

BindingScopeGuard Bind(Fn func) {
int id = BindUnmanaged(func);

return lcf::ScopeGuard<std::function<void()>>([this, id]() {
Unbind(id);
});
}

void Unbind(int id) {
auto it = std::find_if(listeners.begin(), listeners.end(), [id](auto& listener){
return listener.first == id;
});
assert(it != listeners.end());

listeners.erase(it);
}

void Call(Args... args) {
for (auto& listener: listeners) {
listener.second(std::forward<Args>(args)...);
}
}

void Clear() {
listeners.clear();
}

private:
std::vector<std::pair<int, Fn>> listeners;

int next_id = 1;
};

#endif
6 changes: 5 additions & 1 deletion src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/

// Headers

#include <algorithm>
#include <cstring>
#include <cstdlib>
Expand Down Expand Up @@ -81,6 +80,7 @@
#include "instrumentation.h"
#include "transition.h"
#include <lcf/scope_guard.h>
#include <lcf/log_handler.h>
#include "baseui.h"
#include "game_clock.h"
#include "message_overlay.h"
Expand Down Expand Up @@ -152,6 +152,10 @@ namespace {
}

void Player::Init(std::vector<std::string> args) {
lcf::LogHandler::SetHandler([](lcf::LogHandler::Level level, StringView message, lcf::LogHandler::UserData) {
Output::Debug("lcf ({}): {}", lcf::LogHandler::kLevelTags.tag(level), message);
});

frames = 0;

// Must be called before the first call to Output
Expand Down
Loading