v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
65
source/application/CMakeLists.txt
Normal file
65
source/application/CMakeLists.txt
Normal 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})
|
31
source/application/StarApplication.cpp
Normal file
31
source/application/StarApplication.cpp
Normal 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() {}
|
||||
}
|
76
source/application/StarApplication.hpp
Normal file
76
source/application/StarApplication.hpp
Normal 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
|
67
source/application/StarApplicationController.hpp
Normal file
67
source/application/StarApplicationController.hpp
Normal 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
|
11
source/application/StarDesktopService_pc_steam.cpp
Normal file
11
source/application/StarDesktopService_pc_steam.cpp
Normal 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());
|
||||
}
|
||||
|
||||
}
|
19
source/application/StarDesktopService_pc_steam.hpp
Normal file
19
source/application/StarDesktopService_pc_steam.hpp
Normal 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
|
166
source/application/StarInputEvent.cpp
Normal file
166
source/application/StarInputEvent.cpp
Normal 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"}
|
||||
};
|
||||
|
||||
}
|
274
source/application/StarInputEvent.hpp
Normal file
274
source/application/StarInputEvent.hpp
Normal 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
|
34
source/application/StarMainApplication.hpp
Normal file
34
source/application/StarMainApplication.hpp
Normal 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
|
692
source/application/StarMainApplication_sdl.cpp
Normal file
692
source/application/StarMainApplication_sdl.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
616
source/application/StarP2PNetworkingService_pc.cpp
Normal file
616
source/application/StarP2PNetworkingService_pc.cpp
Normal 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
|
||||
}
|
||||
|
||||
}
|
146
source/application/StarP2PNetworkingService_pc.hpp
Normal file
146
source/application/StarP2PNetworkingService_pc.hpp
Normal 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
|
175
source/application/StarPlatformServices_pc.cpp
Normal file
175
source/application/StarPlatformServices_pc.cpp
Normal 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
|
||||
}
|
||||
|
||||
}
|
83
source/application/StarPlatformServices_pc.hpp
Normal file
83
source/application/StarPlatformServices_pc.hpp
Normal 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
|
60
source/application/StarRenderer.cpp
Normal file
60
source/application/StarRenderer.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
147
source/application/StarRenderer.hpp
Normal file
147
source/application/StarRenderer.hpp
Normal 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
|
828
source/application/StarRenderer_opengl20.cpp
Normal file
828
source/application/StarRenderer_opengl20.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
199
source/application/StarRenderer_opengl20.hpp
Normal file
199
source/application/StarRenderer_opengl20.hpp
Normal 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
|
130
source/application/StarStatisticsService_pc_steam.cpp
Normal file
130
source/application/StarStatisticsService_pc_steam.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
41
source/application/StarStatisticsService_pc_steam.hpp
Normal file
41
source/application/StarStatisticsService_pc_steam.hpp
Normal 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
|
417
source/application/StarTextureAtlas.hpp
Normal file
417
source/application/StarTextureAtlas.hpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
177
source/application/discord/activity_manager.cpp
Normal file
177
source/application/discord/activity_manager.cpp
Normal 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
|
42
source/application/discord/activity_manager.h
Normal file
42
source/application/discord/activity_manager.h
Normal 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
|
63
source/application/discord/application_manager.cpp
Normal file
63
source/application/discord/application_manager.cpp
Normal 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
|
29
source/application/discord/application_manager.h
Normal file
29
source/application/discord/application_manager.h
Normal 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
|
162
source/application/discord/core.cpp
Normal file
162
source/application/discord/core.cpp
Normal 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(¶ms);
|
||||
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, ¶ms, &((*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
|
58
source/application/discord/core.h
Normal file
58
source/application/discord/core.h
Normal 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
|
14
source/application/discord/discord.h
Normal file
14
source/application/discord/discord.h
Normal 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"
|
59
source/application/discord/event.h
Normal file
59
source/application/discord/event.h
Normal 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
|
790
source/application/discord/ffi.h
Normal file
790
source/application/discord/ffi.h
Normal 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
|
57
source/application/discord/image_manager.cpp
Normal file
57
source/application/discord/image_manager.cpp
Normal 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
|
28
source/application/discord/image_manager.h
Normal file
28
source/application/discord/image_manager.h
Normal 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
|
547
source/application/discord/lobby_manager.cpp
Normal file
547
source/application/discord/lobby_manager.cpp
Normal 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
|
88
source/application/discord/lobby_manager.h
Normal file
88
source/application/discord/lobby_manager.h
Normal 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
|
103
source/application/discord/network_manager.cpp
Normal file
103
source/application/discord/network_manager.cpp
Normal 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
|
39
source/application/discord/network_manager.h
Normal file
39
source/application/discord/network_manager.h
Normal 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
|
97
source/application/discord/overlay_manager.cpp
Normal file
97
source/application/discord/overlay_manager.cpp
Normal 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
|
32
source/application/discord/overlay_manager.h
Normal file
32
source/application/discord/overlay_manager.h
Normal 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
|
90
source/application/discord/relationship_manager.cpp
Normal file
90
source/application/discord/relationship_manager.cpp
Normal 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
|
32
source/application/discord/relationship_manager.h
Normal file
32
source/application/discord/relationship_manager.h
Normal 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
|
148
source/application/discord/storage_manager.cpp
Normal file
148
source/application/discord/storage_manager.cpp
Normal 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
|
45
source/application/discord/storage_manager.h
Normal file
45
source/application/discord/storage_manager.h
Normal 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
|
160
source/application/discord/store_manager.cpp
Normal file
160
source/application/discord/store_manager.cpp
Normal 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
|
38
source/application/discord/store_manager.h
Normal file
38
source/application/discord/store_manager.h
Normal 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
|
707
source/application/discord/types.cpp
Normal file
707
source/application/discord/types.cpp
Normal 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
|
426
source/application/discord/types.h
Normal file
426
source/application/discord/types.h
Normal 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
|
58
source/application/discord/user_manager.cpp
Normal file
58
source/application/discord/user_manager.cpp
Normal 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
|
29
source/application/discord/user_manager.h
Normal file
29
source/application/discord/user_manager.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue