This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

View file

@ -0,0 +1,65 @@
INCLUDE_DIRECTORIES (
${STAR_EXTERN_INCLUDES}
${STAR_CORE_INCLUDES}
${STAR_PLATFORM_INCLUDES}
${STAR_APPLICATION_INCLUDES}
)
SET (star_application_HEADERS
StarApplication.hpp
StarApplicationController.hpp
StarMainApplication.hpp
StarInputEvent.hpp
StarRenderer.hpp
)
SET (star_application_SOURCES
StarApplication.cpp
StarInputEvent.cpp
StarRenderer.cpp
)
SET (star_application_HEADERS ${star_application_HEADERS}
StarP2PNetworkingService_pc.hpp
StarPlatformServices_pc.hpp
StarRenderer_opengl20.hpp
)
SET (star_application_SOURCES ${star_application_SOURCES}
StarMainApplication_sdl.cpp
StarP2PNetworkingService_pc.cpp
StarPlatformServices_pc.cpp
StarRenderer_opengl20.cpp
)
IF (STAR_ENABLE_STEAM_INTEGRATION)
SET (star_application_HEADERS ${star_application_HEADERS}
StarDesktopService_pc_steam.hpp
StarStatisticsService_pc_steam.hpp
StarUserGeneratedContentService_pc_steam.hpp
)
SET (star_application_SOURCES ${star_application_SOURCES}
StarDesktopService_pc_steam.cpp
StarStatisticsService_pc_steam.cpp
StarUserGeneratedContentService_pc_steam.cpp
)
ENDIF ()
IF (STAR_ENABLE_DISCORD_INTEGRATION)
SET (star_application_SOURCES ${star_application_SOURCES}
discord/activity_manager.cpp
discord/application_manager.cpp
discord/core.cpp
discord/image_manager.cpp
discord/lobby_manager.cpp
discord/network_manager.cpp
discord/overlay_manager.cpp
discord/relationship_manager.cpp
discord/storage_manager.cpp
discord/store_manager.cpp
discord/types.cpp
discord/user_manager.cpp
)
ENDIF ()
ADD_LIBRARY (star_application OBJECT ${star_application_SOURCES} ${star_application_HEADERS})

View file

@ -0,0 +1,31 @@
#include "StarApplication.hpp"
#include "StarTime.hpp"
#include "StarLogging.hpp"
namespace Star {
void Application::startup(StringList const&) {}
void Application::applicationInit(ApplicationControllerPtr appController) {
m_appController = move(appController);
}
void Application::renderInit(RendererPtr renderer) {
m_renderer = move(renderer);
}
void Application::windowChanged(WindowMode, Vec2U) {}
void Application::processInput(InputEvent const&) {}
void Application::update() {}
void Application::render() {}
void Application::getAudioData(int16_t* samples, size_t sampleCount) {
for (size_t i = 0; i < sampleCount; ++i)
samples[i] = 0;
}
void Application::shutdown() {}
}

View file

@ -0,0 +1,76 @@
#ifndef STAR_APPLICATION_HPP
#define STAR_APPLICATION_HPP
#include "StarInputEvent.hpp"
namespace Star {
STAR_CLASS(ApplicationController);
STAR_CLASS(Renderer);
STAR_CLASS(Application);
STAR_EXCEPTION(ApplicationException, StarException);
enum class WindowMode {
Normal,
Maximized,
Fullscreen,
Borderless
};
class Application {
public:
virtual ~Application() = default;
// Called once on application startup, before any other methods.
virtual void startup(StringList const& cmdLineArgs);
// Called on application initialization, before rendering initialization. If
// overriden, must call base class instance.
virtual void applicationInit(ApplicationControllerPtr appController);
// Called immediately after application initialization on startup, and then
// also whenever the renderer invalidated and recreated. If overridden, must
// call base class instance.
virtual void renderInit(RendererPtr renderer);
// Called when the window mode or size is changed.
virtual void windowChanged(WindowMode windowMode, Vec2U screenSize);
// Called before update, once for every pending event.
virtual void processInput(InputEvent const& event);
// Will be called at updateRate hz, or as close as possible.
virtual void update();
// Will be called at updateRate hz, or more or less depending on settings and
// performance. update() is always prioritized over render().
virtual void render();
// Will be called *from a different thread* to retrieve audio data (if audio
// is playing). Default implementation simply fills the buffer with silence.
virtual void getAudioData(int16_t* sampleData, size_t frameCount);
// Will be called once on application shutdown, including when shutting down
// due to an Application exception.
virtual void shutdown();
ApplicationControllerPtr const& appController() const;
RendererPtr const& renderer() const;
private:
ApplicationControllerPtr m_appController;
RendererPtr m_renderer;
};
inline ApplicationControllerPtr const& Application::appController() const {
return m_appController;
}
inline RendererPtr const& Application::renderer() const {
return m_renderer;
}
}
#endif

View file

@ -0,0 +1,67 @@
#ifndef STAR_APPLICATION_CONTROLLER_HPP
#define STAR_APPLICATION_CONTROLLER_HPP
#include "StarApplication.hpp"
#include "StarStatisticsService.hpp"
#include "StarP2PNetworkingService.hpp"
#include "StarUserGeneratedContentService.hpp"
#include "StarDesktopService.hpp"
namespace Star {
STAR_CLASS(ApplicationController);
// Audio format is always 16 bit signed integer samples
struct AudioFormat {
unsigned sampleRate;
unsigned channels;
};
// Window size defaults to 800x600, target update rate to 60hz, maximized and
// fullscreen are false, vsync is on, the cursor is visible, and audio and text
// input are disabled.
class ApplicationController {
public:
virtual ~ApplicationController() = default;
// Target hz at which update() will be called
virtual void setTargetUpdateRate(float targetUpdateRate) = 0;
// Window that controls how long the update rate will be increased or
// decreased to make up for rate errors in the past.
virtual void setUpdateTrackWindow(float updateTrackWindow) = 0;
// Maximum number of calls to update() that can occur before we force
// 'render()' to be called, even if we are still behind on our update rate.
virtual void setMaxFrameSkip(unsigned maxFrameSkip) = 0;
virtual void setApplicationTitle(String title) = 0;
virtual void setFullscreenWindow(Vec2U fullScreenResolution) = 0;
virtual void setNormalWindow(Vec2U windowSize) = 0;
virtual void setMaximizedWindow() = 0;
virtual void setBorderlessWindow() = 0;
virtual void setVSyncEnabled(bool vSync) = 0;
virtual void setCursorVisible(bool cursorVisible) = 0;
virtual void setAcceptingTextInput(bool acceptingTextInput) = 0;
virtual AudioFormat enableAudio() = 0;
virtual void disableAudio() = 0;
virtual void setClipboard(String text) = 0;
virtual Maybe<String> getClipboard() = 0;
// Returns the latest actual measured update and render rate, which may be
// different than the target update rate.
virtual float updateRate() const = 0;
virtual float renderFps() const = 0;
virtual StatisticsServicePtr statisticsService() const = 0;
virtual P2PNetworkingServicePtr p2pNetworkingService() const = 0;
virtual UserGeneratedContentServicePtr userGeneratedContentService() const = 0;
virtual DesktopServicePtr desktopService() const = 0;
// Signals the application to quit
virtual void quit() = 0;
};
}
#endif

View file

@ -0,0 +1,11 @@
#include "StarDesktopService_pc_steam.hpp"
namespace Star {
SteamDesktopService::SteamDesktopService(PcPlatformServicesStatePtr) {}
void SteamDesktopService::openUrl(String const& url) {
SteamFriends()->ActivateGameOverlayToWebPage(url.utf8Ptr());
}
}

View file

@ -0,0 +1,19 @@
#ifndef STAR_DESKTOP_SERVICE_PC_STEAM_HPP
#define STAR_DESKTOP_SERVICE_PC_STEAM_HPP
#include "StarPlatformServices_pc.hpp"
namespace Star {
STAR_CLASS(SteamDesktopService);
class SteamDesktopService : public DesktopService {
public:
SteamDesktopService(PcPlatformServicesStatePtr state);
void openUrl(String const& url) override;
};
}
#endif

View file

@ -0,0 +1,166 @@
#include "StarInputEvent.hpp"
namespace Star {
EnumMap<Key> const KeyNames{
{Key::Backspace, "Backspace"},
{Key::Tab, "Tab"},
{Key::Clear, "Clear"},
{Key::Return, "Return"},
{Key::Escape, "Esc"},
{Key::Space, "Space"},
{Key::Exclaim, "!"},
{Key::QuotedBL, "\""},
{Key::Hash, "#"},
{Key::Dollar, "$"},
{Key::Ampersand, "&"},
{Key::Quote, "\'"},
{Key::LeftParen, "("},
{Key::RightParen, ")"},
{Key::Asterisk, "*"},
{Key::Plus, "+"},
{Key::Comma, ","},
{Key::Minus, "-"},
{Key::Period, "."},
{Key::Slash, "/"},
{Key::Zero, "0"},
{Key::One, "1"},
{Key::Two, "2"},
{Key::Three, "3"},
{Key::Four, "4"},
{Key::Five, "5"},
{Key::Six, "6"},
{Key::Seven, "7"},
{Key::Eight, "8"},
{Key::Nine, "9"},
{Key::Colon, ":"},
{Key::Semicolon, ";"},
{Key::Less, "<"},
{Key::Equals, "="},
{Key::Greater, ">"},
{Key::Question, "?"},
{Key::At, "@"},
{Key::LeftBracket, "["},
{Key::Backslash, "\\"},
{Key::RightBracket, "]"},
{Key::Caret, "^"},
{Key::Underscore, "_"},
{Key::Backquote, "`"},
{Key::A, "A"},
{Key::B, "B"},
{Key::C, "C"},
{Key::D, "D"},
{Key::E, "E"},
{Key::F, "F"},
{Key::G, "G"},
{Key::H, "H"},
{Key::I, "I"},
{Key::J, "J"},
{Key::K, "K"},
{Key::L, "L"},
{Key::M, "M"},
{Key::N, "N"},
{Key::O, "O"},
{Key::P, "P"},
{Key::Q, "Q"},
{Key::R, "R"},
{Key::S, "S"},
{Key::T, "T"},
{Key::U, "U"},
{Key::V, "V"},
{Key::W, "W"},
{Key::X, "X"},
{Key::Y, "Y"},
{Key::Z, "Z"},
{Key::Delete, "Del"},
{Key::Kp0, "Kp0"},
{Key::Kp1, "Kp1"},
{Key::Kp2, "Kp2"},
{Key::Kp3, "Kp3"},
{Key::Kp4, "Kp4"},
{Key::Kp5, "Kp5"},
{Key::Kp6, "Kp6"},
{Key::Kp7, "Kp7"},
{Key::Kp8, "Kp8"},
{Key::Kp9, "Kp9"},
{Key::Kp_period, "Kp_period"},
{Key::Kp_divide, "Kp_divide"},
{Key::Kp_multiply, "Kp_multiply"},
{Key::Kp_minus, "Kp_minus"},
{Key::Kp_plus, "Kp_plus"},
{Key::Kp_enter, "Kp_enter"},
{Key::Kp_equals, "Kp_equals"},
{Key::Up, "Up"},
{Key::Down, "Down"},
{Key::Right, "Right"},
{Key::Left, "Left"},
{Key::Insert, "Ins"},
{Key::Home, "Home"},
{Key::End, "End"},
{Key::PageUp, "PageUp"},
{Key::PageDown, "PageDown"},
{Key::F1, "F1"},
{Key::F2, "F2"},
{Key::F3, "F3"},
{Key::F4, "F4"},
{Key::F5, "F5"},
{Key::F6, "F6"},
{Key::F7, "F7"},
{Key::F8, "F8"},
{Key::F9, "F9"},
{Key::F10, "F10"},
{Key::F11, "F11"},
{Key::F12, "F12"},
{Key::F13, "F13"},
{Key::F14, "F14"},
{Key::F15, "F15"},
{Key::NumLock, "NumLock"},
{Key::CapsLock, "CapsLock"},
{Key::ScrollLock, "ScrollLock"},
{Key::RShift, "RShift"},
{Key::LShift, "LShift"},
{Key::RCtrl, "RCtrl"},
{Key::LCtrl, "LCtrl"},
{Key::RAlt, "RAlt"},
{Key::LAlt, "LAlt"},
{Key::RGui, "RGui"},
{Key::LGui, "LGui"},
{Key::AltGr, "AltGr"},
{Key::Compose, "Compose"},
{Key::Help, "Help"},
{Key::PrintScreen, "PrintScreen"},
{Key::SysReq, "SysReq"},
{Key::Pause, "Pause"},
{Key::Menu, "Menu"},
{Key::Power, "Power"}
};
EnumMap<KeyMod> const KeyModNames{
{KeyMod::NoMod, "NoMod"},
{KeyMod::LShift, "LShift"},
{KeyMod::RShift, "RShift"},
{KeyMod::LCtrl, "LCtrl"},
{KeyMod::RCtrl, "RCtrl"},
{KeyMod::LAlt, "LAlt"},
{KeyMod::RAlt, "RAlt"},
{KeyMod::LGui, "LMeta"},
{KeyMod::RGui, "RMeta"},
{KeyMod::Num, "Num"},
{KeyMod::Caps, "Caps"},
{KeyMod::AltGr, "AltGr"}
};
EnumMap<MouseButton> const MouseButtonNames{
{MouseButton::Left, "MouseLeft"},
{MouseButton::Middle, "MouseMiddle"},
{MouseButton::Right, "MouseRight"},
{MouseButton::FourthButton, "MouseFourth"},
{MouseButton::FifthButton, "MouseFifth"}
};
EnumMap<MouseWheel> const MouseWheelNames{
{MouseWheel::Up, "MouseWheelUp"},
{MouseWheel::Down, "MouseWheelDown"}
};
}

View file

@ -0,0 +1,274 @@
#ifndef STAR_INPUT_EVENT_HPP
#define STAR_INPUT_EVENT_HPP
#include "StarString.hpp"
#include "StarBiMap.hpp"
#include "StarVariant.hpp"
#include "StarVector.hpp"
namespace Star {
enum class Key : uint16_t {
Backspace,
Tab,
Clear,
Return,
Escape,
Space,
Exclaim,
QuotedBL,
Hash,
Dollar,
Ampersand,
Quote,
LeftParen,
RightParen,
Asterisk,
Plus,
Comma,
Minus,
Period,
Slash,
Zero,
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Colon,
Semicolon,
Less,
Equals,
Greater,
Question,
At,
LeftBracket,
Backslash,
RightBracket,
Caret,
Underscore,
Backquote,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Delete,
Kp0,
Kp1,
Kp2,
Kp3,
Kp4,
Kp5,
Kp6,
Kp7,
Kp8,
Kp9,
Kp_period,
Kp_divide,
Kp_multiply,
Kp_minus,
Kp_plus,
Kp_enter,
Kp_equals,
Up,
Down,
Right,
Left,
Insert,
Home,
End,
PageUp,
PageDown,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
NumLock,
CapsLock,
ScrollLock,
RShift,
LShift,
RCtrl,
LCtrl,
RAlt,
LAlt,
RGui,
LGui,
AltGr,
Compose,
Help,
PrintScreen,
SysReq,
Pause,
Menu,
Power
};
extern EnumMap<Key> const KeyNames;
enum class KeyMod : uint16_t {
NoMod = 0x0000,
LShift = 0x0001,
RShift = 0x0002,
LCtrl = 0x0040,
RCtrl = 0x0080,
LAlt = 0x0100,
RAlt = 0x0200,
LGui = 0x0400,
RGui = 0x0800,
Num = 0x1000,
Caps = 0x2000,
AltGr = 0x4000
};
extern EnumMap<KeyMod> const KeyModNames;
KeyMod operator|(KeyMod a, KeyMod b);
KeyMod operator&(KeyMod a, KeyMod b);
KeyMod operator~(KeyMod a);
KeyMod& operator|=(KeyMod& a, KeyMod b);
KeyMod& operator&=(KeyMod& a, KeyMod b);
enum class MouseButton : uint8_t {
Left,
Middle,
Right,
FourthButton,
FifthButton
};
extern EnumMap<MouseButton> const MouseButtonNames;
enum class MouseWheel : uint8_t {
Up,
Down
};
extern EnumMap<MouseWheel> const MouseWheelNames;
typedef uint32_t ControllerId;
struct KeyDownEvent {
Key key;
KeyMod mods;
};
struct KeyUpEvent {
Key key;
};
struct TextInputEvent {
String text;
};
struct MouseMoveEvent {
Vec2I mouseMove;
Vec2I mousePosition;
};
struct MouseButtonDownEvent {
MouseButton mouseButton;
Vec2I mousePosition;
};
struct MouseButtonUpEvent {
MouseButton mouseButton;
Vec2I mousePosition;
};
struct MouseWheelEvent {
MouseWheel mouseWheel;
Vec2I mousePosition;
};
struct ControllerAxisEvent {
ControllerId controller;
unsigned controllerAxis;
float controllerAxisValue;
};
struct ControllerButtonDownEvent {
ControllerId controller;
unsigned controllerButton;
};
struct ControllerButtonUpEvent {
ControllerId controller;
unsigned controllerButton;
};
typedef Variant<
KeyDownEvent,
KeyUpEvent,
TextInputEvent,
MouseMoveEvent,
MouseButtonDownEvent,
MouseButtonUpEvent,
MouseWheelEvent,
ControllerAxisEvent,
ControllerButtonDownEvent,
ControllerButtonUpEvent>
InputEvent;
inline KeyMod operator|(KeyMod a, KeyMod b) {
return (KeyMod)((uint16_t)a | (uint16_t)b);
}
inline KeyMod operator&(KeyMod a, KeyMod b) {
return (KeyMod)((uint16_t)a & (uint16_t)b);
}
inline KeyMod operator~(KeyMod a) {
return (KeyMod) ~(uint16_t)a;
}
inline KeyMod& operator|=(KeyMod& a, KeyMod b) {
uint16_t a_cast = (uint16_t)a;
a_cast |= (uint16_t)b;
a = (KeyMod)a_cast;
return a;
}
inline KeyMod& operator&=(KeyMod& a, KeyMod b) {
uint16_t a_cast = (uint16_t)a;
a_cast &= (uint16_t)b;
a = (KeyMod)a_cast;
return a;
}
}
#endif

View file

@ -0,0 +1,34 @@
#ifndef STAR_MAIN_APPLICATION_HPP
#define STAR_MAIN_APPLICATION_HPP
#include "StarApplication.hpp"
#include "StarApplicationController.hpp"
#include "StarRenderer.hpp"
namespace Star {
int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs);
}
#if defined STAR_SYSTEM_WINDOWS
#include <windows.h>
#define STAR_MAIN_APPLICATION(ApplicationClass) \
int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { \
int nArgs; \
LPWSTR* argsList = CommandLineToArgvW(GetCommandLineW(), &nArgs); \
Star::StringList args; \
for (int i = 0; i < nArgs; ++i) args.append(Star::String(argsList[i])); \
return Star::runMainApplication(Star::make_unique<ApplicationClass>(), args); \
}
#else
#define STAR_MAIN_APPLICATION(ApplicationClass) \
int main(int argc, char** argv) { \
return Star::runMainApplication(Star::make_unique<ApplicationClass>(), Star::StringList(argc, argv)); \
}
#endif
#endif

View file

