diff --git a/Content/Beamable/Content/Manifests/Cooked/BCC_Global.uasset b/Content/Beamable/Content/Manifests/Cooked/BCC_Global.uasset deleted file mode 100644 index 096d5fdb..00000000 --- a/Content/Beamable/Content/Manifests/Cooked/BCC_Global.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc1cd70712d37ddb6ecd8d7464b63f1912739173744f126ada7cfd52db1a4c02 -size 3707 diff --git a/Docs/docs/features/matchmaking.md b/Docs/docs/features/matchmaking.md new file mode 100644 index 00000000..21423352 --- /dev/null +++ b/Docs/docs/features/matchmaking.md @@ -0,0 +1,76 @@ +# Matchmaking +## Overview + +The Beamable SDK Matchmaking feature allows player to join a matchmaking queue (defined by a `UBeamGameTypeContent` instance), configure rules for matches to be made, receive notifications of progress and, optionally, provision a Game Server with a 3rd Party Game Server Orchestrator for the resulting match. + +## `UBeamGameTypeContent` as Queues +Beamable's Matchmaking system depends on Beamable's [Content System](../features/content.md) in order for you to define various matchmaking queues. + +Each Matchmaking queue is described by a `UBeamGameTypeContent`. This content type defines a few things about a queue: + +- `TArray Teams`: Defines the number of teams (one per entry in the array) and, for each of those teams, defines the number of players and a name. + +!!! note "Dynamic Team Sizes" + This is for fixed-team-size queues. For teams that are built *after* the match is made, you can use the resulting [Lobby](../features/lobbies.md)'s data and [Federated Game Server](../guides/federations/federated-game-server.md) to compute and store the dynamic team split. + +- `FOptionalBeamStatComparisonRule EntryRules`: Optionally defines a set of [Stat](../features/stats.md) comparison rules. Only players whose [Stats](../features/stats.md) match those comparisons will be allowed into this queue. + +!!! note "Gating on Rank" + Failing to meet entry rule requirements will cause the Join Operation to fail --- so these can be used to gate queues on a player's account level or rank for example. + +- `Numeric Rules` and `String Rules`: These are match grouping rules. + - **Numeric Rules** tries to group players with a particular stat within certain delta range. + - **String Rules** groups players whose values for a particular stat match a certain value. + +!!! note "Grouping by WinRate" + If you compute and store an Win Percentage value in a `Stat`, for example, you can tell the queue to group players that are closer in win-rate than others using **Numeric Rules**. + +- `MaxWaitDurationSecs`: Defines how long the player can stay in the queue without being matched; after this time passes, the matchmaking fails and `OnMatchTimedOut` is triggered. +- `MatchingIntervalSecs`: Defines the ticking interval for the queue. Defaults to 10 seconds, which means that new sets of matches are produced every 10 seconds. + - If the time it takes to tick a queue is longer than the value set here, the longer value becomes the new tick. +- `FederatedGameServerNamespace`: Defines a [Federation Id](../concepts/federation.md#federation-id) for a [Federated Game Server](../guides/federations/federated-game-server) federation. + +## Joining/Leaving Queues +The **Matchmaking Subsystem** the SDK provides out of the box provides you a few things: + +- A "Join a Queue" Operation. +- A "Leave a Queue" Operation. +- A "I'm in the queue, but wasn't matched yet" callback (`OnMatchSearching`) +- A "I was in the queue for too long without a match" callback (`OnMatchTimedOut`) +- A "I got matched and my match is ready" callback (`OnMatchReady`) +- A "I left the queue before getting matched" callback (`OnMatchCancelled`) + +!!! warning "Party System" + Beamable's backend supports party matchmaking. The `OnMatchRemoteSearchStarted` and `OnMatchCancelled` are how a party member who is not the leader becomes aware that the leader has joined/left a queue for their party. + + The SDK does not have nice ergonomics for using this yet, but it is possible to use our Party system writing your own operations on top of `UBeamPartyApi`. + +Each player can only be in a single queue at a time. When joining a queue, you can optionally pass in a set of key/value pairs called `FBeamTag`. When a match gets made with that particular user/party, these tags end up inside the [Lobby](../features/lobbies.md)'s per-player data. If you're in a party, the party leader is the only one allowed to join a queue on behalf of the party. + +!!! warning "Party and Tags" + When joining a queue as the party leader and passing in `FBeamTag`, those tags are only for the party leader. If you need to gather data for every user, we recommend using [Federated Game Server](../guides/federations/federated-game-server) and [Stats](../features/stats.md) to get that data into the [Lobby](../features/lobbies.md) instead. + +Leaving a queue is very straight-forward; just call the function with the appropriate `FUserSlot` and at the end of the Operation the "matchmaking ticket" will be invalidated and you'll no longer be in the queue. When using this, keep in mind that the ticket is only invalidated *after* the operation completes; not after this function is called. + +### Match Found and Tickets +When you join a queue in Beamable's matchmaking, you get back a `FBeamMatchmakingTicket`. This ticket contains information about the entry onto the queue: + +- **GameType** is the queue type. +- **GamerTagsInTicket** hold the list of players that are in the ticket. +- **SlotsInTicket** hold the list of local `FUserSlot` that are in the ticket (just the Owner Player, unless your game has multiple local players and matchmaking). +- **FoundMatchLobbyId** is only filled inside the `OnMatchReady` callback and has the id for the resulting [Lobby](../features/lobbies.md) for the match. You can use this to retrieve data from the [Lobby Subsystem](../features/lobbies.md) inside the `OnMatchReady` callback to get connection information and more. + +If you want to understand a bit more about these tickets, we recommend taking a look at the source code of the `UBeamMatchmakingSubsystem` (it is pretty simple and should give you a lot more confidence in understanding the system). +## Getting Started +To use `UBeamMatchmakingSubsystem` via blueprints (or C++), you'll need to: + +- Use the [Content Window](../features/content.md) to create a `game_type` content with a single team with a Min/Max player count of 1. +- Publish that content to your realm. + +???+ warning "Assumptions" + Make sure that user is logged in when the code below runs. See [Runtime Concepts](runtime-concepts.md) + +- Assign delegates to `OnMatchReady`, `OnMatchCancelled`, `OnMatchTimedOut`, so on... +- Call `TryJoinQueueOperation` with the signed in `FUserSlot` and the created `game_type` content. +- After a short duration, you should see the `OnMatchReady` callback being triggered with a lobby containing just you as a player. +- That is it! \ No newline at end of file diff --git a/Plugins/BeamableCore/Source/BeamableCoreBlueprintNodes/Public/BeamFlow/Operations/Runtime/K2BeamNode_Operation_Store.h b/Plugins/BeamableCore/Source/BeamableCoreBlueprintNodes/Public/BeamFlow/Operations/Runtime/K2BeamNode_Operation_Store.h index 30f1e216..46448074 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreBlueprintNodes/Public/BeamFlow/Operations/Runtime/K2BeamNode_Operation_Store.h +++ b/Plugins/BeamableCore/Source/BeamableCoreBlueprintNodes/Public/BeamFlow/Operations/Runtime/K2BeamNode_Operation_Store.h @@ -7,18 +7,18 @@ #include "Subsystems/Store/BeamStoreSubsystem.h" #include "K2BeamNode_Operation_Store.generated.h" -#define LOCTEXT_NAMESPACE "UK2BeamNode_Operation_StorePerformPurchase" +#define LOCTEXT_NAMESPACE "UK2BeamNode_Operation_PerformPurchase" UCLASS(meta=(BeamFlowNode)) -class BEAMABLECOREBLUEPRINTNODES_API UK2BeamNode_Operation_StorePerformPurchase : public UK2BeamNode_Operation +class BEAMABLECOREBLUEPRINTNODES_API UK2BeamNode_Operation_PerformPurchase : public UK2BeamNode_Operation { GENERATED_BODY() - virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override { return LOCTEXT("Title", "Operation - Store - PerformPurchase"); } + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override { return LOCTEXT("Title", "Operation - Store - Perform Purchase"); } virtual FName GetSubsystemSelfFunctionName() const override { return GET_FUNCTION_NAME_CHECKED(UBeamStoreSubsystem, GetSelf); } - virtual FName GetOperationFunctionName() const override { return GET_FUNCTION_NAME_CHECKED(UBeamStoreSubsystem, CommitPurchaseListingOperation); } + virtual FName GetOperationFunctionName() const override { return GET_FUNCTION_NAME_CHECKED(UBeamStoreSubsystem, PerformPurchaseOperation); } virtual UClass* GetRuntimeSubsystemClass() const override { return UBeamStoreSubsystem::StaticClass(); } }; diff --git a/Plugins/BeamableCore/Source/BeamableCoreEditor/Private/BeamableCoreEditor.cpp b/Plugins/BeamableCore/Source/BeamableCoreEditor/Private/BeamableCoreEditor.cpp index 7c2f61b5..21a07b85 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreEditor/Private/BeamableCoreEditor.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreEditor/Private/BeamableCoreEditor.cpp @@ -15,6 +15,7 @@ #include "AutoGen/Optionals/OptionalArrayOfBeamGamerTag.h" #include "AutoGen/Optionals/OptionalArrayOfBeamTag.h" #include "BeamBackend/ReplacementTypes/BeamClientPermission.h" +#include "Content/BeamContentTypes/BeamListingContent.h" #include "Kismet2/KismetEditorUtilities.h" #include "PropertyType/BeamClientPermissionCustomization.h" #include "PropertyType/BeamContentIdCustomization.h" @@ -131,6 +132,13 @@ void FBeamableCoreEditorModule::StartupModule() FOptionalBeamAccountId::StaticStruct()->GetFName(), // this is where our MakeInstance() method is useful FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FBeamOptionalCustomization::MakeInstance)); + + PropertyModule.RegisterCustomPropertyTypeLayout( + // This is the name of the Struct this tells the property editor which is the struct property our customization will applied on. + FBeamOptionalSchedule::StaticStruct()->GetFName(), + // this is where our MakeInstance() method is useful + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FBeamOptionalCustomization::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); } diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Runtime/BeamRuntime.cpp b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Runtime/BeamRuntime.cpp index 548da8c4..66742d73 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Runtime/BeamRuntime.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Runtime/BeamRuntime.cpp @@ -1355,11 +1355,15 @@ void UBeamRuntime::LoginExternalIdentity(FUserSlot UserSlot, FString ExternalSer if (UserSlotSystem->GetUserDataAtSlot(UserSlot, RealmUser, this)) { // Configure us to wait until the slot is fully unauthenticated and then sign in. - OnUserClearedCode.AddLambda([this, AuthSubsystem](FUserSlot UserSlot, FBeamOperationHandle OpHandle, UAuthenticateRequest* AuthReq) + UserSlotClearedEnqueuedHandle = OnUserClearedCode.AddLambda([this, AuthSubsystem](FUserSlot UserSlot, FBeamOperationHandle OpHandle, UAuthenticateRequest* AuthReq) { const auto AuthenticateHandler = FOnAuthenticateFullResponse::CreateUObject(this, &UBeamRuntime::OnAuthenticated, UserSlot, OpHandle, FDelayedOperation{}); FBeamRequestContext RequestContext; AuthSubsystem->CPP_Authenticate(AuthReq, AuthenticateHandler, RequestContext, OpHandle); + + // Clean Up handle + OnUserClearedCode.Remove(UserSlotClearedEnqueuedHandle); + UserSlotClearedEnqueuedHandle = {}; }, Op, Req); UserSlotSystem->ClearUserAtSlot(UserSlot, USCR_Manual, true, this); } @@ -1386,11 +1390,15 @@ void UBeamRuntime::LoginEmailAndPassword(FUserSlot UserSlot, FString Email, FStr if (UserSlotSystem->GetUserDataAtSlot(UserSlot, RealmUser, this)) { // Configure us to wait until the slot is fully unauthenticated and then sign in. - OnUserClearedCode.AddLambda([this, AuthSubsystem](FUserSlot UserSlot, FBeamOperationHandle OpHandle, UAuthenticateRequest* AuthReq) + UserSlotClearedEnqueuedHandle = OnUserClearedCode.AddLambda([this, AuthSubsystem](FUserSlot UserSlot, FBeamOperationHandle OpHandle, UAuthenticateRequest* AuthReq) { const auto AuthenticateHandler = FOnAuthenticateFullResponse::CreateUObject(this, &UBeamRuntime::OnAuthenticated, UserSlot, OpHandle, FDelayedOperation{}); FBeamRequestContext RequestContext; AuthSubsystem->CPP_Authenticate(AuthReq, AuthenticateHandler, RequestContext, OpHandle); + + // Clean Up handle + OnUserClearedCode.Remove(UserSlotClearedEnqueuedHandle); + UserSlotClearedEnqueuedHandle = {}; }, Op, Req); UserSlotSystem->ClearUserAtSlot(UserSlot, USCR_Manual, true, this); } @@ -1643,12 +1651,12 @@ void UBeamRuntime::Logout(FUserSlot UserSlot, EUserSlotClearedReason Reason, boo FBeamRealmUser RealmUser; if (UserSlotSystem->GetUserDataAtSlot(UserSlot, RealmUser, this)) { - // Configure us to wait until the slot is fully unauthenticated and then sign in. - FDelegateHandle Handle; - Handle = OnUserClearedCode.AddLambda([this, Op, &Handle](FUserSlot UserSlot) + // Configure us to wait until the slot is fully unauthenticated and then sign in. + UserSlotClearedEnqueuedHandle = OnUserClearedCode.AddLambda([this, Op](FUserSlot UserSlot) { // Remove this lambda from the list of callbacks - OnUserClearedCode.Remove(Handle); + OnUserClearedCode.Remove(UserSlotClearedEnqueuedHandle); + UserSlotClearedEnqueuedHandle = {}; // Trigger the operation success RequestTrackerSystem->TriggerOperationSuccess(Op, UserSlot.Name); diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Content/BeamContentSubsystem.cpp b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Content/BeamContentSubsystem.cpp index 165f8f25..cbcde166 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Content/BeamContentSubsystem.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Content/BeamContentSubsystem.cpp @@ -680,7 +680,7 @@ void UBeamContentSubsystem::OnBeamableStarting_Implementation(FBeamOperationHand const auto ManifestId = FBeamContentManifestId{TEXT("global")}; const auto bShouldDownloadIndividuals = GetDefault()->bDownloadIndividualContentOnStart; - FetchContentManifest(ManifestId, bShouldDownloadIndividuals, Op); + FetchContentManifest(ManifestId, bShouldDownloadIndividuals, false, Op); ResultOp = Op; } @@ -710,7 +710,7 @@ void UBeamContentSubsystem::OnUserSignedIn_Implementation(const FUserSlot& UserS // TODO: Only fetch the updated content items (scopes) const FBeamOperationHandle DelayedFetchAllOp = GEngine->GetEngineSubsystem()->CPP_BeginOperation({}, GetName(), DelayedFetchAllOpHandler); - FetchContentManifest(ContentRefreshNotificationMessage.Manifest.Id, true, DelayedFetchAllOp); + FetchContentManifest(ContentRefreshNotificationMessage.Manifest.Id, true, false, DelayedFetchAllOp); UE_LOG(LogBeamContent, Warning, TEXT("Delay for ContentRefresh is over. Fetching the updated content manifest.")); }), DelayCount, false, DelayCount); @@ -719,8 +719,8 @@ void UBeamContentSubsystem::OnUserSignedIn_Implementation(const FUserSlot& UserS GEngine->GetEngineSubsystem()->CPP_SubscribeToContentRefresh(UserSlot, Runtime->DefaultNotificationChannel, NotificationHandler, this); } -void UBeamContentSubsystem::DownloadLiveContentObjects(const FBeamContentManifestId ManifestId, const TArray Rows, const TMap Checksums, - FBeamOperationHandle Op) +void UBeamContentSubsystem::DownloadContentObjects(const FBeamContentManifestId ManifestId, const TArray Rows, const TMap Checksums, + const bool bIgnoreFilterMap, FBeamOperationHandle Op) { // If there is no rows to download we complete the operation. if (Rows.Num() == 0) @@ -729,85 +729,112 @@ void UBeamContentSubsystem::DownloadLiveContentObjects(const FBeamContentManifes return; } + // We build a filter map (if requested), from the UBeamRuntimeSettings::IndividualContentDownloadFilter map. + // This mostly runs only during the automatic content refreshes due to content notifications AND the initial content fetch if UBeamRuntimeSettings::bDownloadIndividualContentOnStart is set. + // Users calling FetchContentManifestOperation manually, can opt-into using this filter or not. + auto StringFilterMap = TMap{}; + if (!bIgnoreFilterMap) + { + const auto FilterMap = GetDefault()->IndividualContentDownloadFilter; + for (const auto& Kvp : FilterMap) + { + const auto TypeString = Kvp.Key.GetDefaultObject()->BuildContentTypeString(); + StringFilterMap.Add(TypeString, Kvp.Value); + } + } + // Let's look for the list of content that needs fetching... TArray IndividualDownloadRequests; for (const auto& ContentEntry : Rows) { if (ContentEntry.Type == EContentType::BEAM_content) { - // We only download changed content from the given manifest - const auto Checksum = Checksums.Find({ContentEntry.ContentId}); - const auto bNotDownloaded = !Checksum; - const auto bOlderVersionCached = Checksum && !ContentEntry.Version.Equals(*Checksum); - if (bNotDownloaded || bOlderVersionCached) + // We verify if the optional is not set OR empty + const auto ContentTypeId = ContentEntry.ContentId.GetTypeId(); + const auto FilteredContents = StringFilterMap.Find(ContentTypeId); + + // We should only try to download if the user did... + const auto bShouldDownload = !FilteredContents || // Not enter a specific list of contents of this type to download + !FilteredContents->IsSet || // Not enter a specific list of contents of this type to download + FilteredContents->Val.Contains(ContentEntry.ContentId); // OR, this content we are checking is on the list of things to download. + // In other words, if the user wants to opt-out of downloading a particular content type, they can add an entry to the filter dictionary and leave its array as empty. + + if (bShouldDownload) { - auto Req = NewObject(); - Req->RequestTypeName = TEXT("BeamIndividualContentDownload"); - Req->Body = TEXT(""); - Req->Verb = TEXT("GET"); - Req->URL = ContentEntry.Uri; - Req->CustomHeaders.Add(UBeamBackend::HEADER_ACCEPT, UBeamBackend::HEADER_VALUE_ACCEPT_CONTENT_TYPE); - - // For each download that we'll make, register a lambda that: - // - Tries to save the downloaded file to the local '.beamable' folder. - // - Checks to see if it was the last download and, if so, invoke the appropriate on success/error callback. - const auto IndividualContentHandler = FOnGenericBeamRequestFullResponse::CreateLambda([this, Op, ManifestId, ContentEntry](FGenericBeamRequestFullResponse Resp) + // We only download changed content from the given manifest + const auto Checksum = Checksums.Find({ContentEntry.ContentId}); + const auto bNotDownloaded = !Checksum; + const auto bOlderVersionCached = Checksum && !ContentEntry.Version.Equals(*Checksum); + if (bNotDownloaded || bOlderVersionCached) { - if (Resp.State == RS_Success) + auto Req = NewObject(); + Req->RequestTypeName = TEXT("BeamIndividualContentDownload"); + Req->Body = TEXT(""); + Req->Verb = TEXT("GET"); + Req->URL = ContentEntry.Uri; + Req->CustomHeaders.Add(UBeamBackend::HEADER_ACCEPT, UBeamBackend::HEADER_VALUE_ACCEPT_CONTENT_TYPE); + + // For each download that we'll make, register a lambda that: + // - Tries to save the downloaded file to the local '.beamable' folder. + // - Checks to see if it was the last download and, if so, invoke the appropriate on success/error callback. + const auto IndividualContentHandler = FOnGenericBeamRequestFullResponse::CreateLambda([this, Op, ManifestId, ContentEntry](FGenericBeamRequestFullResponse Resp) { - auto ResponseJson = Resp.SuccessData->ResponseBody; + if (Resp.State == RS_Success) + { + auto ResponseJson = Resp.SuccessData->ResponseBody; - // We create the object from the downloaded JSON and store it in the created cache. - const auto Id = ContentEntry.ContentId; - const auto Tags = ContentEntry.Tags; - const auto ContentTypeId = Id.GetTypeId(); + // We create the object from the downloaded JSON and store it in the created cache. + const auto Id = ContentEntry.ContentId; + const auto Tags = ContentEntry.Tags; + const auto ContentTypeId = Id.GetTypeId(); - // Fix-up since Unreal's JSON serializer expects true/false/null values to be upper case... Its not the correct spec, but... it is what it is... - ResponseJson.ReplaceInline(TEXT("\":true"), TEXT("\":True")); - ResponseJson.ReplaceInline(TEXT("\":false"), TEXT("\":False")); - ResponseJson.ReplaceInline(TEXT("\":null"), TEXT("\":Null")); + // Fix-up since Unreal's JSON serializer expects true/false/null values to be upper case... Its not the correct spec, but... it is what it is... + ResponseJson.ReplaceInline(TEXT("\":true"), TEXT("\":True")); + ResponseJson.ReplaceInline(TEXT("\":false"), TEXT("\":False")); + ResponseJson.ReplaceInline(TEXT("\":null"), TEXT("\":Null")); - // Create the ContentObject instance of the appropriate type. - UBeamContentObject* ContentObject; - UBeamContentObject::NewFromTypeId(ContentTypeStringToContentClass, ContentTypeId, ContentObject); + // Create the ContentObject instance of the appropriate type. + UBeamContentObject* ContentObject; + UBeamContentObject::NewFromTypeId(ContentTypeStringToContentClass, ContentTypeId, ContentObject); - // We should never reach here without a ContentObject instance. - ensureAlwaysMsgf(ContentObject, TEXT("ContentObject was not created successfully. ManifestId=%s, ContentId=%s"), *ManifestId.AsString, *Id.AsString); - UE_LOG(LogBeamContent, Verbose, TEXT("Downloaded content and preparing to parse its Json. CONTENT_ID=%s, JSON=%s, SUPPORT_LEVEL=%s"), - *Id.AsString, *ResponseJson, *StaticEnum()->GetValueAsString(ContentObject->SupportLevel)) + // We should never reach here without a ContentObject instance. + ensureAlwaysMsgf(ContentObject, TEXT("ContentObject was not created successfully. ManifestId=%s, ContentId=%s"), *ManifestId.AsString, *Id.AsString); + UE_LOG(LogBeamContent, Verbose, TEXT("Downloaded content and preparing to parse its Json. CONTENT_ID=%s, JSON=%s, SUPPORT_LEVEL=%s"), + *Id.AsString, *ResponseJson, *StaticEnum()->GetValueAsString(ContentObject->SupportLevel)) - // Deserialize the content object into the instance - ContentObject->FromBasicJson(ResponseJson); - ContentObject->Tags = Tags; + // Deserialize the content object into the instance + ContentObject->FromBasicJson(ResponseJson); + ContentObject->Tags = Tags; - // Cache the content object data in memory and update the hashes so that subsequent calls can figure out whether or not we need to redownload. - const auto LiveContentCache = LiveContent.FindChecked(ManifestId); - const auto PropertyHash = ContentObject->CreatePropertiesHash(); - LiveContentCache->Cache.Add(Id, ContentObject); - LiveContentCache->Hashes.Add(Id, PropertyHash); + // Cache the content object data in memory and update the hashes so that subsequent calls can figure out whether or not we need to redownload. + const auto LiveContentCache = LiveContent.FindChecked(ManifestId); + const auto PropertyHash = ContentObject->CreatePropertiesHash(); + LiveContentCache->Cache.Add(Id, ContentObject); + LiveContentCache->Hashes.Add(Id, PropertyHash); - UE_LOG(LogBeamContent, Verbose, TEXT("Downloaded and parsed content. CONTENT_ID=%s, HASH=%s, CONTENT_MANIFEST_ID=%s"), *Id.AsString, - *LiveContentCache->Hashes.FindChecked(Id), - *ManifestId.AsString) + UE_LOG(LogBeamContent, Verbose, TEXT("Downloaded and parsed content. CONTENT_ID=%s, HASH=%s, CONTENT_MANIFEST_ID=%s"), *Id.AsString, + *LiveContentCache->Hashes.FindChecked(Id), + *ManifestId.AsString) - Runtime->RequestTrackerSystem->TriggerOperationEvent(Op, OET_SUCCESS, FName(TEXT("DOWNLOADED_INDIVIDUAL_CONTENT_SUCCESS")), ContentEntry.ContentId.AsString, - Resp.Context.RequestId); - } + Runtime->RequestTrackerSystem->TriggerOperationEvent(Op, OET_SUCCESS, FName(TEXT("DOWNLOADED_INDIVIDUAL_CONTENT_SUCCESS")), ContentEntry.ContentId.AsString, + Resp.Context.RequestId); + } - if (Resp.State == RS_Error) - { - Runtime->RequestTrackerSystem->TriggerOperationEvent(Op, OET_SUCCESS, FName(TEXT("DOWNLOADED_INDIVIDUAL_CONTENT_FAILED")), ContentEntry.ContentId.AsString, - Resp.Context.RequestId); - } - }); + if (Resp.State == RS_Error) + { + Runtime->RequestTrackerSystem->TriggerOperationEvent(Op, OET_SUCCESS, FName(TEXT("DOWNLOADED_INDIVIDUAL_CONTENT_FAILED")), ContentEntry.ContentId.AsString, + Resp.Context.RequestId); + } + }); - if (bNotDownloaded) UE_LOG(LogBeamContent, Verbose, TEXT("Content Id %s not in memory. Preparing to fetch its JSON blob."), *ContentEntry.ContentId.AsString); - if (bOlderVersionCached) UE_LOG(LogBeamContent, Verbose, TEXT("Detected Changes in Content Id %s. Preparing to fetch its JSON blob."), *ContentEntry.ContentId.AsString); + if (bNotDownloaded) UE_LOG(LogBeamContent, Verbose, TEXT("Content Id %s not in memory. Preparing to fetch its JSON blob."), *ContentEntry.ContentId.AsString); + if (bOlderVersionCached) UE_LOG(LogBeamContent, Verbose, TEXT("Detected Changes in Content Id %s. Preparing to fetch its JSON blob."), *ContentEntry.ContentId.AsString); - // Make the request - const auto ReqId = GenericApi->CPP_ExecuteNonBeamRequest(Req, IndividualContentHandler, Op, this); - IndividualDownloadRequests.Add(ReqId); + // Make the request + const auto ReqId = GenericApi->CPP_ExecuteNonBeamRequest(Req, IndividualContentHandler, Op, this); + IndividualDownloadRequests.Add(ReqId); + } } } } @@ -964,17 +991,19 @@ int UBeamContentSubsystem::GetIdsOfContentType(TSubclassOf T return Ids.Num(); } -FBeamOperationHandle UBeamContentSubsystem::FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationEventHandler OnOperationEvent) +FBeamOperationHandle UBeamContentSubsystem::FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, + FBeamOperationEventHandler OnOperationEvent) { const auto Handle = Runtime->RequestTrackerSystem->BeginOperation({}, GetClass()->GetFName().ToString(), OnOperationEvent); - FetchContentManifest(ManifestId, bDownloadIndividualContent, Handle); + FetchContentManifest(ManifestId, bDownloadIndividualContent, bIgnoreFilterMap, Handle); return Handle; } -FBeamOperationHandle UBeamContentSubsystem::CPP_FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationEventHandlerCode OnOperationEvent) +FBeamOperationHandle UBeamContentSubsystem::CPP_FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, + FBeamOperationEventHandlerCode OnOperationEvent) { const auto Handle = Runtime->RequestTrackerSystem->CPP_BeginOperation({}, GetClass()->GetFName().ToString(), OnOperationEvent); - FetchContentManifest(ManifestId, bDownloadIndividualContent, Handle); + FetchContentManifest(ManifestId, bDownloadIndividualContent, bIgnoreFilterMap, Handle); return Handle; } @@ -1008,10 +1037,10 @@ FBeamOperationHandle UBeamContentSubsystem::CPP_FetchIndividualContentOperation( return Handle; } -void UBeamContentSubsystem::FetchContentManifest(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationHandle Op) +void UBeamContentSubsystem::FetchContentManifest(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, FBeamOperationHandle Op) { const auto Request = UGetManifestPublicJsonRequest::Make(FOptionalBeamContentManifestId(ManifestId), GetTransientPackage(), {}); - const auto Handler = FOnGetManifestPublicJsonFullResponse::CreateLambda([this, ManifestId, Op, bDownloadIndividualContent](FGetManifestPublicJsonFullResponse Resp) + const auto Handler = FOnGetManifestPublicJsonFullResponse::CreateLambda([this, ManifestId, Op, bDownloadIndividualContent, bIgnoreFilterMap](FGetManifestPublicJsonFullResponse Resp) { if (Resp.State == RS_Retrying) return; @@ -1057,7 +1086,7 @@ void UBeamContentSubsystem::FetchContentManifest(FBeamContentManifestId Manifest } }); const auto DownloadOp = Runtime->RequestTrackerSystem->CPP_BeginOperation({}, GetName(), DownloadOpHandler); - DownloadLiveContentObjects(ManifestId, ContentCache->LatestRemoteManifest, CurrentLocalHashes, DownloadOp); + DownloadContentObjects(ManifestId, ContentCache->LatestRemoteManifest, CurrentLocalHashes, bIgnoreFilterMap, DownloadOp); } else { @@ -1113,7 +1142,7 @@ void UBeamContentSubsystem::FetchIndividualContent(FBeamContentManifestId Manife } } - DownloadLiveContentObjects(ManifestId, EntriesToDownload, Cache->Hashes, Op); + DownloadContentObjects(ManifestId, EntriesToDownload, Cache->Hashes, true, Op); } bool UBeamContentSubsystem::EnforceLinks(FBeamContentManifestId ManifestId, TArray ManifestRows, TArray& OutLinksToFetch) diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Matchmaking/BeamMatchmakingSubsystem.cpp b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Matchmaking/BeamMatchmakingSubsystem.cpp index b32c53e7..9a0cb1e8 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Matchmaking/BeamMatchmakingSubsystem.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Matchmaking/BeamMatchmakingSubsystem.cpp @@ -45,6 +45,20 @@ void UBeamMatchmakingSubsystem::OnPostUserSignedIn_Implementation(const FUserSlo Super::OnPostUserSignedIn_Implementation(UserSlot, BeamRealmUser, bIsOwnerUserAuth, ResultOp); } +void UBeamMatchmakingSubsystem::OnUserSignedOut_Implementation(const FUserSlot& UserSlot, const EUserSlotClearedReason Reason, const FBeamRealmUser& BeamRealmUser, FBeamOperationHandle& ResultOp) +{ + this->Slots[UserSlot] = FBeamMatchmakingState{UserSlot, FGuid{}}; + for (auto& LiveTicket : this->LiveTickets) + { + if (LiveTicket.SlotsInTicket.Contains(UserSlot)) + { + InvalidateLiveTicket(LiveTicket); + } + } + + Super::OnUserSignedOut_Implementation(UserSlot, Reason, BeamRealmUser, ResultOp); +} + TArray> UBeamMatchmakingSubsystem::GetDependingOnSubsystems() { TArray> DependantSubsystems = diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Stats/BeamStatsSubsystem.cpp b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Stats/BeamStatsSubsystem.cpp index 9f295856..500370a4 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Stats/BeamStatsSubsystem.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Stats/BeamStatsSubsystem.cpp @@ -103,11 +103,8 @@ void UBeamStatsSubsystem::OnUserSignedIn_Implementation(const FUserSlot& UserSlo void UBeamStatsSubsystem::OnUserSignedOut_Implementation(const FUserSlot& UserSlot, const EUserSlotClearedReason Reason, const FBeamRealmUser& BeamRealmUser, FBeamOperationHandle& ResultOp) { - FBeamRealmUser RealmUser; - checkf(UserSlots->GetUserDataAtSlot(UserSlot, RealmUser, this), TEXT("This should be impossible!")); - // Let's just stop keeping a cache for each possible user key when they log out. - const FBeamGamerTag UserGamerTag = RealmUser.GamerTag; + const FBeamGamerTag UserGamerTag = BeamRealmUser.GamerTag; const UEnum* DomainEnum = StaticEnum(); const UEnum* VisibilityEnum = StaticEnum(); for (int i = 0; i < DomainEnum->NumEnums(); ++i) diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Store/BeamStoreSubsystem.cpp b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Store/BeamStoreSubsystem.cpp index d2addad4..303846d0 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Store/BeamStoreSubsystem.cpp +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Private/Subsystems/Store/BeamStoreSubsystem.cpp @@ -19,199 +19,87 @@ void UBeamStoreSubsystem::Deinitialize() Super::Deinitialize(); } -void UBeamStoreSubsystem::OnBeamableContentReady_Implementation(FBeamOperationHandle& ResultOp) -{ - const auto Handle = Runtime->RequestTrackerSystem->BeginOperation({}, GetName(), {}); - RefreshStores(Handle); - ResultOp = Handle; -} - -bool UBeamStoreSubsystem::TryCreatePurchaseListingOperation(FUserSlot Slot, FBeamContentId StoreId, - FBeamContentId ListingId, - UBeamPurchaseListingOperation*& Command) -{ - // Ensure we have a user at the given slot. - FBeamRealmUser RealmUser; - if (!UserSlots->GetUserDataAtSlot(Slot, RealmUser, this)) - { - Command = nullptr; - return false; - } - - // If we already have - if (PurchaseCommands.Contains(Slot)) - { - UE_LOG(LogBeamStats, Warning, TEXT("Trying to start a new update command while one is building.")) - Command = nullptr; - return false; - } - - - // Create a new update command buffer from it. - Command = NewObject(GetTransientPackage()); - Command->Init(this, Slot, StoreId, ListingId); - - PurchaseCommands.Add(Slot, Command); - return true; -} - -FBeamOperationHandle UBeamStoreSubsystem::CommitPurchaseListingOperation(FUserSlot UserSlot, - FBeamOperationEventHandler OnOperationEvent) +TArray> UBeamStoreSubsystem::GetDependingOnSubsystems() { - const auto Handle = Runtime->RequestTrackerSystem->BeginOperation({UserSlot}, GetClass()->GetFName().ToString(), - OnOperationEvent); - CommitPurchaseListings(UserSlot, Handle); - return Handle; + auto DependentSystems = Super::GetDependingOnSubsystems(); + DependentSystems.Add(UBeamInventorySubsystem::StaticClass()); + return DependentSystems; } -FBeamOperationHandle UBeamStoreSubsystem::CPP_CommitPurchaseListingOperation(FUserSlot UserSlot, - FBeamOperationEventHandlerCode - OnOperationEvent) +void UBeamStoreSubsystem::OnBeamableContentReady_Implementation(FBeamOperationHandle& ResultOp) { - const auto Handle = Runtime->RequestTrackerSystem->CPP_BeginOperation( - {UserSlot}, GetClass()->GetFName().ToString(), OnOperationEvent); - CommitPurchaseListings(UserSlot, Handle); - return Handle; + ResultOp = Runtime->RequestTrackerSystem->CPP_BeginSuccessfulOperation({}, GetName(), {}, {}); } -FBeamOperationHandle UBeamStoreSubsystem::PerformPurchaseOperation(FUserSlot UserSlot, - FBeamOperationEventHandler OnOperationEvent, - FBeamContentId StoreId, FBeamContentId ListingId) +FBeamOperationHandle UBeamStoreSubsystem::PerformPurchaseOperation(FUserSlot UserSlot, FBeamOperationEventHandler OnOperationEvent, FBeamContentId StoreId, FBeamContentId ListingId) { - const auto Handle = Runtime->RequestTrackerSystem->BeginOperation({UserSlot}, GetClass()->GetFName().ToString(), - OnOperationEvent); + const auto Handle = Runtime->RequestTrackerSystem->BeginOperation({UserSlot}, GetClass()->GetFName().ToString(), OnOperationEvent); PerformPurchase(UserSlot, StoreId, ListingId, Handle); return Handle; } -FBeamOperationHandle UBeamStoreSubsystem::CPP_PerformPurchaseOperation(FUserSlot UserSlot, - FBeamOperationEventHandlerCode OnOperationEvent, - FBeamContentId StoreId, FBeamContentId ListingId) +FBeamOperationHandle UBeamStoreSubsystem::CPP_PerformPurchaseOperation(FUserSlot UserSlot, FBeamOperationEventHandlerCode OnOperationEvent, FBeamContentId StoreId, FBeamContentId ListingId) { - const auto Handle = Runtime->RequestTrackerSystem->CPP_BeginOperation( - {UserSlot}, GetClass()->GetFName().ToString(), OnOperationEvent); + const auto Handle = Runtime->RequestTrackerSystem->CPP_BeginOperation({UserSlot}, GetClass()->GetFName().ToString(), OnOperationEvent); PerformPurchase(UserSlot, StoreId, ListingId, Handle); return Handle; } -FBeamOperationHandle UBeamStoreSubsystem::RefreshStoreOperation(FBeamOperationEventHandler ResultOp) -{ - const auto Handle = Runtime->RequestTrackerSystem->BeginOperation( - {}, GetClass()->GetFName().ToString(), ResultOp); - RefreshStores(Handle); - return Handle; -} - - -FBeamOperationHandle UBeamStoreSubsystem::CPP_RefreshStoreOperation(FBeamOperationEventHandlerCode ResultOp) -{ - const auto Handle = Runtime->RequestTrackerSystem->CPP_BeginOperation( - {}, GetClass()->GetFName().ToString(), ResultOp); - RefreshStores(Handle); - return Handle; -} - -void UBeamStoreSubsystem::PerformPurchase(FUserSlot UserSlot, FBeamContentId StoreId, FBeamContentId ListingId, - FBeamOperationHandle Op) +void UBeamStoreSubsystem::PerformPurchase(FUserSlot UserSlot, FBeamContentId StoreId, FBeamContentId ListingId, FBeamOperationHandle Op) { FBeamRealmUser RealmUser; - checkf(UserSlots->GetUserDataAtSlot(UserSlot, RealmUser, this), - TEXT("You must only call this function with an authenticated UserSlot")); - checkf(StoreId.GetTypeId().Equals(FString("stores")), TEXT("StoreId item must be stores type")); - checkf(ListingId.GetTypeId().Equals(FString("listings")), TEXT("StoreId item must be stores type")); - FString purchaseRequestName = ListingId.AsString; - purchaseRequestName.AppendChar(':'); - purchaseRequestName.Append(StoreId.AsString); - - const auto request = UPostPurchaseRequest::Make(RealmUser.GamerTag.AsLong, purchaseRequestName, this, {}); - - const auto handler = FOnPostPurchaseFullResponse::CreateLambda( - [this, Op, UserSlot, &RealmUser](const FPostPurchaseFullResponse& Resp) - { - if (Resp.State == RS_Retrying) - { - return; - } - - if (Resp.State != RS_Success) - { - UE_LOG(LogTemp, Verbose, TEXT("[BeamStore] Request failed: %s"), *Resp.ErrorData.error); - RequestTracker->TriggerOperationError(Op, Resp.ErrorData.error); - return; - } - UE_LOG(LogTemp, Verbose, TEXT("[BeamStore] Request succesfull: %s"), *Resp.SuccessData->Result); - const auto handler = FBeamOperationEventHandlerCode::CreateLambda( - [this, Op, UserSlot](FBeamOperationEvent Evt) - { - UE_LOG(LogTemp, Verbose, - TEXT("[BeamStore] Inventory updated")); - RequestTracker->TriggerOperationSuccess(Op, {}); - }); - auto Inventory = UBeamInventorySubsystem::GetSelf(this); - Inventory->CPP_FetchPlayerInventoryOperation(UserSlot, RealmUser.GamerTag, handler); - }); - FBeamRequestContext Ctx; - CommerceApi->CPP_PostPurchase(UserSlot, request, handler, Ctx, Op, this); -} - -void UBeamStoreSubsystem::RefreshStores(FBeamOperationHandle Op) -{ - const auto DefaultStores = GetDefault()->StoreContentToDownload; - const auto ContentHandle = FBeamOperationEventHandlerCode::CreateLambda( - [this, Op, DefaultStores](const FBeamOperationEvent& Resp) - { - this->StoreContents.Reset(); - const auto getAllStores = DefaultStores.IsEmpty(); - TArray ContentIds; - const auto contentAmount = ContentSubsystem->GetIdsOfContentType( - UBeamStoreContent::StaticClass(), ContentIds); - for (int i = 0; i < contentAmount; i++) - { - const auto id = ContentIds[i]; - if (getAllStores || DefaultStores.Contains(id)) - { - UBeamStoreContent* store; - if (ContentSubsystem->TryGetContentOfType(id, store)) - { - this->StoreContents.Add(store); - } - } - } - RequestTracker->TriggerOperationSuccess(Op, {}); - }); - - if (DefaultStores.IsEmpty()) + if (!UserSlots->GetUserDataAtSlot(UserSlot, RealmUser, this)) { - ContentSubsystem-> - CPP_FetchContentManifestOperation(FBeamContentManifestId(TEXT("global")), true, ContentHandle); + RequestTracker->TriggerOperationError(Op, TEXT("NO_AUTHENTICATED_USER_AT_SLOT")); + return; } - else + + if (!StoreId.GetTypeId().StartsWith(FString("stores"))) { - ContentSubsystem->CPP_FetchIndividualContentBatchOperation(FBeamContentManifestId(TEXT("global")), - DefaultStores, ContentHandle); + RequestTracker->TriggerOperationError(Op, TEXT("INVALID_STORE_CONTENT_ID")); + return; } -} -void UBeamStoreSubsystem::CommitPurchaseListings(FUserSlot UserSlot, FBeamOperationHandle Op) -{ - // Ensure we have a user at the given slot. - FBeamRealmUser RealmUser; - if (!UserSlots->GetUserDataAtSlot(UserSlot, RealmUser, this)) + if (!ListingId.GetTypeId().StartsWith(FString("listings"))) { - RequestTracker->TriggerOperationError(Op, TEXT("NO_AUTHENTICATED_USER_AT_SLOT")); + RequestTracker->TriggerOperationError(Op, TEXT("INVALID_LISTING_CONTENT_ID")); return; } - // Get the command buffer that is being build. - const UBeamPurchaseListingOperation* CommandBuffer = PurchaseCommands.FindChecked(UserSlot); - PerformPurchase(UserSlot, CommandBuffer->StoreId, CommandBuffer->ListingId, Op); - PurchaseCommands.Remove(UserSlot); + FString PurchaseRequestName = ListingId.AsString; + PurchaseRequestName.AppendChar(':'); + PurchaseRequestName.Append(StoreId.AsString); + + const auto Request = UPostPurchaseRequest::Make(RealmUser.GamerTag.AsLong, PurchaseRequestName, this, {}); + const auto Handler = FOnPostPurchaseFullResponse::CreateLambda([this, Op, UserSlot, &RealmUser](const FPostPurchaseFullResponse& Resp) + { + if (Resp.State == RS_Retrying) + return; + + if (Resp.State != RS_Success) + { + UE_LOG(LogBeamStore, Verbose, TEXT("[BeamStore] Request failed: %s"), *Resp.ErrorData.error); + RequestTracker->TriggerOperationError(Op, Resp.ErrorData.error); + return; + } + + UE_LOG(LogBeamStore, Verbose, TEXT("[BeamStore] Request succesfull: %s"), *Resp.SuccessData->Result); + const auto InventoryHandler = FBeamOperationEventHandlerCode::CreateLambda([this, Op, UserSlot](FBeamOperationEvent) + { + UE_LOG(LogBeamStore, Verbose, TEXT("[BeamStore] Inventory updated")); + RequestTracker->TriggerOperationSuccess(Op, {}); + }); + + const auto Inventory = UBeamInventorySubsystem::GetSelf(this); + Inventory->CPP_FetchPlayerInventoryOperation(UserSlot, RealmUser.GamerTag, InventoryHandler); + }); + FBeamRequestContext Ctx; + CommerceApi->CPP_PostPurchase(UserSlot, Request, Handler, Ctx, Op, this); } bool UBeamStoreSubsystem::TryGetItemsFromListing(FBeamContentId ListingId, TArray& items) { - if(UBeamListingContent* content; ContentSubsystem->TryGetContentOfType( - ListingId, content)) + if (UBeamListingContent* content; ContentSubsystem->TryGetContentOfType(ListingId, content)) { items = content->offer.ObtainItems; } @@ -220,7 +108,7 @@ bool UBeamStoreSubsystem::TryGetItemsFromListing(FBeamContentId ListingId, TArra bool UBeamStoreSubsystem::TryGetCurrenciesFromListing(FBeamContentId ListingId, TArray& currencies) { - if(UBeamListingContent* content; ContentSubsystem->TryGetContentOfType( + if (UBeamListingContent* content; ContentSubsystem->TryGetContentOfType( ListingId, content)) { currencies = content->offer.ObtainCurrency; @@ -230,7 +118,7 @@ bool UBeamStoreSubsystem::TryGetCurrenciesFromListing(FBeamContentId ListingId, bool UBeamStoreSubsystem::GetFormattedPrice(FBeamContentId ListingId, FString& FormattedPrice) { - if(UBeamListingContent* content; ContentSubsystem->TryGetContentOfType( + if (UBeamListingContent* content; ContentSubsystem->TryGetContentOfType( ListingId, content)) { FormattedPrice = TEXT(""); @@ -241,13 +129,3 @@ bool UBeamStoreSubsystem::GetFormattedPrice(FBeamContentId ListingId, FString& F } return false; } - - -void UBeamPurchaseListingOperation::Init(UBeamStoreSubsystem* Subsystem, FUserSlot Slot, FBeamContentId StoreIdContent, - FBeamContentId ListingIdContent) -{ - this->StoreSubsystem = Subsystem; - this->UserSlot = Slot; - this->ListingId = ListingIdContent; - this->StoreId = StoreIdContent; -} diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntime.h b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntime.h index 0f525756..ae27c821 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntime.h +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntime.h @@ -316,6 +316,13 @@ class BEAMABLECORERUNTIME_API UBeamRuntime : public UGameInstanceSubsystem */ FDelegateHandle UserSlotClearedHandler; + /** + * @brief In a bunch of cases, we want to automatically sign a user out and, once done, sign a new user in. + * This is done in most login calls, and we need a place to store the lambda handle so that we can clean it up after it runs. + */ + FDelegateHandle UserSlotClearedEnqueuedHandle; + + /** * @brief When we boot up the game instance (and it's subsystems), after all Initialize calls have finished, we allow BeamSubsystems to kick-off operations in parallel. * They return operation handles that we wait on. When done, these subsystems are be ready to make unauthenticated requests to the Beamable backend. diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntimeSettings.h b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntimeSettings.h index da8a806e..45ccbac6 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntimeSettings.h +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Runtime/BeamRuntimeSettings.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "AutoGen/Optionals/OptionalArrayOfBeamContentId.h" #include "BeamBackend/SemanticTypes/BeamContentId.h" #include "Engine/StreamableManager.h" @@ -11,6 +12,8 @@ class UBeamContentCache; class UDataTable; class UBeamRuntimeSubsystem; +class UBeamContentObject; + /** * */ @@ -46,9 +49,9 @@ class BEAMABLECORERUNTIME_API UBeamRuntimeSettings : public UDeveloperSettings * By adding subsystems to this list they will not be initialized at the game start however these subsystems could be * initialized later by calling ManuallyInitializeSubsystems Function at BeamRuntime */ - UPROPERTY(Config,EditAnywhere, BlueprintReadOnly, Category="Beam Systems") + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category="Beam Systems") TArray> ManualyInitializedRuntimeSubsystems; - + /** * @brief As per UE docs, we have a streamable manager declared to load up beamable content asynchronously at runtime: https://docs.unrealengine.com/5.1/en-US/asynchronous-asset-loading-in-unreal-engine/. */ @@ -59,13 +62,17 @@ class BEAMABLECORERUNTIME_API UBeamRuntimeSettings : public UDeveloperSettings */ UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Content") bool bDownloadIndividualContentOnStart = false; - + /** - * @brief Default stores to download by UBeamStoreSubsystem. - * If none are specified it will download all the available Stores content. + * @brief Specify filters for which content to download when bDownloadIndividualContentOnStart is true. This has 3 cases: + * - If the content type is NOT listed in this map OR it IS listed but the FOptionalArrayOfBeamContentId is NOT set, we download all the content of that type. + * - If the content type IS listed in this map and FOptionalArrayOfBeamContentId is set as an Empty array, we download NONE of the content of that type. + * - If the content type IS listed in this map and FOptionalArrayOfBeamContentId is set as an non-Empty array, we download ONLY of the content of that type that matches the IDs in the array. + * */ UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Content") - TArray StoreContentToDownload; - + TMap, FOptionalArrayOfBeamContentId> IndividualContentDownloadFilter = {}; + + FStreamableManager& GetStreamableManager() { return ContentStreamingManager; } }; diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Content/BeamContentSubsystem.h b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Content/BeamContentSubsystem.h index 6ff7dea8..9a692ac2 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Content/BeamContentSubsystem.h +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Content/BeamContentSubsystem.h @@ -87,8 +87,8 @@ class BEAMABLECORERUNTIME_API UBeamContentSubsystem : public UBeamRuntimeSubsyst * Given a list of FBeamRemoteContentManifestEntry (contains public content only) and a FBeamContentManifestId, we make requests to the URLs where the actual content JSON is stored. * Run OnSuccess if all downloads succeed. OnError, otherwise. */ - void DownloadLiveContentObjects(const FBeamContentManifestId ManifestId, const TArray Rows, const TMap Checksums, - FBeamOperationHandle Op); + void DownloadContentObjects(FBeamContentManifestId ManifestId, TArray Rows, TMap Checksums, + bool bIgnoreFilterMap, FBeamOperationHandle Op); public: /** @@ -169,23 +169,25 @@ class BEAMABLECORERUNTIME_API UBeamContentSubsystem : public UBeamRuntimeSubsyst } /** - * @brief Asks the Beamable server for the newest CSV representing the public content manifest with the given Id. Updates the runtime content cache's received manifest, but only downloads each individual content - * if the flag is set. + * @brief Asks the Beamable server for the newest CSV representing the public content manifest with the given Id. + * Updates the runtime content cache's received manifest, but only downloads each individual content if the flag is set. * * @param ManifestId The ManifestId to fetch. - * @param bDownloadIndividualContent Whether or not we should download the entire manifest. + * @param bDownloadIndividualContent Whether we should download the entire manifest. + * @param bIgnoreFilterMap Whether the UBeamRuntimeSettings::IndividualContentDownloadFilter should be used to filter the individual content that is downloaded (only useful when bDownloadIndividualContent is true). */ UFUNCTION(BlueprintCallable, Category="Beam|Operation|Content", meta=(DefaultToSelf="CallingContext", AdvancedDisplay="CallingContext")) - FBeamOperationHandle FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationEventHandler OnOperationEvent); + FBeamOperationHandle FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, FBeamOperationEventHandler OnOperationEvent); /** * @brief Asks the Beamable server for the newest CSV representing the public content manifest with the given Id. Updates the runtime content cache's received manifest, but only downloads each individual content * if the flag is set. * * @param ManifestId The ManifestId to fetch. - * @param bDownloadIndividualContent Whether or not we should download the entire manifest. + * @param bDownloadIndividualContent Whether we should download the entire manifest. + * @param bIgnoreFilterMap Whether the UBeamRuntimeSettings::IndividualContentDownloadFilter should be used to filter the individual content that is downloaded (only useful when bDownloadIndividualContent is true). */ - FBeamOperationHandle CPP_FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationEventHandlerCode OnOperationEvent); + FBeamOperationHandle CPP_FetchContentManifestOperation(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, FBeamOperationEventHandlerCode OnOperationEvent); /** * @brief Downloads the newest individual content objects of the given manifest. @@ -223,7 +225,7 @@ class BEAMABLECORERUNTIME_API UBeamContentSubsystem : public UBeamRuntimeSubsyst // Operation Definitions UFUNCTION() - void FetchContentManifest(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, FBeamOperationHandle Op); + void FetchContentManifest(FBeamContentManifestId ManifestId, bool bDownloadIndividualContent, bool bIgnoreFilterMap, FBeamOperationHandle Op); UFUNCTION() void FetchIndividualContent(FBeamContentManifestId ManifestId, TArray ContentToDownloadFetch, FBeamOperationHandle Op); diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Matchmaking/BeamMatchmakingSubsystem.h b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Matchmaking/BeamMatchmakingSubsystem.h index e8ebcc8d..c4dc4af4 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Matchmaking/BeamMatchmakingSubsystem.h +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Matchmaking/BeamMatchmakingSubsystem.h @@ -150,6 +150,7 @@ class BEAMABLECORERUNTIME_API UBeamMatchmakingSubsystem : public UBeamRuntimeSub virtual void InitializeWhenUnrealReady_Implementation(FBeamOperationHandle& ResultOp) override; virtual void OnPostUserSignedIn_Implementation(const FUserSlot& UserSlot, const FBeamRealmUser& BeamRealmUser, const bool bIsOwnerUserAuth, FBeamOperationHandle& ResultOp) override; + virtual void OnUserSignedOut_Implementation(const FUserSlot& UserSlot, const EUserSlotClearedReason Reason, const FBeamRealmUser& BeamRealmUser, FBeamOperationHandle& ResultOp) override; //Returns a list of subsystems that this system is depending on virtual TArray> GetDependingOnSubsystems() override; diff --git a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Store/BeamStoreSubsystem.h b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Store/BeamStoreSubsystem.h index 73f2a188..e448ba36 100644 --- a/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Store/BeamStoreSubsystem.h +++ b/Plugins/BeamableCore/Source/BeamableCoreRuntime/Public/Subsystems/Store/BeamStoreSubsystem.h @@ -8,37 +8,15 @@ #include "Runtime/BeamRuntimeSubsystem.h" #include "BeamStoreSubsystem.generated.h" - -UCLASS(BlueprintType) -class BEAMABLECORERUNTIME_API UBeamPurchaseListingOperation : public UObject -{ - GENERATED_BODY() - - UPROPERTY() - UBeamStoreSubsystem* StoreSubsystem; - -public: - explicit UBeamPurchaseListingOperation() = default; - - void Init(UBeamStoreSubsystem* Subsystem, FUserSlot Slot, FBeamContentId StoreId, FBeamContentId ListingId); - - UPROPERTY(BlueprintReadOnly) - FUserSlot UserSlot; - - UPROPERTY(BlueprintReadOnly) - FBeamContentId StoreId; - - UPROPERTY(BlueprintReadOnly) - FBeamContentId ListingId; -}; - UCLASS() class BEAMABLECORERUNTIME_API UBeamStoreSubsystem : public UBeamRuntimeSubsystem { GENERATED_BODY() + public: UFUNCTION(BlueprintPure, BlueprintInternalUseOnly, meta=(DefaultToSelf="CallingContext")) static UBeamStoreSubsystem* GetSelf(const UObject* CallingContext) { return CallingContext->GetWorld()->GetGameInstance()->GetSubsystem(); } + UPROPERTY() UBeamUserSlots* UserSlots; UPROPERTY() @@ -47,31 +25,15 @@ class BEAMABLECORERUNTIME_API UBeamStoreSubsystem : public UBeamRuntimeSubsystem UBeamRequestTracker* RequestTracker; UPROPERTY() UBeamContentSubsystem* ContentSubsystem; - UPROPERTY() - TArray StoreContents; - UPROPERTY(BlueprintReadOnly, VisibleAnywhere) - TMap PurchaseCommands; private: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; + virtual TArray> GetDependingOnSubsystems() override; public: virtual void OnBeamableContentReady_Implementation(FBeamOperationHandle& ResultOp) override; - - UFUNCTION(BlueprintCallable, meta=(AutoCreateRefTerm="Store", ExpandBoolAsExecs="ReturnValue")) - bool TryCreatePurchaseListingOperation(FUserSlot Slot, FBeamContentId StoreId, FBeamContentId ListingId, UBeamPurchaseListingOperation*& Command); - - /** - * @brief Takes whatever is the current purchase listing command for the given user slot and submits it to the Beamable backend. - * Call this only after you've called TryCreatePurchaseListingOperation for this slot. - */ - UFUNCTION(BlueprintCallable, Category="Beam|Operation|Store", meta=(DefaultToSelf="CallingContext", AdvancedDisplay="CallingContext")) - FBeamOperationHandle CommitPurchaseListingOperation(FUserSlot UserSlot, FBeamOperationEventHandler OnOperationEvent); - /** - * @copydoc CommitStatsOperation - */ - FBeamOperationHandle CPP_CommitPurchaseListingOperation(FUserSlot UserSlot, FBeamOperationEventHandlerCode OnOperationEvent); + /** * @brief Performs a purchase operation of a listing content from a specified store content. */ @@ -83,16 +45,6 @@ class BEAMABLECORERUNTIME_API UBeamStoreSubsystem : public UBeamRuntimeSubsystem */ FBeamOperationHandle CPP_PerformPurchaseOperation(FUserSlot UserSlot, FBeamOperationEventHandlerCode OnOperationEvent, FBeamContentId StoreId, FBeamContentId ListingId); - /** - * @brief Performs a purchase operation of a listing content from a specified store content. - */ - UFUNCTION(BlueprintCallable, Category="Beam|Operation|Store") - FBeamOperationHandle RefreshStoreOperation(FBeamOperationEventHandler ResultOp); - /** - * @copydoc RefreshStoreOperation - */ - FBeamOperationHandle CPP_RefreshStoreOperation(FBeamOperationEventHandlerCode ResultOp); - // Helper Functions UFUNCTION(BlueprintCallable) bool TryGetItemsFromListing(FBeamContentId ListingId, TArray& items); @@ -100,9 +52,7 @@ class BEAMABLECORERUNTIME_API UBeamStoreSubsystem : public UBeamRuntimeSubsystem bool TryGetCurrenciesFromListing(FBeamContentId ListingId, TArray& currencies); UFUNCTION(BlueprintCallable) bool GetFormattedPrice(FBeamContentId ListingId, FString& FormattedPrice); -private: - void PerformPurchase(FUserSlot UserSlot, FBeamContentId StoreId, FBeamContentId ListingId, FBeamOperationHandle Op = {}); - void RefreshStores(FBeamOperationHandle Op = {}); - void CommitPurchaseListings(FUserSlot UserSlot, FBeamOperationHandle Op); +private: + void PerformPurchase(FUserSlot UserSlot, FBeamContentId StoreId, FBeamContentId ListingId, FBeamOperationHandle Op = {}); };