diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index 7ce410aa7..4c0ea9564 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -294,6 +294,7 @@
True
True
True
+ True
True
True
True
diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h
index 592b47ef3..a6a6ec630 100644
--- a/Source/Engine/Core/Types/BaseTypes.h
+++ b/Source/Engine/Core/Types/BaseTypes.h
@@ -86,6 +86,8 @@ struct BoundingFrustum;
struct Color;
struct Color32;
struct Variant;
+template
+class Span;
class HeapAllocation;
template
class FixedAllocation;
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index 9de22b48a..33a416b38 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -773,6 +773,21 @@ Variant::Variant(const Dictionary& v)
AsDictionary = New>(v);
}
+Variant::Variant(const Span& v)
+ : Type(VariantType::Blob)
+{
+ AsBlob.Length = v.Length();
+ if (AsBlob.Length > 0)
+ {
+ AsBlob.Data = Allocator::Allocate(AsBlob.Length);
+ Platform::MemoryCopy(AsBlob.Data, v.Get(), AsBlob.Length);
+ }
+ else
+ {
+ AsBlob.Data = nullptr;
+ }
+}
+
Variant::Variant(const CommonValue& value)
: Variant()
{
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index b3a2bb73b..9d6a97688 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -229,6 +229,7 @@ public:
Variant(Array&& v);
Variant(const Array& v);
explicit Variant(const Dictionary& v);
+ explicit Variant(const Span& v);
explicit Variant(const CommonValue& v);
~Variant();
diff --git a/Source/Engine/Engine/Base/GameBase.cpp b/Source/Engine/Engine/Base/GameBase.cpp
index 42bc866c5..ed87c19aa 100644
--- a/Source/Engine/Engine/Base/GameBase.cpp
+++ b/Source/Engine/Engine/Base/GameBase.cpp
@@ -52,17 +52,15 @@ int32 GameBase::LoadProduct()
// Load build game header file
{
- int32 tmp;
- Array data;
- FileReadStream* stream = nullptr;
-
#if 1
// Open file
+ FileReadStream* stream = nullptr;
stream = FileReadStream::Open(Globals::ProjectFolder / TEXT("Content/head"));
if (stream == nullptr)
goto LOAD_GAME_HEAD_FAILED;
// Check header
+ int32 tmp;
stream->ReadInt32(&tmp);
if (tmp != ('x' + 'D') * 131)
goto LOAD_GAME_HEAD_FAILED;
@@ -73,6 +71,7 @@ int32 GameBase::LoadProduct()
goto LOAD_GAME_HEAD_FAILED;
// Load game primary data
+ Array data;
stream->ReadArray(&data);
if (data.Count() != 808 + sizeof(Guid))
goto LOAD_GAME_HEAD_FAILED;
diff --git a/Source/Engine/Engine/Engine.Build.cs b/Source/Engine/Engine/Engine.Build.cs
index e7bf85034..85c88024d 100644
--- a/Source/Engine/Engine/Engine.Build.cs
+++ b/Source/Engine/Engine/Engine.Build.cs
@@ -37,6 +37,7 @@ public class Engine : EngineModule
options.PublicDependencies.Add("Utilities");
options.PublicDependencies.Add("Visject");
options.PublicDependencies.Add("Localization");
+ options.PublicDependencies.Add("Online");
// Use source folder per platform group
switch (options.Platform.Target)
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
index af5fa5550..5aae0febf 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp
@@ -251,8 +251,8 @@ bool GPUDeviceDX12::Init()
updateFrameEvents();
#if PLATFORM_GDK
- GDKPlatform::OnSuspend.Bind(this);
- GDKPlatform::OnResume.Bind(this);
+ GDKPlatform::Suspended.Bind(this);
+ GDKPlatform::Resumed.Bind(this);
#endif
#else
// Get DXGI adapter
@@ -838,12 +838,12 @@ void GPUDeviceDX12::updateRes2Dispose()
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
-void GPUDeviceDX12::OnSuspend()
+void GPUDeviceDX12::OnSuspended()
{
_commandQueue->GetCommandQueue()->SuspendX(0);
}
-void GPUDeviceDX12::OnResume()
+void GPUDeviceDX12::OnResumed()
{
_commandQueue->GetCommandQueue()->ResumeX();
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h
index fdc86912d..f60f11c56 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.h
@@ -158,8 +158,8 @@ public:
}
#if PLATFORM_XBOX_SCARLETT ||PLATFORM_XBOX_ONE
- void OnSuspend();
- void OnResume();
+ void OnSuspended();
+ void OnResumed();
#endif
private:
diff --git a/Source/Engine/Online/IOnlinePlatform.h b/Source/Engine/Online/IOnlinePlatform.h
new file mode 100644
index 000000000..6d387021d
--- /dev/null
+++ b/Source/Engine/Online/IOnlinePlatform.h
@@ -0,0 +1,237 @@
+// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Online.h"
+#include "Engine/Core/Types/Span.h"
+#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/DateTime.h"
+
+///
+/// Online platform user presence common states.
+///
+API_ENUM(Namespace="FlaxEngine.Online") enum class OnlinePresenceStates
+{
+ ///
+ /// User is offline.
+ ///
+ Offline = 0,
+
+ ///
+ /// User is online.
+ ///
+ Online,
+
+ ///
+ /// User is online but busy.
+ ///
+ Busy,
+
+ ///
+ /// User is online but away (no activity for some time).
+ ///
+ Away,
+};
+
+///
+/// Online platform user description.
+///
+API_STRUCT(Namespace="FlaxEngine.Online") struct FLAXENGINE_API OnlineUser
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(OnlineUser);
+
+ ///
+ /// Unique player identifier. Specific for a certain online platform.
+ ///
+ API_FIELD() Guid Id;
+
+ ///
+ /// The player name.
+ ///
+ API_FIELD() String Name;
+
+ ///
+ /// The current player presence state.
+ ///
+ API_FIELD() OnlinePresenceStates PresenceState;
+};
+
+///
+/// Online platform achievement description.
+///
+API_STRUCT(Namespace="FlaxEngine.Online") struct FLAXENGINE_API OnlineAchievement
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(OnlineAchievement);
+
+ ///
+ /// Unique achievement identifier. Specific for a certain online platform.
+ ///
+ API_FIELD() String Identifier;
+
+ ///
+ /// Achievement name. Specific for a game.
+ ///
+ API_FIELD() String Name;
+
+ ///
+ /// The achievement title text.
+ ///
+ API_FIELD() String Title;
+
+ ///
+ /// The achievement description text.
+ ///
+ API_FIELD() String Description;
+
+ ///
+ /// True if achievement is hidden from user (eg. can see it once it's unlocked).
+ ///
+ API_FIELD() bool IsHidden = false;
+
+ ///
+ /// Achievement unlock percentage progress (normalized to 0-100 range).
+ ///
+ API_FIELD() float Progress = 0.0f;
+
+ ///
+ /// Date and time at which player unlocked the achievement.
+ ///
+ API_FIELD() DateTime UnlockTime = DateTime::MinValue();
+};
+
+///
+/// Interface for online platform providers for communicating with various multiplayer services such as player info, achievements, game lobby or in-game store.
+///
+API_INTERFACE(Namespace="FlaxEngine.Online") class FLAXENGINE_API IOnlinePlatform
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(IOnlinePlatform);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ virtual ~IOnlinePlatform() = default;
+
+ ///
+ /// Initializes the online platform services.
+ ///
+ /// Called only by Online system.
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool Initialize() = 0;
+
+ ///
+ /// Shutdowns the online platform services.
+ ///
+ /// Called only by Online system. Can be used to destroy the object.
+ API_FUNCTION() virtual void Deinitialize() = 0;
+
+public:
+ ///
+ /// Logins the local user into the online platform.
+ ///
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool UserLogin(User* localUser = nullptr) = 0;
+
+ ///
+ /// Logout the local user from the online platform.
+ ///
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool UserLogout(User* localUser = nullptr) = 0;
+
+ ///
+ /// Checks if the local user is logged in.
+ ///
+ /// The local user (null if use default one).
+ /// True if user is logged, otherwise false.
+ API_FUNCTION() virtual bool GetUserLoggedIn(User* localUser = nullptr) = 0;
+
+ ///
+ /// Gets the player from the online platform.
+ ///
+ /// The local player user info.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool GetUser(API_PARAM(Out) OnlineUser& user, User* localUser = nullptr) = 0;
+
+ ///
+ /// Gets the list of friends of the user from the online platform.
+ ///
+ /// The result local player friends user infos.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool GetFriends(API_PARAM(Out) Array& friends, User* localUser = nullptr) = 0;
+
+public:
+ ///
+ /// Gets the list of all achievements for this game.
+ ///
+ /// The result achievements list
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool GetAchievements(API_PARAM(Out) Array& achievements, User* localUser = nullptr) = 0;
+
+ ///
+ /// Unlocks the achievement.
+ ///
+ /// The achievement name. Specific for a game.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool UnlockAchievement(const StringView& name, User* localUser = nullptr) = 0;
+
+ ///
+ /// Updates the achievement unlocking progress (in range 0-100).
+ ///
+ /// The achievement name. Specific for a game.
+ /// The achievement unlock progress (in range 0-100).
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool UnlockAchievementProgress(const StringView& name, float progress, User* localUser = nullptr) = 0;
+
+#if !BUILD_RELEASE
+ ///
+ /// Resets the all achievements progress for this game.
+ ///
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool ResetAchievements(User* localUser = nullptr) = 0;
+#endif
+
+public:
+ ///
+ /// Gets the online statistical value.
+ ///
+ /// The stat name.
+ /// The result value.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool GetStat(const StringView& name, API_PARAM(Out) float& value, User* localUser = nullptr) = 0;
+
+ ///
+ /// Sets the online statistical value.
+ ///
+ /// The stat name.
+ /// The value.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool SetStat(const StringView& name, float value, User* localUser = nullptr) = 0;
+
+public:
+ ///
+ /// Gets the online savegame data. Returns empty if savegame slot is unused.
+ ///
+ /// The savegame slot name.
+ /// The result data. Empty or null for unused slot name.
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool GetSaveGame(const StringView& name, API_PARAM(Out) Array& data, User* localUser = nullptr) = 0;
+
+ ///
+ /// Sets the online savegame data.
+ ///
+ /// The savegame slot name.
+ /// The data. Empty or null to delete slot (or mark as unused).
+ /// The local user (null if use default one).
+ /// True if failed, otherwise false.
+ API_FUNCTION() virtual bool SetSaveGame(const StringView& name, const Span& data, User* localUser = nullptr) = 0;
+};
diff --git a/Source/Engine/Online/Online.Build.cs b/Source/Engine/Online/Online.Build.cs
new file mode 100644
index 000000000..bb4ff1ba5
--- /dev/null
+++ b/Source/Engine/Online/Online.Build.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
+
+using Flax.Build;
+
+///
+/// Online services base module.
+///
+public class Online : EngineModule
+{
+}
diff --git a/Source/Engine/Online/Online.cpp b/Source/Engine/Online/Online.cpp
new file mode 100644
index 000000000..ca53ed78e
--- /dev/null
+++ b/Source/Engine/Online/Online.cpp
@@ -0,0 +1,51 @@
+// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
+
+#include "Online.h"
+#include "IOnlinePlatform.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Engine/EngineService.h"
+#include "Engine/Scripting/ScriptingObject.h"
+
+class OnlineService : public EngineService
+{
+public:
+ OnlineService()
+ : EngineService(TEXT("Online"), 100)
+ {
+ }
+
+ void Dispose() override
+ {
+ // Cleanup current online platform
+ Online::Initialize(nullptr);
+ }
+};
+
+IOnlinePlatform* Online::Platform = nullptr;
+Action Online::PlatformChanged;
+OnlineService OnlineServiceInstance;
+
+bool Online::Initialize(IOnlinePlatform* platform)
+{
+ if (Platform == platform)
+ return false;
+ const auto object = ScriptingObject::FromInterface(platform, IOnlinePlatform::TypeInitializer);
+ LOG(Info, "Changing online platform to {0}", object ? object->ToString() : (platform ? TEXT("?") : TEXT("none")));
+
+ if (Platform)
+ {
+ Platform->Deinitialize();
+ }
+ Platform = platform;
+ if (Platform)
+ {
+ if (Platform->Initialize())
+ {
+ Platform = nullptr;
+ LOG(Error, "Failed to initialize online platform.");
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/Source/Engine/Online/Online.h b/Source/Engine/Online/Online.h
new file mode 100644
index 000000000..7d8bdc820
--- /dev/null
+++ b/Source/Engine/Online/Online.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Delegate.h"
+#include "Engine/Scripting/ScriptingType.h"
+
+class IOnlinePlatform;
+
+///
+/// The online system for communicating with various multiplayer services such as player info, achievements, game lobby or in-game store.
+///
+API_CLASS(Static, Namespace="FlaxEngine.Online") class FLAXENGINE_API Online
+{
+ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Online);
+public:
+ ///
+ /// The current online platform.
+ ///
+ API_FIELD(ReadOnly) static IOnlinePlatform* Platform;
+
+ ///
+ /// Event called when current online platform gets changed.
+ ///
+ API_EVENT() static Action PlatformChanged;
+
+public:
+ ///
+ /// Initializes the online system with a given online platform implementation.
+ ///
+ /// Destroys the current platform (in any already in-use).
+ /// The online platform object.
+ /// True if failed, otherwise false.
+ API_FUNCTION() static bool Initialize(IOnlinePlatform* platform);
+};
diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp
index 4cb340838..bf4f10149 100644
--- a/Source/Engine/Platform/Base/StringUtilsBase.cpp
+++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp
@@ -406,6 +406,12 @@ bool StringUtils::Parse(const Char* str, float* result)
return false;
}
+bool StringUtils::Parse(const char* str, float* result)
+{
+ *result = (float)atof(str);
+ return false;
+}
+
String StringUtils::ToString(int32 value)
{
char buf[STRING_UTILS_ITOSTR_BUFFER_SIZE];
diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp
index 1a2cfe969..c259acd37 100644
--- a/Source/Engine/Platform/GDK/GDKPlatform.cpp
+++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp
@@ -21,6 +21,10 @@
#include
#include
+#define GDK_LOG(result, method) \
+ if (FAILED(result)) \
+ LOG(Error, "GDK method {0} failed with result 0x{1:x}", TEXT(method), (uint32)result)
+
inline bool operator==(const APP_LOCAL_DEVICE_ID& l, const APP_LOCAL_DEVICE_ID& r)
{
return Platform::MemoryCompare(&l, &r, sizeof(APP_LOCAL_DEVICE_ID)) == 0;
@@ -28,8 +32,8 @@ inline bool operator==(const APP_LOCAL_DEVICE_ID& l, const APP_LOCAL_DEVICE_ID&
const Char* GDKPlatform::ApplicationWindowClass = TEXT("FlaxWindow");
void* GDKPlatform::Instance = nullptr;
-Delegate<> GDKPlatform::OnSuspend;
-Delegate<> GDKPlatform::OnResume;
+Delegate<> GDKPlatform::Suspended;
+Delegate<> GDKPlatform::Resumed;
namespace
{
@@ -51,7 +55,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
LOG(Info, "Suspending application");
IsSuspended = true;
- GDKPlatform::OnSuspend();
+ GDKPlatform::Suspended();
// Complete deferral
SetEvent(PlmSuspendComplete);
@@ -60,7 +64,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
IsSuspended = false;
LOG(Info, "Resuming application");
- GDKPlatform::OnResume();
+ GDKPlatform::Resumed();
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
@@ -93,6 +97,7 @@ void CALLBACK UserChangeEventCallback(_In_opt_ void* context, _In_ XUserLocalId
if (user)
{
// Logout
+ LOG(Info, "GDK user '{0}' logged out", user->GetName());
OnPlatformUserRemove(user);
}
break;
@@ -161,8 +166,9 @@ void OnMainWindowCreated(HWND hWnd)
void CALLBACK AddUserComplete(_In_ XAsyncBlock* ab)
{
XUserHandle userHandle;
- HRESULT hr = XUserAddResult(ab, &userHandle);
- if (SUCCEEDED(hr))
+ HRESULT result = XUserAddResult(ab, &userHandle);
+ delete ab;
+ if (SUCCEEDED(result))
{
XUserLocalId userLocalId;
XUserGetLocalId(userHandle, &userLocalId);
@@ -173,12 +179,24 @@ void CALLBACK AddUserComplete(_In_ XAsyncBlock* ab)
if (Platform::FindUser(localId) == nullptr)
{
// Login
- auto user = New(userHandle, userLocalId, String::Empty);
+ char gamerTag[XUserGamertagComponentModernMaxBytes];
+ size_t gamerTagSize;
+ XUserGetGamertag(userHandle, XUserGamertagComponent::Modern, ARRAY_COUNT(gamerTag), gamerTag, &gamerTagSize);
+ String name;
+ name.SetUTF8(gamerTag, StringUtils::Length(gamerTag));
+ LOG(Info, "GDK user '{0}' logged in", name);
+ auto user = New(userHandle, userLocalId, name);
OnPlatformUserAdd(user);
}
}
-
- delete ab;
+ else if (result == E_GAMEUSER_NO_DEFAULT_USER || result == E_GAMEUSER_RESOLVE_USER_ISSUE_REQUIRED || result == 0x8015DC12)
+ {
+ Platform::SignInWithUI();
+ }
+ else
+ {
+ GDK_LOG(result, "XUserAddResult");
+ }
}
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
@@ -312,6 +330,32 @@ bool GDKPlatform::IsRunningOnDevKit()
return deviceType == XSystemDeviceType::XboxOneXDevkit || deviceType == XSystemDeviceType::XboxScarlettDevkit;
}
+void GDKPlatform::SignInSilently()
+{
+ auto asyncBlock = new XAsyncBlock();
+ asyncBlock->queue = TaskQueue;
+ asyncBlock->callback = AddUserComplete;
+ HRESULT result = XUserAddAsync(XUserAddOptions::AddDefaultUserSilently, asyncBlock);
+ if (FAILED(result))
+ {
+ GDK_LOG(result, "XUserAddAsync");
+ delete asyncBlock;
+ }
+}
+
+void GDKPlatform::SignInWithUI()
+{
+ auto ab = new XAsyncBlock();
+ ab->queue = TaskQueue;
+ ab->callback = AddUserComplete;
+ HRESULT result = XUserAddAsync(XUserAddOptions::AllowGuests, ab);
+ if (FAILED(result))
+ {
+ GDK_LOG(result, "XUserAddAsync");
+ delete ab;
+ }
+}
+
User* GDKPlatform::FindUser(const XUserLocalId& id)
{
User* result = nullptr;
@@ -366,29 +410,27 @@ bool GDKPlatform::Init()
&UserDeviceAssociationChangedCallbackToken
);
- // Login the default user
- {
- auto asyncBlock = new XAsyncBlock();
- asyncBlock->queue = TaskQueue;
- asyncBlock->callback = AddUserComplete;
- HRESULT hr = XUserAddAsync(XUserAddOptions::AddDefaultUserAllowingUI, asyncBlock);
- if (FAILED(hr))
- delete asyncBlock;
- }
-
GDKInput::Init();
return false;
}
-void GDKPlatform::BeforeRun()
+void GDKPlatform::LogInfo()
{
+ Win32Platform::LogInfo();
+
// Log system info
const XSystemAnalyticsInfo analyticsInfo = XSystemGetAnalyticsInfo();
LOG(Info, "{0}, {1}", StringAsUTF16<64>(analyticsInfo.family).Get(), StringAsUTF16<64>(analyticsInfo.form).Get());
LOG(Info, "OS Version {0}.{1}.{2}.{3}", analyticsInfo.osVersion.major, analyticsInfo.osVersion.minor, analyticsInfo.osVersion.build, analyticsInfo.osVersion.revision);
}
+void GDKPlatform::BeforeRun()
+{
+ // Login the default user
+ SignInSilently();
+}
+
void GDKPlatform::Tick()
{
PROFILE_CPU_NAMED("Application.Tick");
diff --git a/Source/Engine/Platform/GDK/GDKPlatform.h b/Source/Engine/Platform/GDK/GDKPlatform.h
index 818b1874a..97298fc55 100644
--- a/Source/Engine/Platform/GDK/GDKPlatform.h
+++ b/Source/Engine/Platform/GDK/GDKPlatform.h
@@ -23,8 +23,8 @@ public:
///
static void* Instance;
- static Delegate<> OnSuspend;
- static Delegate<> OnResume;
+ static Delegate<> Suspended;
+ static Delegate<> Resumed;
public:
@@ -44,12 +44,15 @@ public:
static bool IsRunningOnDevKit();
+ static void SignInSilently();
+ static void SignInWithUI();
static User* FindUser(const struct XUserLocalId& id);
public:
// [Win32Platform]
static bool Init();
+ static void LogInfo();
static void BeforeRun();
static void Tick();
static void BeforeExit();
diff --git a/Source/Engine/Platform/GDK/GDKPlatformSettings.h b/Source/Engine/Platform/GDK/GDKPlatformSettings.h
index f59c5b2c4..dd0429a5c 100644
--- a/Source/Engine/Platform/GDK/GDKPlatformSettings.h
+++ b/Source/Engine/Platform/GDK/GDKPlatformSettings.h
@@ -15,9 +15,8 @@ class Texture;
///
API_CLASS(Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GDKPlatformSettings : public SettingsBase
{
-DECLARE_SCRIPTING_TYPE_MINIMAL(GDKPlatformSettings);
+ DECLARE_SCRIPTING_TYPE_MINIMAL(GDKPlatformSettings);
public:
-
///
/// Game identity name stored in game package manifest (for store). If empty the product name will be used from Game Settings.
///
@@ -96,6 +95,12 @@ public:
API_FIELD(Attributes="EditorOrder(320), EditorDisplay(\"Xbox Live\")")
bool RequiresXboxLive = false;
+ ///
+ /// Service Configuration ID (see Xbox Live docs).
+ ///
+ API_FIELD(Attributes="EditorOrder(330), EditorDisplay(\"Xbox Live\")")
+ StringAnsi SCID;
+
///
/// Specifies if the Game DVR system component is enabled or not.
///
@@ -106,16 +111,15 @@ public:
/// Specifies if broadcasting the title should be blocked or allowed.
///
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Media Capture\")")
- bool BlockBroadcast = false;
+ bool BlockBroadcast = false;
///
/// Specifies if Game DVR of the title should be blocked or allowed.
///
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Media Capture\")")
- bool BlockGameDVR = false;
+ bool BlockGameDVR = false;
public:
-
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override
{
@@ -131,6 +135,7 @@ public:
DESERIALIZE(TitleId);
DESERIALIZE(StoreId);
DESERIALIZE(RequiresXboxLive);
+ DESERIALIZE(SCID);
DESERIALIZE(GameDVRSystemComponent);
DESERIALIZE(BlockBroadcast);
DESERIALIZE(BlockGameDVR);
diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h
index 7dfffc4e9..f2efa9a5d 100644
--- a/Source/Engine/Platform/StringUtils.h
+++ b/Source/Engine/Platform/StringUtils.h
@@ -428,6 +428,7 @@ public:
// @return Result value
// @returns True if cannot convert data, otherwise false
static bool Parse(const Char* str, float* result);
+ static bool Parse(const char* str, float* result);
public:
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 3208691f2..77821e29f 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -411,8 +411,7 @@ namespace Flax.Build.Bindings
if ((typeInfo.Type == "Array" || typeInfo.Type == "Span") && typeInfo.GenericArgs != null)
{
type = "MonoArray*";
- var valueClass = GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo);
- return "MUtils::ToArray({0}, " + valueClass + ")";
+ return "MUtils::ToArray({0}, " + GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
}
// BytesContainer
@@ -735,6 +734,43 @@ namespace Flax.Build.Bindings
}
}
+ private static string GenerateCppWrapperNativeToBox(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value)
+ {
+ // Optimize passing scripting objects
+ var apiType = FindApiTypeInfo(buildData, typeInfo, caller);
+ if (apiType != null && apiType.IsScriptingObject)
+ return $"ScriptingObject::ToManaged((ScriptingObject*){value})";
+
+ // Array or Span
+ if ((typeInfo.Type == "Array" || typeInfo.Type == "Span") && typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count >= 1)
+ return $"MUtils::ToArray({value}, {GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, null)})";
+
+ // BytesContainer
+ if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
+ return "MUtils::ToArray({0})";
+
+ // Construct native typename for MUtils template argument
+ var nativeType = new StringBuilder(64);
+ nativeType.Append(typeInfo.Type);
+ if (typeInfo.GenericArgs != null)
+ {
+ nativeType.Append('<');
+ for (var j = 0; j < typeInfo.GenericArgs.Count; j++)
+ {
+ if (j != 0)
+ nativeType.Append(", ");
+ nativeType.Append(typeInfo.GenericArgs[j]);
+ }
+
+ nativeType.Append('>');
+ }
+ if (typeInfo.IsPtr)
+ nativeType.Append('*');
+
+ // Use MUtils to box the value
+ return $"MUtils::Box<{nativeType}>({value}, {GenerateCppGetNativeClass(buildData, typeInfo, caller, null)})";
+ }
+
private static void GenerateCppWrapperFunction(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, string callFormat = "{0}({1})")
{
// Setup function binding glue to ensure that wrapper method signature matches for C++ and C#
@@ -1127,33 +1163,8 @@ namespace Flax.Build.Bindings
separator = true;
thunkParams += "void*";
- // Optimize passing scripting objects
- var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, classInfo);
- if (apiType != null && apiType.IsScriptingObject)
- {
- thunkCall += $"ScriptingObject::ToManaged((ScriptingObject*){parameterInfo.Name})";
- continue;
- }
-
- var nativeType = new StringBuilder(64);
- nativeType.Append(parameterInfo.Type.Type);
- if (parameterInfo.Type.GenericArgs != null)
- {
- nativeType.Append('<');
- for (var j = 0; j < parameterInfo.Type.GenericArgs.Count; j++)
- {
- if (j != 0)
- nativeType.Append(", ");
- nativeType.Append(parameterInfo.Type.GenericArgs[j]);
- }
-
- nativeType.Append('>');
- }
- if (parameterInfo.Type.IsPtr)
- nativeType.Append('*');
-
// Mono thunk call uses boxed values as objects
- thunkCall += $"MUtils::Box<{nativeType}>({parameterInfo.Name}, {GenerateCppGetNativeClass(buildData, parameterInfo.Type, classInfo, null)})";
+ thunkCall += GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, parameterInfo.Name);
}
if (functionInfo.ReturnType.IsVoid)