@ -0,0 +1,692 @@
#include "StarMainApplication.hpp"
#include "StarLogging.hpp"
#include "StarSignalHandler.hpp"
#include "StarTickRateMonitor.hpp"
#include "StarRenderer_opengl20.hpp"
#include "SDL.h"
#include "StarPlatformServices_pc.hpp"
namespace Star {
Maybe<Key> keyFromSdlKeyCode(SDL_Keycode sym) {
static HashMap<int, Key> KeyCodeMap{
{SDLK_BACKSPACE, Key::Backspace},
{SDLK_TAB, Key::Tab},
{SDLK_CLEAR, Key::Clear},
{SDLK_RETURN, Key::Return},
{SDLK_PAUSE, Key::Pause},
{SDLK_ESCAPE, Key::Escape},
{SDLK_SPACE, Key::Space},
{SDLK_EXCLAIM, Key::Exclaim},
{SDLK_QUOTEDBL, Key::QuotedBL},
{SDLK_HASH, Key::Hash},
{SDLK_DOLLAR, Key::Dollar},
{SDLK_AMPERSAND, Key::Ampersand},
{SDLK_QUOTE, Key::Quote},
{SDLK_LEFTPAREN, Key::LeftParen},
{SDLK_RIGHTPAREN, Key::RightParen},
{SDLK_ASTERISK, Key::Asterisk},
{SDLK_PLUS, Key::Plus},
{SDLK_COMMA, Key::Comma},
{SDLK_MINUS, Key::Minus},
{SDLK_PERIOD, Key::Period},
{SDLK_SLASH, Key::Slash},
{SDLK_0, Key::Zero},
{SDLK_1, Key::One},
{SDLK_2, Key::Two},
{SDLK_3, Key::Three},
{SDLK_4, Key::Four},
{SDLK_5, Key::Five},
{SDLK_6, Key::Six},
{SDLK_7, Key::Seven},
{SDLK_8, Key::Eight},
{SDLK_9, Key::Nine},
{SDLK_COLON, Key::Colon},
{SDLK_SEMICOLON, Key::Semicolon},
{SDLK_LESS, Key::Less},
{SDLK_EQUALS, Key::Equals},
{SDLK_GREATER, Key::Greater},
{SDLK_QUESTION, Key::Question},
{SDLK_AT, Key::At},
{SDLK_LEFTBRACKET, Key::LeftBracket},
{SDLK_BACKSLASH, Key::Backslash},
{SDLK_RIGHTBRACKET, Key::RightBracket},
{SDLK_CARET, Key::Caret},
{SDLK_UNDERSCORE, Key::Underscore},
{SDLK_BACKQUOTE, Key::Backquote},
{SDLK_a, Key::A},
{SDLK_b, Key::B},
{SDLK_c, Key::C},
{SDLK_d, Key::D},
{SDLK_e, Key::E},
{SDLK_f, Key::F},
{SDLK_g, Key::G},
{SDLK_h, Key::H},
{SDLK_i, Key::I},
{SDLK_j, Key::J},
{SDLK_k, Key::K},
{SDLK_l, Key::L},
{SDLK_m, Key::M},
{SDLK_n, Key::N},
{SDLK_o, Key::O},
{SDLK_p, Key::P},
{SDLK_q, Key::Q},
{SDLK_r, Key::R},
{SDLK_s, Key::S},
{SDLK_t, Key::T},
{SDLK_u, Key::U},
{SDLK_v, Key::V},
{SDLK_w, Key::W},
{SDLK_x, Key::X},
{SDLK_y, Key::Y},
{SDLK_z, Key::Z},
{SDLK_DELETE, Key::Delete},
{SDLK_KP_0, Key::Kp0},
{SDLK_KP_1, Key::Kp1},
{SDLK_KP_2, Key::Kp2},
{SDLK_KP_3, Key::Kp3},
{SDLK_KP_4, Key::Kp4},
{SDLK_KP_5, Key::Kp5},
{SDLK_KP_6, Key::Kp6},
{SDLK_KP_7, Key::Kp7},
{SDLK_KP_8, Key::Kp8},
{SDLK_KP_9, Key::Kp9},
{SDLK_KP_PERIOD, Key::Kp_period},
{SDLK_KP_DIVIDE, Key::Kp_divide},
{SDLK_KP_MULTIPLY, Key::Kp_multiply},
{SDLK_KP_MINUS, Key::Kp_minus},
{SDLK_KP_PLUS, Key::Kp_plus},
{SDLK_KP_ENTER, Key::Kp_enter},
{SDLK_KP_EQUALS, Key::Kp_equals},
{SDLK_UP, Key::Up},
{SDLK_DOWN, Key::Down},
{SDLK_RIGHT, Key::Right},
{SDLK_LEFT, Key::Left},
{SDLK_INSERT, Key::Insert},
{SDLK_HOME, Key::Home},
{SDLK_END, Key::End},
{SDLK_PAGEUP, Key::PageUp},
{SDLK_PAGEDOWN, Key::PageDown},
{SDLK_F1, Key::F1},
{SDLK_F2, Key::F2},
{SDLK_F3, Key::F3},
{SDLK_F4, Key::F4},
{SDLK_F5, Key::F5},
{SDLK_F6, Key::F6},
{SDLK_F7, Key::F7},
{SDLK_F8, Key::F8},
{SDLK_F9, Key::F9},
{SDLK_F10, Key::F10},
{SDLK_F11, Key::F11},
{SDLK_F12, Key::F12},
{SDLK_F13, Key::F13},
{SDLK_F14, Key::F14},
{SDLK_F15, Key::F15},
{SDLK_NUMLOCKCLEAR, Key::NumLock},
{SDLK_CAPSLOCK, Key::CapsLock},
{SDLK_SCROLLLOCK, Key::ScrollLock},
{SDLK_RSHIFT, Key::RShift},
{SDLK_LSHIFT, Key::LShift},
{SDLK_RCTRL, Key::RCtrl},
{SDLK_LCTRL, Key::LCtrl},
{SDLK_RALT, Key::RAlt},
{SDLK_LALT, Key::LAlt},
{SDLK_RGUI, Key::RGui},
{SDLK_LGUI, Key::LGui},
{SDLK_MODE, Key::AltGr},
{SDLK_APPLICATION, Key::Compose},
{SDLK_HELP, Key::Help},
{SDLK_PRINTSCREEN, Key::PrintScreen},
{SDLK_SYSREQ, Key::SysReq},
{SDLK_PAUSE, Key::Pause},
{SDLK_MENU, Key::Menu},
{SDLK_POWER, Key::Power}
};
return KeyCodeMap.maybe(sym);
}
KeyMod keyModsFromSdlKeyMods(uint16_t mod) {
KeyMod keyMod = KeyMod::NoMod;
if (mod & KMOD_LSHIFT)
keyMod |= KeyMod::LShift;
if (mod & KMOD_RSHIFT)
keyMod |= KeyMod::RShift;
if (mod & KMOD_LCTRL)
keyMod |= KeyMod::LCtrl;
if (mod & KMOD_RCTRL)
keyMod |= KeyMod::RCtrl;
if (mod & KMOD_LALT)
keyMod |= KeyMod::LAlt;
if (mod & KMOD_RALT)
keyMod |= KeyMod::RAlt;
if (mod & KMOD_LGUI)
keyMod |= KeyMod::LGui;
if (mod & KMOD_RGUI)
keyMod |= KeyMod::RGui;
if (mod & KMOD_NUM)
keyMod |= KeyMod::Num;
if (mod & KMOD_CAPS)
keyMod |= KeyMod::Caps;
if (mod & KMOD_MODE)
keyMod |= KeyMod::AltGr;
return keyMod;
}
MouseButton mouseButtonFromSdlMouseButton(uint8_t button) {
if (button == SDL_BUTTON_LEFT)
return MouseButton::Left;
else if (button == SDL_BUTTON_MIDDLE)
return MouseButton::Middle;
else if (button == SDL_BUTTON_RIGHT)
return MouseButton::Right;
else if (button == SDL_BUTTON_X1)
return MouseButton::FourthButton;
else
return MouseButton::FifthButton;
}
class SdlPlatform {
public:
SdlPlatform(ApplicationUPtr application, StringList cmdLineArgs) {
m_application = move(application);
// extract application path from command line args
String applicationPath = cmdLineArgs.first();
cmdLineArgs = cmdLineArgs.slice(1);
StringList platformArguments;
eraseWhere(cmdLineArgs, [&platformArguments](String& argument) {
if (argument.beginsWith("+platform")) {
platformArguments.append(move(argument));
return true;
}
return false;
});
Logger::info("Application: Initializing SDL");
if (SDL_Init(0))
throw ApplicationException(strf("Couldn't initialize SDL: %s", SDL_GetError()));
if (char* basePath = SDL_GetBasePath()) {
File::changeDirectory(basePath);
SDL_free(basePath);
}
m_signalHandler.setHandleInterrupt(true);
m_signalHandler.setHandleFatal(true);
try {
Logger::info("Application: startup...");
m_application->startup(cmdLineArgs);
} catch (std::exception const& e) {
throw ApplicationException("Application threw exception during startup", e);
}
Logger::info("Application: Initializing SDL Video");
if (SDL_InitSubSystem(SDL_INIT_VIDEO))
throw ApplicationException(strf("Couldn't initialize SDL Video: %s", SDL_GetError()));
Logger::info("Application: Initializing SDL Joystick");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK))
throw ApplicationException(strf("Couldn't initialize SDL Joystick: %s", SDL_GetError()));
Logger::info("Application: Initializing SDL Sound");
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
throw ApplicationException(strf("Couldn't initialize SDL Sound: %s", SDL_GetError()));
SDL_JoystickEventState(SDL_ENABLE);
m_platformServices = PcPlatformServices::create(applicationPath, platformArguments);
if (!m_platformServices)
Logger::info("Application: No platform services available");
Logger::info("Application: Creating SDL Window");
m_sdlWindow = SDL_CreateWindow(m_windowTitle.utf8Ptr(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
m_windowSize[0], m_windowSize[1], SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!m_sdlWindow)
throw ApplicationException::format("Application: Could not create SDL window: %s", SDL_GetError());
SDL_ShowWindow(m_sdlWindow);
SDL_RaiseWindow(m_sdlWindow);
int width;
int height;
SDL_GetWindowSize(m_sdlWindow, &width, &height);
m_windowSize = Vec2U(width, height);
m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow);
if (!m_sdlGlContext)
throw ApplicationException::format("Application: Could not create OpenGL context: %s", SDL_GetError());
setVSyncEnabled(m_windowVSync);
SDL_StopTextInput();
SDL_AudioSpec desired = {};
desired.freq = 44100;
desired.format = AUDIO_S16SYS;
desired.samples = 2048;
desired.channels = 2;
desired.userdata = this;
desired.callback = [](void* userdata, Uint8* stream, int len) {
((SdlPlatform*)(userdata))->getAudioData(stream, len);
};
SDL_AudioSpec obtained = {};
if (SDL_OpenAudio(&desired, &obtained) < 0) {
Logger::error("Application: Could not open audio device, no sound available!");
} else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) {
SDL_CloseAudio();
Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!");
} else {
Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, %s sample size buffer", obtained.samples);
}
m_renderer = make_shared<OpenGl20Renderer>();
m_renderer->setScreenSize(m_windowSize);
}
~SdlPlatform() {
SDL_CloseAudio();
m_renderer.reset();
Logger::info("Application: Destroying SDL Window");
SDL_DestroyWindow(m_sdlWindow);
SDL_Quit();
}
void run() {
try {
Logger::info("Application: initialization...");
m_application->applicationInit(make_shared<Controller>(this));
Logger::info("Application: renderer initialization...");
m_application->renderInit(m_renderer);
Logger::info("Application: main update loop...");
m_updateTicker.reset();
m_renderTicker.reset();
bool quit = false;
while (true) {
for (auto const& event : processEvents())
m_application->processInput(event);
if (m_platformServices)
m_platformServices->update();
if (m_platformServices->overlayActive())
SDL_ShowCursor(1);
else
SDL_ShowCursor(m_cursorVisible ? 1 : 0);
int updatesBehind = max<int>(round(m_updateTicker.ticksBehind()), 1);
updatesBehind = min<int>(updatesBehind, m_maxFrameSkip + 1);
for (int i = 0; i < updatesBehind; ++i) {
m_application->update();
m_updateRate = m_updateTicker.tick();
}
m_renderer->startFrame();
m_application->render();
m_renderer->finishFrame();
SDL_GL_SwapWindow(m_sdlWindow);
m_renderRate = m_renderTicker.tick();
if (m_quitRequested) {
Logger::info("Application: quit requested");
quit = true;
}
if (m_signalHandler.interruptCaught()) {
Logger::info("Application: Interrupt caught");
quit = true;
}
if (quit) {
Logger::info("Application: quitting...");
break;
}
int64_t spareMilliseconds = round(m_updateTicker.spareTime() * 1000);
if (spareMilliseconds > 0)
Thread::sleepPrecise(spareMilliseconds);
}
} catch (std::exception const& e) {
Logger::error("Application: exception thrown, shutting down: %s", outputException(e, true));
}
try {
Logger::info("Application: shutdown...");
m_application->shutdown();
} catch (std::exception const& e) {
Logger::error("Application: threw exception during shutdown: %s", outputException(e, true));
}
SDL_CloseAudio();
m_application.reset();
}
private:
struct Controller : public ApplicationController {
Controller(SdlPlatform* parent)
: parent(parent) {}
Maybe<String> getClipboard() override {
if (SDL_HasClipboardText())
return String(SDL_GetClipboardText());
return {};
}
void setClipboard(String text) override {
SDL_SetClipboardText(text.utf8Ptr());
}
void setTargetUpdateRate(float targetUpdateRate) override {
parent->m_updateTicker.setTargetTickRate(targetUpdateRate);
}
void setUpdateTrackWindow(float updateTrackWindow) override {
parent->m_updateTicker.setWindow(updateTrackWindow);
}
void setApplicationTitle(String title) override {
parent->m_windowTitle = move(title);
if (parent->m_sdlWindow)
SDL_SetWindowTitle(parent->m_sdlWindow, parent->m_windowTitle.utf8Ptr());
}
void setFullscreenWindow(Vec2U fullScreenResolution) override {
if (parent->m_windowMode != WindowMode::Fullscreen || parent->m_windowSize != fullScreenResolution) {
SDL_DisplayMode requestedDisplayMode = {SDL_PIXELFORMAT_RGB888, (int)fullScreenResolution[0], (int)fullScreenResolution[1], 0, 0};
int currentDisplayIndex = SDL_GetWindowDisplayIndex(parent->m_sdlWindow);
SDL_DisplayMode targetDisplayMode;
if (SDL_GetClosestDisplayMode(currentDisplayIndex, &requestedDisplayMode, &targetDisplayMode) != NULL) {
if (SDL_SetWindowDisplayMode(parent->m_sdlWindow, &requestedDisplayMode) == 0) {
if (parent->m_windowMode == WindowMode::Fullscreen)
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
else
parent->m_windowMode = WindowMode::Fullscreen;
SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN);
} else {
Logger::warn("Failed to set resolution %s, %s", (unsigned)requestedDisplayMode.w, (unsigned)requestedDisplayMode.h);
}
} else {
Logger::warn("Unable to set requested display resolution %s, %s", (int)fullScreenResolution[0], (int)fullScreenResolution[1]);
}
SDL_DisplayMode actualDisplayMode;
if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) {
parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h};
// call these manually since no SDL_WindowEvent is triggered when changing between fullscreen resolutions for some reason
parent->m_renderer->setScreenSize(parent->m_windowSize);
parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize);
} else {
Logger::error("Couldn't get window display mode!");
}
}
}
void setNormalWindow(Vec2U windowSize) override {
if (parent->m_windowMode != WindowMode::Normal || parent->m_windowSize != windowSize) {
if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless)
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
else if (parent->m_windowMode == WindowMode::Maximized)
SDL_RestoreWindow(parent->m_sdlWindow);
SDL_SetWindowSize(parent->m_sdlWindow, windowSize[0], windowSize[1]);
SDL_SetWindowPosition(parent->m_sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
parent->m_windowMode = WindowMode::Normal;
parent->m_windowSize = windowSize;
}
}
void setMaximizedWindow() override {
if (parent->m_windowMode != WindowMode::Maximized) {
if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless)
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
SDL_MaximizeWindow(parent->m_sdlWindow);
parent->m_windowMode = WindowMode::Maximized;
}
}
void setBorderlessWindow() override {
if (parent->m_windowMode != WindowMode::Borderless) {
SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
parent->m_windowMode = WindowMode::Borderless;
SDL_DisplayMode actualDisplayMode;
if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) {
parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h};
parent->m_renderer->setScreenSize(parent->m_windowSize);
parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize);
} else {
Logger::error("Couldn't get window display mode!");
}
}
}
void setVSyncEnabled(bool vSync) override {
if (parent->m_windowVSync != vSync) {
parent->setVSyncEnabled(vSync);
parent->m_windowVSync = vSync;
}
}
void setMaxFrameSkip(unsigned maxFrameSkip) override {
parent->m_maxFrameSkip = maxFrameSkip;
}
void setCursorVisible(bool cursorVisible) override {
parent->m_cursorVisible = cursorVisible;
}
void setAcceptingTextInput(bool acceptingTextInput) override {
if (acceptingTextInput != parent->m_acceptingTextInput) {
if (acceptingTextInput)
SDL_StartTextInput();
else
SDL_StopTextInput();
parent->m_acceptingTextInput = acceptingTextInput;
}
}
AudioFormat enableAudio() override {
parent->m_audioEnabled = true;
SDL_PauseAudio(false);
return AudioFormat{44100, 2};
}
void disableAudio() override {
parent->m_audioEnabled = false;
SDL_PauseAudio(true);
}
float updateRate() const override {
return parent->m_updateRate;
}
float renderFps() const override {
return parent->m_renderRate;
}
StatisticsServicePtr statisticsService() const override {
if (parent->m_platformServices)
return parent->m_platformServices->statisticsService();
return {};
}
P2PNetworkingServicePtr p2pNetworkingService() const override {
if (parent->m_platformServices)
return parent->m_platformServices->p2pNetworkingService();
return {};
}
UserGeneratedContentServicePtr userGeneratedContentService() const override {
if (parent->m_platformServices)
return parent->m_platformServices->userGeneratedContentService();
return {};
}
DesktopServicePtr desktopService() const override {
if (parent->m_platformServices)
return parent->m_platformServices->desktopService();
return {};
}
void quit() override {
parent->m_quitRequested = true;
}
SdlPlatform* parent;
};
List<InputEvent> processEvents() {
List<InputEvent> inputEvents;
SDL_Event event;
while (SDL_PollEvent(&event)) {
Maybe<InputEvent> starEvent;
if (event.type == SDL_WINDOWEVENT) {
if (event.window.event == SDL_WINDOWEVENT_MAXIMIZED || event.window.event == SDL_WINDOWEVENT_RESTORED) {
auto windowFlags = SDL_GetWindowFlags(m_sdlWindow);
if (windowFlags & SDL_WINDOW_MAXIMIZED) {
m_windowMode = WindowMode::Maximized;
} else if (windowFlags & SDL_WINDOW_FULLSCREEN || windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
if (m_windowMode != WindowMode::Fullscreen && m_windowMode != WindowMode::Borderless)
m_windowMode = WindowMode::Fullscreen;
} else {
m_windowMode = WindowMode::Normal;
}
m_application->windowChanged(m_windowMode, m_windowSize);
} else if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
m_windowSize = Vec2U(event.window.data1, event.window.data2);
m_renderer->setScreenSize(m_windowSize);
m_application->windowChanged(m_windowMode, m_windowSize);
}
} else if (event.type == SDL_KEYDOWN) {
if (!event.key.repeat) {
if (auto key = keyFromSdlKeyCode(event.key.keysym.sym))
starEvent.set(KeyDownEvent{*key, keyModsFromSdlKeyMods(event.key.keysym.mod)});
}
} else if (event.type == SDL_KEYUP) {
if (auto key = keyFromSdlKeyCode(event.key.keysym.sym))
starEvent.set(KeyUpEvent{*key});
} else if (event.type == SDL_TEXTINPUT) {
starEvent.set(TextInputEvent{String(event.text.text)});
} else if (event.type == SDL_MOUSEMOTION) {
starEvent.set(MouseMoveEvent{
{event.motion.xrel, -event.motion.yrel}, {event.motion.x, (int)m_windowSize[1] - event.motion.y}});
} else if (event.type == SDL_MOUSEBUTTONDOWN) {
starEvent.set(MouseButtonDownEvent{mouseButtonFromSdlMouseButton(event.button.button),
{event.button.x, (int)m_windowSize[1] - event.button.y}});
} else if (event.type == SDL_MOUSEBUTTONUP) {
starEvent.set(MouseButtonUpEvent{mouseButtonFromSdlMouseButton(event.button.button),
{event.button.x, (int)m_windowSize[1] - event.button.y}});
} else if (event.type == SDL_MOUSEWHEEL) {
int x, y;
SDL_GetMouseState(&x, &y);
starEvent.set(MouseWheelEvent{event.wheel.y < 0 ? MouseWheel::Down : MouseWheel::Up, {x, (int)m_windowSize[1] - y}});
} else if (event.type == SDL_QUIT) {
m_quitRequested = true;
starEvent.reset();
}
if (starEvent)
inputEvents.append(starEvent.take());
}
return inputEvents;
}
void getAudioData(Uint8* stream, int len) {
if (m_audioEnabled) {
m_application->getAudioData((int16_t*)stream, len / 4);
} else {
for (int i = 0; i < len; ++i)
stream[i] = 0;
}
}
void setVSyncEnabled(bool vsyncEnabled) {
if (vsyncEnabled) {
// If VSync is requested, try for late swap tearing first, then fall back
// to regular VSync
Logger::info("Application: Enabling VSync with late swap tearing");
if (SDL_GL_SetSwapInterval(-1) < 0) {
Logger::info("Application: Enabling VSync late swap tearing failed, falling back to full VSync");
SDL_GL_SetSwapInterval(1);
}
} else {
Logger::info("Application: Disabling VSync");
SDL_GL_SetSwapInterval(0);
}
}
SignalHandler m_signalHandler;
TickRateApproacher m_updateTicker = TickRateApproacher(60.0f, 1.0f);
float m_updateRate = 0.0f;
TickRateMonitor m_renderTicker = TickRateMonitor(1.0f);
float m_renderRate = 0.0f;
SDL_Window* m_sdlWindow = nullptr;
SDL_GLContext m_sdlGlContext = nullptr;
Vec2U m_windowSize = {800, 600};
WindowMode m_windowMode = WindowMode::Normal;
String m_windowTitle = "";
bool m_windowVSync = true;
unsigned m_maxFrameSkip = 5;
bool m_cursorVisible = true;
bool m_acceptingTextInput = false;
bool m_audioEnabled = false;
bool m_quitRequested = false;
OpenGl20RendererPtr m_renderer;
ApplicationUPtr m_application;
PcPlatformServicesUPtr m_platformServices;
};
int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs) {
try {
{
SdlPlatform platform(move(application), move(cmdLineArgs));
platform.run();
}
Logger::info("Application: stopped gracefully");
return 0;
} catch (std::exception const& e) {
fatalException(e, true);
} catch (...) {
fatalError("Unknown Exception", true);
}
return 1;
}
}

View file

@ -0,0 +1,616 @@
#include "StarP2PNetworkingService_pc.hpp"
#include "StarLexicalCast.hpp"
#include "StarEither.hpp"
#include "StarLogging.hpp"
#include "StarRandom.hpp"
#include "StarEncode.hpp"
#include "StarUuid.hpp"
namespace Star {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
discord::NetworkChannelId const DiscordMainNetworkChannel = 0;
#endif
PcP2PNetworkingService::PcP2PNetworkingService(PcPlatformServicesStatePtr state)
#ifdef STAR_ENABLE_STEAM_INTEGRATION
: m_callbackConnectionFailure(this, &PcP2PNetworkingService::steamOnConnectionFailure),
m_callbackJoinRequested(this, &PcP2PNetworkingService::steamOnJoinRequested),
m_callbackSessionRequest(this, &PcP2PNetworkingService::steamOnSessionRequest),
m_state(move(state)) {
#else
: m_state(move(state)) {
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (m_state->discordAvailable) {
MutexLocker discordLocker(m_state->discordMutex);
m_discordPartySize = {};
m_discordOnActivityJoinToken = m_state->discordCore->ActivityManager().OnActivityJoin.Connect([this](char const* peerId) {
MutexLocker serviceLocker(m_mutex);
Logger::info("Joining discord peer at '%s'", peerId);
addPendingJoin(strf("+platform:%s", peerId));
});
m_discordOnActivityRequestToken = m_state->discordCore->ActivityManager().OnActivityJoinRequest.Connect([this](discord::User const& user) {
MutexLocker serviceLocker(m_mutex);
String userName = String(user.GetUsername());
Logger::info("Received join request from user '%s'", userName);
m_discordJoinRequests.emplace_back(make_pair(user.GetId(), userName));
});
m_discordOnReceiveMessage = m_state->discordCore->LobbyManager().OnNetworkMessage.Connect(bind(&PcP2PNetworkingService::discordOnReceiveMessage, this, _1, _2, _3, _4, _5));
m_discordOnLobbyMemberConnect = m_state->discordCore->LobbyManager().OnMemberConnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberConnect, this, _1, _2));
m_discordOnLobbyMemberUpdate = m_state->discordCore->LobbyManager().OnMemberUpdate.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberUpdate, this, _1, _2));
m_discordOnLobbyMemberDisconnect = m_state->discordCore->LobbyManager().OnMemberDisconnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberDisconnect, this, _1, _2));
}
#endif
}
PcP2PNetworkingService::~PcP2PNetworkingService() {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (m_state->discordAvailable) {
MutexLocker discordLocker(m_state->discordMutex);
if (m_discordServerLobby) {
Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first);
m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) {
Logger::error("Could not connect delete server lobby (err %s)", (int)res);
});
}
m_state->discordCore->ActivityManager().OnActivityJoin.Disconnect(m_discordOnActivityJoinToken);
m_state->discordCore->LobbyManager().OnNetworkMessage.Disconnect(m_discordOnReceiveMessage);
m_state->discordCore->LobbyManager().OnMemberConnect.Disconnect(m_discordOnLobbyMemberConnect);
m_state->discordCore->LobbyManager().OnMemberUpdate.Disconnect(m_discordOnLobbyMemberUpdate);
m_state->discordCore->LobbyManager().OnMemberDisconnect.Disconnect(m_discordOnLobbyMemberDisconnect);
}
#endif
}
void PcP2PNetworkingService::setJoinUnavailable() {
setJoinLocation(JoinUnavailable());
}
void PcP2PNetworkingService::setJoinLocal(uint32_t capacity) {
setJoinLocation(JoinLocal{capacity});
}
void PcP2PNetworkingService::setJoinRemote(HostAddressWithPort location) {
setJoinLocation(JoinRemote(location));
}
void Star::PcP2PNetworkingService::setActivityData(String const& title, Maybe<pair<uint16_t, uint16_t>> party) {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(m_state->discordMutex);
#endif
MutexLocker serviceLocker(m_mutex);
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (m_state->discordAvailable && m_state->discordCurrentUser) {
if (m_discordUpdatingActivity)
return;
if (title != m_discordActivityTitle || party != m_discordPartySize || m_discordForceUpdateActivity) {
m_discordForceUpdateActivity = false;
m_discordPartySize = party;
m_discordActivityTitle = title;
discord::Activity activity = {};
activity.SetType(discord::ActivityType::Playing);
activity.SetName("Starbound");
activity.SetState(title.utf8Ptr());
if (auto p = party) {
activity.GetParty().GetSize().SetCurrentSize(p->first);
activity.GetParty().GetSize().SetMaxSize(p->second);
}
if (auto lobby = m_discordServerLobby)
activity.GetParty().SetId(strf("%s", lobby->first).c_str());
if (m_joinLocation.is<JoinLocal>()) {
if (auto lobby = m_discordServerLobby) {
String joinSecret = strf("connect:discord_%s_%s_%s", m_state->discordCurrentUser->GetId(), lobby->first, lobby->second);
Logger::info("Setting discord join secret as %s", joinSecret);
activity.GetSecrets().SetJoin(joinSecret.utf8Ptr());
}
} else if (m_joinLocation.is<JoinRemote>()) {
String address = strf("%s", (HostAddressWithPort)m_joinLocation.get<JoinRemote>());
String joinSecret = strf("connect:address_%s", address);
Logger::info("Setting discord join secret as %s", joinSecret);
activity.GetSecrets().SetJoin(joinSecret.utf8Ptr());
activity.GetParty().SetId(address.utf8Ptr());
}
m_discordUpdatingActivity = true;
m_state->discordCore->ActivityManager().UpdateActivity(activity, [this](discord::Result res) {
if (res != discord::Result::Ok)
Logger::error("failed to set discord activity (err %s)", (int)res);
MutexLocker serviceLocker(m_mutex);
m_discordUpdatingActivity = false;
});
}
}
#endif
}
MVariant<P2PNetworkingPeerId, HostAddressWithPort> PcP2PNetworkingService::pullPendingJoin() {
MutexLocker serviceLocker(m_mutex);
return take(m_pendingJoin);
}
Maybe<pair<String, RpcPromiseKeeper<P2PJoinRequestReply>>> Star::PcP2PNetworkingService::pullJoinRequest() {
MutexLocker serviceLocker(m_mutex);
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (auto request = m_discordJoinRequests.maybeTakeLast()) {
auto promisePair = RpcPromise<P2PJoinRequestReply>::createPair();
m_pendingDiscordJoinRequests.push_back(make_pair(request->first, promisePair.first));
return make_pair(request->second, promisePair.second);
}
#endif
return {};
}
void PcP2PNetworkingService::setAcceptingP2PConnections(bool acceptingP2PConnections) {
MutexLocker serviceLocker(m_mutex);
m_acceptingP2PConnections = acceptingP2PConnections;
if (!m_acceptingP2PConnections)
m_pendingIncomingConnections.clear();
}
List<P2PSocketUPtr> PcP2PNetworkingService::acceptP2PConnections() {
MutexLocker serviceLocker(m_mutex);
return take(m_pendingIncomingConnections);
}
void Star::PcP2PNetworkingService::update() {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(m_state->discordMutex);
#endif
MutexLocker serviceLocker(m_mutex);
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
for (auto& p : m_pendingDiscordJoinRequests) {
if (auto res = p.second.result()) {
discord::ActivityJoinRequestReply reply;
switch (*res) {
case P2PJoinRequestReply::Yes:
reply = discord::ActivityJoinRequestReply::Yes;
break;
case P2PJoinRequestReply::No:
reply = discord::ActivityJoinRequestReply::No;
break;
case P2PJoinRequestReply::Ignore:
reply = discord::ActivityJoinRequestReply::Ignore;
break;
}
m_state->discordCore->ActivityManager().SendRequestReply(p.first, reply, [](discord::Result res) {
if (res != discord::Result::Ok)
Logger::error("Could not send discord activity join response (err %s)", (int)res);
});
}
}
m_pendingDiscordJoinRequests = m_pendingDiscordJoinRequests.filtered([this](pair<discord::UserId, RpcPromise<P2PJoinRequestReply>>& p) {
return !p.second.finished();
});
#endif
}
Either<String, P2PSocketUPtr> PcP2PNetworkingService::connectToPeer(P2PNetworkingPeerId peerId) {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(m_state->discordMutex);
#endif
MutexLocker serviceLocker(m_mutex);
String type = peerId.extract("_");
#ifdef STAR_ENABLE_STEAM_INTEGRATION
if (m_state->steamAvailable) {
if (type == "steamid") {
CSteamID steamId(lexicalCast<uint64>(peerId));
return makeRight(createSteamP2PSocket(steamId));
}
}
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (m_state->discordAvailable) {
if (type == "discord") {
auto remoteUserId = lexicalCast<discord::UserId>(peerId.extract("_"));
auto lobbyId = lexicalCast<discord::LobbyId>(peerId.extract("_"));
String lobbySecret = move(peerId);
return makeRight(discordConnectRemote(remoteUserId, lobbyId, lobbySecret));
}
}
#endif
return makeLeft(strf("Unsupported peer type '%s'", type));
}
void PcP2PNetworkingService::addPendingJoin(String connectionString) {
MutexLocker serviceLocker(m_mutex);
if (connectionString.extract(":") != "+platform")
throw ApplicationException::format("malformed connection string '%s'", connectionString);
if (connectionString.extract(":") != "connect")
throw ApplicationException::format("malformed connection string '%s'", connectionString);
String target = move(connectionString);
String targetType = target.extract("_");
if (targetType == "address")
m_pendingJoin = HostAddressWithPort(target);
else
m_pendingJoin = P2PNetworkingPeerId(strf("%s_%s", targetType, target));
}
#ifdef STAR_ENABLE_STEAM_INTEGRATION
PcP2PNetworkingService::SteamP2PSocket::~SteamP2PSocket() {
MutexLocker serviceLocker(parent->m_mutex);
MutexLocker socketLocker(mutex);
parent->steamCloseSocket(this);
}
bool PcP2PNetworkingService::SteamP2PSocket::isOpen() {
MutexLocker socketLocker(mutex);
return connected;
}
bool PcP2PNetworkingService::SteamP2PSocket::sendMessage(ByteArray const& message) {
MutexLocker socketLocker(mutex);
if (!connected)
return false;
if (!SteamNetworking()->SendP2PPacket(steamId, message.ptr(), message.size(), k_EP2PSendReliable))
throw ApplicationException("SteamNetworking::SendP2PPacket unexpectedly returned false");
return true;
}
Maybe<ByteArray> PcP2PNetworkingService::SteamP2PSocket::receiveMessage() {
MutexLocker socketLocker(mutex);
if (!incoming.empty())
return incoming.takeFirst();
if (connected) {
socketLocker.unlock();
{
MutexLocker serviceLocker(parent->m_mutex);
parent->steamReceiveAll();
}
socketLocker.lock();
if (!incoming.empty())
return incoming.takeFirst();
}
return {};
}
auto PcP2PNetworkingService::createSteamP2PSocket(CSteamID steamId) -> unique_ptr<SteamP2PSocket> {
if (auto oldSocket = m_steamOpenSockets.value(steamId.ConvertToUint64())) {
MutexLocker socketLocker(oldSocket->mutex);
steamCloseSocket(oldSocket);
}
unique_ptr<SteamP2PSocket> socket(new SteamP2PSocket);
socket->parent = this;
socket->steamId = steamId;
socket->connected = true;
m_steamOpenSockets[steamId.ConvertToUint64()] = socket.get();
return socket;
}
void PcP2PNetworkingService::steamOnConnectionFailure(P2PSessionConnectFail_t* callback) {
MutexLocker serviceLocker(m_mutex);
Logger::warn("Connection with steam user %s failed", callback->m_steamIDRemote.ConvertToUint64());
if (auto socket = m_steamOpenSockets.value(callback->m_steamIDRemote.ConvertToUint64())) {
MutexLocker socketLocker(socket->mutex);
steamCloseSocket(socket);
}
}
void PcP2PNetworkingService::steamOnJoinRequested(GameRichPresenceJoinRequested_t* callback) {
Logger::info("Queueing join request with steam friend id %s to address %s", callback->m_steamIDFriend.ConvertToUint64(), callback->m_rgchConnect);
addPendingJoin(callback->m_rgchConnect);
}
void PcP2PNetworkingService::steamOnSessionRequest(P2PSessionRequest_t* callback) {
MutexLocker serviceLocker(m_mutex);
// Not sure whether this HasFriend call is actually necessary, or whether
// non-friends can even initiate P2P sessions.
if (m_acceptingP2PConnections && SteamFriends()->HasFriend(callback->m_steamIDRemote, k_EFriendFlagImmediate)) {
if (SteamNetworking()->AcceptP2PSessionWithUser(callback->m_steamIDRemote)) {
Logger::info("Accepted steam p2p connection with user %s", callback->m_steamIDRemote.ConvertToUint64());
m_pendingIncomingConnections.append(createSteamP2PSocket(callback->m_steamIDRemote));
} else {
Logger::error("Accepting steam p2p connection from user %s failed!", callback->m_steamIDRemote.ConvertToUint64());
}
} else {
Logger::error("Ignoring steam p2p connection from user %s", callback->m_steamIDRemote.ConvertToUint64());
}
}
void PcP2PNetworkingService::steamCloseSocket(SteamP2PSocket* socket) {
if (socket->connected) {
Logger::info("Closing p2p connection with steam user %s", socket->steamId.ConvertToUint64());
m_steamOpenSockets.remove(socket->steamId.ConvertToUint64());
socket->connected = false;
}
SteamNetworking()->CloseP2PSessionWithUser(socket->steamId);
}
void PcP2PNetworkingService::steamReceiveAll() {
uint32_t messageSize;
CSteamID messageRemoteUser;
while (SteamNetworking()->IsP2PPacketAvailable(&messageSize)) {
ByteArray data(messageSize, 0);
SteamNetworking()->ReadP2PPacket(data.ptr(), messageSize, &messageSize, &messageRemoteUser);
if (auto openSocket = m_steamOpenSockets.value(messageRemoteUser.ConvertToUint64())) {
MutexLocker socketLocker(openSocket->mutex);
openSocket->incoming.append(move(data));
}
}
}
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
PcP2PNetworkingService::DiscordP2PSocket::~DiscordP2PSocket() {
MutexLocker discordLocker(parent->m_state->discordMutex);
MutexLocker serviceLocker(parent->m_mutex);
MutexLocker socketLocker(mutex);
parent->discordCloseSocket(this);
}
bool PcP2PNetworkingService::DiscordP2PSocket::isOpen() {
MutexLocker socketLocker(mutex);
return mode != DiscordSocketMode::Disconnected;
}
bool PcP2PNetworkingService::DiscordP2PSocket::sendMessage(ByteArray const& message) {
MutexLocker discordLocker(parent->m_state->discordMutex);
MutexLocker socketLocker(mutex);
if (mode != DiscordSocketMode::Connected)
return false;
discord::Result res = parent->m_state->discordCore->LobbyManager().SendNetworkMessage(lobbyId, remoteUserId, DiscordMainNetworkChannel, (uint8_t*)message.ptr(), message.size());
if (res != discord::Result::Ok)
throw ApplicationException::format("discord::Network::Send returned error (err %s)", (int)res);
return true;
}
Maybe<ByteArray> PcP2PNetworkingService::DiscordP2PSocket::receiveMessage() {
MutexLocker socketLocker(mutex);
if (!incoming.empty())
return incoming.takeFirst();
else
return {};
}
void PcP2PNetworkingService::discordCloseSocket(DiscordP2PSocket* socket) {
if (socket->mode != DiscordSocketMode::Disconnected) {
m_discordOpenSockets.remove(socket->remoteUserId);
if (socket->mode == DiscordSocketMode::Connected) {
if (!m_joinLocation.is<JoinLocal>() && m_discordOpenSockets.empty()) {
auto res = m_state->discordCore->LobbyManager().DisconnectNetwork(socket->lobbyId);
if (res != discord::Result::Ok)
Logger::error("failed to leave network for lobby %s (err %s)", socket->lobbyId, (int)res);
m_state->discordCore->LobbyManager().DisconnectLobby(socket->lobbyId, [this, lobbyId = socket->lobbyId](discord::Result res) {
if (res != discord::Result::Ok)
Logger::error("failed to leave discord lobby %s", lobbyId);
Logger::info("Left discord lobby %s", lobbyId);
MutexLocker serviceLocker(m_mutex);
m_discordServerLobby = {};
m_discordForceUpdateActivity = true;
});
}
}
socket->mode = DiscordSocketMode::Disconnected;
}
}
P2PSocketUPtr PcP2PNetworkingService::discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret) {
if (auto oldSocket = m_discordOpenSockets.value(remoteUserId)) {
MutexLocker socketLocker(oldSocket->mutex);
discordCloseSocket(oldSocket);
}
unique_ptr<DiscordP2PSocket> socket(new DiscordP2PSocket);
socket->parent = this;
socket->mode = DiscordSocketMode::Startup;
socket->remoteUserId = remoteUserId;
socket->lobbyId = lobbyId;
m_discordOpenSockets[remoteUserId] = socket.get();
Logger::info("Connect to discord lobby %s", lobbyId);
m_state->discordCore->LobbyManager().ConnectLobby(lobbyId, lobbySecret.utf8Ptr(), [this, remoteUserId, lobbyId](discord::Result res, discord::Lobby const& lobby) {
MutexLocker serviceLocker(m_mutex);
if (res == discord::Result::Ok) {
if (auto socket = m_discordOpenSockets.value(remoteUserId)) {
MutexLocker socketLocker(socket->mutex);
res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId);
if (res != discord::Result::Ok) {
discordCloseSocket(socket);
return Logger::error("Could not connect to discord lobby network (err %s)", (int)res);
}
res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true);
if (res != discord::Result::Ok) {
discordCloseSocket(socket);
return Logger::error("Could not open discord main network channel (err %s)", (int)res);
}
socket->mode = DiscordSocketMode::Connected;
Logger::info("Discord p2p connection opened to remote user %s via lobby %s", remoteUserId, lobbyId);
m_discordServerLobby = make_pair(lobbyId, String());
m_discordForceUpdateActivity = true;
} else {
Logger::error("discord::Lobbies::Connect callback no matching remoteUserId %s found", remoteUserId);
}
} else {
Logger::error("failed to connect to remote lobby (err %s)", (int)res);
if (auto socket = m_discordOpenSockets.value(remoteUserId)) {
MutexLocker socketLocker(socket->mutex);
discordCloseSocket(socket);
}
}
});
return unique_ptr<P2PSocket>(move(socket));
}
void PcP2PNetworkingService::discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size) {
MutexLocker serviceLocker(m_mutex);
if (lobbyId != m_discordServerLobby->first)
return Logger::error("Received message from unexpected lobby %s", lobbyId);
if (auto socket = m_discordOpenSockets.value(userId)) {
if (channel == DiscordMainNetworkChannel) {
MutexLocker socketLocker(socket->mutex);
socket->incoming.append(ByteArray((char const*)data, size));
} else {
Logger::error("Received discord message on unexpected channel %s, ignoring", channel);
}
} else {
Logger::error("Could not find associated discord socket for user id %s", userId);
}
}
void PcP2PNetworkingService::discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId) {
MutexLocker serviceLocker(m_mutex);
if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) {
if (!m_discordOpenSockets.contains(userId)) {
unique_ptr<DiscordP2PSocket> socket(new DiscordP2PSocket);
socket->parent = this;
socket->lobbyId = lobbyId;
socket->remoteUserId = userId;
socket->mode = DiscordSocketMode::Connected;
m_discordOpenSockets[userId] = socket.get();
m_pendingIncomingConnections.append(move(socket));
Logger::info("Accepted new discord connection from remote user %s", userId);
}
}
}
void PcP2PNetworkingService::discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId) {
discordOnLobbyMemberConnect(lobbyId, userId);
}
void PcP2PNetworkingService::discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId) {
MutexLocker serviceLocker(m_mutex);
if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) {
if (auto socket = m_discordOpenSockets.value(userId)) {
MutexLocker socketLocker(socket->mutex);
discordCloseSocket(socket);
}
}
}
#endif
void PcP2PNetworkingService::setJoinLocation(JoinLocation location) {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(m_state->discordMutex);
#endif
MutexLocker serviceLocker(m_mutex);
if (location == m_joinLocation)
return;
m_joinLocation = location;
#ifdef STAR_ENABLE_STEAM_INTEGRATION
if (m_state->steamAvailable) {
if (m_joinLocation.is<JoinUnavailable>()) {
Logger::info("Clearing steam rich presence connection");
SteamFriends()->SetRichPresence("connect", "");
} else if (m_joinLocation.is<JoinLocal>()) {
auto steamId = SteamUser()->GetSteamID().ConvertToUint64();
Logger::info("Setting steam rich presence connection as steamid_%s", steamId);
SteamFriends()->SetRichPresence("connect", strf("+platform:connect:steamid_%s", steamId).c_str());
} else if (m_joinLocation.is<JoinRemote>()) {
auto address = (HostAddressWithPort)location.get<JoinRemote>();
Logger::info("Setting steam rich presence connection as address_%s", address);
SteamFriends()->SetRichPresence("connect", strf("+platform:connect:address_%s", address).c_str());
}
}
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (m_state->discordAvailable && m_state->discordCurrentUser) {
if (m_discordServerLobby) {
Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first);
m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) {
Logger::error("Could not connect delete server lobby (err %s)", (int)res);
});
}
if (auto joinLocal = m_joinLocation.maybe<JoinLocal>()) {
discord::LobbyTransaction createLobby{};
if (m_state->discordCore->LobbyManager().GetLobbyCreateTransaction(&createLobby) != discord::Result::Ok)
throw ApplicationException::format("discord::Lobbies::CreateLobbyTransaction failed");
createLobby.SetCapacity(joinLocal->capacity);
createLobby.SetType(discord::LobbyType::Private);
m_state->discordCore->LobbyManager().CreateLobby(createLobby, [this](discord::Result res, discord::Lobby const& lobby) {
if (res == discord::Result::Ok) {
MutexLocker serviceLocker(m_mutex);
discord::LobbyId lobbyId = lobby.GetId();
res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId);
if (res == discord::Result::Ok) {
res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true);
if (res == discord::Result::Ok) {
m_discordServerLobby = make_pair(lobbyId, String(lobby.GetSecret()));
m_discordForceUpdateActivity = true;
// successfully joined lobby network
return;
} else {
Logger::error("Failed to open discord main network channel (err %s)", (int)res);
}
} else {
Logger::error("Failed to join discord lobby network (err %s)", (int)res);
}
// Created lobby but failed to join the lobby network, delete lobby
Logger::error("Failed to join discord lobby network (err %s)", (int)res);
Logger::info("Deleting discord lobby %s", lobbyId);
m_state->discordCore->LobbyManager().DeleteLobby(lobbyId, [lobbyId](discord::Result res) {
Logger::error("failed to delete lobby %s (err %s)", lobbyId, (int)res);
});
} else {
Logger::error("failed to create discord lobby (err %s)", (int)res);
}
});
}
}
#endif
}
}

