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)