View file

@ -0,0 +1,146 @@
#ifndef STAR_P2P_NETWORKING_SERVICE_PC_HPP
#define STAR_P2P_NETWORKING_SERVICE_PC_HPP
#include "StarPlatformServices_pc.hpp"
#include "StarAlgorithm.hpp"
#include "StarThread.hpp"
#include "StarStrongTypedef.hpp"
#include "StarRpcPromise.hpp"
namespace Star {
STAR_CLASS(PcP2PNetworkingService);
class PcP2PNetworkingService : public P2PNetworkingService {
public:
PcP2PNetworkingService(PcPlatformServicesStatePtr state);
~PcP2PNetworkingService();
void setJoinUnavailable() override;
void setJoinLocal(uint32_t capacity) override;
void setJoinRemote(HostAddressWithPort location) override;
void setActivityData(String const& title, Maybe<pair<uint16_t, uint16_t>>) override;
MVariant<P2PNetworkingPeerId, HostAddressWithPort> pullPendingJoin() override;
Maybe<pair<String, RpcPromiseKeeper<P2PJoinRequestReply>>> pullJoinRequest() override;
void setAcceptingP2PConnections(bool acceptingP2PConnections) override;
List<P2PSocketUPtr> acceptP2PConnections() override;
void update() override;
Either<String, P2PSocketUPtr> connectToPeer(P2PNetworkingPeerId peerId) override;
void addPendingJoin(String connectionString);
private:
strong_typedef(Empty, JoinUnavailable);
struct JoinLocal {
bool operator==(JoinLocal const& rhs) const { return capacity == rhs.capacity; };
uint32_t capacity;
};
strong_typedef(HostAddressWithPort, JoinRemote);
typedef Variant<JoinUnavailable, JoinLocal, JoinRemote> JoinLocation;
#ifdef STAR_ENABLE_STEAM_INTEGRATION
struct SteamP2PSocket : P2PSocket {
SteamP2PSocket() = default;
~SteamP2PSocket();
bool isOpen() override;
bool sendMessage(ByteArray const& message) override;
Maybe<ByteArray> receiveMessage() override;
Mutex mutex;
PcP2PNetworkingService* parent = nullptr;
CSteamID steamId = CSteamID();
Deque<ByteArray> incoming;
bool connected = false;
};
unique_ptr<SteamP2PSocket> createSteamP2PSocket(CSteamID steamId);
STEAM_CALLBACK(PcP2PNetworkingService, steamOnConnectionFailure, P2PSessionConnectFail_t, m_callbackConnectionFailure);
STEAM_CALLBACK(PcP2PNetworkingService, steamOnJoinRequested, GameRichPresenceJoinRequested_t, m_callbackJoinRequested);
STEAM_CALLBACK(PcP2PNetworkingService, steamOnSessionRequest, P2PSessionRequest_t, m_callbackSessionRequest);
void steamCloseSocket(SteamP2PSocket* socket);
void steamReceiveAll();
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
enum class DiscordSocketMode {
Startup,
Connected,
Disconnected
};
struct DiscordP2PSocket : P2PSocket {
DiscordP2PSocket() = default;
~DiscordP2PSocket();
bool isOpen() override;
bool sendMessage(ByteArray const& message) override;
Maybe<ByteArray> receiveMessage() override;
Mutex mutex;
PcP2PNetworkingService* parent = nullptr;
DiscordSocketMode mode = DiscordSocketMode::Disconnected;
discord::LobbyId lobbyId = {};
discord::UserId remoteUserId;
Deque<ByteArray> incoming;
};
P2PSocketUPtr discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret);
void discordCloseSocket(DiscordP2PSocket* socket);
void discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size);
void discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId);
void discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId);
void discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId);
#endif
void setJoinLocation(JoinLocation joinLocation);
PcPlatformServicesStatePtr m_state;
Mutex m_mutex;
JoinLocation m_joinLocation;
bool m_acceptingP2PConnections = false;
List<P2PSocketUPtr> m_pendingIncomingConnections;
MVariant<P2PNetworkingPeerId, HostAddressWithPort> m_pendingJoin;
#ifdef STAR_ENABLE_STEAM_INTEGRATION
HashMap<uint64, SteamP2PSocket*> m_steamOpenSockets;
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
List<pair<discord::UserId, String>> m_discordJoinRequests;
List<pair<discord::UserId, RpcPromise<P2PJoinRequestReply>>> m_pendingDiscordJoinRequests;
HashMap<discord::UserId, DiscordP2PSocket*> m_discordOpenSockets;
String m_discordActivityTitle;
Maybe<pair<uint16_t, uint16_t>> m_discordPartySize;
bool m_discordForceUpdateActivity = false;
bool m_discordUpdatingActivity = false;
Maybe<pair<discord::LobbyId, String>> m_discordServerLobby = {};
int m_discordOnActivityJoinToken = 0;
int m_discordOnActivityRequestToken = 0;
int m_discordOnReceiveMessage = 0;
int m_discordOnLobbyMemberConnect = 0;
int m_discordOnLobbyMemberUpdate = 0;
int m_discordOnLobbyMemberDisconnect = 0;
#endif
};
}
#endif

View file

@ -0,0 +1,175 @@
#include "StarLogging.hpp"
#include "StarPlatformServices_pc.hpp"
#include "StarP2PNetworkingService_pc.hpp"
#ifdef STAR_ENABLE_STEAM_INTEGRATION
#include "StarStatisticsService_pc_steam.hpp"
#include "StarUserGeneratedContentService_pc_steam.hpp"
#include "StarDesktopService_pc_steam.hpp"
#endif
namespace Star {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
uint64_t const DiscordClientId = 467102538278109224;
#endif
PcPlatformServicesState::PcPlatformServicesState()
#ifdef STAR_ENABLE_STEAM_INTEGRATION
: callbackGameOverlayActivated(this, &PcPlatformServicesState::onGameOverlayActivated) {
#else
{
#endif
#ifdef STAR_ENABLE_STEAM_INTEGRATION
if (SteamAPI_Init()) {
steamAvailable = true;
Logger::info("Initialized Steam platform services");
} else {
Logger::info("Failed to initialize Steam platform services");
}
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
static int64_t const DiscordEventSleep = 3;
discord::Core* discordCorePtr = nullptr;
discord::Result res = discord::Core::Create(DiscordClientId, DiscordCreateFlags_NoRequireDiscord, &discordCorePtr);
if (res == discord::Result::Ok && discordCorePtr) {
discordCore.reset(discordCorePtr);
discordAvailable = true;
discordCore->UserManager().OnCurrentUserUpdate.Connect([this](){
discord::User user;
auto res = discordCore->UserManager().GetCurrentUser(&user);
if (res != discord::Result::Ok)
Logger::error("Could not get current discord user. (err %s)", (int)res);
else
discordCurrentUser = user;
});
} else {
Logger::error("Failed to instantiate discord core (err %s)", (int)res);
}
if (discordAvailable) {
MutexLocker locker(discordMutex);
discordCore->SetLogHook(discord::LogLevel::Info, [](discord::LogLevel level, char const* msg) {
if (level == discord::LogLevel::Debug)
Logger::debug("[DISCORD]: %s", msg);
else if (level == discord::LogLevel::Error)
Logger::debug("[DISCORD]: %s", msg);
else if (level == discord::LogLevel::Info)
Logger::info("[DISCORD]: %s", msg);
else if (level == discord::LogLevel::Warn)
Logger::warn("[DISCORD]: %s", msg);
});
discordEventShutdown = false;
discordEventThread = Thread::invoke("PcPlatformServices::discordEventThread", [this]() {
while (!discordEventShutdown) {
{
MutexLocker locker(discordMutex);
discordCore->RunCallbacks();
discordCore->LobbyManager().FlushNetwork();
}
Thread::sleep(DiscordEventSleep);
}
});
Logger::info("Initialized Discord platform services");
} else {
Logger::info("Was not able to authenticate with Discord and create all components, Discord services will be unavailable");
}
#endif
}
PcPlatformServicesState::~PcPlatformServicesState() {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
if (discordAvailable) {
discordEventShutdown = true;
discordEventThread.finish();
}
#endif
}
#ifdef STAR_ENABLE_STEAM_INTEGRATION
void PcPlatformServicesState::onGameOverlayActivated(GameOverlayActivated_t* callback) {
overlayActive = callback->m_bActive;
}
#endif
PcPlatformServicesUPtr PcPlatformServices::create(String const& path, StringList platformArguments) {
auto services = unique_ptr<PcPlatformServices>(new PcPlatformServices);
services->m_state = make_shared<PcPlatformServicesState>();
bool provideP2PNetworking = false;
#ifdef STAR_ENABLE_STEAM_INTEGRATION
provideP2PNetworking |= services->m_state->steamAvailable;
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
provideP2PNetworking |= services->m_state->discordAvailable;
#endif
if (provideP2PNetworking) {
auto p2pNetworkingService = make_shared<PcP2PNetworkingService>(services->m_state);
for (auto& argument : platformArguments) {
if (argument.beginsWith("+platform:connect:")) {
Logger::info("PC platform services joining from command line argument '%s'", argument);
p2pNetworkingService->addPendingJoin(move(argument));
} else {
throw ApplicationException::format("Unrecognized PC platform services command line argument '%s'", argument);
}
}
services->m_p2pNetworkingService = p2pNetworkingService;
}
#ifdef STAR_ENABLE_STEAM_INTEGRATION
if (services->m_state->steamAvailable) {
services->m_statisticsService = make_shared<SteamStatisticsService>(services->m_state);
services->m_userGeneratedContentService = make_shared<SteamUserGeneratedContentService>(services->m_state);
services->m_desktopService = make_shared<SteamDesktopService>(services->m_state);
}
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(services->m_state->discordMutex);
if (services->m_state->discordAvailable) {
Logger::debug("Registering starbound to discord at path: %s", path);
services->m_state->discordCore->ActivityManager().RegisterCommand(path.utf8Ptr());
}
#endif
return services;
}
StatisticsServicePtr PcPlatformServices::statisticsService() const {
return m_statisticsService;
}
P2PNetworkingServicePtr PcPlatformServices::p2pNetworkingService() const {
return m_p2pNetworkingService;
}
UserGeneratedContentServicePtr PcPlatformServices::userGeneratedContentService() const {
return m_userGeneratedContentService;
}
DesktopServicePtr PcPlatformServices::desktopService() const {
return m_desktopService;
}
bool PcPlatformServices::overlayActive() const {
return m_state->overlayActive;
}
void PcPlatformServices::update() {
#ifdef STAR_ENABLE_STEAM_INTEGRATION
SteamAPI_RunCallbacks();
#endif
}
}

View file

@ -0,0 +1,83 @@
#ifndef STAR_PLATFORM_SERVICES_PC_HPP
#define STAR_PLATFORM_SERVICES_PC_HPP
#include "StarThread.hpp"
#include "StarApplication.hpp"
#include "StarStatisticsService.hpp"
#include "StarP2PNetworkingService.hpp"
#include "StarUserGeneratedContentService.hpp"
#include "StarDesktopService.hpp"
#ifdef STAR_ENABLE_STEAM_INTEGRATION
#include "steam/steam_api.h"
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
#include "discord/discord.h"
#endif
namespace Star {
STAR_CLASS(PcPlatformServices);
STAR_STRUCT(PcPlatformServicesState);
struct PcPlatformServicesState {
PcPlatformServicesState();
~PcPlatformServicesState();
#ifdef STAR_ENABLE_STEAM_INTEGRATION
STEAM_CALLBACK(PcPlatformServicesState, onGameOverlayActivated, GameOverlayActivated_t, callbackGameOverlayActivated);
bool steamAvailable = false;
#endif
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
bool discordAvailable = false;
// Must lock discordMutex before accessing any of the managers when not inside
// a discord callback.
Mutex discordMutex;
unique_ptr<discord::Core> discordCore;
Maybe<discord::User> discordCurrentUser;
ThreadFunction<void> discordEventThread;
atomic<bool> discordEventShutdown;
#endif
bool overlayActive = false;
};
class PcPlatformServices {
public:
// Any command line arguments that start with '+platform' will be stripped
// out and passed here
static PcPlatformServicesUPtr create(String const& path, StringList platformArguments);
StatisticsServicePtr statisticsService() const;
P2PNetworkingServicePtr p2pNetworkingService() const;
UserGeneratedContentServicePtr userGeneratedContentService() const;
DesktopServicePtr desktopService() const;
// Will return true if there is an in-game overlay active. This is important
// because the cursor must be visible when such an overlay is active,
// regardless of the ApplicationController setting.
bool overlayActive() const;
void update();
private:
PcPlatformServices() = default;
PcPlatformServicesStatePtr m_state;
StatisticsServicePtr m_statisticsService;
P2PNetworkingServicePtr m_p2pNetworkingService;
UserGeneratedContentServicePtr m_userGeneratedContentService;
DesktopServicePtr m_desktopService;
};
}
#endif

View file

@ -0,0 +1,60 @@
#include "StarRenderer.hpp"
namespace Star {
EnumMap<TextureAddressing> const TextureAddressingNames{
{TextureAddressing::Clamp, "Clamp"},
{TextureAddressing::Wrap, "Wrap"}
};
EnumMap<TextureFiltering> const TextureFilteringNames{
{TextureFiltering::Nearest, "Nearest"},
{TextureFiltering::Linear, "Linear"}
};
RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minPosition, float textureScale, Vec4B color, float param1) {
if (!texture)
throw RendererException("renderTexturedRect called with null texture");
auto textureSize = Vec2F(texture->size());
return {
move(texture),
RenderVertex{minPosition, Vec2F(0, 0), color, param1},
RenderVertex{minPosition + Vec2F(textureSize[0], 0) * textureScale, Vec2F(textureSize[0], 0), color, param1},
RenderVertex{minPosition + Vec2F(textureSize[0], textureSize[1]) * textureScale, Vec2F(textureSize[0], textureSize[1]), color, param1},
RenderVertex{minPosition + Vec2F(0, textureSize[1]) * textureScale, Vec2F(0, textureSize[1]), color, param1}
};
}
RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color, float param1) {
if (!texture)
throw RendererException("renderTexturedRect called with null texture");
auto textureSize = Vec2F(texture->size());
return {
move(texture),
RenderVertex{{screenCoords.xMin(), screenCoords.yMin()}, Vec2F(0, 0), color, param1},
RenderVertex{{screenCoords.xMax(), screenCoords.yMin()}, Vec2F(textureSize[0], 0), color, param1},
RenderVertex{{screenCoords.xMax(), screenCoords.yMax()}, Vec2F(textureSize[0], textureSize[1]), color, param1},
RenderVertex{{screenCoords.xMin(), screenCoords.yMax()}, Vec2F(0, textureSize[1]), color, param1}
};
}
RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1) {
return {
{},
RenderVertex{{rect.xMin(), rect.yMin()}, {}, color, param1},
RenderVertex{{rect.xMax(), rect.yMin()}, {}, color, param1},
RenderVertex{{rect.xMax(), rect.yMax()}, {}, color, param1},
RenderVertex{{rect.xMin(), rect.yMax()}, {}, color, param1}
};
}
RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1) {
RenderPoly renderPoly;
for (auto const& v : poly)
renderPoly.vertexes.append({v, {}, color, param1});
return renderPoly;
}
}

View file

@ -0,0 +1,147 @@
#ifndef STAR_RENDERER_HPP
#define STAR_RENDERER_HPP
#include "StarVariant.hpp"
#include "StarImage.hpp"
#include "StarPoly.hpp"
#include "StarJson.hpp"
#include "StarBiMap.hpp"
#include "StarRefPtr.hpp"
namespace Star {
STAR_EXCEPTION(RendererException, StarException);
class Texture;
typedef RefPtr<Texture> TexturePtr;
STAR_CLASS(TextureGroup);
STAR_CLASS(RenderBuffer);
STAR_CLASS(Renderer);
enum class TextureAddressing {
Clamp,
Wrap
};
extern EnumMap<TextureAddressing> const TextureAddressingNames;
enum class TextureFiltering {
Nearest,
Linear
};
extern EnumMap<TextureFiltering> const TextureFilteringNames;
// Medium is the maximum guaranteed texture group size
// Where a Medium sized texture group is expected to fill a single page Large can be used,
// but is not guaranteed to be supported by all systems.
// Where Large sized textures are not supported, a Medium one is used
enum class TextureGroupSize {
Small,
Medium,
Large
};
// Both screen coordinates and texture coordinates are in pixels from the
// bottom left to top right.
struct RenderVertex {
Vec2F screenCoordinate;
Vec2F textureCoordinate;
Vec4B color;
float param1;
};
struct RenderTriangle {
TexturePtr texture;
RenderVertex a, b, c;
};
struct RenderQuad {
TexturePtr texture;
RenderVertex a, b, c, d;
};
struct RenderPoly {
TexturePtr texture;
List<RenderVertex> vertexes;
};
RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minScreen, float textureScale = 1.0f, Vec4B color = Vec4B::filled(255), float param1 = 0.0f);
RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color = Vec4B::filled(255), float param1 = 0.0f);
RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1 = 0.0f);
RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1 = 0.0f);
typedef Variant<RenderTriangle, RenderQuad, RenderPoly> RenderPrimitive;
class Texture : public RefCounter {
public:
virtual ~Texture() = default;
virtual Vec2U size() const = 0;
virtual TextureFiltering filtering() const = 0;
virtual TextureAddressing addressing() const = 0;
};
// Textures may be created individually, or in a texture group. Textures in
// a texture group will be faster to render when rendered together, and will
// use less texture memory when many small textures are in a common group.
// Texture groups must all have the same texture parameters, and will always
// use clamped texture addressing.
class TextureGroup {
public:
virtual ~TextureGroup() = default;
virtual TextureFiltering filtering() const = 0;
virtual TexturePtr create(Image const& texture) = 0;
};
class RenderBuffer {
public:
virtual ~RenderBuffer() = default;
// Transforms the given primitives into a form suitable for the underlying
// graphics system and stores it for fast replaying.
virtual void set(List<RenderPrimitive> primitives) = 0;
};
typedef Variant<bool, int, float, Vec2F, Vec3F, Vec4F> RenderEffectParameter;
class Renderer {
public:
virtual ~Renderer() = default;
virtual String rendererId() const = 0;
virtual Vec2U screenSize() const = 0;
// The actual shaders used by this renderer will be in a default no effects
// state when constructed, but can be overridden here. This config will be
// specific to each type of renderer, so it will be necessary to key the
// configuration off of the renderId string. This should not be called every
// frame, because it will result in a recompile of the underlying shader set.
virtual void setEffectConfig(Json const& effectConfig) = 0;
// The effect config will specify named parameters and textures which can be
// set here.
virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0;
virtual void setEffectTexture(String const& textureName, Image const& image) = 0;
// Any further rendering will be scissored based on this rect, specified in
// pixels
virtual void setScissorRect(Maybe<RectI> const& scissorRect) = 0;
virtual TexturePtr createTexture(Image const& texture,
TextureAddressing addressing = TextureAddressing::Clamp,
TextureFiltering filtering = TextureFiltering::Nearest) = 0;
virtual void setSizeLimitEnabled(bool enabled) = 0;
virtual void setMultiTexturingEnabled(bool enabled) = 0;
virtual TextureGroupPtr createTextureGroup(TextureGroupSize size = TextureGroupSize::Medium, TextureFiltering filtering = TextureFiltering::Nearest) = 0;
virtual RenderBufferPtr createRenderBuffer() = 0;
virtual void render(RenderPrimitive primitive) = 0;
virtual void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation = Mat3F::identity()) = 0;
virtual void flush() = 0;
};
}
#endif

View file

@ -0,0 +1,828 @@
#include "StarRenderer_opengl20.hpp"
#include "StarJsonExtra.hpp"
#include "StarCasting.hpp"
#include "StarLogging.hpp"
namespace Star {
size_t const MultiTextureCount = 4;
char const* DefaultEffectConfig = R"JSON(
{
"vertexShader" : "
#version 110
uniform vec2 textureSize0;
uniform vec2 textureSize1;
uniform vec2 textureSize2;
uniform vec2 textureSize3;
uniform vec2 screenSize;
uniform mat3 vertexTransform;
attribute vec2 vertexPosition;
attribute vec2 vertexTextureCoordinate;
attribute float vertexTextureIndex;
attribute vec4 vertexColor;
attribute float vertexParam1;
varying vec2 fragmentTextureCoordinate;
varying float fragmentTextureIndex;
varying vec4 fragmentColor;
void main() {
vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy;
gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0);
if (vertexTextureIndex > 2.9) {
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3;
} else if (vertexTextureIndex > 1.9) {
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2;
} else if (vertexTextureIndex > 0.9) {
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1;
} else {
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0;
}
fragmentTextureIndex = vertexTextureIndex;
fragmentColor = vertexColor;
}
",
"fragmentShader" : "
#version 110
uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
varying vec2 fragmentTextureCoordinate;
varying float fragmentTextureIndex;
varying vec4 fragmentColor;
void main() {
if (fragmentTextureIndex > 2.9) {
gl_FragColor = texture2D(texture3, fragmentTextureCoordinate) * fragmentColor;
} else if (fragmentTextureIndex > 1.9) {
gl_FragColor = texture2D(texture2, fragmentTextureCoordinate) * fragmentColor;
} else if (fragmentTextureIndex > 0.9) {
gl_FragColor = texture2D(texture1, fragmentTextureCoordinate) * fragmentColor;
} else {
gl_FragColor = texture2D(texture0, fragmentTextureCoordinate) * fragmentColor;
}
}
"
}
)JSON";
OpenGl20Renderer::OpenGl20Renderer() {
if (glewInit() != GLEW_OK)
throw RendererException("Could not initialize GLEW");
if (!GLEW_VERSION_2_0)
throw RendererException("OpenGL 2.0 not available!");
Logger::info("OpenGL version: '%s' vendor: '%s' renderer: '%s' shader: '%s'",
glGetString(GL_VERSION),
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_SHADING_LANGUAGE_VERSION));
glClearColor(0.0, 0.0, 0.0, 1.0);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
TextureAddressing::Clamp,
TextureFiltering::Nearest);
m_immediateRenderBuffer = createGlRenderBuffer();
setEffectConfig(Json::parse(DefaultEffectConfig));
m_limitTextureGroupSize = false;
m_useMultiTexturing = true;
logGlErrorSummary("OpenGL errors during renderer initialization");
}
OpenGl20Renderer::~OpenGl20Renderer() {
glDeleteProgram(m_program);
logGlErrorSummary("OpenGL errors during shutdown");
}
String OpenGl20Renderer::rendererId() const {
return "OpenGL20";
}
Vec2U OpenGl20Renderer::screenSize() const {
return m_screenSize;
}
void OpenGl20Renderer::setEffectConfig(Json const& effectConfig) {
flushImmediatePrimitives();
GLint status = 0;
char logBuffer[1024];
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
String vertexSource = effectConfig.getString("vertexShader");
char const* vertexSourcePtr = vertexSource.utf8Ptr();
glShaderSource(vertexShader, 1, &vertexSourcePtr, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
if (!status) {
glGetShaderInfoLog(vertexShader, sizeof(logBuffer), NULL, logBuffer);
throw RendererException(strf("Failed to compile vertex shader: %s\n", logBuffer));
}
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
String fragmentSource = effectConfig.getString("fragmentShader");
char const* fragmentSourcePtr = fragmentSource.utf8Ptr();
glShaderSource(fragmentShader, 1, &fragmentSourcePtr, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
if (!status) {
glGetShaderInfoLog(fragmentShader, sizeof(logBuffer), NULL, logBuffer);
throw RendererException(strf("Failed to compile fragment shader: %s\n", logBuffer));
}
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (!status) {
glGetProgramInfoLog(program, sizeof(logBuffer), NULL, logBuffer);
glDeleteProgram(program);
throw RendererException(strf("Failed to link program: %s\n", logBuffer));
}
if (m_program != 0)
glDeleteProgram(m_program);
m_program = program;
glUseProgram(m_program);
m_positionAttribute = glGetAttribLocation(m_program, "vertexPosition");
m_texCoordAttribute = glGetAttribLocation(m_program, "vertexTextureCoordinate");
m_texIndexAttribute = glGetAttribLocation(m_program, "vertexTextureIndex");
m_colorAttribute = glGetAttribLocation(m_program, "vertexColor");
m_param1Attribute = glGetAttribLocation(m_program, "vertexParam1");
m_textureUniforms.clear();
m_textureSizeUniforms.clear();
for (size_t i = 0; i < MultiTextureCount; ++i) {
m_textureUniforms.append(glGetUniformLocation(m_program, strf("texture%s", i).c_str()));
m_textureSizeUniforms.append(glGetUniformLocation(m_program, strf("textureSize%s", i).c_str()));
}
m_screenSizeUniform = glGetUniformLocation(m_program, "screenSize");
m_vertexTransformUniform = glGetUniformLocation(m_program, "vertexTransform");
for (size_t i = 0; i < MultiTextureCount; ++i) {
glUniform1i(m_textureUniforms[i], i);
}
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
m_effectParameters.clear();
for (auto const& p : effectConfig.getObject("effectParameters", {})) {
EffectParameter effectParameter;
effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr());
if (effectParameter.parameterUniform == -1) {
Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
} else {
String type = p.second.getString("type");
if (type == "bool") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<bool>();
} else if (type == "int") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<int>();
} else if (type == "float") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<float>();
} else if (type == "vec2") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec2F>();
} else if (type == "vec3") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec3F>();
} else if (type == "vec4") {
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec4F>();
} else {
throw RendererException::format("Unrecognized effect parameter type '%s'", type);
}
m_effectParameters[p.first] = effectParameter;
if (Json def = p.second.get("default", {})) {
if (type == "bool") {
setEffectParameter(p.first, def.toBool());
} else if (type == "int") {
setEffectParameter(p.first, (int)def.toInt());
} else if (type == "float") {
setEffectParameter(p.first, def.toFloat());
} else if (type == "vec2") {
setEffectParameter(p.first, jsonToVec2F(def));
} else if (type == "vec3") {
setEffectParameter(p.first, jsonToVec3F(def));
} else if (type == "vec4") {
setEffectParameter(p.first, jsonToVec4F(def));
}
}
}
}
m_effectTextures.clear();
// Assign each texture parameter a texture unit starting with MultiTextureCount, the first
// few texture units are used by the primary textures being drawn. Currently,
// maximum texture units are not checked.
unsigned parameterTextureUnit = MultiTextureCount;
for (auto const& p : effectConfig.getObject("effectTextures", {})) {
EffectTexture effectTexture;
effectTexture.textureUniform = glGetUniformLocation(m_program, p.second.getString("textureUniform").utf8Ptr());
if (effectTexture.textureUniform == -1) {
Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
} else {
effectTexture.textureUnit = parameterTextureUnit++;
glUniform1i(effectTexture.textureUniform, effectTexture.textureUnit);
effectTexture.textureAddressing = TextureAddressingNames.getLeft(p.second.getString("textureAddressing", "clamp"));
effectTexture.textureFiltering = TextureFilteringNames.getLeft(p.second.getString("textureFiltering", "nearest"));
if (auto tsu = p.second.optString("textureSizeUniform")) {
effectTexture.textureSizeUniform = glGetUniformLocation(m_program, tsu->utf8Ptr());
if (effectTexture.textureSizeUniform == -1)
Logger::warn("OpenGL20 effect parameter '%s' has textureSizeUniform '%s' with no associated uniform", p.first, *tsu);
}
m_effectTextures[p.first] = effectTexture;
}
}
if (DebugEnabled)
logGlErrorSummary("OpenGL errors setting effect config");
}
void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) {
auto ptr = m_effectParameters.ptr(parameterName);
if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value))
return;
if (ptr->parameterType != value.typeIndex())
throw RendererException::format("OpenGL20Renderer::setEffectParameter '%s' parameter type mismatch", parameterName);
flushImmediatePrimitives();
if (auto v = value.ptr<bool>())
glUniform1i(ptr->parameterUniform, *v);
else if (auto v = value.ptr<int>())
glUniform1i(ptr->parameterUniform, *v);
else if (auto v = value.ptr<float>())
glUniform1f(ptr->parameterUniform, *v);
else if (auto v = value.ptr<Vec2F>())
glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]);
else if (auto v = value.ptr<Vec3F>())
glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]);
else if (auto v = value.ptr<Vec4F>())
glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
ptr->parameterValue = value;
}
void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& image) {
auto ptr = m_effectTextures.ptr(textureName);
if (!ptr)
return;
flushImmediatePrimitives();
if (!ptr->textureValue || ptr->textureValue->textureId == 0) {
ptr->textureValue = createGlTexture(image, ptr->textureAddressing, ptr->textureFiltering);
} else {
glBindTexture(GL_TEXTURE_2D, ptr->textureValue->textureId);
ptr->textureValue->textureSize = image.size();
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
}
if (ptr->textureSizeUniform != -1) {
auto textureSize = ptr->textureValue->glTextureSize();
glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]);
}
}
void OpenGl20Renderer::setScissorRect(Maybe<RectI> const& scissorRect) {
if (scissorRect == m_scissorRect)
return;
flushImmediatePrimitives();
m_scissorRect = scissorRect;
if (m_scissorRect) {
glEnable(GL_SCISSOR_TEST);
glScissor(m_scissorRect->xMin(), m_scissorRect->yMin(), m_scissorRect->width(), m_scissorRect->height());
} else {
glDisable(GL_SCISSOR_TEST);
}
}
TexturePtr OpenGl20Renderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) {
return createGlTexture(texture, addressing, filtering);
}
void OpenGl20Renderer::setSizeLimitEnabled(bool enabled) {
m_limitTextureGroupSize = enabled;
}
void OpenGl20Renderer::setMultiTexturingEnabled(bool enabled) {
m_useMultiTexturing = enabled;
}
TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) {
int maxTextureSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
// Large texture sizes are not always supported
if (textureSize == TextureGroupSize::Large && (m_limitTextureGroupSize || maxTextureSize < 4096))
textureSize = TextureGroupSize::Medium;
unsigned atlasNumCells;
if (textureSize == TextureGroupSize::Large)
atlasNumCells = 256;
else if (textureSize == TextureGroupSize::Medium)
atlasNumCells = 128;
else // TextureGroupSize::Small
atlasNumCells = 64;
Logger::info("detected supported OpenGL texture size %s, using atlasNumCells %s", maxTextureSize, atlasNumCells);
auto glTextureGroup = make_shared<GlTextureGroup>(atlasNumCells);
glTextureGroup->textureAtlasSet.textureFiltering = filtering;
m_liveTextureGroups.append(glTextureGroup);
return glTextureGroup;
}
RenderBufferPtr OpenGl20Renderer::createRenderBuffer() {
return createGlRenderBuffer();
}
void OpenGl20Renderer::render(RenderPrimitive primitive) {
m_immediatePrimitives.append(move(primitive));
}
void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) {
flushImmediatePrimitives();
renderGlBuffer(*convert<GlRenderBuffer>(renderBuffer.get()), transformation);
}
void OpenGl20Renderer::flush() {
flushImmediatePrimitives();
}
void OpenGl20Renderer::setScreenSize(Vec2U screenSize) {
m_screenSize = screenSize;
glViewport(0, 0, m_screenSize[0], m_screenSize[1]);
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
}
void OpenGl20Renderer::startFrame() {
if (m_scissorRect)
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
if (m_scissorRect)
glEnable(GL_SCISSOR_TEST);
}
void OpenGl20Renderer::finishFrame() {
flushImmediatePrimitives();
// Make sure that the immediate render buffer doesn't needlessly lock texutres
// from being compressed.
m_immediateRenderBuffer->set({});
filter(m_liveTextureGroups, [](auto const& p) {
unsigned const CompressionsPerFrame = 1;
if (!p.unique() || p->textureAtlasSet.totalTextures() > 0) {
p->textureAtlasSet.compressionPass(CompressionsPerFrame);
return true;
}
return false;
});
if (DebugEnabled)
logGlErrorSummary("OpenGL errors this frame");
}
OpenGl20Renderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells)
: TextureAtlasSet(16, atlasNumCells) {}
GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) {
GLuint glTextureId;
glGenTextures(1, &glTextureId);
if (glTextureId == 0)
throw RendererException("Could not generate texture in OpenGL20Renderer::TextureGroup::createAtlasTexture()");
glBindTexture(GL_TEXTURE_2D, glTextureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (textureFiltering == TextureFiltering::Nearest) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} else {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
uploadTextureImage(pixelFormat, size, nullptr);
return glTextureId;
}
void OpenGl20Renderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) {
glDeleteTextures(1, &glTexture);
}
void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels(
GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) {
glBindTexture(GL_TEXTURE_2D, glTexture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (image.pixelFormat() == PixelFormat::RGB24) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGB, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::RGBA32) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::BGR24) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGR, GL_UNSIGNED_BYTE, image.data());
} else if (image.pixelFormat() == PixelFormat::BGRA32) {
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGRA, GL_UNSIGNED_BYTE, image.data());
} else {
throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels");
}
}
OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells)
: textureAtlasSet(atlasNumCells) {}
OpenGl20Renderer::GlTextureGroup::~GlTextureGroup() {
textureAtlasSet.reset();
}
TextureFiltering OpenGl20Renderer::GlTextureGroup::filtering() const {
return textureAtlasSet.textureFiltering;
}
TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) {
// If the image is empty, or would not fit in the texture atlas with border
// pixels, just create a regular texture
Vec2U atlasTextureSize = textureAtlasSet.atlasTextureSize();
if (texture.empty() || texture.width() + 2 > atlasTextureSize[0] || texture.height() + 2 > atlasTextureSize[1])
return createGlTexture(texture, TextureAddressing::Clamp, textureAtlasSet.textureFiltering);
auto glGroupedTexture = make_ref<GlGroupedTexture>();
glGroupedTexture->parentGroup = shared_from_this();
glGroupedTexture->parentAtlasTexture = textureAtlasSet.addTexture(texture);
return glGroupedTexture;
}
OpenGl20Renderer::GlGroupedTexture::~GlGroupedTexture() {
if (parentAtlasTexture)
parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture);
}
Vec2U OpenGl20Renderer::GlGroupedTexture::size() const {
return parentAtlasTexture->imageSize();
}
TextureFiltering OpenGl20Renderer::GlGroupedTexture::filtering() const {
return parentGroup->filtering();
}
TextureAddressing OpenGl20Renderer::GlGroupedTexture::addressing() const {
return TextureAddressing::Clamp;
}
GLuint OpenGl20Renderer::GlGroupedTexture::glTextureId() const {
return parentAtlasTexture->atlasTexture();
}
Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureSize() const {
return parentGroup->textureAtlasSet.atlasTextureSize();
}
Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureCoordinateOffset() const {
return parentAtlasTexture->atlasTextureCoordinates().min();
}
void OpenGl20Renderer::GlGroupedTexture::incrementBufferUseCount() {
if (bufferUseCount == 0)
parentAtlasTexture->setLocked(true);
++bufferUseCount;
}
void OpenGl20Renderer::GlGroupedTexture::decrementBufferUseCount() {
starAssert(bufferUseCount != 0);
if (bufferUseCount == 1)
parentAtlasTexture->setLocked(false);
--bufferUseCount;
}
OpenGl20Renderer::GlLoneTexture::~GlLoneTexture() {
if (textureId != 0)
glDeleteTextures(1, &textureId);
}
Vec2U OpenGl20Renderer::GlLoneTexture::size() const {
return textureSize;
}
TextureFiltering OpenGl20Renderer::GlLoneTexture::filtering() const {
return textureFiltering;
}
TextureAddressing OpenGl20Renderer::GlLoneTexture::addressing() const {
return textureAddressing;
}
GLuint OpenGl20Renderer::GlLoneTexture::glTextureId() const {
return textureId;
}
Vec2U OpenGl20Renderer::GlLoneTexture::glTextureSize() const {
return textureSize;
}
Vec2U OpenGl20Renderer::GlLoneTexture::glTextureCoordinateOffset() const {
return Vec2U();
}
OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() {
for (auto const& texture : usedTextures) {
if (auto gt = as<GlGroupedTexture>(texture.get()))
gt->decrementBufferUseCount();
}
for (auto const& vb : vertexBuffers)
glDeleteBuffers(1, &vb.vertexBuffer);
}
void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive> primitives) {
for (auto const& texture : usedTextures) {
if (auto gt = as<GlGroupedTexture>(texture.get()))
gt->decrementBufferUseCount();
}
usedTextures.clear();
auto oldVertexBuffers = take(vertexBuffers);
List<GLuint> currentTextures;
List<Vec2U> currentTextureSizes;
size_t currentVertexCount = 0;
auto finishCurrentBuffer = [&]() {
if (currentVertexCount > 0) {
GlVertexBuffer vb;
for (size_t i = 0; i < currentTextures.size(); ++i) {
vb.textures.append(GlVertexBufferTexture{currentTextures[i], currentTextureSizes[i]});
}
vb.vertexCount = currentVertexCount;
if (!oldVertexBuffers.empty()) {
auto oldVb = oldVertexBuffers.takeLast();
vb.vertexBuffer = oldVb.vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
if (oldVb.vertexCount >= vb.vertexCount)
glBufferSubData(GL_ARRAY_BUFFER, 0, accumulationBuffer.size(), accumulationBuffer.ptr());
else
glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
} else {
glGenBuffers(1, &vb.vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
}
vertexBuffers.append(vb);
currentTextures.clear();
currentTextureSizes.clear();
accumulationBuffer.clear();
currentVertexCount = 0;
}
};
auto textureCount = useMultiTexturing ? MultiTextureCount : 1;
auto addCurrentTexture = [&](TexturePtr texture) -> pair<uint8_t, Vec2F> {
if (!texture)
texture = whiteTexture;
auto glTexture = as<GlTexture>(texture.get());
GLuint glTextureId = glTexture->glTextureId();
auto textureIndex = currentTextures.indexOf(glTextureId);
if (textureIndex == NPos) {
if (currentTextures.size() >= textureCount)
finishCurrentBuffer();
textureIndex = currentTextures.size();
currentTextures.append(glTextureId);
currentTextureSizes.append(glTexture->glTextureSize());
}
if (auto gt = as<GlGroupedTexture>(texture.get()))
gt->incrementBufferUseCount();
usedTextures.add(move(texture));
return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())};
};
auto appendBufferVertex = [&](RenderVertex v, float textureIndex, Vec2F textureCoordinateOffset) {
GlRenderVertex glv {
v.screenCoordinate,
v.textureCoordinate + textureCoordinateOffset,
textureIndex,
v.color,
v.param1
};
accumulationBuffer.append((char const*)&glv, sizeof(GlRenderVertex));
++currentVertexCount;
};
for (auto& primitive : primitives) {
float textureIndex;
Vec2F textureOffset;
if (auto tri = primitive.ptr<RenderTriangle>()) {
tie(textureIndex, textureOffset) = addCurrentTexture(move(tri->texture));
appendBufferVertex(tri->a, textureIndex, textureOffset);
appendBufferVertex(tri->b, textureIndex, textureOffset);
appendBufferVertex(tri->c, textureIndex, textureOffset);
} else if (auto quad = primitive.ptr<RenderQuad>()) {
tie(textureIndex, textureOffset) = addCurrentTexture(move(quad->texture));
appendBufferVertex(quad->a, textureIndex, textureOffset);
appendBufferVertex(quad->b, textureIndex, textureOffset);
appendBufferVertex(quad->c, textureIndex, textureOffset);
appendBufferVertex(quad->a, textureIndex, textureOffset);
appendBufferVertex(quad->c, textureIndex, textureOffset);
appendBufferVertex(quad->d, textureIndex, textureOffset);
} else if (auto poly = primitive.ptr<RenderPoly>()) {
if (poly->vertexes.size() > 2) {
tie(textureIndex, textureOffset) = addCurrentTexture(move(poly->texture));
for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) {
appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset);
appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset);
appendBufferVertex(poly->vertexes[i + 1], textureIndex, textureOffset);
}
}
}
}
finishCurrentBuffer();
for (auto const& vb : oldVertexBuffers)
glDeleteBuffers(1, &vb.vertexBuffer);
}
void OpenGl20Renderer::logGlErrorSummary(String prefix) {
List<GLenum> errors;
while (GLenum error = glGetError())
errors.append(error);
if (!errors.empty()) {
String errorMessage = move(prefix);
errorMessage.append(": ");
for (auto const& error : errors) {
if (error == GL_INVALID_ENUM) {
errorMessage += " GL_INVALID_ENUM";
} else if (error == GL_INVALID_VALUE) {
errorMessage += " GL_INVALID_VALUE";
} else if (error == GL_INVALID_OPERATION) {
errorMessage += " GL_INVALID_OPERATION";
} else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) {
errorMessage += " GL_INVALID_FRAMEBUFFER_OPERATION";
} else if (error == GL_OUT_OF_MEMORY) {
errorMessage += " GL_OUT_OF_MEMORY";
} else if (error == GL_STACK_UNDERFLOW) {
errorMessage += " GL_STACK_UNDERFLOW";
} else if (error == GL_STACK_OVERFLOW) {
errorMessage += " GL_STACK_OVERFLOW";
} else {
errorMessage += " <UNRECOGNIZED GL ERROR>";
}
}
Logger::error(errorMessage.utf8Ptr());
}
}
void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (pixelFormat == PixelFormat::RGB24) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::RGBA32) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::BGR24) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGR, size[0], size[1], 0, GL_BGR, GL_UNSIGNED_BYTE, data);
} else if (pixelFormat == PixelFormat::BGRA32) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
} else {
starAssert(false);
}
}
void OpenGl20Renderer::flushImmediatePrimitives() {
if (m_immediatePrimitives.empty())
return;
m_immediateRenderBuffer->set(take(m_immediatePrimitives));
renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity());
}
auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing addressing, TextureFiltering filtering)
-> RefPtr<GlLoneTexture> {
auto glLoneTexture = make_ref<GlLoneTexture>();
glLoneTexture->textureFiltering = filtering;
glLoneTexture->textureAddressing = addressing;
glLoneTexture->textureSize = image.size();
if (image.empty())
return glLoneTexture;
glGenTextures(1, &glLoneTexture->textureId);
if (glLoneTexture->textureId == 0)
throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture");
glBindTexture(GL_TEXTURE_2D, glLoneTexture->textureId);
if (addressing == TextureAddressing::Clamp) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
if (filtering == TextureFiltering::Nearest) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} else {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
return glLoneTexture;
}
auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr<GlRenderBuffer> {
auto glrb = make_shared<GlRenderBuffer>();
glrb->whiteTexture = m_whiteTexture;
glrb->useMultiTexturing = m_useMultiTexturing;
return glrb;
}
void OpenGl20Renderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation) {
for (auto const& vb : renderBuffer.vertexBuffers) {
glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr());
for (size_t i = 0; i < vb.textures.size(); ++i) {
glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]);
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture);
}
for (auto const& p : m_effectTextures) {
if (p.second.textureValue) {
glActiveTexture(GL_TEXTURE0 + p.second.textureUnit);
glBindTexture(GL_TEXTURE_2D, p.second.textureValue->textureId);
}
}
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
glEnableVertexAttribArray(m_positionAttribute);
glEnableVertexAttribArray(m_texCoordAttribute);
glEnableVertexAttribArray(m_texIndexAttribute);
glEnableVertexAttribArray(m_colorAttribute);
glEnableVertexAttribArray(m_param1Attribute);
glVertexAttribPointer(m_positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, screenCoordinate));
glVertexAttribPointer(m_texCoordAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureCoordinate));
glVertexAttribPointer(m_texIndexAttribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureIndex));
glVertexAttribPointer(m_colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, color));
glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1));
glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount);
}
}
}

View file

@ -0,0 +1,199 @@
#ifndef STAR_RENDERER_OPENGL_HPP
#define STAR_RENDERER_OPENGL_HPP
#include "StarTextureAtlas.hpp"
#include "StarRenderer.hpp"
#include "GL/glew.h"
namespace Star {
STAR_CLASS(OpenGl20Renderer);
// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and
// active during construction, destruction, and all method calls.
class OpenGl20Renderer : public Renderer {
public:
OpenGl20Renderer();
~OpenGl20Renderer();
String rendererId() const override;
Vec2U screenSize() const override;
void setEffectConfig(Json const& effectConfig) override;
void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override;
void setEffectTexture(String const& textureName, Image const& image) override;
void setScissorRect(Maybe<RectI> const& scissorRect) override;
TexturePtr createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) override;
void setSizeLimitEnabled(bool enabled) override;
void setMultiTexturingEnabled(bool enabled) override;
TextureGroupPtr createTextureGroup(TextureGroupSize size, TextureFiltering filtering) override;
RenderBufferPtr createRenderBuffer() override;
void render(RenderPrimitive primitive) override;
void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) override;
void flush() override;
void setScreenSize(Vec2U screenSize);
void startFrame();
void finishFrame();
private:
struct GlTextureAtlasSet : public TextureAtlasSet<GLuint> {
public:
GlTextureAtlasSet(unsigned atlasNumCells);
GLuint createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) override;
void destroyAtlasTexture(GLuint const& glTexture) override;
void copyAtlasPixels(GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) override;
TextureFiltering textureFiltering;
};
struct GlTextureGroup : enable_shared_from_this<GlTextureGroup>, public TextureGroup {
GlTextureGroup(unsigned atlasNumCells);
~GlTextureGroup();
TextureFiltering filtering() const override;
TexturePtr create(Image const& texture) override;
GlTextureAtlasSet textureAtlasSet;
};
struct GlTexture : public Texture {
virtual GLuint glTextureId() const = 0;
virtual Vec2U glTextureSize() const = 0;
virtual Vec2U glTextureCoordinateOffset() const = 0;
};
struct GlGroupedTexture : public GlTexture {
~GlGroupedTexture();
Vec2U size() const override;
TextureFiltering filtering() const override;
TextureAddressing addressing() const override;
GLuint glTextureId() const override;
Vec2U glTextureSize() const override;
Vec2U glTextureCoordinateOffset() const override;
void incrementBufferUseCount();
void decrementBufferUseCount();
unsigned bufferUseCount = 0;
shared_ptr<GlTextureGroup> parentGroup;
GlTextureAtlasSet::TextureHandle parentAtlasTexture = nullptr;
};
struct GlLoneTexture : public GlTexture {
~GlLoneTexture();
Vec2U size() const override;
TextureFiltering filtering() const override;
TextureAddressing addressing() const override;
GLuint glTextureId() const override;
Vec2U glTextureSize() const override;
Vec2U glTextureCoordinateOffset() const override;
GLuint textureId = 0;
Vec2U textureSize;
TextureAddressing textureAddressing = TextureAddressing::Clamp;
TextureFiltering textureFiltering = TextureFiltering::Nearest;
};
struct GlRenderVertex {
Vec2F screenCoordinate;
Vec2F textureCoordinate;
float textureIndex;
Vec4B color;
float param1;
};
struct GlRenderBuffer : public RenderBuffer {
struct GlVertexBufferTexture {
GLuint texture;
Vec2U size;
};
struct GlVertexBuffer {
List<GlVertexBufferTexture> textures;
GLuint vertexBuffer = 0;
size_t vertexCount = 0;
};
~GlRenderBuffer();
void set(List<RenderPrimitive> primitives) override;
RefPtr<GlTexture> whiteTexture;
ByteArray accumulationBuffer;
HashSet<TexturePtr> usedTextures;
List<GlVertexBuffer> vertexBuffers;
bool useMultiTexturing;
};
struct EffectParameter {
GLint parameterUniform = -1;
VariantTypeIndex parameterType;
Maybe<RenderEffectParameter> parameterValue;
};
struct EffectTexture {
GLint textureUniform = -1;
unsigned textureUnit;
TextureAddressing textureAddressing = TextureAddressing::Clamp;
TextureFiltering textureFiltering = TextureFiltering::Linear;
GLint textureSizeUniform = -1;
RefPtr<GlLoneTexture> textureValue;
};
static void logGlErrorSummary(String prefix);
static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data);
static RefPtr<GlLoneTexture> createGlTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering);
shared_ptr<GlRenderBuffer> createGlRenderBuffer();
void flushImmediatePrimitives();
void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation);
Vec2U m_screenSize;
GLuint m_program = 0;
GLint m_positionAttribute = -1;
GLint m_texCoordAttribute = -1;
GLint m_texIndexAttribute = -1;
GLint m_colorAttribute = -1;
GLint m_param1Attribute = -1;
List<GLint> m_textureUniforms = {};
List<GLint> m_textureSizeUniforms = {};
GLint m_screenSizeUniform = -1;
GLint m_vertexTransformUniform = -1;
RefPtr<GlTexture> m_whiteTexture;
StringMap<EffectParameter> m_effectParameters;
StringMap<EffectTexture> m_effectTextures;
Maybe<RectI> m_scissorRect;
bool m_limitTextureGroupSize;
bool m_useMultiTexturing;
List<shared_ptr<GlTextureGroup>> m_liveTextureGroups;
List<RenderPrimitive> m_immediatePrimitives;
shared_ptr<GlRenderBuffer> m_immediateRenderBuffer;
};
}
#endif

View file

@ -0,0 +1,130 @@
#include "StarStatisticsService_pc_steam.hpp"
#include "StarLogging.hpp"
namespace Star {
SteamStatisticsService::SteamStatisticsService(PcPlatformServicesStatePtr)
: m_callbackUserStatsReceived(this, &SteamStatisticsService::onUserStatsReceived),
m_callbackUserStatsStored(this, &SteamStatisticsService::onUserStatsStored),
m_callbackAchievementStored(this, &SteamStatisticsService::onAchievementStored) {
m_appId = SteamUtils()->GetAppID();
refresh();
}
bool SteamStatisticsService::initialized() const {
return m_initialized;
}
Maybe<String> SteamStatisticsService::error() const {
return m_error;
}
bool SteamStatisticsService::setStat(String const& name, String const& type, Json const& value) {
if (type == "int")
return SteamUserStats()->SetStat(name.utf8Ptr(), (int32_t)value.toInt());
if (type == "float")
return SteamUserStats()->SetStat(name.utf8Ptr(), value.toFloat());
return false;
}
Json SteamStatisticsService::getStat(String const& name, String const& type, Json def) const {
if (type == "int") {
int32_t intValue = 0;
if (SteamUserStats()->GetStat(name.utf8Ptr(), &intValue))
return Json(intValue);
}
if (type == "float") {
float floatValue = 0.0f;
if (SteamUserStats()->GetStat(name.utf8Ptr(), &floatValue))
return Json(floatValue);
}
return def;
}
bool SteamStatisticsService::reportEvent(String const&, Json const&) {
// Steam doesn't support events
return false;
}
bool SteamStatisticsService::unlockAchievement(String const& name) {
if (!SteamUserStats()->SetAchievement(name.utf8Ptr())) {
Logger::error("Cannot set Steam achievement %s", name);
return false;
}
return true;
}
StringSet SteamStatisticsService::achievementsUnlocked() const {
StringSet achievements;
for (uint32_t i = 0; i < SteamUserStats()->GetNumAchievements(); ++i) {
String achievement = SteamUserStats()->GetAchievementName(i);
bool unlocked = false;
if (SteamUserStats()->GetAchievement(achievement.utf8Ptr(), &unlocked) && unlocked) {
achievements.add(achievement);
}
}
return {};
}
void SteamStatisticsService::refresh() {
if (!SteamUser()->BLoggedOn()) {
m_error = {"Not logged in"};
return;
}
SteamUserStats()->RequestCurrentStats();
}
void SteamStatisticsService::flush() {
SteamUserStats()->StoreStats();
}
bool SteamStatisticsService::reset() {
SteamUserStats()->ResetAllStats(true);
return true;
}
void SteamStatisticsService::onUserStatsReceived(UserStatsReceived_t* callback) {
if (callback->m_nGameID != m_appId)
return;
if (callback->m_eResult != k_EResultOK) {
m_error = {strf("Steam RequestCurrentStats failed with code %d", callback->m_eResult)};
return;
}
Logger::debug("Steam RequestCurrentStats successful");
m_initialized = true;
}
void SteamStatisticsService::onUserStatsStored(UserStatsStored_t* callback) {
if (callback->m_nGameID != m_appId)
return;
if (callback->m_eResult == k_EResultOK) {
Logger::debug("Steam StoreStats successful");
return;
}
if (callback->m_eResult == k_EResultInvalidParam) {
// A stat we set broke a constraint and was reverted on the service.
Logger::info("Steam StoreStats: Some stats failed validation");
return;
}
m_error = {strf("Steam StoreStats failed with code %d", callback->m_eResult)};
}
void SteamStatisticsService::onAchievementStored(UserAchievementStored_t* callback) {
if (callback->m_nGameID != m_appId)
return;
Logger::debug("Steam achievement %s stored successfully", callback->m_rgchAchievementName);
}
}

View file

@ -0,0 +1,41 @@
#ifndef STAR_STATISTICS_SERVICE_PC_STEAM_HPP
#define STAR_STATISTICS_SERVICE_PC_STEAM_HPP
#include "StarPlatformServices_pc.hpp"
namespace Star {
STAR_CLASS(SteamStatisticsService);
class SteamStatisticsService : public StatisticsService {
public:
SteamStatisticsService(PcPlatformServicesStatePtr state);
bool initialized() const override;
Maybe<String> error() const override;
bool setStat(String const& name, String const& type, Json const& value) override;
Json getStat(String const& name, String const& type, Json def = {}) const override;
bool reportEvent(String const& name, Json const& fields) override;
bool unlockAchievement(String const& name) override;
StringSet achievementsUnlocked() const override;
void refresh() override;
void flush() override;
bool reset() override;
private:
STEAM_CALLBACK(SteamStatisticsService, onUserStatsReceived, UserStatsReceived_t, m_callbackUserStatsReceived);
STEAM_CALLBACK(SteamStatisticsService, onUserStatsStored, UserStatsStored_t, m_callbackUserStatsStored);
STEAM_CALLBACK(SteamStatisticsService, onAchievementStored, UserAchievementStored_t, m_callbackAchievementStored);
uint64_t m_appId;
bool m_initialized;
Maybe<String> m_error;
};
}
#endif

View file

@ -0,0 +1,417 @@
#ifndef STAR_TEXTURE_ATLAS_HPP
#define STAR_TEXTURE_ATLAS_HPP
#include "StarRect.hpp"
#include "StarImage.hpp"
#include "StarCasting.hpp"
namespace Star {
STAR_EXCEPTION(TextureAtlasException, StarException);
// Implements a set of "texture atlases" or, sets of smaller textures grouped
// as a larger texture.
template <typename AtlasTextureHandle>
class TextureAtlasSet {
public:
struct Texture {
virtual Vec2U imageSize() const = 0;
virtual AtlasTextureHandle const& atlasTexture() const = 0;
virtual RectU atlasTextureCoordinates() const = 0;
// A locked texture will never be moved during compression, so its
// atlasTexture and textureCoordinates will not change.
virtual void setLocked(bool locked) = 0;
// Returns true if this texture has been freed or the parent
// TextureAtlasSet has been destructed.
virtual bool expired() const = 0;
};
typedef shared_ptr<Texture> TextureHandle;
TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells);
// The constant square size of all atlas textures
Vec2U atlasTextureSize() const;
// Removes all existing textures and destroys all texture atlases.
void reset();
// Adds texture to some TextureAtlas. Texture must fit in a single atlas
// texture, otherwise an exception is thrown. Returns a pointer to the new
// texture. If borderPixels is true, then fills a 1px border around the
// given image in the atlas with the nearest color value, to prevent
// bleeding.
TextureHandle addTexture(Image const& image, bool borderPixels = true);
// Removes the given texture from the TextureAtlasSet and invalidates the
// pointer.
void freeTexture(TextureHandle const& texture);
unsigned totalAtlases() const;
unsigned totalTextures() const;
float averageFillLevel() const;
// Takes images from sparsely filled atlases and moves them to less sparsely
// filled atlases in an effort to free up room. This method tages the atlas
// with the lowest fill level and picks a texture from it, removes it, and
// re-adds it to the AtlasSet. It does this up to textureCount textures,
// until it finds a texture where re-adding it to the texture atlas simply
// moves the texture into the same atlas, at which point it stops.
void compressionPass(size_t textureCount = NPos);
// The number of atlases that the AtlasSet will attempt to fit a texture in
// before giving up and creating a new atlas. Tries in order of least full
// to most full. Defaults to 3.
unsigned textureFitTries() const;
void setTextureFitTries(unsigned textureFitTries);
protected:
virtual AtlasTextureHandle createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) = 0;
virtual void destroyAtlasTexture(AtlasTextureHandle const& atlasTexture) = 0;
virtual void copyAtlasPixels(AtlasTextureHandle const& atlasTexture, Vec2U const& bottomLeft, Image const& image) = 0;
private:
struct TextureAtlas {
AtlasTextureHandle atlasTexture;
unique_ptr<bool[]> usedCells;
unsigned usedCellCount;
};
struct AtlasPlacement {
TextureAtlas* atlas;
bool borderPixels = false;
RectU occupiedCells;
RectU textureCoords;
};
struct TextureEntry : Texture {
Vec2U imageSize() const override;
AtlasTextureHandle const& atlasTexture() const override;
RectU atlasTextureCoordinates() const override;
// A locked texture will never be moved during compression, so its
// atlasTexture and textureCoordinates will not change.
void setLocked(bool locked) override;
bool expired() const override;
Image textureImage;
AtlasPlacement atlasPlacement;
bool placementLocked = false;
bool textureExpired = false;
};
void setAtlasRegionUsed(TextureAtlas* extureAtlas, RectU const& region, bool used) const;
Maybe<AtlasPlacement> addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels);
void sortAtlases();
unsigned m_atlasCellSize;
unsigned m_atlasNumCells;
unsigned m_textureFitTries;
List<shared_ptr<TextureAtlas>> m_atlases;
HashSet<shared_ptr<TextureEntry>> m_textures;
};
template <typename AtlasTextureHandle>
TextureAtlasSet<AtlasTextureHandle>::TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells)
: m_atlasCellSize(cellSize), m_atlasNumCells(atlasNumCells), m_textureFitTries(3) {}
template <typename AtlasTextureHandle>
Vec2U TextureAtlasSet<AtlasTextureHandle>::atlasTextureSize() const {
return Vec2U::filled(m_atlasCellSize * m_atlasNumCells);
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::reset() {
for (auto const& texture : m_textures)
texture->textureExpired = true;
for (auto const& atlas : m_atlases)
destroyAtlasTexture(atlas->atlasTexture);
m_atlases.clear();
m_textures.clear();
}
template <typename AtlasTextureHandle>
auto TextureAtlasSet<AtlasTextureHandle>::addTexture(Image const& image, bool borderPixels) -> TextureHandle {
if (image.empty())
throw TextureAtlasException("Empty image given in TextureAtlasSet::addTexture");
Image finalImage;
if (borderPixels) {
Vec2U imageSize = image.size();
Vec2U finalImageSize = imageSize + Vec2U(2, 2);
finalImage = Image(finalImageSize, PixelFormat::RGBA32);
// Fill 1px border on all sides of the image
for (unsigned y = 0; y < finalImageSize[1]; ++y) {
for (unsigned x = 0; x < finalImageSize[0]; ++x) {
unsigned xind = clamp<unsigned>(x, 1, imageSize[0]) - 1;
unsigned yind = clamp<unsigned>(y, 1, imageSize[1]) - 1;
finalImage.set32(x, y, image.getrgb(xind, yind));
}
}
} else {
finalImage = image;
}
auto tryAtlas = [&](TextureAtlas* atlas) -> TextureHandle {
auto placement = addTextureToAtlas(atlas, finalImage, borderPixels);
if (!placement)
return nullptr;
auto textureEntry = make_shared<TextureEntry>();
textureEntry->textureImage = move(finalImage);
textureEntry->atlasPlacement = *placement;
m_textures.add(textureEntry);
sortAtlases();
return textureEntry;
};
// Try the first 'm_textureFitTries' atlases to see if we can fit a given
// texture in an existing atlas. Do this from the most full to the least
// full atlas to maximize compression.
size_t startAtlas = m_atlases.size() - min<size_t>(m_atlases.size(), m_textureFitTries);
for (size_t i = startAtlas; i < m_atlases.size(); ++i) {
if (auto texturePtr = tryAtlas(m_atlases[i].get()))
return texturePtr;
}
// If we have not found an existing atlas to put the texture, need to create
// a new atlas
m_atlases.append(make_shared<TextureAtlas>(TextureAtlas{
createAtlasTexture(Vec2U::filled(m_atlasCellSize * m_atlasNumCells), PixelFormat::RGBA32),
unique_ptr<bool[]>(new bool[m_atlasNumCells * m_atlasNumCells]()), 0
}));
if (auto texturePtr = tryAtlas(m_atlases.last().get()))
return texturePtr;
// If it can't fit in a brand new empty atlas, it will not fit in any atlas
destroyAtlasTexture(m_atlases.last()->atlasTexture);
m_atlases.removeLast();
throw TextureAtlasException("Could not add texture to new atlas in TextureAtlasSet::addTexture, too large");
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::freeTexture(TextureHandle const& texture) {
auto textureEntry = convert<TextureEntry>(texture);
setAtlasRegionUsed(textureEntry->atlasPlacement.atlas, textureEntry->atlasPlacement.occupiedCells, false);
sortAtlases();
textureEntry->textureExpired = true;
m_textures.remove(textureEntry);
}
template <typename AtlasTextureHandle>
unsigned TextureAtlasSet<AtlasTextureHandle>::totalAtlases() const {
return m_atlases.size();
}
template <typename AtlasTextureHandle>
unsigned TextureAtlasSet<AtlasTextureHandle>::totalTextures() const {
return m_textures.size();
}
template <typename AtlasTextureHandle>
float TextureAtlasSet<AtlasTextureHandle>::averageFillLevel() const {
if (m_atlases.empty())
return 0.0f;
float atlasFillLevelSum = 0.0f;
for (auto const& atlas : m_atlases)
atlasFillLevelSum += atlas->usedCellCount / (float)square(m_atlasNumCells);
return atlasFillLevelSum / m_atlases.size();
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::compressionPass(size_t textureCount) {
while (m_atlases.size() > 1 && textureCount > 0) {
// Find the least full atlas, If it is empty, remove it and start at the
// next atlas.
auto const& smallestAtlas = m_atlases.last();
if (smallestAtlas->usedCellCount == 0) {
destroyAtlasTexture(smallestAtlas->atlasTexture);
m_atlases.removeLast();
continue;
}
// Loop over the currently loaded textures to find the smallest texture in
// the smallest atlas that is not locked.
TextureEntry* smallestTexture = nullptr;
for (auto const& texture : m_textures) {
if (texture->atlasPlacement.atlas == m_atlases.last().get()) {
if (!texture->placementLocked) {
if (!smallestTexture || texture->atlasPlacement.occupiedCells.volume() < smallestTexture->atlasPlacement.occupiedCells.volume())
smallestTexture = texture.get();
}
}
}
// If we were not able to find a smallest texture because the texture is
// locked, then simply stop. TODO: This could be done better, this will
// prevent compressing textures that are not from the smallest atlas if the
// smallest atlas has only locked textures.
if (!smallestTexture)
break;
// Try to add the texture to any atlas that isn't the last (most empty) one
size_t startAtlas = m_atlases.size() - 1 - min<size_t>(m_atlases.size() - 1, m_textureFitTries);
for (size_t i = startAtlas; i < m_atlases.size() - 1; ++i) {
if (auto placement = addTextureToAtlas(m_atlases[i].get(), smallestTexture->textureImage, smallestTexture->atlasPlacement.borderPixels)) {
setAtlasRegionUsed(smallestTexture->atlasPlacement.atlas, smallestTexture->atlasPlacement.occupiedCells, false);
smallestTexture->atlasPlacement = *placement;
smallestTexture = nullptr;
sortAtlases();
break;
}
}
// If we have not managed to move the smallest texture into any other
// atlas, assume the atlas set is compressed enough and quit.
if (smallestTexture)
break;
--textureCount;
}
}
template <typename AtlasTextureHandle>
unsigned TextureAtlasSet<AtlasTextureHandle>::textureFitTries() const {
return m_textureFitTries;
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::setTextureFitTries(unsigned textureFitTries) {
m_textureFitTries = textureFitTries;
}
template <typename AtlasTextureHandle>
Vec2U TextureAtlasSet<AtlasTextureHandle>::TextureEntry::imageSize() const {
if (atlasPlacement.borderPixels)
return textureImage.size() - Vec2U(2, 2);
else
return textureImage.size();
}
template <typename AtlasTextureHandle>
AtlasTextureHandle const& TextureAtlasSet<AtlasTextureHandle>::TextureEntry::atlasTexture() const {
return atlasPlacement.atlas->atlasTexture;
}
template <typename AtlasTextureHandle>
RectU TextureAtlasSet<AtlasTextureHandle>::TextureEntry::atlasTextureCoordinates() const {
return atlasPlacement.textureCoords;
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::TextureEntry::setLocked(bool locked) {
placementLocked = locked;
}
template <typename AtlasTextureHandle>
bool TextureAtlasSet<AtlasTextureHandle>::TextureEntry::expired() const {
return textureExpired;
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::setAtlasRegionUsed(TextureAtlas* textureAtlas, RectU const& region, bool used) const {
for (unsigned y = region.yMin(); y < region.yMax(); ++y) {
for (unsigned x = region.xMin(); x < region.xMax(); ++x) {
auto& val = textureAtlas->usedCells[y * m_atlasNumCells + x];
bool oldVal = val;
val = used;
if (oldVal && !val) {
starAssert(textureAtlas->usedCellCount != 0);
textureAtlas->usedCellCount -= 1;
} else if (!oldVal && used) {
textureAtlas->usedCellCount += 1;
starAssert(textureAtlas->usedCellCount <= square(m_atlasNumCells));
}
}
}
}
template <typename AtlasTextureHandle>
void TextureAtlasSet<AtlasTextureHandle>::sortAtlases() {
sort(m_atlases, [](auto const& a1, auto const& a2) {
return a1->usedCellCount > a2->usedCellCount;
});
}
template <typename AtlasTextureHandle>
auto TextureAtlasSet<AtlasTextureHandle>::addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels) -> Maybe<AtlasPlacement> {
bool found = false;
// Minimum cell indexes where this texture fits in this atlas.
unsigned fitCellX = 0;
unsigned fitCellY = 0;
Vec2U imageSize = image.size();
// Number of cells this image will take.
size_t numCellsX = (imageSize[0] + m_atlasCellSize - 1) / m_atlasCellSize;
size_t numCellsY = (imageSize[1] + m_atlasCellSize - 1) / m_atlasCellSize;
if (numCellsX > m_atlasNumCells || numCellsY > m_atlasNumCells)
return {};
for (size_t cellY = 0; cellY <= m_atlasNumCells - numCellsY; ++cellY) {
for (size_t cellX = 0; cellX <= m_atlasNumCells - numCellsX; ++cellX) {
// Check this box of numCellsX x numCellsY for fit.
found = true;
size_t fx;
size_t fy;
for (fy = cellY; fy < cellY + numCellsY; ++fy) {
for (fx = cellX; fx < cellX + numCellsX; ++fx) {
if (atlas->usedCells[fy * m_atlasNumCells + fx]) {
found = false;
break;
}
}
if (!found)
break;
}
if (!found) {
// If it does not fit, then we can skip to the block past the first
// horizontal used block;
cellX = fx;
} else {
fitCellX = cellX;
fitCellY = cellY;
break;
}
}
if (found)
break;
}
if (!found)
return {};
setAtlasRegionUsed(atlas, RectU::withSize({fitCellX, fitCellY}, {(unsigned)numCellsX, (unsigned)numCellsY}), true);
copyAtlasPixels(atlas->atlasTexture, Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), image);
AtlasPlacement atlasPlacement;
atlasPlacement.atlas = atlas;
atlasPlacement.borderPixels = borderPixels;
atlasPlacement.occupiedCells = RectU::withSize(Vec2U(fitCellX, fitCellY), Vec2U(numCellsX, numCellsY));
if (borderPixels)
atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize + 1, fitCellY * m_atlasCellSize + 1), imageSize - Vec2U(2, 2));
else
atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), imageSize);
return atlasPlacement;
}
}
#endif

View file

@ -0,0 +1,60 @@
#include "StarUserGeneratedContentService_pc_steam.hpp"
#include "StarLogging.hpp"
#include "StarLexicalCast.hpp"
namespace Star {
SteamUserGeneratedContentService::SteamUserGeneratedContentService(PcPlatformServicesStatePtr)
: m_callbackDownloadResult(this, &SteamUserGeneratedContentService::onDownloadResult) {};
StringList SteamUserGeneratedContentService::subscribedContentIds() const {
List<PublishedFileId_t> contentIds(SteamUGC()->GetNumSubscribedItems(), {});
SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size());
return contentIds.transformed([](PublishedFileId_t id) {
return String(toString(id));
});
}
Maybe<String> SteamUserGeneratedContentService::contentDownloadDirectory(String const& contentId) const {
PublishedFileId_t id = lexicalCast<PublishedFileId_t>(contentId);
uint32 itemState = SteamUGC()->GetItemState(id);
if (itemState & k_EItemStateInstalled) {
char path[4096];
if (SteamUGC()->GetItemInstallInfo(id, nullptr, path, sizeof(path), nullptr))
return String(path);
}
return {};
}
bool SteamUserGeneratedContentService::triggerContentDownload() {
List<PublishedFileId_t> contentIds(SteamUGC()->GetNumSubscribedItems(), {});
SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size());
for (uint64 contentId : contentIds) {
if (!m_currentDownloadState.contains(contentId)) {
uint32 itemState = SteamUGC()->GetItemState(contentId);
if (!(itemState & k_EItemStateInstalled) || itemState & k_EItemStateNeedsUpdate) {
SteamUGC()->DownloadItem(contentId, true);
itemState = SteamUGC()->GetItemState(contentId);
m_currentDownloadState[contentId] = !(itemState & k_EItemStateDownloading);
} else {
m_currentDownloadState[contentId] = true;
}
}
}
bool allDownloaded = true;
for (auto const& p : m_currentDownloadState) {
if (!p.second)
allDownloaded = false;
}
return allDownloaded;
}
void SteamUserGeneratedContentService::onDownloadResult(DownloadItemResult_t* result) {
m_currentDownloadState[result->m_nPublishedFileId] = true;
}
}

View file

@ -0,0 +1,26 @@
#ifndef STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP
#define STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP
#include "StarPlatformServices_pc.hpp"
namespace Star {
STAR_CLASS(SteamUserGeneratedContentService);
class SteamUserGeneratedContentService : public UserGeneratedContentService {
public:
SteamUserGeneratedContentService(PcPlatformServicesStatePtr state);
StringList subscribedContentIds() const override;
Maybe<String> contentDownloadDirectory(String const& contentId) const override;
bool triggerContentDownload() override;
private:
STEAM_CALLBACK(SteamUserGeneratedContentService, onDownloadResult, DownloadItemResult_t, m_callbackDownloadResult);
HashMap<PublishedFileId_t, bool> m_currentDownloadState;
};
}
#endif

View file

@ -0,0 +1,177 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "activity_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class ActivityEvents final {
public:
static void OnActivityJoin(void* callbackData, char const* secret)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->ActivityManager();
module.OnActivityJoin(static_cast<const char*>(secret));
}
static void OnActivitySpectate(void* callbackData, char const* secret)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->ActivityManager();
module.OnActivitySpectate(static_cast<const char*>(secret));
}
static void OnActivityJoinRequest(void* callbackData, DiscordUser* user)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->ActivityManager();
module.OnActivityJoinRequest(*reinterpret_cast<User const*>(user));
}
static void OnActivityInvite(void* callbackData,
EDiscordActivityActionType type,
DiscordUser* user,
DiscordActivity* activity)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->ActivityManager();
module.OnActivityInvite(static_cast<ActivityActionType>(type),
*reinterpret_cast<User const*>(user),
*reinterpret_cast<Activity const*>(activity));
}
};
IDiscordActivityEvents ActivityManager::events_{
&ActivityEvents::OnActivityJoin,
&ActivityEvents::OnActivitySpectate,
&ActivityEvents::OnActivityJoinRequest,
&ActivityEvents::OnActivityInvite,
};
Result ActivityManager::RegisterCommand(char const* command)
{
auto result = internal_->register_command(internal_, const_cast<char*>(command));
return static_cast<Result>(result);
}
Result ActivityManager::RegisterSteam(std::uint32_t steamId)
{
auto result = internal_->register_steam(internal_, steamId);
return static_cast<Result>(result);
}
void ActivityManager::UpdateActivity(Activity const& activity, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->update_activity(internal_,
reinterpret_cast<DiscordActivity*>(const_cast<Activity*>(&activity)),
cb.release(),
wrapper);
}
void ActivityManager::ClearActivity(std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->clear_activity(internal_, cb.release(), wrapper);
}
void ActivityManager::SendRequestReply(UserId userId,
ActivityJoinRequestReply reply,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->send_request_reply(internal_,
userId,
static_cast<EDiscordActivityJoinRequestReply>(reply),
cb.release(),
wrapper);
}
void ActivityManager::SendInvite(UserId userId,
ActivityActionType type,
char const* content,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->send_invite(internal_,
userId,
static_cast<EDiscordActivityActionType>(type),
const_cast<char*>(content),
cb.release(),
wrapper);
}
void ActivityManager::AcceptInvite(UserId userId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->accept_invite(internal_, userId, cb.release(), wrapper);
}
} // namespace discord

View file

@ -0,0 +1,42 @@
#pragma once
#include "types.h"
namespace discord {
class ActivityManager final {
public:
~ActivityManager() = default;
Result RegisterCommand(char const* command);
Result RegisterSteam(std::uint32_t steamId);
void UpdateActivity(Activity const& activity, std::function<void(Result)> callback);
void ClearActivity(std::function<void(Result)> callback);
void SendRequestReply(UserId userId,
ActivityJoinRequestReply reply,
std::function<void(Result)> callback);
void SendInvite(UserId userId,
ActivityActionType type,
char const* content,
std::function<void(Result)> callback);
void AcceptInvite(UserId userId, std::function<void(Result)> callback);
Event<char const*> OnActivityJoin;
Event<char const*> OnActivitySpectate;
Event<User const&> OnActivityJoinRequest;
Event<ActivityActionType, User const&, Activity const&> OnActivityInvite;
private:
friend class Core;
ActivityManager() = default;
ActivityManager(ActivityManager const& rhs) = delete;
ActivityManager& operator=(ActivityManager const& rhs) = delete;
ActivityManager(ActivityManager&& rhs) = delete;
ActivityManager& operator=(ActivityManager&& rhs) = delete;
IDiscordActivityManager* internal_;
static IDiscordActivityEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,63 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "application_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
void ApplicationManager::ValidateOrExit(std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->validate_or_exit(internal_, cb.release(), wrapper);
}
void ApplicationManager::GetCurrentLocale(char locale[128])
{
if (!locale) {
return;
}
internal_->get_current_locale(internal_, reinterpret_cast<DiscordLocale*>(locale));
}
void ApplicationManager::GetCurrentBranch(char branch[4096])
{
if (!branch) {
return;
}
internal_->get_current_branch(internal_, reinterpret_cast<DiscordBranch*>(branch));
}
void ApplicationManager::GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void {
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb(
reinterpret_cast<std::function<void(Result, OAuth2Token const&)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<OAuth2Token const*>(oauth2Token));
};
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb{};
cb.reset(new std::function<void(Result, OAuth2Token const&)>(std::move(callback)));
internal_->get_oauth2_token(internal_, cb.release(), wrapper);
}
} // namespace discord

View file

@ -0,0 +1,29 @@
#pragma once
#include "types.h"
namespace discord {
class ApplicationManager final {
public:
~ApplicationManager() = default;
void ValidateOrExit(std::function<void(Result)> callback);
void GetCurrentLocale(char locale[128]);
void GetCurrentBranch(char branch[4096]);
void GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback);
private:
friend class Core;
ApplicationManager() = default;
ApplicationManager(ApplicationManager const& rhs) = delete;
ApplicationManager& operator=(ApplicationManager const& rhs) = delete;
ApplicationManager(ApplicationManager&& rhs) = delete;
ApplicationManager& operator=(ApplicationManager&& rhs) = delete;
IDiscordApplicationManager* internal_;
static IDiscordApplicationEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,162 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance)
{
if (!instance) {
return Result::InternalError;
}
(*instance) = new Core();
DiscordCreateParams params{};
DiscordCreateParamsSetDefault(&params);
params.client_id = clientId;
params.flags = flags;
params.events = nullptr;
params.event_data = *instance;
params.user_events = &UserManager::events_;
params.activity_events = &ActivityManager::events_;
params.relationship_events = &RelationshipManager::events_;
params.lobby_events = &LobbyManager::events_;
params.network_events = &NetworkManager::events_;
params.overlay_events = &OverlayManager::events_;
params.store_events = &StoreManager::events_;
auto result = DiscordCreate(DISCORD_VERSION, &params, &((*instance)->internal_));
if (result != DiscordResult_Ok || !(*instance)->internal_) {
delete (*instance);
(*instance) = nullptr;
}
return static_cast<Result>(result);
}
Core::~Core()
{
if (internal_) {
internal_->destroy(internal_);
internal_ = nullptr;
}
}
Result Core::RunCallbacks()
{
auto result = internal_->run_callbacks(internal_);
return static_cast<Result>(result);
}
void Core::SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook)
{
setLogHook_.DisconnectAll();
setLogHook_.Connect(std::move(hook));
static auto wrapper =
[](void* callbackData, EDiscordLogLevel level, char const* message) -> void {
auto cb(reinterpret_cast<decltype(setLogHook_)*>(callbackData));
if (!cb) {
return;
}
(*cb)(static_cast<LogLevel>(level), static_cast<const char*>(message));
};
internal_->set_log_hook(
internal_, static_cast<EDiscordLogLevel>(minLevel), &setLogHook_, wrapper);
}
discord::ApplicationManager& Core::ApplicationManager()
{
if (!applicationManager_.internal_) {
applicationManager_.internal_ = internal_->get_application_manager(internal_);
}
return applicationManager_;
}
discord::UserManager& Core::UserManager()
{
if (!userManager_.internal_) {
userManager_.internal_ = internal_->get_user_manager(internal_);
}
return userManager_;
}
discord::ImageManager& Core::ImageManager()
{
if (!imageManager_.internal_) {
imageManager_.internal_ = internal_->get_image_manager(internal_);
}
return imageManager_;
}
discord::ActivityManager& Core::ActivityManager()
{
if (!activityManager_.internal_) {
activityManager_.internal_ = internal_->get_activity_manager(internal_);
}
return activityManager_;
}
discord::RelationshipManager& Core::RelationshipManager()
{
if (!relationshipManager_.internal_) {
relationshipManager_.internal_ = internal_->get_relationship_manager(internal_);
}
return relationshipManager_;
}
discord::LobbyManager& Core::LobbyManager()
{
if (!lobbyManager_.internal_) {
lobbyManager_.internal_ = internal_->get_lobby_manager(internal_);
}
return lobbyManager_;
}
discord::NetworkManager& Core::NetworkManager()
{
if (!networkManager_.internal_) {
networkManager_.internal_ = internal_->get_network_manager(internal_);
}
return networkManager_;
}
discord::OverlayManager& Core::OverlayManager()
{
if (!overlayManager_.internal_) {
overlayManager_.internal_ = internal_->get_overlay_manager(internal_);
}
return overlayManager_;
}
discord::StorageManager& Core::StorageManager()
{
if (!storageManager_.internal_) {
storageManager_.internal_ = internal_->get_storage_manager(internal_);
}
return storageManager_;
}
discord::StoreManager& Core::StoreManager()
{
if (!storeManager_.internal_) {
storeManager_.internal_ = internal_->get_store_manager(internal_);
}
return storeManager_;
}
} // namespace discord

View file

@ -0,0 +1,58 @@
#pragma once
#include "types.h"
#include "application_manager.h"
#include "user_manager.h"
#include "image_manager.h"
#include "activity_manager.h"
#include "relationship_manager.h"
#include "lobby_manager.h"
#include "network_manager.h"
#include "overlay_manager.h"
#include "storage_manager.h"
#include "store_manager.h"
namespace discord {
class Core final {
public:
static Result Create(ClientId clientId, std::uint64_t flags, Core** instance);
~Core();
Result RunCallbacks();
void SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook);
discord::ApplicationManager& ApplicationManager();
discord::UserManager& UserManager();
discord::ImageManager& ImageManager();
discord::ActivityManager& ActivityManager();
discord::RelationshipManager& RelationshipManager();
discord::LobbyManager& LobbyManager();
discord::NetworkManager& NetworkManager();
discord::OverlayManager& OverlayManager();
discord::StorageManager& StorageManager();
discord::StoreManager& StoreManager();
private:
Core() = default;
Core(Core const& rhs) = delete;
Core& operator=(Core const& rhs) = delete;
Core(Core&& rhs) = delete;
Core& operator=(Core&& rhs) = delete;
IDiscordCore* internal_;
Event<LogLevel, char const*> setLogHook_;
discord::ApplicationManager applicationManager_;
discord::UserManager userManager_;
discord::ImageManager imageManager_;
discord::ActivityManager activityManager_;
discord::RelationshipManager relationshipManager_;
discord::LobbyManager lobbyManager_;
discord::NetworkManager networkManager_;
discord::OverlayManager overlayManager_;
discord::StorageManager storageManager_;
discord::StoreManager storeManager_;
};
} // namespace discord

View file

@ -0,0 +1,14 @@
#pragma once
#include "types.h"
#include "core.h"
#include "application_manager.h"
#include "user_manager.h"
#include "image_manager.h"
#include "activity_manager.h"
#include "relationship_manager.h"
#include "lobby_manager.h"
#include "network_manager.h"
#include "overlay_manager.h"
#include "storage_manager.h"
#include "store_manager.h"

View file

@ -0,0 +1,59 @@
#pragma once
#include <functional>
#include <vector>
namespace discord {
template <typename... Args>
class Event final {
public:
using Token = int;
Event() { slots_.reserve(4); }
Event(Event const&) = default;
Event(Event&&) = default;
~Event() = default;
Event& operator=(Event const&) = default;
Event& operator=(Event&&) = default;
template <typename EventHandler>
Token Connect(EventHandler slot)
{
slots_.emplace_back(Slot{nextToken_, std::move(slot)});
return nextToken_++;
}
void Disconnect(Token token)
{
for (auto& slot : slots_) {
if (slot.token == token) {
slot = slots_.back();
slots_.pop_back();
break;
}
}
}
void DisconnectAll() { slots_ = {}; }
void operator()(Args... args)
{
for (auto const& slot : slots_) {
slot.fn(std::forward<Args>(args)...);
}
}
private:
struct Slot {
Token token;
std::function<void(Args...)> fn;
};
Token nextToken_{};
std::vector<Slot> slots_{};
};
} // namespace discord

View file

@ -0,0 +1,790 @@
#ifndef _DISCORD_GAME_SDK_H_
#define _DISCORD_GAME_SDK_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <string.h>
#ifndef __cplusplus
#include <stdbool.h>
#endif
#define DISCORD_VERSION 2
#define DISCORD_APPLICATION_MANAGER_VERSION 1
#define DISCORD_USER_MANAGER_VERSION 1
#define DISCORD_IMAGE_MANAGER_VERSION 1
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
#define DISCORD_LOBBY_MANAGER_VERSION 1
#define DISCORD_NETWORK_MANAGER_VERSION 1
#define DISCORD_OVERLAY_MANAGER_VERSION 1
#define DISCORD_STORAGE_MANAGER_VERSION 1
#define DISCORD_STORE_MANAGER_VERSION 1
enum EDiscordResult {
DiscordResult_Ok,
DiscordResult_ServiceUnavailable,
DiscordResult_InvalidVersion,
DiscordResult_LockFailed,
DiscordResult_InternalError,
DiscordResult_InvalidPayload,
DiscordResult_InvalidCommand,
DiscordResult_InvalidPermissions,
DiscordResult_NotFetched,
DiscordResult_NotFound,
DiscordResult_Conflict,
DiscordResult_InvalidSecret,
DiscordResult_InvalidJoinSecret,
DiscordResult_NoEligibleActivity,
DiscordResult_InvalidInvite,
DiscordResult_NotAuthenticated,
DiscordResult_InvalidAccessToken,
DiscordResult_ApplicationMismatch,
DiscordResult_InvalidDataUrl,
DiscordResult_InvalidBase64,
DiscordResult_NotFiltered,
DiscordResult_LobbyFull,
DiscordResult_InvalidLobbySecret,
DiscordResult_InvalidFilename,
DiscordResult_InvalidFileSize,
DiscordResult_InvalidEntitlement,
DiscordResult_NotInstalled,
DiscordResult_NotRunning,
DiscordResult_InsufficientBuffer,
DiscordResult_PurchaseCanceled,
};
enum EDiscordCreateFlags {
DiscordCreateFlags_Default = 0,
DiscordCreateFlags_NoRequireDiscord = 1,
};
enum EDiscordLogLevel {
DiscordLogLevel_Error = 1,
DiscordLogLevel_Warn,
DiscordLogLevel_Info,
DiscordLogLevel_Debug,
};
enum EDiscordImageType {
DiscordImageType_User,
};
enum EDiscordActivityType {
DiscordActivityType_Playing,
DiscordActivityType_Streaming,
DiscordActivityType_Listening,
DiscordActivityType_Watching,
};
enum EDiscordActivityActionType {
DiscordActivityActionType_Join = 1,
DiscordActivityActionType_Spectate,
};
enum EDiscordActivityJoinRequestReply {
DiscordActivityJoinRequestReply_No,
DiscordActivityJoinRequestReply_Yes,
DiscordActivityJoinRequestReply_Ignore,
};
enum EDiscordStatus {
DiscordStatus_Offline = 0,
DiscordStatus_Online = 1,
DiscordStatus_Idle = 2,
DiscordStatus_DoNotDisturb = 3,
};
enum EDiscordRelationshipType {
DiscordRelationshipType_None,
DiscordRelationshipType_Friend,
DiscordRelationshipType_Blocked,
DiscordRelationshipType_PendingIncoming,
DiscordRelationshipType_PendingOutgoing,
DiscordRelationshipType_Implicit,
};
enum EDiscordLobbyType {
DiscordLobbyType_Private = 1,
DiscordLobbyType_Public,
};
enum EDiscordLobbySearchComparison {
DiscordLobbySearchComparison_LessThanOrEqual = -2,
DiscordLobbySearchComparison_LessThan,
DiscordLobbySearchComparison_Equal,
DiscordLobbySearchComparison_GreaterThan,
DiscordLobbySearchComparison_GreaterThanOrEqual,
DiscordLobbySearchComparison_NotEqual,
};
enum EDiscordLobbySearchCast {
DiscordLobbySearchCast_String = 1,
DiscordLobbySearchCast_Number,
};
enum EDiscordLobbySearchDistance {
DiscordLobbySearchDistance_Local,
DiscordLobbySearchDistance_Default,
DiscordLobbySearchDistance_Extended,
DiscordLobbySearchDistance_Global,
};
enum EDiscordEntitlementType {
DiscordEntitlementType_Purchase = 1,
DiscordEntitlementType_PremiumSubscription,
DiscordEntitlementType_DeveloperGift,
};
enum EDiscordSkuType {
DiscordSkuType_Application = 1,
DiscordSkuType_DLC,
DiscordSkuType_Consumable,
DiscordSkuType_Bundle,
};
typedef int64_t DiscordClientId;
typedef int32_t DiscordVersion;
typedef int64_t DiscordSnowflake;
typedef int64_t DiscordTimestamp;
typedef DiscordSnowflake DiscordUserId;
typedef char DiscordLocale[128];
typedef char DiscordBranch[4096];
typedef DiscordSnowflake DiscordLobbyId;
typedef char DiscordLobbySecret[128];
typedef char DiscordMetadataKey[256];
typedef char DiscordMetadataValue[4096];
typedef uint64_t DiscordNetworkPeerId;
typedef uint8_t DiscordNetworkChannelId;
struct DiscordUser {
DiscordUserId id;
char username[256];
char discriminator[8];
char avatar[128];
bool bot;
};
struct DiscordOAuth2Token {
char access_token[128];
char scopes[1024];
DiscordTimestamp expires;
};
struct DiscordImageHandle {
enum EDiscordImageType type;
int64_t id;
uint32_t size;
};
struct DiscordImageDimensions {
uint32_t width;
uint32_t height;
};
struct DiscordActivityTimestamps {
DiscordTimestamp start;
DiscordTimestamp end;
};
struct DiscordActivityAssets {
char large_image[128];
char large_text[128];
char small_image[128];
char small_text[128];
};
struct DiscordPartySize {
int32_t current_size;
int32_t max_size;
};
struct DiscordActivityParty {
char id[128];
struct DiscordPartySize size;
};
struct DiscordActivitySecrets {
char match[128];
char join[128];
char spectate[128];
};
struct DiscordActivity {
enum EDiscordActivityType type;
int64_t application_id;
char name[128];
char state[128];
char details[128];
struct DiscordActivityTimestamps timestamps;
struct DiscordActivityAssets assets;
struct DiscordActivityParty party;
struct DiscordActivitySecrets secrets;
bool instance;
};
struct DiscordPresence {
enum EDiscordStatus status;
struct DiscordActivity activity;
};
struct DiscordRelationship {
enum EDiscordRelationshipType type;
struct DiscordUser user;
struct DiscordPresence presence;
};
struct DiscordLobby {
DiscordLobbyId id;
enum EDiscordLobbyType type;
DiscordUserId owner_id;
DiscordLobbySecret secret;
uint32_t capacity;
bool locked;
};
struct DiscordFileStat {
char filename[260];
uint64_t size;
uint64_t last_modified;
};
struct DiscordEntitlement {
DiscordSnowflake id;
enum EDiscordEntitlementType type;
DiscordSnowflake sku_id;
};
struct DiscordSkuPrice {
uint32_t amount;
char currency[16];
};
struct DiscordSku {
DiscordSnowflake id;
enum EDiscordSkuType type;
char name[256];
struct DiscordSkuPrice price;
};
struct IDiscordLobbyTransaction {
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction,
enum EDiscordLobbyType type);
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction,
DiscordUserId owner_id);
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction,
uint32_t capacity);
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
DiscordMetadataKey key,
DiscordMetadataValue value);
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
DiscordMetadataKey key);
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction,
bool locked);
};
struct IDiscordLobbyMemberTransaction {
enum EDiscordResult (*set_metadata)(
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
DiscordMetadataKey key,
DiscordMetadataValue value);
enum EDiscordResult (*delete_metadata)(
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
DiscordMetadataKey key);
};
struct IDiscordLobbySearchQuery {
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query,
DiscordMetadataKey key,
enum EDiscordLobbySearchComparison comparison,
enum EDiscordLobbySearchCast cast,
DiscordMetadataValue value);
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query,
DiscordMetadataKey key,
enum EDiscordLobbySearchCast cast,
DiscordMetadataValue value);
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query,
uint32_t limit);
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query,
enum EDiscordLobbySearchDistance distance);
};
typedef void* IDiscordApplicationEvents;
struct IDiscordApplicationManager {
void (*validate_or_exit)(struct IDiscordApplicationManager* manager,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordOAuth2Token* oauth2_token));
};
struct IDiscordUserEvents {
void (*on_current_user_update)(void* event_data);
};
struct IDiscordUserManager {
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager,
struct DiscordUser* current_user);
void (*get_user)(struct IDiscordUserManager* manager,
DiscordUserId user_id,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordUser* user));
};
typedef void* IDiscordImageEvents;
struct IDiscordImageManager {
void (*fetch)(struct IDiscordImageManager* manager,
struct DiscordImageHandle handle,
bool refresh,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordImageHandle handle_result));
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager,
struct DiscordImageHandle handle,
struct DiscordImageDimensions* dimensions);
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager,
struct DiscordImageHandle handle,
uint8_t* data,
uint32_t data_length);
};
struct IDiscordActivityEvents {
void (*on_activity_join)(void* event_data, const char* secret);
void (*on_activity_spectate)(void* event_data, const char* secret);
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
void (*on_activity_invite)(void* event_data,
enum EDiscordActivityActionType type,
struct DiscordUser* user,
struct DiscordActivity* activity);
};
struct IDiscordActivityManager {
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager,
const char* command);
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager,
uint32_t steam_id);
void (*update_activity)(struct IDiscordActivityManager* manager,
struct DiscordActivity* activity,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*clear_activity)(struct IDiscordActivityManager* manager,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*send_request_reply)(struct IDiscordActivityManager* manager,
DiscordUserId user_id,
enum EDiscordActivityJoinRequestReply reply,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*send_invite)(struct IDiscordActivityManager* manager,
DiscordUserId user_id,
enum EDiscordActivityActionType type,
const char* content,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*accept_invite)(struct IDiscordActivityManager* manager,
DiscordUserId user_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
};
struct IDiscordRelationshipEvents {
void (*on_refresh)(void* event_data);
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
};
struct IDiscordRelationshipManager {
void (*filter)(struct IDiscordRelationshipManager* manager,
void* filter_data,
bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager,
DiscordUserId user_id,
struct DiscordRelationship* relationship);
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager,
uint32_t index,
struct DiscordRelationship* relationship);
};
struct IDiscordLobbyEvents {
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
void (*on_lobby_message)(void* event_data,
int64_t lobby_id,
int64_t user_id,
uint8_t* data,
uint32_t data_length);
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
void (*on_network_message)(void* event_data,
int64_t lobby_id,
int64_t user_id,
uint8_t channel_id,
uint8_t* data,
uint32_t data_length);
};
struct IDiscordLobbyManager {
enum EDiscordResult (*get_lobby_create_transaction)(
struct IDiscordLobbyManager* manager,
struct IDiscordLobbyTransaction** transaction);
enum EDiscordResult (*get_lobby_update_transaction)(
struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
struct IDiscordLobbyTransaction** transaction);
enum EDiscordResult (*get_member_update_transaction)(
struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
struct IDiscordLobbyMemberTransaction** transaction);
void (*create_lobby)(struct IDiscordLobbyManager* manager,
struct IDiscordLobbyTransaction* transaction,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordLobby* lobby));
void (*update_lobby)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
struct IDiscordLobbyTransaction* transaction,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*delete_lobby)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*connect_lobby)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordLobbySecret secret,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordLobby* lobby));
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager,
DiscordLobbySecret activity_secret,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
struct DiscordLobby* lobby));
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
struct DiscordLobby* lobby);
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordLobbySecret* secret);
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordMetadataKey key,
DiscordMetadataValue* value);
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
int32_t index,
DiscordMetadataKey* key);
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
int32_t* count);
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
int32_t* count);
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
int32_t index,
DiscordUserId* user_id);
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
struct DiscordUser* user);
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
DiscordMetadataKey key,
DiscordMetadataValue* value);
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
int32_t index,
DiscordMetadataKey* key);
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
int32_t* count);
void (*update_member)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
struct IDiscordLobbyMemberTransaction* transaction,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*send_lobby_message)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
uint8_t* data,
uint32_t data_length,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager,
struct IDiscordLobbySearchQuery** query);
void (*search)(struct IDiscordLobbyManager* manager,
struct IDiscordLobbySearchQuery* query,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager,
int32_t index,
DiscordLobbyId* lobby_id);
void (*connect_voice)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*disconnect_voice)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id);
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id);
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
uint8_t channel_id,
bool reliable);
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager,
DiscordLobbyId lobby_id,
DiscordUserId user_id,
uint8_t channel_id,
uint8_t* data,
uint32_t data_length);
};
struct IDiscordNetworkEvents {
void (*on_message)(void* event_data,
DiscordNetworkPeerId peer_id,
DiscordNetworkChannelId channel_id,
uint8_t* data,
uint32_t data_length);
void (*on_route_update)(void* event_data, const char* route_data);
};
struct IDiscordNetworkManager {
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id,
const char* route_data);
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id,
const char* route_data);
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id);
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id,
DiscordNetworkChannelId channel_id,
bool reliable);
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id,
DiscordNetworkChannelId channel_id);
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager,
DiscordNetworkPeerId peer_id,
DiscordNetworkChannelId channel_id,
uint8_t* data,
uint32_t data_length);
};
struct IDiscordOverlayEvents {
void (*on_toggle)(void* event_data, bool locked);
};
struct IDiscordOverlayManager {
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
void (*set_locked)(struct IDiscordOverlayManager* manager,
bool locked,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*open_activity_invite)(struct IDiscordOverlayManager* manager,
enum EDiscordActivityActionType type,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*open_guild_invite)(struct IDiscordOverlayManager* manager,
const char* code,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
};
typedef void* IDiscordStorageEvents;
struct IDiscordStorageManager {
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager,
const char* name,
uint8_t* data,
uint32_t data_length,
uint32_t* read);
void (*read_async)(struct IDiscordStorageManager* manager,
const char* name,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
uint8_t* data,
uint32_t data_length));
void (*read_async_partial)(struct IDiscordStorageManager* manager,
const char* name,
uint64_t offset,
uint64_t length,
void* callback_data,
void (*callback)(void* callback_data,
enum EDiscordResult result,
uint8_t* data,
uint32_t data_length));
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager,
const char* name,
uint8_t* data,
uint32_t data_length);
void (*write_async)(struct IDiscordStorageManager* manager,
const char* name,
uint8_t* data,
uint32_t data_length,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager,
const char* name,
bool* exists);
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager,
const char* name,
struct DiscordFileStat* stat);
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager,
int32_t index,
struct DiscordFileStat* stat);
};
struct IDiscordStoreEvents {
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
};
struct IDiscordStoreManager {
void (*fetch_skus)(struct IDiscordStoreManager* manager,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager,
DiscordSnowflake sku_id,
struct DiscordSku* sku);
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager,
int32_t index,
struct DiscordSku* sku);
void (*fetch_entitlements)(struct IDiscordStoreManager* manager,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager,
DiscordSnowflake entitlement_id,
struct DiscordEntitlement* entitlement);
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager,
int32_t index,
struct DiscordEntitlement* entitlement);
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager,
DiscordSnowflake sku_id,
bool* has_entitlement);
void (*start_purchase)(struct IDiscordStoreManager* manager,
DiscordSnowflake sku_id,
void* callback_data,
void (*callback)(void* callback_data, enum EDiscordResult result));
};
typedef void* IDiscordCoreEvents;
struct IDiscordCore {
void (*destroy)(struct IDiscordCore* core);
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
void (*set_log_hook)(struct IDiscordCore* core,
enum EDiscordLogLevel min_level,
void* hook_data,
void (*hook)(void* hook_data,
enum EDiscordLogLevel level,
const char* message));
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
};
struct DiscordCreateParams {
DiscordClientId client_id;
uint64_t flags;
IDiscordCoreEvents* events;
void* event_data;
IDiscordApplicationEvents* application_events;
DiscordVersion application_version;
struct IDiscordUserEvents* user_events;
DiscordVersion user_version;
IDiscordImageEvents* image_events;
DiscordVersion image_version;
struct IDiscordActivityEvents* activity_events;
DiscordVersion activity_version;
struct IDiscordRelationshipEvents* relationship_events;
DiscordVersion relationship_version;
struct IDiscordLobbyEvents* lobby_events;
DiscordVersion lobby_version;
struct IDiscordNetworkEvents* network_events;
DiscordVersion network_version;
struct IDiscordOverlayEvents* overlay_events;
DiscordVersion overlay_version;
IDiscordStorageEvents* storage_events;
DiscordVersion storage_version;
struct IDiscordStoreEvents* store_events;
DiscordVersion store_version;
};
#ifdef __cplusplus
inline
#else
static
#endif
void
DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
{
memset(params, 0, sizeof(struct DiscordCreateParams));
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
params->user_version = DISCORD_USER_MANAGER_VERSION;
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
params->store_version = DISCORD_STORE_MANAGER_VERSION;
}
enum EDiscordResult DiscordCreate(DiscordVersion version,
struct DiscordCreateParams* params,
struct IDiscordCore** result);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,57 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "image_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
void ImageManager::Fetch(ImageHandle handle,
bool refresh,
std::function<void(Result, ImageHandle)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void {
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb(
reinterpret_cast<std::function<void(Result, ImageHandle)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<ImageHandle const*>(&handleResult));
};
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb{};
cb.reset(new std::function<void(Result, ImageHandle)>(std::move(callback)));
internal_->fetch(internal_,
*reinterpret_cast<DiscordImageHandle const*>(&handle),
(refresh ? 1 : 0),
cb.release(),
wrapper);
}
Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions)
{
if (!dimensions) {
return Result::InternalError;
}
auto result = internal_->get_dimensions(internal_,
*reinterpret_cast<DiscordImageHandle const*>(&handle),
reinterpret_cast<DiscordImageDimensions*>(dimensions));
return static_cast<Result>(result);
}
Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength)
{
auto result = internal_->get_data(internal_,
*reinterpret_cast<DiscordImageHandle const*>(&handle),
reinterpret_cast<uint8_t*>(data),
dataLength);
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,28 @@
#pragma once
#include "types.h"
namespace discord {
class ImageManager final {
public:
~ImageManager() = default;
void Fetch(ImageHandle handle, bool refresh, std::function<void(Result, ImageHandle)> callback);
Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions);
Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength);
private:
friend class Core;
ImageManager() = default;
ImageManager(ImageManager const& rhs) = delete;
ImageManager& operator=(ImageManager const& rhs) = delete;
ImageManager(ImageManager&& rhs) = delete;
ImageManager& operator=(ImageManager&& rhs) = delete;
IDiscordImageManager* internal_;
static IDiscordImageEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,547 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "lobby_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class LobbyEvents final {
public:
static void OnLobbyUpdate(void* callbackData, int64_t lobbyId)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnLobbyUpdate(lobbyId);
}
static void OnLobbyDelete(void* callbackData, int64_t lobbyId, uint32_t reason)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnLobbyDelete(lobbyId, reason);
}
static void OnMemberConnect(void* callbackData, int64_t lobbyId, int64_t userId)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnMemberConnect(lobbyId, userId);
}
static void OnMemberUpdate(void* callbackData, int64_t lobbyId, int64_t userId)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnMemberUpdate(lobbyId, userId);
}
static void OnMemberDisconnect(void* callbackData, int64_t lobbyId, int64_t userId)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnMemberDisconnect(lobbyId, userId);
}
static void OnLobbyMessage(void* callbackData,
int64_t lobbyId,
int64_t userId,
uint8_t* data,
uint32_t dataLength)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnLobbyMessage(lobbyId, userId, data, dataLength);
}
static void OnSpeaking(void* callbackData, int64_t lobbyId, int64_t userId, bool speaking)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnSpeaking(lobbyId, userId, (speaking != 0));
}
static void OnNetworkMessage(void* callbackData,
int64_t lobbyId,
int64_t userId,
uint8_t channelId,
uint8_t* data,
uint32_t dataLength)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->LobbyManager();
module.OnNetworkMessage(lobbyId, userId, channelId, data, dataLength);
}
};
IDiscordLobbyEvents LobbyManager::events_{
&LobbyEvents::OnLobbyUpdate,
&LobbyEvents::OnLobbyDelete,
&LobbyEvents::OnMemberConnect,
&LobbyEvents::OnMemberUpdate,
&LobbyEvents::OnMemberDisconnect,
&LobbyEvents::OnLobbyMessage,
&LobbyEvents::OnSpeaking,
&LobbyEvents::OnNetworkMessage,
};
Result LobbyManager::GetLobbyCreateTransaction(LobbyTransaction* transaction)
{
if (!transaction) {
return Result::InternalError;
}
auto result = internal_->get_lobby_create_transaction(internal_, transaction->Receive());
return static_cast<Result>(result);
}
Result LobbyManager::GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction)
{
if (!transaction) {
return Result::InternalError;
}
auto result =
internal_->get_lobby_update_transaction(internal_, lobbyId, transaction->Receive());
return static_cast<Result>(result);
}
Result LobbyManager::GetMemberUpdateTransaction(LobbyId lobbyId,
UserId userId,
LobbyMemberTransaction* transaction)
{
if (!transaction) {
return Result::InternalError;
}
auto result =
internal_->get_member_update_transaction(internal_, lobbyId, userId, transaction->Receive());
return static_cast<Result>(result);
}
void LobbyManager::CreateLobby(LobbyTransaction const& transaction,
std::function<void(Result, Lobby const&)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
};
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
internal_->create_lobby(
internal_, const_cast<LobbyTransaction&>(transaction).Internal(), cb.release(), wrapper);
}
void LobbyManager::UpdateLobby(LobbyId lobbyId,
LobbyTransaction const& transaction,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->update_lobby(internal_,
lobbyId,
const_cast<LobbyTransaction&>(transaction).Internal(),
cb.release(),
wrapper);
}
void LobbyManager::DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->delete_lobby(internal_, lobbyId, cb.release(), wrapper);
}
void LobbyManager::ConnectLobby(LobbyId lobbyId,
LobbySecret secret,
std::function<void(Result, Lobby const&)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
};
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
internal_->connect_lobby(internal_, lobbyId, const_cast<char*>(secret), cb.release(), wrapper);
}
void LobbyManager::ConnectLobbyWithActivitySecret(
LobbySecret activitySecret,
std::function<void(Result, Lobby const&)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
};
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
internal_->connect_lobby_with_activity_secret(
internal_, const_cast<char*>(activitySecret), cb.release(), wrapper);
}
void LobbyManager::DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->disconnect_lobby(internal_, lobbyId, cb.release(), wrapper);
}
Result LobbyManager::GetLobby(LobbyId lobbyId, Lobby* lobby)
{
if (!lobby) {
return Result::InternalError;
}
auto result = internal_->get_lobby(internal_, lobbyId, reinterpret_cast<DiscordLobby*>(lobby));
return static_cast<Result>(result);
}
Result LobbyManager::GetLobbyActivitySecret(LobbyId lobbyId, char secret[128])
{
if (!secret) {
return Result::InternalError;
}
auto result = internal_->get_lobby_activity_secret(
internal_, lobbyId, reinterpret_cast<DiscordLobbySecret*>(secret));
return static_cast<Result>(result);
}
Result LobbyManager::GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096])
{
if (!value) {
return Result::InternalError;
}
auto result = internal_->get_lobby_metadata_value(
internal_, lobbyId, const_cast<char*>(key), reinterpret_cast<DiscordMetadataValue*>(value));
return static_cast<Result>(result);
}
Result LobbyManager::GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256])
{
if (!key) {
return Result::InternalError;
}
auto result = internal_->get_lobby_metadata_key(
internal_, lobbyId, index, reinterpret_cast<DiscordMetadataKey*>(key));
return static_cast<Result>(result);
}
Result LobbyManager::LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count)
{
if (!count) {
return Result::InternalError;
}
auto result =
internal_->lobby_metadata_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
return static_cast<Result>(result);
}
Result LobbyManager::MemberCount(LobbyId lobbyId, std::int32_t* count)
{
if (!count) {
return Result::InternalError;
}
auto result = internal_->member_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
return static_cast<Result>(result);
}
Result LobbyManager::GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId)
{
if (!userId) {
return Result::InternalError;
}
auto result =
internal_->get_member_user_id(internal_, lobbyId, index, reinterpret_cast<int64_t*>(userId));
return static_cast<Result>(result);
}
Result LobbyManager::GetMemberUser(LobbyId lobbyId, UserId userId, User* user)
{
if (!user) {
return Result::InternalError;
}
auto result =
internal_->get_member_user(internal_, lobbyId, userId, reinterpret_cast<DiscordUser*>(user));
return static_cast<Result>(result);
}
Result LobbyManager::GetMemberMetadataValue(LobbyId lobbyId,
UserId userId,
MetadataKey key,
char value[4096])
{
if (!value) {
return Result::InternalError;
}
auto result =
internal_->get_member_metadata_value(internal_,
lobbyId,
userId,
const_cast<char*>(key),
reinterpret_cast<DiscordMetadataValue*>(value));
return static_cast<Result>(result);
}
Result LobbyManager::GetMemberMetadataKey(LobbyId lobbyId,
UserId userId,
std::int32_t index,
char key[256])
{
if (!key) {
return Result::InternalError;
}
auto result = internal_->get_member_metadata_key(
internal_, lobbyId, userId, index, reinterpret_cast<DiscordMetadataKey*>(key));
return static_cast<Result>(result);
}
Result LobbyManager::MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count)
{
if (!count) {
return Result::InternalError;
}
auto result = internal_->member_metadata_count(
internal_, lobbyId, userId, reinterpret_cast<int32_t*>(count));
return static_cast<Result>(result);
}
void LobbyManager::UpdateMember(LobbyId lobbyId,
UserId userId,
LobbyMemberTransaction const& transaction,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->update_member(internal_,
lobbyId,
userId,
const_cast<LobbyMemberTransaction&>(transaction).Internal(),
cb.release(),
wrapper);
}
void LobbyManager::SendLobbyMessage(LobbyId lobbyId,
std::uint8_t* data,
std::uint32_t dataLength,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->send_lobby_message(
internal_, lobbyId, reinterpret_cast<uint8_t*>(data), dataLength, cb.release(), wrapper);
}
Result LobbyManager::GetSearchQuery(LobbySearchQuery* query)
{
if (!query) {
return Result::InternalError;
}
auto result = internal_->get_search_query(internal_, query->Receive());
return static_cast<Result>(result);
}
void LobbyManager::Search(LobbySearchQuery const& query, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->search(
internal_, const_cast<LobbySearchQuery&>(query).Internal(), cb.release(), wrapper);
}
void LobbyManager::LobbyCount(std::int32_t* count)
{
if (!count) {
return;
}
internal_->lobby_count(internal_, reinterpret_cast<int32_t*>(count));
}
Result LobbyManager::GetLobbyId(std::int32_t index, LobbyId* lobbyId)
{
if (!lobbyId) {
return Result::InternalError;
}
auto result = internal_->get_lobby_id(internal_, index, reinterpret_cast<int64_t*>(lobbyId));
return static_cast<Result>(result);
}
void LobbyManager::ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->connect_voice(internal_, lobbyId, cb.release(), wrapper);
}
void LobbyManager::DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->disconnect_voice(internal_, lobbyId, cb.release(), wrapper);
}
Result LobbyManager::ConnectNetwork(LobbyId lobbyId)
{
auto result = internal_->connect_network(internal_, lobbyId);
return static_cast<Result>(result);
}
Result LobbyManager::DisconnectNetwork(LobbyId lobbyId)
{
auto result = internal_->disconnect_network(internal_, lobbyId);
return static_cast<Result>(result);
}
Result LobbyManager::FlushNetwork()
{
auto result = internal_->flush_network(internal_);
return static_cast<Result>(result);
}
Result LobbyManager::OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable)
{
auto result =
internal_->open_network_channel(internal_, lobbyId, channelId, (reliable ? 1 : 0));
return static_cast<Result>(result);
}
Result LobbyManager::SendNetworkMessage(LobbyId lobbyId,
UserId userId,
std::uint8_t channelId,
std::uint8_t* data,
std::uint32_t dataLength)
{
auto result = internal_->send_network_message(
internal_, lobbyId, userId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,88 @@
#pragma once
#include "types.h"
namespace discord {
class LobbyManager final {
public:
~LobbyManager() = default;
Result GetLobbyCreateTransaction(LobbyTransaction* transaction);
Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction);
Result GetMemberUpdateTransaction(LobbyId lobbyId,
UserId userId,
LobbyMemberTransaction* transaction);
void CreateLobby(LobbyTransaction const& transaction,
std::function<void(Result, Lobby const&)> callback);
void UpdateLobby(LobbyId lobbyId,
LobbyTransaction const& transaction,
std::function<void(Result)> callback);
void DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback);
void ConnectLobby(LobbyId lobbyId,
LobbySecret secret,
std::function<void(Result, Lobby const&)> callback);
void ConnectLobbyWithActivitySecret(LobbySecret activitySecret,
std::function<void(Result, Lobby const&)> callback);
void DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback);
Result GetLobby(LobbyId lobbyId, Lobby* lobby);
Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]);
Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]);
Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]);
Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count);
Result MemberCount(LobbyId lobbyId, std::int32_t* count);
Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId);
Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user);
Result GetMemberMetadataValue(LobbyId lobbyId,
UserId userId,
MetadataKey key,
char value[4096]);
Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]);
Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count);
void UpdateMember(LobbyId lobbyId,
UserId userId,
LobbyMemberTransaction const& transaction,
std::function<void(Result)> callback);
void SendLobbyMessage(LobbyId lobbyId,
std::uint8_t* data,
std::uint32_t dataLength,
std::function<void(Result)> callback);
Result GetSearchQuery(LobbySearchQuery* query);
void Search(LobbySearchQuery const& query, std::function<void(Result)> callback);
void LobbyCount(std::int32_t* count);
Result GetLobbyId(std::int32_t index, LobbyId* lobbyId);
void ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
void DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
Result ConnectNetwork(LobbyId lobbyId);
Result DisconnectNetwork(LobbyId lobbyId);
Result FlushNetwork();
Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable);
Result SendNetworkMessage(LobbyId lobbyId,
UserId userId,
std::uint8_t channelId,
std::uint8_t* data,
std::uint32_t dataLength);
Event<std::int64_t> OnLobbyUpdate;
Event<std::int64_t, std::uint32_t> OnLobbyDelete;
Event<std::int64_t, std::int64_t> OnMemberConnect;
Event<std::int64_t, std::int64_t> OnMemberUpdate;
Event<std::int64_t, std::int64_t> OnMemberDisconnect;
Event<std::int64_t, std::int64_t, std::uint8_t*, std::uint32_t> OnLobbyMessage;
Event<std::int64_t, std::int64_t, bool> OnSpeaking;
Event<std::int64_t, std::int64_t, std::uint8_t, std::uint8_t*, std::uint32_t> OnNetworkMessage;
private:
friend class Core;
LobbyManager() = default;
LobbyManager(LobbyManager const& rhs) = delete;
LobbyManager& operator=(LobbyManager const& rhs) = delete;
LobbyManager(LobbyManager&& rhs) = delete;
LobbyManager& operator=(LobbyManager&& rhs) = delete;
IDiscordLobbyManager* internal_;
static IDiscordLobbyEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,103 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "network_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class NetworkEvents final {
public:
static void OnMessage(void* callbackData,
DiscordNetworkPeerId peerId,
DiscordNetworkChannelId channelId,
uint8_t* data,
uint32_t dataLength)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->NetworkManager();
module.OnMessage(peerId, channelId, data, dataLength);
}
static void OnRouteUpdate(void* callbackData, char const* routeData)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->NetworkManager();
module.OnRouteUpdate(static_cast<const char*>(routeData));
}
};
IDiscordNetworkEvents NetworkManager::events_{
&NetworkEvents::OnMessage,
&NetworkEvents::OnRouteUpdate,
};
void NetworkManager::GetPeerId(NetworkPeerId* peerId)
{
if (!peerId) {
return;
}
internal_->get_peer_id(internal_, reinterpret_cast<uint64_t*>(peerId));
}
Result NetworkManager::Flush()
{
auto result = internal_->flush(internal_);
return static_cast<Result>(result);
}
Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData)
{
auto result = internal_->open_peer(internal_, peerId, const_cast<char*>(routeData));
return static_cast<Result>(result);
}
Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData)
{
auto result = internal_->update_peer(internal_, peerId, const_cast<char*>(routeData));
return static_cast<Result>(result);
}
Result NetworkManager::ClosePeer(NetworkPeerId peerId)
{
auto result = internal_->close_peer(internal_, peerId);
return static_cast<Result>(result);
}
Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable)
{
auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0));
return static_cast<Result>(result);
}
Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId)
{
auto result = internal_->close_channel(internal_, peerId, channelId);
return static_cast<Result>(result);
}
Result NetworkManager::SendMessage(NetworkPeerId peerId,
NetworkChannelId channelId,
std::uint8_t* data,
std::uint32_t dataLength)
{
auto result = internal_->send_message(
internal_, peerId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,39 @@
#pragma once
#include "types.h"
namespace discord {
class NetworkManager final {
public:
~NetworkManager() = default;
void GetPeerId(NetworkPeerId* peerId);
Result Flush();
Result OpenPeer(NetworkPeerId peerId, char const* routeData);
Result UpdatePeer(NetworkPeerId peerId, char const* routeData);
Result ClosePeer(NetworkPeerId peerId);
Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable);
Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId);
Result SendMessage(NetworkPeerId peerId,
NetworkChannelId channelId,
std::uint8_t* data,
std::uint32_t dataLength);
Event<NetworkPeerId, NetworkChannelId, std::uint8_t*, std::uint32_t> OnMessage;
Event<char const*> OnRouteUpdate;
private:
friend class Core;
NetworkManager() = default;
NetworkManager(NetworkManager const& rhs) = delete;
NetworkManager& operator=(NetworkManager const& rhs) = delete;
NetworkManager(NetworkManager&& rhs) = delete;
NetworkManager& operator=(NetworkManager&& rhs) = delete;
IDiscordNetworkManager* internal_;
static IDiscordNetworkEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,97 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "overlay_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class OverlayEvents final {
public:
static void OnToggle(void* callbackData, bool locked)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->OverlayManager();
module.OnToggle((locked != 0));
}
};
IDiscordOverlayEvents OverlayManager::events_{
&OverlayEvents::OnToggle,
};
void OverlayManager::IsEnabled(bool* enabled)
{
if (!enabled) {
return;
}
internal_->is_enabled(internal_, reinterpret_cast<bool*>(enabled));
}
void OverlayManager::IsLocked(bool* locked)
{
if (!locked) {
return;
}
internal_->is_locked(internal_, reinterpret_cast<bool*>(locked));
}
void OverlayManager::SetLocked(bool locked, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper);
}
void OverlayManager::OpenActivityInvite(ActivityActionType type,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->open_activity_invite(
internal_, static_cast<EDiscordActivityActionType>(type), cb.release(), wrapper);
}
void OverlayManager::OpenGuildInvite(char const* code, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->open_guild_invite(internal_, const_cast<char*>(code), cb.release(), wrapper);
}
} // namespace discord

View file

@ -0,0 +1,32 @@
#pragma once
#include "types.h"
namespace discord {
class OverlayManager final {
public:
~OverlayManager() = default;
void IsEnabled(bool* enabled);
void IsLocked(bool* locked);
void SetLocked(bool locked, std::function<void(Result)> callback);
void OpenActivityInvite(ActivityActionType type, std::function<void(Result)> callback);
void OpenGuildInvite(char const* code, std::function<void(Result)> callback);
Event<bool> OnToggle;
private:
friend class Core;
OverlayManager() = default;
OverlayManager(OverlayManager const& rhs) = delete;
OverlayManager& operator=(OverlayManager const& rhs) = delete;
OverlayManager(OverlayManager&& rhs) = delete;
OverlayManager& operator=(OverlayManager&& rhs) = delete;
IDiscordOverlayManager* internal_;
static IDiscordOverlayEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,90 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "relationship_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class RelationshipEvents final {
public:
static void OnRefresh(void* callbackData)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->RelationshipManager();
module.OnRefresh();
}
static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->RelationshipManager();
module.OnRelationshipUpdate(*reinterpret_cast<Relationship const*>(relationship));
}
};
IDiscordRelationshipEvents RelationshipManager::events_{
&RelationshipEvents::OnRefresh,
&RelationshipEvents::OnRelationshipUpdate,
};
void RelationshipManager::Filter(std::function<bool(Relationship const&)> filter)
{
static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool {
auto cb(reinterpret_cast<std::function<bool(Relationship const&)>*>(callbackData));
if (!cb || !(*cb)) {
return {};
}
return (*cb)(*reinterpret_cast<Relationship const*>(relationship));
};
std::unique_ptr<std::function<bool(Relationship const&)>> cb{};
cb.reset(new std::function<bool(Relationship const&)>(std::move(filter)));
internal_->filter(internal_, cb.get(), wrapper);
}
Result RelationshipManager::Count(std::int32_t* count)
{
if (!count) {
return Result::InternalError;
}
auto result = internal_->count(internal_, reinterpret_cast<int32_t*>(count));
return static_cast<Result>(result);
}
Result RelationshipManager::Get(UserId userId, Relationship* relationship)
{
if (!relationship) {
return Result::InternalError;
}
auto result =
internal_->get(internal_, userId, reinterpret_cast<DiscordRelationship*>(relationship));
return static_cast<Result>(result);
}
Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship)
{
if (!relationship) {
return Result::InternalError;
}
auto result =
internal_->get_at(internal_, index, reinterpret_cast<DiscordRelationship*>(relationship));
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,32 @@
#pragma once
#include "types.h"
namespace discord {
class RelationshipManager final {
public:
~RelationshipManager() = default;
void Filter(std::function<bool(Relationship const&)> filter);
Result Count(std::int32_t* count);
Result Get(UserId userId, Relationship* relationship);
Result GetAt(std::uint32_t index, Relationship* relationship);
Event<> OnRefresh;
Event<Relationship const&> OnRelationshipUpdate;
private:
friend class Core;
RelationshipManager() = default;
RelationshipManager(RelationshipManager const& rhs) = delete;
RelationshipManager& operator=(RelationshipManager const& rhs) = delete;
RelationshipManager(RelationshipManager&& rhs) = delete;
RelationshipManager& operator=(RelationshipManager&& rhs) = delete;
IDiscordRelationshipManager* internal_;
static IDiscordRelationshipEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,148 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "storage_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
Result StorageManager::Read(char const* name,
std::uint8_t* data,
std::uint32_t dataLength,
std::uint32_t* read)
{
if (!read) {
return Result::InternalError;
}
auto result = internal_->read(internal_,
const_cast<char*>(name),
reinterpret_cast<uint8_t*>(data),
dataLength,
reinterpret_cast<uint32_t*>(read));
return static_cast<Result>(result);
}
void StorageManager::ReadAsync(char const* name,
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), data, dataLength);
};
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
internal_->read_async(internal_, const_cast<char*>(name), cb.release(), wrapper);
}
void StorageManager::ReadAsyncPartial(
char const* name,
std::uint64_t offset,
std::uint64_t length,
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
{
static auto wrapper =
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), data, dataLength);
};
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
internal_->read_async_partial(
internal_, const_cast<char*>(name), offset, length, cb.release(), wrapper);
}
Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength)
{
auto result = internal_->write(
internal_, const_cast<char*>(name), reinterpret_cast<uint8_t*>(data), dataLength);
return static_cast<Result>(result);
}
void StorageManager::WriteAsync(char const* name,
std::uint8_t* data,
std::uint32_t dataLength,
std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->write_async(internal_,
const_cast<char*>(name),
reinterpret_cast<uint8_t*>(data),
dataLength,
cb.release(),
wrapper);
}
Result StorageManager::Delete(char const* name)
{
auto result = internal_->delete_(internal_, const_cast<char*>(name));
return static_cast<Result>(result);
}
Result StorageManager::Exists(char const* name, bool* exists)
{
if (!exists) {
return Result::InternalError;
}
auto result =
internal_->exists(internal_, const_cast<char*>(name), reinterpret_cast<bool*>(exists));
return static_cast<Result>(result);
}
void StorageManager::Count(std::int32_t* count)
{
if (!count) {
return;
}
internal_->count(internal_, reinterpret_cast<int32_t*>(count));
}
Result StorageManager::Stat(char const* name, FileStat* stat)
{
if (!stat) {
return Result::InternalError;
}
auto result =
internal_->stat(internal_, const_cast<char*>(name), reinterpret_cast<DiscordFileStat*>(stat));
return static_cast<Result>(result);
}
Result StorageManager::StatAt(std::int32_t index, FileStat* stat)
{
if (!stat) {
return Result::InternalError;
}
auto result = internal_->stat_at(internal_, index, reinterpret_cast<DiscordFileStat*>(stat));
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,45 @@
#pragma once
#include "types.h"
namespace discord {
class StorageManager final {
public:
~StorageManager() = default;
Result Read(char const* name,
std::uint8_t* data,
std::uint32_t dataLength,
std::uint32_t* read);
void ReadAsync(char const* name,
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
void ReadAsyncPartial(char const* name,
std::uint64_t offset,
std::uint64_t length,
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength);
void WriteAsync(char const* name,
std::uint8_t* data,
std::uint32_t dataLength,
std::function<void(Result)> callback);
Result Delete(char const* name);
Result Exists(char const* name, bool* exists);
void Count(std::int32_t* count);
Result Stat(char const* name, FileStat* stat);
Result StatAt(std::int32_t index, FileStat* stat);
private:
friend class Core;
StorageManager() = default;
StorageManager(StorageManager const& rhs) = delete;
StorageManager& operator=(StorageManager const& rhs) = delete;
StorageManager(StorageManager&& rhs) = delete;
StorageManager& operator=(StorageManager&& rhs) = delete;
IDiscordStorageManager* internal_;
static IDiscordStorageEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,160 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "store_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class StoreEvents final {
public:
static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->StoreManager();
module.OnEntitlementCreate(*reinterpret_cast<Entitlement const*>(entitlement));
}
static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->StoreManager();
module.OnEntitlementDelete(*reinterpret_cast<Entitlement const*>(entitlement));
}
};
IDiscordStoreEvents StoreManager::events_{
&StoreEvents::OnEntitlementCreate,
&StoreEvents::OnEntitlementDelete,
};
void StoreManager::FetchSkus(std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->fetch_skus(internal_, cb.release(), wrapper);
}
void StoreManager::CountSkus(std::int32_t* count)
{
if (!count) {
return;
}
internal_->count_skus(internal_, reinterpret_cast<int32_t*>(count));
}
Result StoreManager::GetSku(Snowflake skuId, Sku* sku)
{
if (!sku) {
return Result::InternalError;
}
auto result = internal_->get_sku(internal_, skuId, reinterpret_cast<DiscordSku*>(sku));
return static_cast<Result>(result);
}
Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku)
{
if (!sku) {
return Result::InternalError;
}
auto result = internal_->get_sku_at(internal_, index, reinterpret_cast<DiscordSku*>(sku));
return static_cast<Result>(result);
}
void StoreManager::FetchEntitlements(std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->fetch_entitlements(internal_, cb.release(), wrapper);
}
void StoreManager::CountEntitlements(std::int32_t* count)
{
if (!count) {
return;
}
internal_->count_entitlements(internal_, reinterpret_cast<int32_t*>(count));
}
Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement)
{
if (!entitlement) {
return Result::InternalError;
}
auto result = internal_->get_entitlement(
internal_, entitlementId, reinterpret_cast<DiscordEntitlement*>(entitlement));
return static_cast<Result>(result);
}
Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement)
{
if (!entitlement) {
return Result::InternalError;
}
auto result = internal_->get_entitlement_at(
internal_, index, reinterpret_cast<DiscordEntitlement*>(entitlement));
return static_cast<Result>(result);
}
Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement)
{
if (!hasEntitlement) {
return Result::InternalError;
}
auto result =
internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast<bool*>(hasEntitlement));
return static_cast<Result>(result);
}
void StoreManager::StartPurchase(Snowflake skuId, std::function<void(Result)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
std::unique_ptr<std::function<void(Result)>> cb(
reinterpret_cast<std::function<void(Result)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result));
};
std::unique_ptr<std::function<void(Result)>> cb{};
cb.reset(new std::function<void(Result)>(std::move(callback)));
internal_->start_purchase(internal_, skuId, cb.release(), wrapper);
}
} // namespace discord

View file

@ -0,0 +1,38 @@
#pragma once
#include "types.h"
namespace discord {
class StoreManager final {
public:
~StoreManager() = default;
void FetchSkus(std::function<void(Result)> callback);
void CountSkus(std::int32_t* count);
Result GetSku(Snowflake skuId, Sku* sku);
Result GetSkuAt(std::int32_t index, Sku* sku);
void FetchEntitlements(std::function<void(Result)> callback);
void CountEntitlements(std::int32_t* count);
Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement);
Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement);
Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement);
void StartPurchase(Snowflake skuId, std::function<void(Result)> callback);
Event<Entitlement const&> OnEntitlementCreate;
Event<Entitlement const&> OnEntitlementDelete;
private:
friend class Core;
StoreManager() = default;
StoreManager(StoreManager const& rhs) = delete;
StoreManager& operator=(StoreManager const& rhs) = delete;
StoreManager(StoreManager&& rhs) = delete;
StoreManager& operator=(StoreManager&& rhs) = delete;
IDiscordStoreManager* internal_;
static IDiscordStoreEvents events_;
};
} // namespace discord

View file

@ -0,0 +1,707 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "types.h"
#include <cstring>
#include <memory>
namespace discord {
void User::SetId(UserId id)
{
internal_.id = id;
}
UserId User::GetId() const
{
return internal_.id;
}
void User::SetUsername(char const* username)
{
strncpy(internal_.username, username, 256);
internal_.username[256 - 1] = '\0';
}
char const* User::GetUsername() const
{
return internal_.username;
}
void User::SetDiscriminator(char const* discriminator)
{
strncpy(internal_.discriminator, discriminator, 8);
internal_.discriminator[8 - 1] = '\0';
}
char const* User::GetDiscriminator() const
{
return internal_.discriminator;
}
void User::SetAvatar(char const* avatar)
{
strncpy(internal_.avatar, avatar, 128);
internal_.avatar[128 - 1] = '\0';
}
char const* User::GetAvatar() const
{
return internal_.avatar;
}
void User::SetBot(bool bot)
{
internal_.bot = bot;
}
bool User::GetBot() const
{
return internal_.bot != 0;
}
void OAuth2Token::SetAccessToken(char const* accessToken)
{
strncpy(internal_.access_token, accessToken, 128);
internal_.access_token[128 - 1] = '\0';
}
char const* OAuth2Token::GetAccessToken() const
{
return internal_.access_token;
}
void OAuth2Token::SetScopes(char const* scopes)
{
strncpy(internal_.scopes, scopes, 1024);
internal_.scopes[1024 - 1] = '\0';
}
char const* OAuth2Token::GetScopes() const
{
return internal_.scopes;
}
void OAuth2Token::SetExpires(Timestamp expires)
{
internal_.expires = expires;
}
Timestamp OAuth2Token::GetExpires() const
{
return internal_.expires;
}
void ImageHandle::SetType(ImageType type)
{
internal_.type = static_cast<EDiscordImageType>(type);
}
ImageType ImageHandle::GetType() const
{
return static_cast<ImageType>(internal_.type);
}
void ImageHandle::SetId(std::int64_t id)
{
internal_.id = id;
}
std::int64_t ImageHandle::GetId() const
{
return internal_.id;
}
void ImageHandle::SetSize(std::uint32_t size)
{
internal_.size = size;
}
std::uint32_t ImageHandle::GetSize() const
{
return internal_.size;
}
void ImageDimensions::SetWidth(std::uint32_t width)
{
internal_.width = width;
}
std::uint32_t ImageDimensions::GetWidth() const
{
return internal_.width;
}
void ImageDimensions::SetHeight(std::uint32_t height)
{
internal_.height = height;
}
std::uint32_t ImageDimensions::GetHeight() const
{
return internal_.height;
}
void ActivityTimestamps::SetStart(Timestamp start)
{
internal_.start = start;
}
Timestamp ActivityTimestamps::GetStart() const
{
return internal_.start;
}
void ActivityTimestamps::SetEnd(Timestamp end)
{
internal_.end = end;
}
Timestamp ActivityTimestamps::GetEnd() const
{
return internal_.end;
}
void ActivityAssets::SetLargeImage(char const* largeImage)
{
strncpy(internal_.large_image, largeImage, 128);
internal_.large_image[128 - 1] = '\0';
}
char const* ActivityAssets::GetLargeImage() const
{
return internal_.large_image;
}
void ActivityAssets::SetLargeText(char const* largeText)
{
strncpy(internal_.large_text, largeText, 128);
internal_.large_text[128 - 1] = '\0';
}
char const* ActivityAssets::GetLargeText() const
{
return internal_.large_text;
}
void ActivityAssets::SetSmallImage(char const* smallImage)
{
strncpy(internal_.small_image, smallImage, 128);
internal_.small_image[128 - 1] = '\0';
}
char const* ActivityAssets::GetSmallImage() const
{
return internal_.small_image;
}
void ActivityAssets::SetSmallText(char const* smallText)
{
strncpy(internal_.small_text, smallText, 128);
internal_.small_text[128 - 1] = '\0';
}
char const* ActivityAssets::GetSmallText() const
{
return internal_.small_text;
}
void PartySize::SetCurrentSize(std::int32_t currentSize)
{
internal_.current_size = currentSize;
}
std::int32_t PartySize::GetCurrentSize() const
{
return internal_.current_size;
}
void PartySize::SetMaxSize(std::int32_t maxSize)
{
internal_.max_size = maxSize;
}
std::int32_t PartySize::GetMaxSize() const
{
return internal_.max_size;
}
void ActivityParty::SetId(char const* id)
{
strncpy(internal_.id, id, 128);
internal_.id[128 - 1] = '\0';
}
char const* ActivityParty::GetId() const
{
return internal_.id;
}
PartySize& ActivityParty::GetSize()
{
return reinterpret_cast<PartySize&>(internal_.size);
}
PartySize const& ActivityParty::GetSize() const
{
return reinterpret_cast<PartySize const&>(internal_.size);
}
void ActivitySecrets::SetMatch(char const* match)
{
strncpy(internal_.match, match, 128);
internal_.match[128 - 1] = '\0';
}
char const* ActivitySecrets::GetMatch() const
{
return internal_.match;
}
void ActivitySecrets::SetJoin(char const* join)
{
strncpy(internal_.join, join, 128);
internal_.join[128 - 1] = '\0';
}
char const* ActivitySecrets::GetJoin() const
{
return internal_.join;
}
void ActivitySecrets::SetSpectate(char const* spectate)
{
strncpy(internal_.spectate, spectate, 128);
internal_.spectate[128 - 1] = '\0';
}
char const* ActivitySecrets::GetSpectate() const
{
return internal_.spectate;
}
void Activity::SetType(ActivityType type)
{
internal_.type = static_cast<EDiscordActivityType>(type);
}
ActivityType Activity::GetType() const
{
return static_cast<ActivityType>(internal_.type);
}
void Activity::SetApplicationId(std::int64_t applicationId)
{
internal_.application_id = applicationId;
}
std::int64_t Activity::GetApplicationId() const
{
return internal_.application_id;
}
void Activity::SetName(char const* name)
{
strncpy(internal_.name, name, 128);
internal_.name[128 - 1] = '\0';
}
char const* Activity::GetName() const
{
return internal_.name;
}
void Activity::SetState(char const* state)
{
strncpy(internal_.state, state, 128);
internal_.state[128 - 1] = '\0';
}
char const* Activity::GetState() const
{
return internal_.state;
}
void Activity::SetDetails(char const* details)
{
strncpy(internal_.details, details, 128);
internal_.details[128 - 1] = '\0';
}
char const* Activity::GetDetails() const
{
return internal_.details;
}
ActivityTimestamps& Activity::GetTimestamps()
{
return reinterpret_cast<ActivityTimestamps&>(internal_.timestamps);
}
ActivityTimestamps const& Activity::GetTimestamps() const
{
return reinterpret_cast<ActivityTimestamps const&>(internal_.timestamps);
}
ActivityAssets& Activity::GetAssets()
{
return reinterpret_cast<ActivityAssets&>(internal_.assets);
}
ActivityAssets const& Activity::GetAssets() const
{
return reinterpret_cast<ActivityAssets const&>(internal_.assets);
}
ActivityParty& Activity::GetParty()
{
return reinterpret_cast<ActivityParty&>(internal_.party);
}
ActivityParty const& Activity::GetParty() const
{
return reinterpret_cast<ActivityParty const&>(internal_.party);
}
ActivitySecrets& Activity::GetSecrets()
{
return reinterpret_cast<ActivitySecrets&>(internal_.secrets);
}
ActivitySecrets const& Activity::GetSecrets() const
{
return reinterpret_cast<ActivitySecrets const&>(internal_.secrets);
}
void Activity::SetInstance(bool instance)
{
internal_.instance = instance;
}
bool Activity::GetInstance() const
{
return internal_.instance != 0;
}
void Presence::SetStatus(Status status)
{
internal_.status = static_cast<EDiscordStatus>(status);
}
Status Presence::GetStatus() const
{
return static_cast<Status>(internal_.status);
}
Activity& Presence::GetActivity()
{
return reinterpret_cast<Activity&>(internal_.activity);
}
Activity const& Presence::GetActivity() const
{
return reinterpret_cast<Activity const&>(internal_.activity);
}
void Relationship::SetType(RelationshipType type)
{
internal_.type = static_cast<EDiscordRelationshipType>(type);
}
RelationshipType Relationship::GetType() const
{
return static_cast<RelationshipType>(internal_.type);
}
User& Relationship::GetUser()
{
return reinterpret_cast<User&>(internal_.user);
}
User const& Relationship::GetUser() const
{
return reinterpret_cast<User const&>(internal_.user);
}
Presence& Relationship::GetPresence()
{
return reinterpret_cast<Presence&>(internal_.presence);
}
Presence const& Relationship::GetPresence() const
{
return reinterpret_cast<Presence const&>(internal_.presence);
}
void Lobby::SetId(LobbyId id)
{
internal_.id = id;
}
LobbyId Lobby::GetId() const
{
return internal_.id;
}
void Lobby::SetType(LobbyType type)
{
internal_.type = static_cast<EDiscordLobbyType>(type);
}
LobbyType Lobby::GetType() const
{
return static_cast<LobbyType>(internal_.type);
}
void Lobby::SetOwnerId(UserId ownerId)
{
internal_.owner_id = ownerId;
}
UserId Lobby::GetOwnerId() const
{
return internal_.owner_id;
}
void Lobby::SetSecret(LobbySecret secret)
{
strncpy(internal_.secret, secret, 128);
internal_.secret[128 - 1] = '\0';
}
LobbySecret Lobby::GetSecret() const
{
return internal_.secret;
}
void Lobby::SetCapacity(std::uint32_t capacity)
{
internal_.capacity = capacity;
}
std::uint32_t Lobby::GetCapacity() const
{
return internal_.capacity;
}
void Lobby::SetLocked(bool locked)
{
internal_.locked = locked;
}
bool Lobby::GetLocked() const
{
return internal_.locked != 0;
}
void FileStat::SetFilename(char const* filename)
{
strncpy(internal_.filename, filename, 260);
internal_.filename[260 - 1] = '\0';
}
char const* FileStat::GetFilename() const
{
return internal_.filename;
}
void FileStat::SetSize(std::uint64_t size)
{
internal_.size = size;
}
std::uint64_t FileStat::GetSize() const
{
return internal_.size;
}
void FileStat::SetLastModified(std::uint64_t lastModified)
{
internal_.last_modified = lastModified;
}
std::uint64_t FileStat::GetLastModified() const
{
return internal_.last_modified;
}
void Entitlement::SetId(Snowflake id)
{
internal_.id = id;
}
Snowflake Entitlement::GetId() const
{
return internal_.id;
}
void Entitlement::SetType(EntitlementType type)
{
internal_.type = static_cast<EDiscordEntitlementType>(type);
}
EntitlementType Entitlement::GetType() const
{
return static_cast<EntitlementType>(internal_.type);
}
void Entitlement::SetSkuId(Snowflake skuId)
{
internal_.sku_id = skuId;
}
Snowflake Entitlement::GetSkuId() const
{
return internal_.sku_id;
}
void SkuPrice::SetAmount(std::uint32_t amount)
{
internal_.amount = amount;
}
std::uint32_t SkuPrice::GetAmount() const
{
return internal_.amount;
}
void SkuPrice::SetCurrency(char const* currency)
{
strncpy(internal_.currency, currency, 16);
internal_.currency[16 - 1] = '\0';
}
char const* SkuPrice::GetCurrency() const
{
return internal_.currency;
}
void Sku::SetId(Snowflake id)
{
internal_.id = id;
}
Snowflake Sku::GetId() const
{
return internal_.id;
}
void Sku::SetType(SkuType type)
{
internal_.type = static_cast<EDiscordSkuType>(type);
}
SkuType Sku::GetType() const
{
return static_cast<SkuType>(internal_.type);
}
void Sku::SetName(char const* name)
{
strncpy(internal_.name, name, 256);
internal_.name[256 - 1] = '\0';
}
char const* Sku::GetName() const
{
return internal_.name;
}
SkuPrice& Sku::GetPrice()
{
return reinterpret_cast<SkuPrice&>(internal_.price);
}
SkuPrice const& Sku::GetPrice() const
{
return reinterpret_cast<SkuPrice const&>(internal_.price);
}
Result LobbyTransaction::SetType(LobbyType type)
{
auto result = internal_->set_type(internal_, static_cast<EDiscordLobbyType>(type));
return static_cast<Result>(result);
}
Result LobbyTransaction::SetOwner(UserId ownerId)
{
auto result = internal_->set_owner(internal_, ownerId);
return static_cast<Result>(result);
}
Result LobbyTransaction::SetCapacity(std::uint32_t capacity)
{
auto result = internal_->set_capacity(internal_, capacity);
return static_cast<Result>(result);
}
Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value)
{
auto result =
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
return static_cast<Result>(result);
}
Result LobbyTransaction::DeleteMetadata(MetadataKey key)
{
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
return static_cast<Result>(result);
}
Result LobbyTransaction::SetLocked(bool locked)
{
auto result = internal_->set_locked(internal_, (locked ? 1 : 0));
return static_cast<Result>(result);
}
Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value)
{
auto result =
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
return static_cast<Result>(result);
}
Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key)
{
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
return static_cast<Result>(result);
}
Result LobbySearchQuery::Filter(MetadataKey key,
LobbySearchComparison comparison,
LobbySearchCast cast,
MetadataValue value)
{
auto result = internal_->filter(internal_,
const_cast<char*>(key),
static_cast<EDiscordLobbySearchComparison>(comparison),
static_cast<EDiscordLobbySearchCast>(cast),
const_cast<char*>(value));
return static_cast<Result>(result);
}
Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value)
{
auto result = internal_->sort(internal_,
const_cast<char*>(key),
static_cast<EDiscordLobbySearchCast>(cast),
const_cast<char*>(value));
return static_cast<Result>(result);
}
Result LobbySearchQuery::Limit(std::uint32_t limit)
{
auto result = internal_->limit(internal_, limit);
return static_cast<Result>(result);
}
Result LobbySearchQuery::Distance(LobbySearchDistance distance)
{
auto result =
internal_->distance(internal_, static_cast<EDiscordLobbySearchDistance>(distance));
return static_cast<Result>(result);
}
} // namespace discord

View file

@ -0,0 +1,426 @@
#pragma once
#include "ffi.h"
#include "event.h"
namespace discord {
enum class Result {
Ok,
ServiceUnavailable,
InvalidVersion,
LockFailed,
InternalError,
InvalidPayload,
InvalidCommand,
InvalidPermissions,
NotFetched,
NotFound,
Conflict,
InvalidSecret,
InvalidJoinSecret,
NoEligibleActivity,
InvalidInvite,
NotAuthenticated,
InvalidAccessToken,
ApplicationMismatch,
InvalidDataUrl,
InvalidBase64,
NotFiltered,
LobbyFull,
InvalidLobbySecret,
InvalidFilename,
InvalidFileSize,
InvalidEntitlement,
NotInstalled,
NotRunning,
InsufficientBuffer,
PurchaseCanceled,
};
enum class CreateFlags {
Default = 0,
NoRequireDiscord = 1,
};
enum class LogLevel {
Error = 1,
Warn,
Info,
Debug,
};
enum class ImageType {
User,
};
enum class ActivityType {
Playing,
Streaming,
Listening,
Watching,
};
enum class ActivityActionType {
Join = 1,
Spectate,
};
enum class ActivityJoinRequestReply {
No,
Yes,
Ignore,
};
enum class Status {
Offline = 0,
Online = 1,
Idle = 2,
DoNotDisturb = 3,
};
enum class RelationshipType {
None,
Friend,
Blocked,
PendingIncoming,
PendingOutgoing,
Implicit,
};
enum class LobbyType {
Private = 1,
Public,
};
enum class LobbySearchComparison {
LessThanOrEqual = -2,
LessThan,
Equal,
GreaterThan,
GreaterThanOrEqual,
NotEqual,
};
enum class LobbySearchCast {
String = 1,
Number,
};
enum class LobbySearchDistance {
Local,
Default,
Extended,
Global,
};
enum class EntitlementType {
Purchase = 1,
PremiumSubscription,
DeveloperGift,
};
enum class SkuType {
Application = 1,
DLC,
Consumable,
Bundle,
};
using ClientId = std::int64_t;
using Version = std::int32_t;
using Snowflake = std::int64_t;
using Timestamp = std::int64_t;
using UserId = Snowflake;
using Locale = char const*;
using Branch = char const*;
using LobbyId = Snowflake;
using LobbySecret = char const*;
using MetadataKey = char const*;
using MetadataValue = char const*;
using NetworkPeerId = std::uint64_t;
using NetworkChannelId = std::uint8_t;
class User final {
public:
void SetId(UserId id);
UserId GetId() const;
void SetUsername(char const* username);
char const* GetUsername() const;
void SetDiscriminator(char const* discriminator);
char const* GetDiscriminator() const;
void SetAvatar(char const* avatar);
char const* GetAvatar() const;
void SetBot(bool bot);
bool GetBot() const;
private:
DiscordUser internal_;
};
class OAuth2Token final {
public:
void SetAccessToken(char const* accessToken);
char const* GetAccessToken() const;
void SetScopes(char const* scopes);
char const* GetScopes() const;
void SetExpires(Timestamp expires);
Timestamp GetExpires() const;
private:
DiscordOAuth2Token internal_;
};
class ImageHandle final {
public:
void SetType(ImageType type);
ImageType GetType() const;
void SetId(std::int64_t id);
std::int64_t GetId() const;
void SetSize(std::uint32_t size);
std::uint32_t GetSize() const;
private:
DiscordImageHandle internal_;
};
class ImageDimensions final {
public:
void SetWidth(std::uint32_t width);
std::uint32_t GetWidth() const;
void SetHeight(std::uint32_t height);
std::uint32_t GetHeight() const;
private:
DiscordImageDimensions internal_;
};
class ActivityTimestamps final {
public:
void SetStart(Timestamp start);
Timestamp GetStart() const;
void SetEnd(Timestamp end);
Timestamp GetEnd() const;
private:
DiscordActivityTimestamps internal_;
};
class ActivityAssets final {
public:
void SetLargeImage(char const* largeImage);
char const* GetLargeImage() const;
void SetLargeText(char const* largeText);
char const* GetLargeText() const;
void SetSmallImage(char const* smallImage);
char const* GetSmallImage() const;
void SetSmallText(char const* smallText);
char const* GetSmallText() const;
private:
DiscordActivityAssets internal_;
};
class PartySize final {
public:
void SetCurrentSize(std::int32_t currentSize);
std::int32_t GetCurrentSize() const;
void SetMaxSize(std::int32_t maxSize);
std::int32_t GetMaxSize() const;
private:
DiscordPartySize internal_;
};
class ActivityParty final {
public:
void SetId(char const* id);
char const* GetId() const;
PartySize& GetSize();
PartySize const& GetSize() const;
private:
DiscordActivityParty internal_;
};
class ActivitySecrets final {
public:
void SetMatch(char const* match);
char const* GetMatch() const;
void SetJoin(char const* join);
char const* GetJoin() const;
void SetSpectate(char const* spectate);
char const* GetSpectate() const;
private:
DiscordActivitySecrets internal_;
};
class Activity final {
public:
void SetType(ActivityType type);
ActivityType GetType() const;
void SetApplicationId(std::int64_t applicationId);
std::int64_t GetApplicationId() const;
void SetName(char const* name);
char const* GetName() const;
void SetState(char const* state);
char const* GetState() const;
void SetDetails(char const* details);
char const* GetDetails() const;
ActivityTimestamps& GetTimestamps();
ActivityTimestamps const& GetTimestamps() const;
ActivityAssets& GetAssets();
ActivityAssets const& GetAssets() const;
ActivityParty& GetParty();
ActivityParty const& GetParty() const;
ActivitySecrets& GetSecrets();
ActivitySecrets const& GetSecrets() const;
void SetInstance(bool instance);
bool GetInstance() const;
private:
DiscordActivity internal_;
};
class Presence final {
public:
void SetStatus(Status status);
Status GetStatus() const;
Activity& GetActivity();
Activity const& GetActivity() const;
private:
DiscordPresence internal_;
};
class Relationship final {
public:
void SetType(RelationshipType type);
RelationshipType GetType() const;
User& GetUser();
User const& GetUser() const;
Presence& GetPresence();
Presence const& GetPresence() const;
private:
DiscordRelationship internal_;
};
class Lobby final {
public:
void SetId(LobbyId id);
LobbyId GetId() const;
void SetType(LobbyType type);
LobbyType GetType() const;
void SetOwnerId(UserId ownerId);
UserId GetOwnerId() const;
void SetSecret(LobbySecret secret);
LobbySecret GetSecret() const;
void SetCapacity(std::uint32_t capacity);
std::uint32_t GetCapacity() const;
void SetLocked(bool locked);
bool GetLocked() const;
private:
DiscordLobby internal_;
};
class FileStat final {
public:
void SetFilename(char const* filename);
char const* GetFilename() const;
void SetSize(std::uint64_t size);
std::uint64_t GetSize() const;
void SetLastModified(std::uint64_t lastModified);
std::uint64_t GetLastModified() const;
private:
DiscordFileStat internal_;
};
class Entitlement final {
public:
void SetId(Snowflake id);
Snowflake GetId() const;
void SetType(EntitlementType type);
EntitlementType GetType() const;
void SetSkuId(Snowflake skuId);
Snowflake GetSkuId() const;
private:
DiscordEntitlement internal_;
};
class SkuPrice final {
public:
void SetAmount(std::uint32_t amount);
std::uint32_t GetAmount() const;
void SetCurrency(char const* currency);
char const* GetCurrency() const;
private:
DiscordSkuPrice internal_;
};
class Sku final {
public:
void SetId(Snowflake id);
Snowflake GetId() const;
void SetType(SkuType type);
SkuType GetType() const;
void SetName(char const* name);
char const* GetName() const;
SkuPrice& GetPrice();
SkuPrice const& GetPrice() const;
private:
DiscordSku internal_;
};
class LobbyTransaction final {
public:
Result SetType(LobbyType type);
Result SetOwner(UserId ownerId);
Result SetCapacity(std::uint32_t capacity);
Result SetMetadata(MetadataKey key, MetadataValue value);
Result DeleteMetadata(MetadataKey key);
Result SetLocked(bool locked);
IDiscordLobbyTransaction** Receive() { return &internal_; }
IDiscordLobbyTransaction* Internal() { return internal_; }
private:
IDiscordLobbyTransaction* internal_;
};
class LobbyMemberTransaction final {
public:
Result SetMetadata(MetadataKey key, MetadataValue value);
Result DeleteMetadata(MetadataKey key);
IDiscordLobbyMemberTransaction** Receive() { return &internal_; }
IDiscordLobbyMemberTransaction* Internal() { return internal_; }
private:
IDiscordLobbyMemberTransaction* internal_;
};
class LobbySearchQuery final {
public:
Result Filter(MetadataKey key,
LobbySearchComparison comparison,
LobbySearchCast cast,
MetadataValue value);
Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value);
Result Limit(std::uint32_t limit);
Result Distance(LobbySearchDistance distance);
IDiscordLobbySearchQuery** Receive() { return &internal_; }
IDiscordLobbySearchQuery* Internal() { return internal_; }
private:
IDiscordLobbySearchQuery* internal_;
};
} // namespace discord

View file

@ -0,0 +1,58 @@
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "user_manager.h"
#include "core.h"
#include <cstring>
#include <memory>
namespace discord {
class UserEvents final {
public:
static void OnCurrentUserUpdate(void* callbackData)
{
auto* core = reinterpret_cast<Core*>(callbackData);
if (!core) {
return;
}
auto& module = core->UserManager();
module.OnCurrentUserUpdate();
}
};
IDiscordUserEvents UserManager::events_{
&UserEvents::OnCurrentUserUpdate,
};
Result UserManager::GetCurrentUser(User* currentUser)
{
if (!currentUser) {
return Result::InternalError;
}
auto result =
internal_->get_current_user(internal_, reinterpret_cast<DiscordUser*>(currentUser));
return static_cast<Result>(result);
}
void UserManager::GetUser(UserId userId, std::function<void(Result, User const&)> callback)
{
static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void {
std::unique_ptr<std::function<void(Result, User const&)>> cb(
reinterpret_cast<std::function<void(Result, User const&)>*>(callbackData));
if (!cb || !(*cb)) {
return;
}
(*cb)(static_cast<Result>(result), *reinterpret_cast<User const*>(user));
};
std::unique_ptr<std::function<void(Result, User const&)>> cb{};
cb.reset(new std::function<void(Result, User const&)>(std::move(callback)));
internal_->get_user(internal_, userId, cb.release(), wrapper);
}
} // namespace discord

View file

@ -0,0 +1,29 @@
#pragma once
#include "types.h"
namespace discord {
class UserManager final {
public:
~UserManager() = default;
Result GetCurrentUser(User* currentUser);
void GetUser(UserId userId, std::function<void(Result, User const&)> callback);
Event<> OnCurrentUserUpdate;
private:
friend class Core;
UserManager() = default;
UserManager(UserManager const& rhs) = delete;
UserManager& operator=(UserManager const& rhs) = delete;
UserManager(UserManager&& rhs) = delete;
UserManager& operator=(UserManager&& rhs) = delete;
IDiscordUserManager* internal_;
static IDiscordUserEvents events_;
};
} // namespace discord