diff --git a/.pubnub.yml b/.pubnub.yml index 6813723..a2b0e01 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -3,6 +3,17 @@ schema: 1 version: v1.0.0 scm: github.com/pubnub/unreal-engine changelog: + - date: 2025-09-11 + version: 1.1.0 + changes: + - type: feature + text: "Add implementation of Entities: `Channel`, `ChannelGroup`, `ChannelMetadata`, `UserMetadata` with all related PubNub functions." + - type: feature + text: "Add implementation of `Subscription` and `SubscriptionSet` objects. Add all necessary operations to add, remove, get subscriptions." + - type: feature + text: "Add implementation of Event Listeners - user can now bind a delegate to subscription for every event type separately or all of them at once." + - type: bug + text: "Clean up properly all C-Core subscriptions after `UnsubscribeFromAll`." - date: 2025-08-19 version: v1.0.0 changes: diff --git a/PubnubLibrary.uplugin b/PubnubLibrary.uplugin index a7bda1b..d1985c2 100644 --- a/PubnubLibrary.uplugin +++ b/PubnubLibrary.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 11, - "VersionName": "1.0.0", + "Version": 12, + "VersionName": "1.1.0", "FriendlyName": "Pubnub Unreal SDK", "Description": "Quickly add interactive features to your game that scale without building your backend infrastructure.", "Category": "Code", diff --git a/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp new file mode 100644 index 0000000..b878bb5 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp @@ -0,0 +1,31 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubBaseEntity.h" +#include "Entities/PubnubSubscription.h" +#include "PubnubSubsystem.h" + +UPubnubSubscription* UPubnubBaseEntity::CreateSubscription(FPubnubSubscribeSettings SubscribeSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot create subscription - PubnubSubsystem is null. Entity not properly initialized.")); + return nullptr; + } + + UPubnubSubscription* Subscription = NewObject(this); + + Subscription->InitSubscription(PubnubSubsystem, this, SubscribeSettings); + + return Subscription; +} + +void UPubnubBaseEntity::InitEntity(UPubnubSubsystem* InPubnubSubsystem) +{ + if(!InPubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Init Entity failed, PubnubSubsystem is invalid")); + return; + } + + PubnubSubsystem = InPubnubSubsystem; +} diff --git a/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp new file mode 100644 index 0000000..e9082c3 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp @@ -0,0 +1,94 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubChannelEntity.h" + + +UPubnubChannelEntity::UPubnubChannelEntity() +{ + EntityType = EPubnubEntityType::PEnT_Channel; +} + +void UPubnubChannelEntity::PublishMessage(FString Message, FOnPublishMessageResponse OnPublishMessageResponse, FPubnubPublishSettings PublishSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot publish message - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + if (EntityID.IsEmpty()) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot publish message - EntityID is empty.")); + return; + } + PubnubSubsystem->PublishMessage(EntityID, Message, OnPublishMessageResponse, PublishSettings); +} + +void UPubnubChannelEntity::PublishMessage(FString Message, FOnPublishMessageResponseNative NativeCallback, FPubnubPublishSettings PublishSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot publish message - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->PublishMessage(EntityID, Message, NativeCallback, PublishSettings); +} + +void UPubnubChannelEntity::PublishMessage(FString Message, FPubnubPublishSettings PublishSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot publish message - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->PublishMessage(EntityID, Message, PublishSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FOnSignalResponse OnSignalResponse, FPubnubSignalSettings SignalSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot send signal - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->Signal(EntityID, Message, OnSignalResponse, SignalSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FOnSignalResponseNative NativeCallback, FPubnubSignalSettings SignalSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot send signal - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->Signal(EntityID, Message, NativeCallback, SignalSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FPubnubSignalSettings SignalSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot send signal - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->Signal(EntityID, Message, SignalSettings); +} + +void UPubnubChannelEntity::ListUsersFromChannel(FOnListUsersFromChannelResponse ListUsersFromChannelResponse, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot list users from channel - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->ListUsersFromChannel(EntityID, ListUsersFromChannelResponse, ListUsersFromChannelSettings); +} + +void UPubnubChannelEntity::ListUsersFromChannel(FOnListUsersFromChannelResponseNative NativeCallback, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot list users from channel - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->ListUsersFromChannel(EntityID, NativeCallback, ListUsersFromChannelSettings); +} diff --git a/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp new file mode 100644 index 0000000..8f4080d --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp @@ -0,0 +1,90 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubChannelGroupEntity.h" + + +UPubnubChannelGroupEntity::UPubnubChannelGroupEntity() +{ + EntityType = EPubnubEntityType::PEnT_ChannelGroup; +} + +void UPubnubChannelGroupEntity::AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponse OnAddChannelToGroupResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot add channel to group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->AddChannelToGroup(Channel, EntityID, OnAddChannelToGroupResponse); +} + +void UPubnubChannelGroupEntity::AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot add channel to group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->AddChannelToGroup(Channel, EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponse OnRemoveChannelFromGroupResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel from group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelFromGroup(Channel, EntityID, OnRemoveChannelFromGroupResponse); +} + +void UPubnubChannelGroupEntity::RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel from group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelFromGroup(Channel, EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::ListChannelsFromGroup(FOnListChannelsFromGroupResponse OnListChannelsResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot list channels from group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->ListChannelsFromGroup(EntityID, OnListChannelsResponse); +} + +void UPubnubChannelGroupEntity::ListChannelsFromGroup(FOnListChannelsFromGroupResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot list channels from group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->ListChannelsFromGroup(EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::RemoveChannelGroup(FOnRemoveChannelGroupResponse OnRemoveChannelGroupResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelGroup(EntityID, OnRemoveChannelGroupResponse); +} + +void UPubnubChannelGroupEntity::RemoveChannelGroup(FOnRemoveChannelGroupResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel group - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelGroup(EntityID, NativeCallback); +} + diff --git a/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp new file mode 100644 index 0000000..569dc02 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp @@ -0,0 +1,70 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubChannelMetadataEntity.h" + + +UPubnubChannelMetadataEntity::UPubnubChannelMetadataEntity() +{ + EntityType = EPubnubEntityType::PEnT_ChannelMetadata; +} + +void UPubnubChannelMetadataEntity::SetChannelMetadata(FPubnubChannelData ChannelMetadata, FOnSetChannelMetadataResponse OnSetChannelMetadataResponse, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot set channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->SetChannelMetadata(EntityID, ChannelMetadata, OnSetChannelMetadataResponse, Include); +} + +void UPubnubChannelMetadataEntity::SetChannelMetadata(FPubnubChannelData ChannelMetadata, FOnSetChannelMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot set channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->SetChannelMetadata(EntityID, ChannelMetadata, NativeCallback, Include); +} + +void UPubnubChannelMetadataEntity::GetChannelMetadata(FOnGetChannelMetadataResponse OnGetChannelMetadataResponse, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot get channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->GetChannelMetadata(EntityID, OnGetChannelMetadataResponse, Include); +} + +void UPubnubChannelMetadataEntity::GetChannelMetadata(FOnGetChannelMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot get channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->GetChannelMetadata(EntityID, NativeCallback, Include); +} + +void UPubnubChannelMetadataEntity::RemoveChannelMetadata(FOnRemoveChannelMetadataResponse OnRemoveChannelMetadataResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelMetadata(EntityID, OnRemoveChannelMetadataResponse); +} + +void UPubnubChannelMetadataEntity::RemoveChannelMetadata(FOnRemoveChannelMetadataResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove channel metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveChannelMetadata(EntityID, NativeCallback); +} + diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp new file mode 100644 index 0000000..8569a6e --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -0,0 +1,591 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubSubscription.h" +#include "PubnubSubsystem.h" +#include "Entities/PubnubBaseEntity.h" +#include "FunctionLibraries/PubnubUtilities.h" + + +void UPubnubSubscriptionBase::BeginDestroy() +{ + CleanUpSubscription(); + + Super::BeginDestroy(); +} + +void UPubnubSubscription::Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor) +{ + FOnSubscribeOperationResponseNative NativeCallback; + NativeCallback.BindLambda([OnSubscribeResponse](FPubnubOperationResult Result) + { + OnSubscribeResponse.ExecuteIfBound(Result); + }); + + Subscribe(NativeCallback, Cursor); +} + +void UPubnubSubscription::Subscribe(FOnSubscribeOperationResponseNative NativeCallback, FPubnubSubscriptionCursor Cursor) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: internal C-Core subscription set is invalid.")); + return; + } + + PubnubSubsystem->SubscribeWithSubscription(this, Cursor, NativeCallback); +} + +void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) +{ + Subscribe(nullptr, Cursor); +} + +void UPubnubSubscription::Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse) +{ + FOnSubscribeOperationResponseNative NativeCallback; + NativeCallback.BindLambda([OnUnsubscribeResponse](FPubnubOperationResult Result) + { + OnUnsubscribeResponse.ExecuteIfBound(Result); + }); + + Unsubscribe(NativeCallback); +} + +void UPubnubSubscription::Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: internal C-Core subscription set is invalid.")); + return; + } + + PubnubSubsystem->UnsubscribeWithSubscription(this, NativeCallback); +} + +UPubnubSubscriptionSet* UPubnubSubscription::AddSubscription(UPubnubSubscription* Subscription) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return nullptr; + } + + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: internal C-Core subscription is invalid.")); + return nullptr; + } + + if(!Subscription) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: Can't add invalid subscription.")); + return nullptr; + } + + if(!Subscription->CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: Provided Subscription's internal C-Core subscription is invalid.")); + return nullptr; + } + + UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); + SubscriptionSet->InitWithSubscriptions(PubnubSubsystem, this, Subscription); + + return SubscriptionSet; +} + +void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings) +{ + if(!InPubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, PubnubSubsystem is invalid.")); + return; + } + if(!Entity) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, Entity is invalid.")); + return; + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscription = UPubnubUtilities::EEGetSubscriptionForEntity(InPubnubSubsystem->ctx_ee, Entity->EntityID, Entity->EntityType, InSubscribeSettings); + + InternalInit(); +} + +void UPubnubSubscription::InitWithCCoreSubscription(UPubnubSubsystem* InPubnubSubsystem, pubnub_subscription_t* InCCoreSubscription) +{ + if(!InPubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, PubnubSubsystem is invalid.")); + return; + } + if(!InCCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, InCCoreSubscription is invalid.")); + return; + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscription = InCCoreSubscription; + + InternalInit(); +} + +void UPubnubSubscription::InternalInit() +{ + // Create callbacks for each Listener type + + // Messages and Presence + pubnub_subscribe_message_callback_t CallbackMessages = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscription* S = SubscriptionWeak.Get(); IsValid(S)) + { + // In C-Core there is no separate listener for Presence Events. They come together with published messages. + // So we check if it's Presence Event here and choose equivalent delegates to call + if(MessageData.Channel.Contains("-pnpres")) + { + S->OnPubnubPresenceEvent.Broadcast(MessageData); + S->OnPubnubPresenceEventNative.Broadcast(MessageData); + } + else + { + S->OnPubnubMessage.Broadcast(MessageData); + S->OnPubnubMessageNative.Broadcast(MessageData); + } + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Signals + pubnub_subscribe_message_callback_t CallbackSignals = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscription* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubSignal.Broadcast(MessageData); + S->OnPubnubSignalNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Objects (App Context) + pubnub_subscribe_message_callback_t CallbackObjects = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscription* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubObjectEvent.Broadcast(MessageData); + S->OnPubnubObjectEventNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Message Actions + pubnub_subscribe_message_callback_t CallbackMessageActions= +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscription* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubMessageAction.Broadcast(MessageData); + S->OnPubnubMessageActionNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Register created callback in subscription + UPubnubUtilities::EEAddSubscriptionListenerOfType(CCoreSubscription, CallbackMessages, EPubnubListenerType::PLT_Message, this); + UPubnubUtilities::EEAddSubscriptionListenerOfType(CCoreSubscription, CallbackSignals, EPubnubListenerType::PLT_Signal, this); + UPubnubUtilities::EEAddSubscriptionListenerOfType(CCoreSubscription, CallbackObjects, EPubnubListenerType::PLT_Objects, this); + UPubnubUtilities::EEAddSubscriptionListenerOfType(CCoreSubscription, CallbackMessageActions, EPubnubListenerType::PLT_MessageAction, this); + + //Bind to OnPubnubSubsystemDeinitialized so subscription is properly Cleaned up, when it's not needed + PubnubSubsystem->OnPubnubSubsystemDeinitialized.AddDynamic(this, &UPubnubSubscription::CleanUpSubscription); + + //Now we are fully initialized + IsInitialized = true; +} + +void UPubnubSubscription::CleanUpSubscription() +{ + if(!IsInitialized) {return;} + + if(CCoreSubscription) + { + pubnub_subscription_free(&CCoreSubscription); + } + + if(PubnubSubsystem) + { + PubnubSubsystem->OnPubnubSubsystemDeinitialized.RemoveDynamic(this, &UPubnubSubscription::CleanUpSubscription); + } + + IsInitialized = false; +} + +void UPubnubSubscriptionSet::Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor) +{ + FOnSubscribeOperationResponseNative NativeCallback; + NativeCallback.BindLambda([OnSubscribeResponse](FPubnubOperationResult Result) + { + OnSubscribeResponse.ExecuteIfBound(Result); + }); + + Subscribe(NativeCallback, Cursor); +} + +void UPubnubSubscriptionSet::Subscribe(FOnSubscribeOperationResponseNative NativeCallback, FPubnubSubscriptionCursor Cursor) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: internal C-Core subscription set is invalid.")); + return; + } + + PubnubSubsystem->SubscribeWithSubscriptionSet(this, Cursor, NativeCallback); +} + +void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) +{ + Subscribe(nullptr, Cursor); +} + +void UPubnubSubscriptionSet::Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse) +{ + FOnSubscribeOperationResponseNative NativeCallback; + NativeCallback.BindLambda([OnUnsubscribeResponse](FPubnubOperationResult Result) + { + OnUnsubscribeResponse.ExecuteIfBound(Result); + }); + + Unsubscribe(NativeCallback); +} + +void UPubnubSubscriptionSet::Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: internal C-Core subscription set is invalid.")); + return; + } + + PubnubSubsystem->UnsubscribeWithSubscriptionSet(this, NativeCallback); +} + +void UPubnubSubscriptionSet::AddSubscription(UPubnubSubscription* Subscription) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: internal C-Core subscription set is invalid.")); + return; + } + + if(!Subscription) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: Can't add invalid subscription.")); + return; + } + + if(!Subscription->CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscription]: Provided Subscription's internal C-Core subscription is invalid.")); + return; + } + + Subscriptions.Add(Subscription); + + pubnub_subscription_set_add(CCoreSubscriptionSet, Subscription->CCoreSubscription); +} + +void UPubnubSubscriptionSet::RemoveSubscription(UPubnubSubscription* Subscription) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscription]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscription]: internal C-Core subscription set is invalid.")); + return; + } + + if(!Subscription) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscription]: Can't remove invalid subscription.")); + return; + } + + if(!Subscription->CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscription]: Provided Subscription's internal C-Core subscription is invalid.")); + return; + } + + Subscriptions.Remove(Subscription); + + pubnub_subscription_set_remove(CCoreSubscriptionSet, &Subscription->CCoreSubscription); +} + +void UPubnubSubscriptionSet::AddSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscriptionSet]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscriptionSet]: internal C-Core subscription set is invalid.")); + return; + } + + if(!SubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscriptionSet]: Can't add invalid subscription set.")); + return; + } + + if(!SubscriptionSet->CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[AddSubscriptionSet]: Provided Subscription Set's internal C-Core subscription set is invalid.")); + return; + } + + pubnub_subscription_set_union(CCoreSubscriptionSet, SubscriptionSet->CCoreSubscriptionSet); +} + +void UPubnubSubscriptionSet::RemoveSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscriptionSet]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + return; + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscriptionSet]: internal C-Core subscription set is invalid.")); + return; + } + + if(!SubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscriptionSet]: Can't remove invalid subscription set.")); + return; + } + + if(!SubscriptionSet->CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("[RemoveSubscriptionSet]: Provided Subscription Set's internal C-Core subscription set is invalid.")); + return; + } + + pubnub_subscription_set_subtract(CCoreSubscriptionSet, SubscriptionSet->CCoreSubscriptionSet); +} + +void UPubnubSubscriptionSet::InitSubscriptionSet(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings) +{ + if(Channels.IsEmpty() && ChannelGroups.IsEmpty()) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize SubscriptionSet, at least one Channel or ChannelGroup is needed.")); + return; + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscriptionSet = UPubnubUtilities::EEGetSubscriptionSetForEntities(InPubnubSubsystem->ctx_ee, Channels, ChannelGroups, InSubscribeSettings); + + InternalInit(); +} + +void UPubnubSubscriptionSet::InitWithSubscriptions(UPubnubSubsystem* InPubnubSubsystem, UPubnubSubscription* Subscription1, UPubnubSubscription* Subscription2) +{ + if(!Subscription1 || !Subscription2 || !Subscription1->CCoreSubscription || !Subscription2->CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize SubscriptionSet, One of provided subscriptions is invalid.")); + return; + } + + PubnubSubsystem = InPubnubSubsystem; + + CCoreSubscriptionSet = pubnub_subscription_set_alloc_with_subscriptions(Subscription1->CCoreSubscription, Subscription2->CCoreSubscription, nullptr); + Subscriptions.Add(Subscription1); + Subscriptions.Add(Subscription2); + + InternalInit(); +} + +void UPubnubSubscriptionSet::InitWithCCoreSubscriptionSet(UPubnubSubsystem* InPubnubSubsystem, pubnub_subscription_set_t* InCCoreSubscriptionSet) +{ + if(!InCCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize SubscriptionSet, InCCoreSubscriptionSet is invalid.")); + return; + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscriptionSet = InCCoreSubscriptionSet; + + InternalInit(); +} + +void UPubnubSubscriptionSet::InternalInit() +{ + // Create callbacks for each Listener type + + // Messages and Presence + pubnub_subscribe_message_callback_t CallbackMessages = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscriptionSet* S = SubscriptionWeak.Get(); IsValid(S)) + { + // In C-Core there is no separate listener for Presence Events. They come together with published messages. + // So we check if it's Presence Event here and choose equivalent delegates to call + if(MessageData.Channel.Contains("-pnpres")) + { + S->OnPubnubPresenceEvent.Broadcast(MessageData); + S->OnPubnubPresenceEventNative.Broadcast(MessageData); + } + else + { + S->OnPubnubMessage.Broadcast(MessageData); + S->OnPubnubMessageNative.Broadcast(MessageData); + } + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Signals + pubnub_subscribe_message_callback_t CallbackSignals = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscriptionSet* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubSignal.Broadcast(MessageData); + S->OnPubnubSignalNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Objects (App Context) + pubnub_subscribe_message_callback_t CallbackObjects = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscriptionSet* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubObjectEvent.Broadcast(MessageData); + S->OnPubnubObjectEventNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Message Actions + pubnub_subscribe_message_callback_t CallbackMessageActions= +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) + { + auto SubscriptionWeak = TWeakObjectPtr(static_cast(user_data)); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); + AsyncTask(ENamedThreads::GameThread, [MessageData, SubscriptionWeak]() + { + if (UPubnubSubscriptionSet* S = SubscriptionWeak.Get(); IsValid(S)) + { + S->OnPubnubMessageAction.Broadcast(MessageData); + S->OnPubnubMessageActionNative.Broadcast(MessageData); + S->FOnPubnubAnyMessageType.Broadcast(MessageData); + S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); + } + }); + }; + + // Register created callback in subscription + UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackMessages, EPubnubListenerType::PLT_Message, this); + UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackSignals, EPubnubListenerType::PLT_Signal, this); + UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackObjects, EPubnubListenerType::PLT_Objects, this); + UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackMessageActions, EPubnubListenerType::PLT_MessageAction, this); + + //Bind to OnPubnubSubsystemDeinitialized so subscription is properly Cleaned up, when it's not needed + PubnubSubsystem->OnPubnubSubsystemDeinitialized.AddDynamic(this, &UPubnubSubscriptionSet::CleanUpSubscription); + + //Now we are fully initialized + IsInitialized = true; +} + +void UPubnubSubscriptionSet::CleanUpSubscription() +{ + if(!IsInitialized) {return;} + + if(CCoreSubscriptionSet) + { + pubnub_subscription_set_free(&CCoreSubscriptionSet); + } + + if(PubnubSubsystem) + { + PubnubSubsystem->OnPubnubSubsystemDeinitialized.RemoveDynamic(this, &UPubnubSubscriptionSet::CleanUpSubscription); + } + + IsInitialized = false; +} diff --git a/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp new file mode 100644 index 0000000..259e73f --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp @@ -0,0 +1,70 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubUserMetadataEntity.h" + + +UPubnubUserMetadataEntity::UPubnubUserMetadataEntity() +{ + EntityType = EPubnubEntityType::PEnT_UserMetadata; +} + +void UPubnubUserMetadataEntity::SetUserMetadata(FPubnubUserData UserMetadata, FOnSetUserMetadataResponse OnSetUserMetadataResponse, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot set user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->SetUserMetadata(EntityID, UserMetadata, OnSetUserMetadataResponse, Include); +} + +void UPubnubUserMetadataEntity::SetUserMetadata(FPubnubUserData UserMetadata, FOnSetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot set user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->SetUserMetadata(EntityID, UserMetadata, NativeCallback, Include); +} + +void UPubnubUserMetadataEntity::GetUserMetadata(FOnGetUserMetadataResponse OnGetUserMetadataResponse, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot get user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->GetUserMetadata(EntityID, OnGetUserMetadataResponse, Include); +} + +void UPubnubUserMetadataEntity::GetUserMetadata(FOnGetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot get user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->GetUserMetadata(EntityID, NativeCallback, Include); +} + +void UPubnubUserMetadataEntity::RemoveUserMetadata(FOnRemoveUserMetadataResponse OnRemoveUserMetadataResponse) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveUserMetadata(EntityID, OnRemoveUserMetadataResponse); +} + +void UPubnubUserMetadataEntity::RemoveUserMetadata(FOnRemoveUserMetadataResponseNative NativeCallback) +{ + if (!PubnubSubsystem) + { + UE_LOG(PubnubLog, Error, TEXT("Cannot remove user metadata - PubnubSubsystem is null. Entity not properly initialized.")); + return; + } + PubnubSubsystem->RemoveUserMetadata(EntityID, NativeCallback); +} + diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index 5ae3a24..3043c97 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -3,6 +3,8 @@ #include "FunctionLibraries/PubnubUtilities.h" #include "Config/PubnubSettings.h" +#include "Entities/PubnubSubscription.h" +#include "FunctionLibraries/PubnubJsonUtilities.h" #include "Kismet/KismetMathLibrary.h" @@ -120,6 +122,27 @@ FString UPubnubUtilities::GetNameFromFunctionMacro(FString FunctionName) return FinalFunctionName; } +FPubnubMessageData UPubnubUtilities::UEMessageFromPubnubMessage(pubnub_v2_message PubnubMessage) +{ + FPubnubMessageData MessageData; + MessageData.Message = PubnubCharMemBlockToString(PubnubMessage.payload); + + //If message was just a string, we need to deserialize it + if(!UPubnubJsonUtilities::IsCorrectJsonString(MessageData.Message, false)) + { + MessageData.Message = UPubnubJsonUtilities::DeserializeString(MessageData.Message); + } + + MessageData.Channel = PubnubCharMemBlockToString(PubnubMessage.channel); + MessageData.UserID = PubnubCharMemBlockToString(PubnubMessage.publisher); + MessageData.Timetoken = PubnubCharMemBlockToString(PubnubMessage.tt); + MessageData.Metadata = PubnubCharMemBlockToString(PubnubMessage.metadata); + MessageData.MessageType = (EPubnubMessageType)(PubnubMessage.message_type); + MessageData.CustomMessageType = PubnubCharMemBlockToString(PubnubMessage.custom_message_type); + MessageData.MatchOrGroup = PubnubCharMemBlockToString(PubnubMessage.match_or_group); + return MessageData; +} + FString UPubnubUtilities::MembershipIncludeToString(const FPubnubMembershipInclude& MembershipInclude) { FString FinalString = ""; @@ -315,36 +338,65 @@ FString UPubnubUtilities::GetAllSortToString(const FPubnubGetAllSort& GetAllIncl return FinalString; } -pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForChannel(pubnub_t* Context, FString Channel, FPubnubSubscribeSettings Options) +pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForEntity(pubnub_t* Context, FString EntityID, EPubnubEntityType EntityType, FPubnubSubscribeSettings Options) { pubnub_subscription_options_t PnOptions = pubnub_subscription_options_defopts(); PnOptions.receive_presence_events = Options.ReceivePresenceEvents; - FUTF8StringHolder ChannelHolder(Channel); - - pubnub_channel_t* PubnubChannel = pubnub_channel_alloc(Context, ChannelHolder.Get()); - - pubnub_subscription_t* Subscription = pubnub_subscription_alloc((pubnub_entity_t*)PubnubChannel, &PnOptions); + FUTF8StringHolder EntityIDHolder(EntityID); + pubnub_entity_t* PubnubEntity = nullptr; + switch (EntityType) + { + case EPubnubEntityType::PEnT_Channel: + PubnubEntity = reinterpret_cast(pubnub_channel_alloc(Context, EntityIDHolder.Get())); + break; + case EPubnubEntityType::PEnT_ChannelGroup: + PubnubEntity = reinterpret_cast(pubnub_channel_group_alloc(Context, EntityIDHolder.Get())); + break; + case EPubnubEntityType::PEnT_ChannelMetadata: + PubnubEntity = reinterpret_cast(pubnub_channel_metadata_alloc(Context, EntityIDHolder.Get())); + break; + case EPubnubEntityType::PEnT_UserMetadata: + PubnubEntity = reinterpret_cast(pubnub_user_metadata_alloc(Context, EntityIDHolder.Get())); + break; + default: + UE_LOG(PubnubLog, Error, TEXT("Unknown entity type: %d"), (int32)EntityType); + return nullptr; + } + pubnub_subscription_t* Subscription = pubnub_subscription_alloc(PubnubEntity, &PnOptions); - pubnub_entity_free((void**)&PubnubChannel); + pubnub_entity_free(reinterpret_cast(&PubnubEntity)); return Subscription; } -pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForChannelGroup(pubnub_t* Context, FString ChannelGroup, FPubnubSubscribeSettings Options) +pubnub_subscription_set_t* UPubnubUtilities::EEGetSubscriptionSetForEntities(pubnub_t* Context, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings Options) { pubnub_subscription_options_t PnOptions = pubnub_subscription_options_defopts(); PnOptions.receive_presence_events = Options.ReceivePresenceEvents; - FUTF8StringHolder ChannelGroupHolder(ChannelGroup); + TArray PubnubEntities; + PubnubEntities.Reserve(Channels.Num() + ChannelGroups.Num()); - pubnub_channel_group_t* PubnubChannelGroup = pubnub_channel_group_alloc(Context, ChannelGroupHolder.Get()); - - pubnub_subscription_t* Subscription = pubnub_subscription_alloc((pubnub_entity_t*)PubnubChannelGroup, &PnOptions); - - pubnub_entity_free((void**)&PubnubChannelGroup); + for(FString Channel : Channels) + { + FUTF8StringHolder EntityIDHolder(Channel); + PubnubEntities.Add(reinterpret_cast(pubnub_channel_alloc(Context, EntityIDHolder.Get()))); + } + for(FString ChannelGroup : ChannelGroups) + { + FUTF8StringHolder EntityIDHolder(ChannelGroup); + PubnubEntities.Add(reinterpret_cast(pubnub_channel_group_alloc(Context, EntityIDHolder.Get()))); + } - return Subscription; + pubnub_subscription_set_t* SubscriptionSet = pubnub_subscription_set_alloc_with_entities(PubnubEntities.GetData(), PubnubEntities.Num(), &PnOptions); + + for(pubnub_entity_t*& Entity : PubnubEntities) + { + pubnub_entity_free(reinterpret_cast(&Entity)); + } + + return SubscriptionSet; } bool UPubnubUtilities::EEAddListenerAndSubscribe(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem) @@ -354,103 +406,218 @@ bool UPubnubUtilities::EEAddListenerAndSubscribe(pubnub_subscription_t* Subscrip UE_LOG(PubnubLog, Error, TEXT("EEAddListenerAndSubscribe Failed, PubnubSubsystem is invalid")); return false; } - - enum pubnub_res AddMessageListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PBSL_LISTENER_ON_MESSAGE, Callback, PubnubSubsystem); - if(PNR_OK != AddMessageListenerResult) + + EEAddSubscriptionListenersOfAllTypes(Subscription, Callback, PubnubSubsystem); + + return EESubscribeWithSubscription(Subscription, FPubnubSubscriptionCursor()); +} + +bool UPubnubUtilities::EERemoveListenerAndUnsubscribe(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem) +{ + if(!PubnubSubsystem) { - FString ResultString(pubnub_res_2_string(AddMessageListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Add_subscription_listener (Message) failed with error: " + ResultString); + UE_LOG(PubnubLog, Error, TEXT("EERemoveListenerAndUnsubscribe Failed, PubnubSubsystem is invalid")); return false; } - - enum pubnub_res AddSignalListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PBSL_LISTENER_ON_SIGNAL, Callback, PubnubSubsystem); - if(PNR_OK != AddSignalListenerResult) + + if(!SubscriptionPtr) { - FString ResultString(pubnub_res_2_string(AddSignalListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Add_subscription_listener (Signal) failed with error: " + ResultString); + PubnubSubsystem->PubnubError("Failed to unsubscribe. Passed subscription pointer is invalid"); return false; } - enum pubnub_res AddMessageActionListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PBSL_LISTENER_ON_MESSAGE_ACTION, Callback, PubnubSubsystem); - if(PNR_OK != AddMessageActionListenerResult) + EERemoveSubscriptionListenersOfAllTypes(SubscriptionPtr, Callback, PubnubSubsystem); + + return EEUnsubscribeWithSubscription(SubscriptionPtr); +} + +bool UPubnubUtilities::EESubscribeWithSubscription(pubnub_subscription_t* Subscription, FPubnubSubscriptionCursor Cursor) +{ + enum pubnub_res SubscribeResult; + if(!Cursor.Timetoken.IsEmpty() || Cursor.Region != 0) { - FString ResultString(pubnub_res_2_string(AddMessageActionListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Add_subscription_listener (Message_Action) failed with error: " + ResultString); - return false; + FUTF8StringHolder CursorTimetokenHolder(Cursor.Timetoken); + pubnub_subscribe_cursor_t PubnubCursor = pubnub_subscribe_cursor(CursorTimetokenHolder.Get()); + PubnubCursor.region = Cursor.Region; + SubscribeResult = pubnub_subscribe_with_subscription(Subscription, &PubnubCursor); + } + else + { + SubscribeResult = pubnub_subscribe_with_subscription(Subscription, nullptr); } - enum pubnub_res AddObjectsListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PBSL_LISTENER_ON_OBJECTS, Callback, PubnubSubsystem); - if(PNR_OK != AddObjectsListenerResult) + if(PNR_OK != SubscribeResult) + { + FString ResultString(pubnub_res_2_string(SubscribeResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to subscribe. Subscribe_with_subscription failed with error: %s"), *ResultString); + return false; + } + + return true; +} + +bool UPubnubUtilities::EEUnsubscribeWithSubscription(pubnub_subscription_t** SubscriptionPtr) +{ + enum pubnub_res UnsubscribeResult = pubnub_unsubscribe_with_subscription(SubscriptionPtr); + if(PNR_OK != UnsubscribeResult) { - FString ResultString(pubnub_res_2_string(AddObjectsListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Add_subscription_listener (Objects) failed with error: " + ResultString); + FString ResultString(pubnub_res_2_string(UnsubscribeResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to unsubscribe. Unsubscribe_with_subscription failed with error: "), *ResultString); return false; } + + return true; +} - enum pubnub_res SubscribeResult = pubnub_subscribe_with_subscription(Subscription, nullptr); +bool UPubnubUtilities::EESubscribeWithSubscriptionSet(pubnub_subscription_set_t* SubscriptionSet, FPubnubSubscriptionCursor Cursor) +{ + enum pubnub_res SubscribeResult; + if(!Cursor.Timetoken.IsEmpty() || Cursor.Region != 0) + { + FUTF8StringHolder CursorTimetokenHolder(Cursor.Timetoken); + pubnub_subscribe_cursor_t PubnubCursor = pubnub_subscribe_cursor(CursorTimetokenHolder.Get()); + PubnubCursor.region = Cursor.Region; + SubscribeResult = pubnub_subscribe_with_subscription_set(SubscriptionSet, &PubnubCursor); + } + else + { + SubscribeResult = pubnub_subscribe_with_subscription_set(SubscriptionSet, nullptr); + } + if(PNR_OK != SubscribeResult) { FString ResultString(pubnub_res_2_string(SubscribeResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Subscribe_with_subscription failed with error: " + ResultString); + UE_LOG(PubnubLog, Error, TEXT("Failed to subscribe. Subscribe_with_subscription_set failed with error: %s"), *ResultString); return false; } return true; } -bool UPubnubUtilities::EERemoveListenerAndUnsubscribe(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem) +bool UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(pubnub_subscription_set_t** SubscriptionSetPtr) { - if(!PubnubSubsystem) + enum pubnub_res UnsubscribeResult = pubnub_unsubscribe_with_subscription_set(SubscriptionSetPtr); + if(PNR_OK != UnsubscribeResult) { - UE_LOG(PubnubLog, Error, TEXT("EERemoveListenerAndUnsubscribe Failed, PubnubSubsystem is invalid")); + FString ResultString(pubnub_res_2_string(UnsubscribeResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to unsubscribe. Unsubscribe_with_subscription failed with error: "), *ResultString); return false; } + + return true; +} - if(!SubscriptionPtr) +bool UPubnubUtilities::EEAddSubscriptionListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +{ + if(ListenerType == EPubnubListenerType::PLT_All) { - PubnubSubsystem->PubnubError("Failed to unsubscribe. Passed subscription pointer is invalid"); + EEAddSubscriptionListenersOfAllTypes(Subscription, Callback, Caller); return false; } - enum pubnub_res RemoveMessageListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_MESSAGE, Callback, PubnubSubsystem); - if(PNR_OK != RemoveMessageListenerResult) + pubnub_subscribe_listener_type PubnubListenerType = static_cast(static_cast(ListenerType)); + enum pubnub_res AddMessageListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PubnubListenerType, Callback, Caller); + + if(PNR_OK != AddMessageListenerResult) { - FString ResultString(pubnub_res_2_string(RemoveMessageListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Message) failed with error: " + ResultString); + FString ResultString(pubnub_res_2_string(AddMessageListenerResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to add listener of type %s. Error: %s "), *StaticEnum()->GetNameStringByValue(static_cast(ListenerType)), *ResultString); return false; } + + return true; +} + +void UPubnubUtilities::EEAddSubscriptionListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) + { + EEAddSubscriptionListenerOfType(Subscription, Callback, Type, Caller); + } +} - enum pubnub_res RemoveSignalListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_SIGNAL, Callback, PubnubSubsystem); - if(PNR_OK != RemoveSignalListenerResult) +bool UPubnubUtilities::EERemoveSubscriptionListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +{ + if(ListenerType == EPubnubListenerType::PLT_All) { - FString ResultString(pubnub_res_2_string(RemoveSignalListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Signal) failed with error: " + ResultString); + EERemoveSubscriptionListenersOfAllTypes(SubscriptionPtr, Callback, Caller); return false; } - enum pubnub_res RemoveMessageActionListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_MESSAGE_ACTION, Callback, PubnubSubsystem); + pubnub_subscribe_listener_type PubnubListenerType = static_cast(static_cast(ListenerType)); + enum pubnub_res RemoveMessageActionListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PubnubListenerType, Callback, Caller); if(PNR_OK != RemoveMessageActionListenerResult) { FString ResultString(pubnub_res_2_string(RemoveMessageActionListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Message) failed with error: " + ResultString); + UE_LOG(PubnubLog, Error, TEXT("Failed to remove listener of type %s. Error: %s "), *StaticEnum()->GetNameStringByValue(static_cast(ListenerType)), *ResultString); return false; } - enum pubnub_res RemoveObjectsListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_OBJECTS, Callback, PubnubSubsystem); - if(PNR_OK != RemoveObjectsListenerResult) + return true; +} + +void UPubnubUtilities::EERemoveSubscriptionListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) + { + EERemoveSubscriptionListenerOfType(SubscriptionPtr, Callback, Type, Caller); + } +} + +bool UPubnubUtilities::EEAddSubscriptionSetListenerOfType(pubnub_subscription_set_t* SubscriptionSet, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +{ + if(ListenerType == EPubnubListenerType::PLT_All) { - FString ResultString(pubnub_res_2_string(RemoveObjectsListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Objects) failed with error: " + ResultString); + EEAddSubscriptionSetListenersOfAllTypes(SubscriptionSet, Callback, Caller); return false; } - enum pubnub_res UnsubscribeResult = pubnub_unsubscribe_with_subscription(SubscriptionPtr); - if(PNR_OK != UnsubscribeResult) + pubnub_subscribe_listener_type PubnubListenerType = static_cast(static_cast(ListenerType)); + enum pubnub_res AddMessageListenerResult = pubnub_subscribe_add_subscription_set_listener(SubscriptionSet, PubnubListenerType, Callback, Caller); + + if(PNR_OK != AddMessageListenerResult) { - FString ResultString(pubnub_res_2_string(UnsubscribeResult)); - PubnubSubsystem->PubnubError("Failed to unsubscribe. Unsubscribe_with_subscription failed with error: " + ResultString); + FString ResultString(pubnub_res_2_string(AddMessageListenerResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to add listener of type %s. Error: %s "), *StaticEnum()->GetNameStringByValue(static_cast(ListenerType)), *ResultString); + return false; + } + + return true; +} + +void UPubnubUtilities::EEAddSubscriptionSetListenersOfAllTypes(pubnub_subscription_set_t* SubscriptionSet, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) + { + EEAddSubscriptionSetListenerOfType(SubscriptionSet, Callback, Type, Caller); + } +} + +bool UPubnubUtilities::EERemoveSubscriptionSetListenerOfType(pubnub_subscription_set_t** SubscriptionSetPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +{ + if(ListenerType == EPubnubListenerType::PLT_All) + { + EERemoveSubscriptionSetListenersOfAllTypes(SubscriptionSetPtr, Callback, Caller); + return false; + } + + pubnub_subscribe_listener_type PubnubListenerType = static_cast(static_cast(ListenerType)); + enum pubnub_res RemoveMessageActionListenerResult = pubnub_subscribe_remove_subscription_set_listener(*SubscriptionSetPtr, PubnubListenerType, Callback, Caller); + if(PNR_OK != RemoveMessageActionListenerResult) + { + FString ResultString(pubnub_res_2_string(RemoveMessageActionListenerResult)); + UE_LOG(PubnubLog, Error, TEXT("Failed to remove listener of type %s. Error: %s "), *StaticEnum()->GetNameStringByValue(static_cast(ListenerType)), *ResultString); return false; } return true; -} \ No newline at end of file +} + +void UPubnubUtilities::EERemoveSubscriptionSetListenersOfAllTypes(pubnub_subscription_set_t** SubscriptionSetPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) + { + EERemoveSubscriptionSetListenerOfType(SubscriptionSetPtr, Callback, Type, Caller); + } +} + diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index f879059..22bb4bf 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -5,12 +5,18 @@ #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" #include "Config/PubnubSettings.h" +#include "Threads/PubnubFunctionThread.h" #include "PubnubInternalMacros.h" #include "Crypto/PubnubAesCryptor.h" #include "FunctionLibraries/PubnubTokenUtilities.h" #include "FunctionLibraries/PubnubJsonUtilities.h" #include "FunctionLibraries/PubnubUtilities.h" -#include "Threads/PubnubFunctionThread.h" +#include "Entities/PubnubBaseEntity.h" +#include "Entities/PubnubChannelEntity.h" +#include "Entities/PubnubChannelGroupEntity.h" +#include "Entities/PubnubChannelMetadataEntity.h" +#include "Entities/PubnubUserMetadataEntity.h" +#include "Entities/PubnubSubscription.h" DEFINE_LOG_CATEGORY(PubnubLog) @@ -95,6 +101,9 @@ void UPubnubSubsystem::DeinitPubnub() ChannelGroupSubscriptions.Empty(); IsUserIDSet = false; delete[] AuthTokenBuffer; + + //Notify that Deinitialization is finished + OnPubnubSubsystemDeinitialized.Broadcast(); } void UPubnubSubsystem::SetUserID(FString UserID) @@ -1151,6 +1160,139 @@ TScriptInterface UPubnubSubsystem::GetCryptoModu return nullptr; } +UPubnubChannelEntity* UPubnubSubsystem::CreateChannelEntity(FString Channel) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(Channel, nullptr); + + UPubnubChannelEntity* ChannelEntity = NewObject(this); + ChannelEntity->InitEntity(this); + ChannelEntity->EntityID = Channel; + return ChannelEntity; +} + +UPubnubChannelGroupEntity* UPubnubSubsystem::CreateChannelGroupEntity(FString ChannelGroup) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelGroup, nullptr); + + UPubnubChannelGroupEntity* ChannelGroupEntity = NewObject(this); + ChannelGroupEntity->InitEntity(this); + ChannelGroupEntity->EntityID = ChannelGroup; + return ChannelGroupEntity; +} + +UPubnubChannelMetadataEntity* UPubnubSubsystem::CreateChannelMetadataEntity(FString Channel) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(Channel, nullptr); + + UPubnubChannelMetadataEntity* ChannelMetadataEntity = NewObject(this); + ChannelMetadataEntity->InitEntity(this); + ChannelMetadataEntity->EntityID = Channel; + return ChannelMetadataEntity; +} + +UPubnubUserMetadataEntity* UPubnubSubsystem::CreateUserMetadataEntity(FString User) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(User, nullptr); + + UPubnubUserMetadataEntity* UserMetadataEntity = NewObject(this); + UserMetadataEntity->InitEntity(this); + UserMetadataEntity->EntityID = User; + return UserMetadataEntity; +} + +UPubnubSubscriptionSet* UPubnubSubsystem::CreateSubscriptionSet(TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings SubscriptionSettings) +{ + if(Channels.IsEmpty() && ChannelGroups.IsEmpty()) + { + PubnubError("[CreateSubscriptionSet]: at least one Channel or ChannelGroup is needed to create SubscriptionSet.", EPubnubErrorType::PET_Warning); + } + UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); + SubscriptionSet->InitSubscriptionSet(this, Channels, ChannelGroups, SubscriptionSettings); + return SubscriptionSet; +} + +UPubnubSubscriptionSet* UPubnubSubsystem::CreateSubscriptionSetFromEntities(TArray Entities, FPubnubSubscribeSettings SubscriptionSettings) +{ + if(Entities.IsEmpty()) + { + PubnubError("[CreateSubscriptionSetFromEntities]: at least one Entity is needed to create SubscriptionSet.", EPubnubErrorType::PET_Warning); + } + TArray Channels, ChannelGroups; + + //Group up entities for those that subscribe to Channel and ChannelGroup + for(auto Entity : Entities) + { + Entity->EntityType == EPubnubEntityType::PEnT_ChannelGroup? ChannelGroups.Add(Entity->EntityID) : Channels.Add(Entity->EntityID); + } + + UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); + SubscriptionSet->InitSubscriptionSet(this, Channels, ChannelGroups, SubscriptionSettings); + return SubscriptionSet; +} + +TArray UPubnubSubsystem::GetActiveSubscriptions() +{ + size_t Count; + pubnub_subscription** CCoreSubs = pubnub_subscriptions(ctx_ee, &Count); + if (!CCoreSubs || Count == 0) { + return {}; + } + + //Free CCoreSubs when the function ends + ON_SCOPE_EXIT { free(CCoreSubs); }; + + TArray Subscriptions; + + for(pubnub_subscription_t* CCoreSub : MakeArrayView(CCoreSubs, Count)) + { + UPubnubSubscription* Subscription = NewObject(this); + Subscription->InitWithCCoreSubscription(this, CCoreSub); + Subscriptions.Add(Subscription); + } + + return Subscriptions; +} + +TArray UPubnubSubsystem::GetActiveSubscriptionSets() +{ + size_t Count; + pubnub_subscription_set** CCoreSubSets = pubnub_subscription_sets(ctx_ee, &Count); + if (!CCoreSubSets || Count == 0) { + return {}; + } + + //Free CCoreSubs when the function ends + ON_SCOPE_EXIT { free(CCoreSubSets); }; + + TArray SubscriptionSets; + + for(pubnub_subscription_set_t* CCoreSubsSet : MakeArrayView(CCoreSubSets, Count)) + { + UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); + SubscriptionSet->InitWithCCoreSubscriptionSet(this, CCoreSubsSet); + SubscriptionSets.Add(SubscriptionSet); + + size_t SubsCount; + + pubnub_subscription** CCoreSubs = pubnub_subscription_set_subscriptions(CCoreSubsSet, &SubsCount); + if (!CCoreSubs || Count == 0) { + continue; + } + + //Free CCoreSubs when the function ends + ON_SCOPE_EXIT { free(CCoreSubs); }; + + for(pubnub_subscription_t* CCoreSub : MakeArrayView(CCoreSubs, Count)) + { + UPubnubSubscription* Subscription = NewObject(this); + Subscription->InitWithCCoreSubscription(this, CCoreSub); + SubscriptionSet->Subscriptions.Add(Subscription); + } + } + + return SubscriptionSets; +} + FString UPubnubSubsystem::GetLastResponse(pubnub_t* context) { FString Response; @@ -1469,7 +1611,7 @@ void UPubnubSubsystem::SubscribeToChannel_priv(FString Channel, FOnSubscribeOper QuickActionThread->LockForSubscribeOperation(); //Create subscription for channel entity - pubnub_subscription_t* Subscription = UPubnubUtilities::EEGetSubscriptionForChannel(ctx_ee, Channel, SubscribeSettings); + pubnub_subscription_t* Subscription = UPubnubUtilities::EEGetSubscriptionForEntity(ctx_ee, Channel, EPubnubEntityType::PEnT_Channel, SubscribeSettings); if(nullptr == Subscription) { @@ -1483,7 +1625,7 @@ void UPubnubSubsystem::SubscribeToChannel_priv(FString Channel, FOnSubscribeOper pubnub_subscribe_message_callback_t Callback = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) { UPubnubSubsystem* ThisSubsystem = static_cast(user_data); - FPubnubMessageData MessageData = UEMessageFromPubnub(message); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); AsyncTask(ENamedThreads::GameThread, [MessageData, ThisSubsystem]() { if(ThisSubsystem) @@ -1525,7 +1667,7 @@ void UPubnubSubsystem::SubscribeToGroup_priv(FString ChannelGroup, FOnSubscribeO QuickActionThread->LockForSubscribeOperation(); //Create subscription for channel group entity - pubnub_subscription_t* Subscription = UPubnubUtilities::EEGetSubscriptionForChannelGroup(ctx_ee, ChannelGroup, SubscribeSettings); + pubnub_subscription_t* Subscription = UPubnubUtilities::EEGetSubscriptionForEntity(ctx_ee, ChannelGroup, EPubnubEntityType::PEnT_ChannelGroup, SubscribeSettings); if(nullptr == Subscription) { @@ -1539,7 +1681,7 @@ void UPubnubSubsystem::SubscribeToGroup_priv(FString ChannelGroup, FOnSubscribeO pubnub_subscribe_message_callback_t Callback = +[](const pubnub_t* pb, struct pubnub_v2_message message, void* user_data) { UPubnubSubsystem* ThisSubsystem = static_cast(user_data); - FPubnubMessageData MessageData = UEMessageFromPubnub(message); + FPubnubMessageData MessageData = UPubnubUtilities::UEMessageFromPubnubMessage(message); AsyncTask(ENamedThreads::GameThread, [MessageData, ThisSubsystem]() { if(ThisSubsystem) @@ -1643,6 +1785,16 @@ void UPubnubSubsystem::UnsubscribeFromAll_priv(FOnSubscribeOperationResponseNati QuickActionThread->LockForSubscribeOperation(); pubnub_unsubscribe_all(ctx_ee); + + //Clean up all subscriptions + for(auto Subscription : ChannelSubscriptions) + { + pubnub_subscription_free(&Subscription.Value.Subscription); + } + for(auto Subscription : ChannelGroupSubscriptions) + { + pubnub_subscription_free(&Subscription.Value.Subscription); + } ChannelSubscriptions.Empty(); ChannelGroupSubscriptions.Empty(); @@ -2627,6 +2779,78 @@ void UPubnubSubsystem::GetMessageActions_priv(FString Channel, FOnGetMessageActi UPubnubUtilities::CallPubnubDelegate(OnGetMessageActionsResponse, Result, MessageActions); } +void UPubnubSubsystem::SubscribeWithSubscription(UPubnubSubscription* Subscription, FPubnubSubscriptionCursor Cursor, FOnSubscribeOperationResponseNative OnSubscribeResponse) +{ + PUBNUB_ENSURE_USER_ID_IS_SET(OnSubscribeResponse); + + //Save this delegate, so it can be called when Subscription Status is changed + SubscriptionResultDelegates.Add(OnSubscribeResponse); + + QuickActionThread->AddFunctionToQueue( [this, Subscription, Cursor, OnSubscribeResponse] + { + if(!UPubnubUtilities::EESubscribeWithSubscription(Subscription->CCoreSubscription, Cursor)) + { + PubnubError("[SubscribeWithSubscription]: Failed to subscribe with subscription.."); + UPubnubUtilities::CallPubnubDelegateWithInvalidArgumentResult(OnSubscribeResponse, "[Subscribe]: Failed to subscribe with Subscription."); + QuickActionThread->UnlockAfterSubscriptionOperationFinished(); + } + }); +} + +void UPubnubSubsystem::SubscribeWithSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet, FPubnubSubscriptionCursor Cursor, FOnSubscribeOperationResponseNative OnSubscribeResponse) +{ + PUBNUB_ENSURE_USER_ID_IS_SET(OnSubscribeResponse); + + //Save this delegate, so it can be called when Subscription Status is changed + SubscriptionResultDelegates.Add(OnSubscribeResponse); + + QuickActionThread->AddFunctionToQueue( [this, SubscriptionSet, Cursor, OnSubscribeResponse] + { + if(!UPubnubUtilities::EESubscribeWithSubscriptionSet(SubscriptionSet->CCoreSubscriptionSet, Cursor)) + { + PubnubError("[SubscribeWithSubscription]: Failed to subscribe with subscription.."); + UPubnubUtilities::CallPubnubDelegateWithInvalidArgumentResult(OnSubscribeResponse, "[Subscribe]: Failed to subscribe with Subscription."); + QuickActionThread->UnlockAfterSubscriptionOperationFinished(); + } + }); +} + +void UPubnubSubsystem::UnsubscribeWithSubscription(UPubnubSubscription* Subscription, FOnSubscribeOperationResponseNative OnUnsubscribeResponse) +{ + PUBNUB_ENSURE_USER_ID_IS_SET(OnUnsubscribeResponse); + + //Save this delegate, so it can be called when Subscription Status is changed + SubscriptionResultDelegates.Add(OnUnsubscribeResponse); + + QuickActionThread->AddFunctionToQueue( [this, Subscription, OnUnsubscribeResponse] + { + if(!UPubnubUtilities::EEUnsubscribeWithSubscription(&Subscription->CCoreSubscription)) + { + PubnubError("[SubscribeWithSubscription]: Failed to subscribe with subscription.."); + UPubnubUtilities::CallPubnubDelegateWithInvalidArgumentResult(OnUnsubscribeResponse, "[Subscribe]: Failed to subscribe with Subscription."); + QuickActionThread->UnlockAfterSubscriptionOperationFinished(); + } + }); +} + +void UPubnubSubsystem::UnsubscribeWithSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet, FOnSubscribeOperationResponseNative OnUnsubscribeResponse) +{ + PUBNUB_ENSURE_USER_ID_IS_SET(OnUnsubscribeResponse); + + //Save this delegate, so it can be called when Subscription Status is changed + SubscriptionResultDelegates.Add(OnUnsubscribeResponse); + + QuickActionThread->AddFunctionToQueue( [this, SubscriptionSet, OnUnsubscribeResponse] + { + if(!UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(&SubscriptionSet->CCoreSubscriptionSet)) + { + PubnubError("[SubscribeWithSubscription]: Failed to subscribe with subscription.."); + UPubnubUtilities::CallPubnubDelegateWithInvalidArgumentResult(OnUnsubscribeResponse, "[Subscribe]: Failed to subscribe with Subscription."); + QuickActionThread->UnlockAfterSubscriptionOperationFinished(); + } + }); +} + void UPubnubSubsystem::PublishUESettingsToPubnubPublishOptions(FPubnubPublishSettings &PublishSettings, pubnub_publish_options& PubnubPublishOptions) { PubnubPublishOptions.store = PublishSettings.StoreInHistory; @@ -2665,27 +2889,6 @@ void UPubnubSubsystem::FetchHistoryUESettingsToPbFetchHistoryOptions(FPubnubFetc FetchHistorySettings.End.IsEmpty() ? PubnubFetchHistoryOptions.end = NULL : nullptr; } -FPubnubMessageData UPubnubSubsystem::UEMessageFromPubnub(pubnub_v2_message PubnubMessage) -{ - FPubnubMessageData MessageData; - MessageData.Message = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.payload); - - //If message was just a string, we need to deserialize it - if(!UPubnubJsonUtilities::IsCorrectJsonString(MessageData.Message, false)) - { - MessageData.Message = UPubnubJsonUtilities::DeserializeString(MessageData.Message); - } - - MessageData.Channel = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.channel); - MessageData.UserID = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.publisher); - MessageData.Timetoken = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.tt); - MessageData.Metadata = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.metadata); - MessageData.MessageType = (EPubnubMessageType)(PubnubMessage.message_type); - MessageData.CustomMessageType = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.custom_message_type); - MessageData.MatchOrGroup = UPubnubUtilities::PubnubCharMemBlockToString(PubnubMessage.match_or_group); - return MessageData; -} - void UPubnubSubsystem::DecryptHistoryMessages(TArray& Messages) { //If crypto module is not set, we can't encrypt anything diff --git a/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h new file mode 100644 index 0000000..4bd5bd4 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h @@ -0,0 +1,58 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PubnubSubsystem.h" +#include "PubnubBaseEntity.generated.h" + + +class UPubnubSubscription; + +/** + * Base class for all PubNub entity types in Unreal Engine. + * + * This class serves as the foundation for all PubNub entities, providing common functionality + * such as subscription creation and entity identification. All PubNub entities inherit from + * this class and are designed to work seamlessly within the Unreal Engine ecosystem with + * full Blueprint support and UObject lifecycle management. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubBaseEntity : public UObject +{ + GENERATED_BODY() + + friend class UPubnubSubsystem; + + +public: + + /** + * Creates a subscription object for this entity. + * + * The returned subscription object is responsible for subscribing and unsubscribing + * to this entity, as well as adding and managing event listeners for receiving + * real-time messages, signals, and other PubNub events. + * + * @note The subscription object lives independently from this entity and requires + * separate lifecycle management. The subscription will remain valid and functional + * even if this entity goes out of scope or is destroyed. Make sure to properly + * manage the subscription's lifetime according to your application's needs. + * + * @param SubscribeSettings Optional settings for the subscription configuration. + * @return A new subscription object configured for this entity. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscribe") + UPubnubSubscription* CreateSubscription(FPubnubSubscribeSettings SubscribeSettings = FPubnubSubscribeSettings()); + + FString EntityID = ""; + EPubnubEntityType EntityType = EPubnubEntityType::PEnT_Channel; + +protected: + + UPROPERTY() + UPubnubSubsystem* PubnubSubsystem = nullptr; + + void InitEntity(UPubnubSubsystem* InPubnubSubsystem); + +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h new file mode 100644 index 0000000..4eb103f --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h @@ -0,0 +1,105 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * Represents a PubNub Channel entity in Unreal Engine. + * + * This class provides a complete interface for PubNub channel operations within Unreal Engine. + * It enables channel-specific operations such as publishing messages, sending signals, + * and managing presence information through Blueprint-compatible functions and delegates. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity +{ + GENERATED_BODY() + +public: + + UPubnubChannelEntity(); + + /** + * Publishes a message to this channel. + * + * @param Message The message to publish. This message can be any data type that can be serialized into JSON. + * @param OnPublishMessageResponse Optional delegate to listen for the publish result. + * @param PublishSettings Optional settings for the publish operation. See FPubnubPublishSettings for more details. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel", meta = (AutoCreateRefTerm = "OnPublishMessageResponse")) + void PublishMessage(FString Message, FOnPublishMessageResponse OnPublishMessageResponse, FPubnubPublishSettings PublishSettings = FPubnubPublishSettings()); + + /** + * Publishes a message to this channel. + * + * @param Message The message to publish. This message can be any data type that can be serialized into JSON. + * @param NativeCallback Optional delegate to listen for the publish result. Delegate in native form that can accept lambdas. + * Can be skipped if publish result is not needed. + * @param PublishSettings Optional settings for the publish operation. See FPubnubPublishSettings for more details. + */ + void PublishMessage(FString Message, FOnPublishMessageResponseNative NativeCallback = nullptr, FPubnubPublishSettings PublishSettings = FPubnubPublishSettings()); + + /** + * Publishes a message to this channel. Overload without delegate to get publish result. + * + * @param Message The message to publish. This message can be any data type that can be serialized into JSON. + * @param PublishSettings Optional settings for the publish operation. See FPubnubPublishSettings for more details. + */ + void PublishMessage(FString Message, FPubnubPublishSettings PublishSettings); + + /** + * Sends a signal to a this channel. + * + * @param Message The message to send as the signal. This message can be any data type that can be serialized into JSON. + * @param OnSignalResponse Optional delegate to listen for the signal result. + * @param SignalSettings Optional settings for the signal operation. See FPubnubSignalSettings for more details. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel", meta = (AutoCreateRefTerm = "OnSignalResponse")) + void Signal(FString Message, FOnSignalResponse OnSignalResponse, FPubnubSignalSettings SignalSettings = FPubnubSignalSettings()); + + /** + * Sends a signal to this channel. + * + * @param Message The message to send as the signal. This message can be any data type that can be serialized into JSON. + * @param NativeCallback Optional delegate to listen for the signal result. Delegate in native form that can accept lambdas. + * Can be skipped if signal result is not needed. + * @param SignalSettings Optional settings for the signal operation. See FPubnubSignalSettings for more details. + */ + void Signal(FString Message, FOnSignalResponseNative NativeCallback = nullptr, FPubnubSignalSettings SignalSettings = FPubnubSignalSettings()); + + /** + * Sends a signal to this channel. Overload without delegate to get signal result. + * + * @param Message The message to send as the signal. This message can be any data type that can be serialized into JSON. + * @param SignalSettings Optional settings for the signal operation. See FPubnubSignalSettings for more details. + */ + void Signal(FString Message, FPubnubSignalSettings SignalSettings); + + /** + * Lists the users currently present on this channel. + * + * @Note Requires the *Presence* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param ListUsersFromChannelResponse The callback function used to handle the result. + * @param ListUsersFromChannelSettings Optional settings for the list users operation. See FPubnubListUsersFromChannelSettings for more details. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Presence") + void ListUsersFromChannel(FOnListUsersFromChannelResponse ListUsersFromChannelResponse, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings = FPubnubListUsersFromChannelSettings()); + + /** + * Lists the users currently present on this channel. + * + * @Note Requires the *Presence* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param NativeCallback The callback function used to handle the result. Delegate in native form that can accept lambdas. + * @param ListUsersFromChannelSettings Optional settings for the list users operation. See FPubnubListUsersFromChannelSettings for more details. + */ + void ListUsersFromChannel(FOnListUsersFromChannelResponseNative NativeCallback, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings = FPubnubListUsersFromChannelSettings()); + +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h new file mode 100644 index 0000000..6f73c4f --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h @@ -0,0 +1,112 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelGroupEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * Represents a PubNub Channel Group entity in Unreal Engine. + * + * This class provides a complete interface for managing PubNub channel groups within + * Unreal Engine. It enables operations for adding/removing channels to/from groups, + * listing channels in a group, and managing channel group lifecycle through + * Blueprint-compatible functions and delegates. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubChannelGroupEntity : public UPubnubBaseEntity +{ + GENERATED_BODY() + +public: + + UPubnubChannelGroupEntity(); + + /** + * Adds a channel to this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param Channel The ID of the channel to add to the channel group. + * @param OnAddChannelToGroupResponse (Optional) Delegate to listen for the operation result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel Groups", meta = (AutoCreateRefTerm = "OnAddChannelToGroupResponse")) + void AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponse OnAddChannelToGroupResponse); + + /** + * Adds a channel to this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param Channel The ID of the channel to add to the channel group. + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + */ + void AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponseNative NativeCallback = nullptr); + + /** + * Removes a channel from this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param Channel The ID of the channel to remove from the channel group. + * @param OnRemoveChannelFromGroupResponse (Optional) Delegate to listen for the operation result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel Groups", meta = (AutoCreateRefTerm = "OnRemoveChannelFromGroupResponse")) + void RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponse OnRemoveChannelFromGroupResponse); + + /** + * Removes a channel from this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param Channel The ID of the channel to remove from the channel group. + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + */ + void RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponseNative NativeCallback = nullptr); + + /** + * Lists the channels that belong to this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param OnListChannelsResponse The callback function used to handle the result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel Groups") + void ListChannelsFromGroup(FOnListChannelsFromGroupResponse OnListChannelsResponse); + + /** + * Lists the channels that belong to this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param NativeCallback The callback function used to handle the result. Delegate in native form that can accept lambdas. + */ + void ListChannelsFromGroup(FOnListChannelsFromGroupResponseNative NativeCallback); + + /** + * Removes this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param OnRemoveChannelGroupResponse (Optional) Delegate to listen for the operation result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel Groups", meta = (AutoCreateRefTerm = "OnRemoveChannelGroupResponse")) + void RemoveChannelGroup(FOnRemoveChannelGroupResponse OnRemoveChannelGroupResponse); + + /** + * Removes this channel group. + * + * @Note Requires the *Stream Controller* add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + */ + void RemoveChannelGroup(FOnRemoveChannelGroupResponseNative NativeCallback = nullptr); + +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h new file mode 100644 index 0000000..f404949 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h @@ -0,0 +1,94 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelMetadataEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * Represents a PubNub Channel Metadata entity in Unreal Engine. + * + * This class provides a complete interface for managing PubNub App Context channel metadata + * within Unreal Engine. It enables operations for setting, retrieving, and removing metadata + * associated with specific channels through Blueprint-compatible functions and delegates. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubChannelMetadataEntity : public UPubnubBaseEntity +{ + GENERATED_BODY() + +public: + + UPubnubChannelMetadataEntity(); + + + /** + * Sets metadata for a specified Channel in the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param ChannelMetadata Channel Metadata object to set. + * @param OnSetChannelMetadataResponse (Optional) Delegate to listen for the operation result. + * @param Include (Optional) List of property names to include in the response. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context", meta = (AutoCreateRefTerm = "OnSetChannelMetadataResponse")) + void SetChannelMetadata(FPubnubChannelData ChannelMetadata, FOnSetChannelMetadataResponse OnSetChannelMetadataResponse, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Sets metadata for a specified Channel in the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param ChannelMetadata Channel Metadata object to set. + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + * @param Include (Optional) List of property names to include in the response. + */ + void SetChannelMetadata(FPubnubChannelData ChannelMetadata, FOnSetChannelMetadataResponseNative NativeCallback = nullptr, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Retrieves metadata for a specified Channel from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param OnGetChannelMetadataResponse The callback function used to handle the result. + * @param Include (Optional) List of property names to include in the response. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context") + void GetChannelMetadata(FOnGetChannelMetadataResponse OnGetChannelMetadataResponse, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Retrieves metadata for a specified Channel from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param NativeCallback The callback function used to handle the result. Delegate in native form that can accept lambdas. + * @param Include (Optional) List of property names to include in the response. + */ + void GetChannelMetadata(FOnGetChannelMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Removes all metadata associated with a specified Channel from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param OnRemoveChannelMetadataResponse (Optional) Delegate to listen for the operation result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context", meta = (AutoCreateRefTerm = "OnRemoveChannelMetadataResponse")) + void RemoveChannelMetadata(FOnRemoveChannelMetadataResponse OnRemoveChannelMetadataResponse); + + /** + * Removes all metadata associated with a specified Channel from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + */ + void RemoveChannelMetadata(FOnRemoveChannelMetadataResponseNative NativeCallback = nullptr); + +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h new file mode 100644 index 0000000..040c119 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -0,0 +1,321 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PubnubSubsystem.h" +#include "PubnubStructLibrary.h" +#include "PubnubSubscription.generated.h" + +// Blueprint-compatible delegate for handling published messages +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubMessage, FPubnubMessageData, Message); +// Native C++ delegate for handling published messages (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageNative, const FPubnubMessageData& Message); +// Blueprint-compatible delegate for handling signals +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubSignal, FPubnubMessageData, Message); +// Native C++ delegate for handling signals (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubSignalNative, const FPubnubMessageData& Message); +// Blueprint-compatible delegate for handling presence events (join/leave/timeout) +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubPresenceEvent, FPubnubMessageData, Message); +// Native C++ delegate for handling presence events (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubPresenceEventNative, const FPubnubMessageData& Message); +// Blueprint-compatible delegate for handling App Context object events (user/channel metadata changes) +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubObjectEvent, FPubnubMessageData, Message); +// Native C++ delegate for handling App Context object events (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubObjectEventNative, const FPubnubMessageData& Message); +// Blueprint-compatible delegate for handling message action events (add/remove reactions) +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageAction, FPubnubMessageData, Message); +// Native C++ delegate for handling message action events (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageActionNative, const FPubnubMessageData& Message); +// Blueprint-compatible delegate that fires for any type of PubNub message/event +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubAnyMessageType, FPubnubMessageData, Message); +// Native C++ delegate that fires for any type of PubNub message/event (can accept lambdas) +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubAnyMessageTypeNative, const FPubnubMessageData& Message); + + +/** + * Base class for PubNub subscriptions in Unreal Engine. + * + * This abstract class provides the foundation for all PubNub subscription types, + * containing event delegates for handling different types of real-time messages + * and events from PubNub channels. It manages the lifecycle of subscriptions + * and provides both Blueprint-compatible and native C++ event handling. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject +{ + GENERATED_BODY() + + friend class UPubnubBaseEntity; + +public: + + /** Event fired when a regular message is received on subscribed channels/channel groups. */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubMessage OnPubnubMessage; + /** Native C++ version of OnPubnubMessage that can accept lambda functions. */ + FOnPubnubMessageNative OnPubnubMessageNative; + + /** Event fired when a signal is received on subscribed channels/channel groups. */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubSignal OnPubnubSignal; + /** Native C++ version of OnPubnubSignal that can accept lambda functions. */ + FOnPubnubSignalNative OnPubnubSignalNative; + + /** Event fired when a presence event occurs (user join/leave/timeout) on subscribed channels. */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubPresenceEvent OnPubnubPresenceEvent; + /** Native C++ version of OnPubnubPresenceEvent that can accept lambda functions. */ + FOnPubnubPresenceEventNative OnPubnubPresenceEventNative; + + /** Event fired when App Context object events occur (user/channel metadata changes). */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubObjectEvent OnPubnubObjectEvent; + /** Native C++ version of OnPubnubObjectEvent that can accept lambda functions. */ + FOnPubnubObjectEventNative OnPubnubObjectEventNative; + + /** Event fired when message action events occur (reactions added/removed). */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubMessageAction OnPubnubMessageAction; + /** Native C++ version of OnPubnubMessageAction that can accept lambda functions. */ + FOnPubnubMessageActionNative OnPubnubMessageActionNative; + + /** Universal event that fires for any type of PubNub message or event received. */ + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubAnyMessageType FOnPubnubAnyMessageType; + /** Native C++ version of FOnPubnubAnyMessageType that can accept lambda functions. */ + FOnPubnubAnyMessageTypeNative FOnPubnubAnyMessageTypeNative; + + /** + * Called when this subscription object is being destroyed. + * + * Ensures proper cleanup of subscription resources and unregisters + * from PubNub services to prevent memory leaks and dangling references. + */ + virtual void BeginDestroy() override; + +protected: + + UPROPERTY() + UPubnubSubsystem* PubnubSubsystem = nullptr; + + bool IsInitialized = false; + virtual void CleanUpSubscription(){}; + +}; + + +/** + * Represents a subscription to a single PubNub entity (channel or channel group). + * + * This class manages subscription state for a specific PubNub entity and provides + * methods for subscribing, unsubscribing, and combining with other subscriptions. + * It handles real-time message delivery and event notifications from the subscribed + * entity through inherited event delegates. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase +{ + GENERATED_BODY() + + friend class UPubnubSubsystem; + friend class UPubnubBaseEntity; + friend class UPubnubSubscriptionSet; + +public: + + /** + * Subscribes to the entity associated with this subscription. + * + * Initiates real-time message delivery from the subscribed entity. + * Events will be delivered through the inherited event delegates. + * + * @param OnSubscribeResponse Callback function to handle the subscription result. + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnSubscribeResponse")) + virtual void Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + + /** + * Subscribes to the entity associated with this subscription (native version). + * + * @param NativeCallback Optional native callback that can accept lambda functions. + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + void Subscribe(FOnSubscribeOperationResponseNative NativeCallback = nullptr, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + + /** + * Subscribes to the entity associated with this subscription without a callback. + * + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + void Subscribe(FPubnubSubscriptionCursor Cursor); + + /** + * Unsubscribes from the entity associated with this subscription. + * + * Stops real-time message delivery from the subscribed entity. + * The subscription object remains valid and can be reused for future subscriptions. + * + * @param OnUnsubscribeResponse Callback function to handle the unsubscription result. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnUnsubscribeResponse")) + void Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse); + + /** + * Unsubscribes from the entity associated with this subscription (native version). + * + * @param NativeCallback Optional native callback that can accept lambda functions. + */ + void Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback); + + /** + * Combines this subscription with another subscription to create a subscription set. + * + * Creates a new subscription set containing both this subscription and the provided + * subscription, allowing you to manage multiple subscriptions as a single unit. + * + * @param Subscription Another subscription to combine with this one. + * @return A new subscription set containing both subscriptions. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + UPubnubSubscriptionSet* AddSubscription(UPubnubSubscription* Subscription); + +private: + + pubnub_subscription_t* CCoreSubscription = nullptr; + + void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings); + void InitWithCCoreSubscription(UPubnubSubsystem* InPubnubSubsystem, pubnub_subscription_t* InCCoreSubscription); + void InternalInit(); + + UFUNCTION() + virtual void CleanUpSubscription() override; +}; + + +/** + * Represents a collection of PubNub subscriptions that can be managed as a single unit. + * + * This class allows you to group multiple individual subscriptions together and perform + * operations like subscribe/unsubscribe on all of them simultaneously. It provides + * efficient management of multiple entity subscriptions and inherits all event handling + * capabilities from the base subscription class. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase +{ + GENERATED_BODY() + + friend class UPubnubSubsystem; + friend class UPubnubSubscription; + +public: + + /** + * Subscribes to all entities in this subscription set. + * + * Initiates real-time message delivery from all subscriptions in this set. + * Events will be delivered through the inherited event delegates. + * + * @param OnSubscribeResponse Callback function to handle the subscription result. + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnSubscribeResponse")) + virtual void Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + + /** + * Subscribes to all entities in this subscription set (native version). + * + * @param NativeCallback Optional native callback that can accept lambda functions. + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + void Subscribe(FOnSubscribeOperationResponseNative NativeCallback = nullptr, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + + /** + * Subscribes to all entities in this subscription set without a callback. + * + * @param Cursor Optional cursor to resume subscription from a specific point in time. + */ + void Subscribe(FPubnubSubscriptionCursor Cursor); + + /** + * Unsubscribes from all entities in this subscription set. + * + * Stops real-time message delivery from all subscriptions in this set. + * The subscription set remains valid and can be reused for future subscriptions. + * + * @param OnUnsubscribeResponse Callback function to handle the unsubscription result. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnUnsubscribeResponse")) + void Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse); + + /** + * Unsubscribes from all entities in this subscription set (native version). + * + * @param NativeCallback Optional native callback that can accept lambda functions. + */ + void Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback); + + /** + * Adds a subscription to this subscription set. + * + * If this subscription set is currently subscribed, the added subscription + * will automatically be subscribed as well. + * + * @param Subscription The subscription to add to this set. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void AddSubscription(UPubnubSubscription* Subscription); + + /** + * Removes a subscription from this subscription set. + * + * The removed subscription will be unsubscribed if the set is currently subscribed. + * + * @param Subscription The subscription to remove from this set. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void RemoveSubscription(UPubnubSubscription* Subscription); + + /** + * Merges another subscription set into this one. + * + * All subscriptions from the other set will be added to this set. + * + * @param SubscriptionSet The subscription set to merge into this one. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void AddSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet); + + /** + * Removes all subscriptions that are also present in another subscription set. + * + * @param SubscriptionSet The subscription set containing subscriptions to remove. + */ + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void RemoveSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet); + + /** + * Gets all individual subscriptions contained in this subscription set. + * + * @return Array of all subscriptions in this set. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Pubnub|SubscriptionSet") + TArray GetSubscriptions() { return Subscriptions;}; + +private: + + UPROPERTY() + TArray Subscriptions; + + + pubnub_subscription_set_t* CCoreSubscriptionSet = nullptr; + + void InitSubscriptionSet(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings); + void InitWithSubscriptions(UPubnubSubsystem* InPubnubSubsystem, UPubnubSubscription* Subscription1, UPubnubSubscription* Subscription2); + void InitWithCCoreSubscriptionSet(UPubnubSubsystem* InPubnubSubsystem, pubnub_subscription_set_t* InCCoreSubscriptionSet); + void InternalInit(); + UFUNCTION() + virtual void CleanUpSubscription() override; +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h new file mode 100644 index 0000000..a10cd85 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h @@ -0,0 +1,95 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubUserMetadataEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * Represents a PubNub User Metadata entity in Unreal Engine. + * + * This class provides a complete interface for managing PubNub App Context user metadata + * within Unreal Engine. It enables operations for setting, retrieving, and removing metadata + * associated with specific users through Blueprint-compatible functions and delegates. + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubUserMetadataEntity : public UPubnubBaseEntity +{ + GENERATED_BODY() + +public: + + UPubnubUserMetadataEntity(); + + + /** + * Sets metadata for this User in the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param UserMetadata User Metadata object to set. + * @param OnSetUserMetadataResponse (Optional) Delegate to listen for the operation result. + * @param Include (Optional) List of property names to include in the response. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context", meta = (AutoCreateRefTerm = "OnSetUserMetadataResponse")) + void SetUserMetadata(FPubnubUserData UserMetadata, FOnSetUserMetadataResponse OnSetUserMetadataResponse, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Sets metadata for this User in the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param UserMetadata User Metadata object to set. + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + * @param Include (Optional) List of property names to include in the response. + */ + void SetUserMetadata(FPubnubUserData UserMetadata, FOnSetUserMetadataResponseNative NativeCallback = nullptr, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Retrieves metadata for this User from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param OnGetUserMetadataResponse The callback function used to handle the result. + * @param Include (Optional) List of property names to include in the response. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context") + void GetUserMetadata(FOnGetUserMetadataResponse OnGetUserMetadataResponse, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + /** + * Retrieves metadata for this User from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param NativeCallback The callback function used to handle the result. Delegate in native form that can accept lambdas. + * @param Include (Optional) List of property names to include in the response. + */ + void GetUserMetadata(FOnGetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + + + /** + * Removes all metadata associated with this User from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param OnRemoveUserMetadataResponse (Optional) Delegate to listen for the operation result. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|App Context", meta = (AutoCreateRefTerm = "OnRemoveUserMetadataResponse")) + void RemoveUserMetadata(FOnRemoveUserMetadataResponse OnRemoveUserMetadataResponse); + + /** + * Removes all metadata associated with this User from the PubNub App Context. + * + * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal + * + * @param NativeCallback (Optional) Delegate to listen for the operation result. Delegate in native form that can accept lambdas. + * Can be skipped if operation result is not needed. + */ + void RemoveUserMetadata(FOnRemoveUserMetadataResponseNative NativeCallback = nullptr); + +}; diff --git a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h index b2c04fc..9109c7c 100644 --- a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h +++ b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h @@ -6,11 +6,13 @@ #include "CoreMinimal.h" #include "Async/Async.h" #include "PubnubStructLibrary.h" +#include "PubnubEnumLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "PubnubUtilities.generated.h" class UPubnubSettings; +class UPubnubSubscription; /** This struct is an utility for more convenient converting FString into const char* while keeping char memory alive * Will not break UTF8 characters. @@ -31,6 +33,7 @@ struct FUTF8StringHolder } }; + /** * */ @@ -65,6 +68,8 @@ class PUBNUBLIBRARY_API UPubnubUtilities : public UBlueprintFunctionLibrary //This is to remove class name and "_priv" from __FUNCTION__ macro output static FString GetNameFromFunctionMacro(FString FunctionName); + static FPubnubMessageData UEMessageFromPubnubMessage(pubnub_v2_message PubnubMessage); + /* CONVERTING INCLUDES */ static FString MembershipIncludeToString(const FPubnubMembershipInclude& MembershipInclude); @@ -83,12 +88,24 @@ class PUBNUBLIBRARY_API UPubnubUtilities : public UBlueprintFunctionLibrary /* C-CORE EVENT ENGINE HELPERS */ - static pubnub_subscription_t* EEGetSubscriptionForChannel(pubnub_t* Context, FString Channel, FPubnubSubscribeSettings Options); - static pubnub_subscription_t* EEGetSubscriptionForChannelGroup(pubnub_t* Context, FString ChannelGroup, FPubnubSubscribeSettings Options); + static pubnub_subscription_t* EEGetSubscriptionForEntity(pubnub_t* Context, FString EntityID, EPubnubEntityType EntityType, FPubnubSubscribeSettings Options); + static pubnub_subscription_set_t* EEGetSubscriptionSetForEntities(pubnub_t* Context, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings Options); static bool EEAddListenerAndSubscribe(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem); static bool EERemoveListenerAndUnsubscribe(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem); + static bool EESubscribeWithSubscription(pubnub_subscription_t* Subscription, FPubnubSubscriptionCursor Cursor); + static bool EEUnsubscribeWithSubscription(pubnub_subscription_t** SubscriptionPtr); + static bool EESubscribeWithSubscriptionSet(pubnub_subscription_set_t* SubscriptionSet, FPubnubSubscriptionCursor Cursor); + static bool EEUnsubscribeWithSubscriptionSet(pubnub_subscription_set_t** SubscriptionSetPtr); + static bool EEAddSubscriptionListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EEAddSubscriptionListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller); + static bool EERemoveSubscriptionListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EERemoveSubscriptionListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller); + static bool EEAddSubscriptionSetListenerOfType(pubnub_subscription_set_t* SubscriptionSet, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EEAddSubscriptionSetListenersOfAllTypes(pubnub_subscription_set_t* SubscriptionSet, pubnub_subscribe_message_callback_t Callback, UObject* Caller); + static bool EERemoveSubscriptionSetListenerOfType(pubnub_subscription_set_t** SubscriptionSetPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EERemoveSubscriptionSetListenersOfAllTypes(pubnub_subscription_set_t** SubscriptionSetPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller); - + /* TEMPLATES TO CALL PUBNUB DELEGATES */ //Template to call any Delegate in case of providing incorrect parameters. Provide error message and FPubnubOperationResult will be made out of it diff --git a/Source/PubnubLibrary/Public/PubnubEnumLibrary.h b/Source/PubnubLibrary/Public/PubnubEnumLibrary.h index 3d74603..c7ea893 100644 --- a/Source/PubnubLibrary/Public/PubnubEnumLibrary.h +++ b/Source/PubnubLibrary/Public/PubnubEnumLibrary.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "Misc/EnumRange.h" #include "PubnubEnumLibrary.generated.h" UENUM(BlueprintType) @@ -42,10 +43,26 @@ enum class EPubnubMessageType : uint8 PMT_Action, /* Message about Objects */ PMT_Objects, - /* Message about Files */ + /* Message about Files - Files are not supported yet*/ PMT_Files, }; +UENUM(BlueprintType) +enum class EPubnubListenerType : uint8 +{ + PLT_Message UMETA(DisplayName="Message"), + PLT_Signal UMETA(DisplayName="Signal"), + PLT_MessageAction UMETA(DisplayName="MessageAction"), + PLT_Objects UMETA(DisplayName="Objects"), + /* Files are not supported yet */ + PLT_Files UMETA(DisplayName="Files"), + PLT_All UMETA(DisplayName="All"), + + Count +}; +//Skip All in count (and files until not supported) +ENUM_RANGE_BY_FIRST_AND_LAST(EPubnubListenerType, EPubnubListenerType::PLT_Message, EPubnubListenerType::PLT_Objects); + UENUM(BlueprintType) enum class EPubnubMembershipSortType : uint8 { @@ -90,4 +107,14 @@ enum class EPubnubSubscriptionStatus : uint8 PSS_DisconnectedUnexpectedly UMETA(DisplayName="DisconnectedUnexpectedly"), PSS_Disconnected UMETA(DisplayName="Disconnected"), PSS_SubscriptionChanged UMETA(DisplayName="SubscriptionChanged") +}; + + +UENUM(BlueprintType) +enum class EPubnubEntityType : uint8 +{ + PEnT_Channel UMETA(DisplayName="Channel"), + PEnT_ChannelGroup UMETA(DisplayName="ChannelGroup"), + PEnT_ChannelMetadata UMETA(DisplayName="ChannelMetadata"), + PEnT_UserMetadata UMETA(DisplayName="UserMetadata"), }; \ No newline at end of file diff --git a/Source/PubnubLibrary/Public/PubnubStructLibrary.h b/Source/PubnubLibrary/Public/PubnubStructLibrary.h index 374922a..72f0f28 100644 --- a/Source/PubnubLibrary/Public/PubnubStructLibrary.h +++ b/Source/PubnubLibrary/Public/PubnubStructLibrary.h @@ -757,4 +757,15 @@ struct FPubnubEncryptedData UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Pubnub") FString EncryptedData = ""; UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Pubnub") FString Metadata = ""; +}; + +USTRUCT(BlueprintType) +struct FPubnubSubscriptionCursor +{ + GENERATED_BODY() + + /**Time from which messages should be retrieved */ + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Pubnub") FString Timetoken = ""; + /**Region of the messages */ + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Pubnub") int Region = 0; }; \ No newline at end of file diff --git a/Source/PubnubLibrary/Public/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index 4a91616..7989fe6 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -20,6 +20,12 @@ class UPubnubSettings; class FPubnubFunctionThread; class UPubnubChatSystem; class UPubnubAesCryptor; +class UPubnubBaseEntity; +class UPubnubChannelEntity; +class UPubnubChannelGroupEntity; +class UPubnubChannelMetadataEntity; +class UPubnubUserMetadataEntity; +class UPubnubSubscriptionSet; struct CCoreSubscriptionData { @@ -27,14 +33,15 @@ struct CCoreSubscriptionData pubnub_subscription_t* Subscription; }; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPubnubSubsystemDeinitialized); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMessageReceived, FPubnubMessageData, Message); DECLARE_MULTICAST_DELEGATE_OneParam(FOnMessageReceivedNative, const FPubnubMessageData& Message); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnPubnubError, FString, ErrorMessage, EPubnubErrorType, ErrorType); DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPubnubErrorNative, FString ErrorMessage, EPubnubErrorType ErrorType); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSubscriptionStatusChanged, EPubnubSubscriptionStatus, Status, FPubnubSubscriptionStatusData, StatusData); DECLARE_MULTICAST_DELEGATE_TwoParams(FOnSubscriptionStatusChangedNative, EPubnubSubscriptionStatus Status, const FPubnubSubscriptionStatusData& StatusData); -DECLARE_DYNAMIC_DELEGATE_OneParam(FOnPubnubResponse, FString, JsonResponse); -DECLARE_DELEGATE_OneParam(FOnPubnubResponseNative, FString JsonResponse); DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnPublishMessageResponse, FPubnubOperationResult, Result, FPubnubMessageData, PublishedMessage); DECLARE_DELEGATE_TwoParams(FOnPublishMessageResponseNative, const FPubnubOperationResult& Result, const FPubnubMessageData& PublishedMessage); @@ -96,7 +103,6 @@ DECLARE_DYNAMIC_DELEGATE_FourParams(FOnSetChannelMembersResponse, FPubnubOperati DECLARE_DELEGATE_FourParams(FOnSetChannelMembersResponseNative, const FPubnubOperationResult& Result, const TArray& MembersData, FString PageNext, FString PagePrev); DECLARE_DYNAMIC_DELEGATE_FourParams(FOnRemoveChannelMembersResponse, FPubnubOperationResult, Result, const TArray&, MembersData, FString, PageNext, FString, PagePrev); DECLARE_DELEGATE_FourParams(FOnRemoveChannelMembersResponseNative, const FPubnubOperationResult& Result, const TArray& MembersData, FString PageNext, FString PagePrev); - DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnGetMessageActionsResponse, FPubnubOperationResult, Result, const TArray&, MessageActions); DECLARE_DELEGATE_TwoParams(FOnGetMessageActionsResponseNative, const FPubnubOperationResult& Result, const TArray& MessageActions); DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnAddMessageActionResponse, FPubnubOperationResult, Result, FPubnubMessageActionData, MessageActionData); @@ -109,13 +115,20 @@ UCLASS() class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() + + friend class UPubnubSubscription; + friend class UPubnubSubscriptionSet; public: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; - /**Global listener for all messages received on subscribed channels */ + /**Delegate that is called after PubnubSubsystem is deinitialized*/ + UPROPERTY(BlueprintAssignable, Category = "Pubnub|Delegates") + FOnPubnubSubsystemDeinitialized OnPubnubSubsystemDeinitialized; + + /**Global listener for all messages received on subscribed channels*/ UPROPERTY(BlueprintAssignable, Category = "Pubnub|Delegates") FOnMessageReceived OnMessageReceived; @@ -349,6 +362,7 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem /** * Unsubscribes from all subscribed channels and groups - basically stop listening for any messages. + * NOTE:: This also unsubscribes all subscribed Subscription and SubscriptionSet Objects. * * @param OnUnsubscribeFromAllResponse Optional delegate to listen for the unsubscribe result. */ @@ -857,6 +871,7 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem * @param Include (Optional) List of property names to include in the response. */ void SetUserMetadata(FString User, FPubnubUserData UserMetadata, FOnSetUserMetadataResponseNative NativeCallback = nullptr, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); + /** * Retrieves metadata for a specified User from the PubNub App Context. @@ -903,8 +918,7 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem * @param Include (Optional) List of property names to include in the response. */ void GetUserMetadata(FString User, FOnGetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude()); - - + /** * Removes all metadata associated with a specified User from the PubNub App Context. @@ -933,7 +947,8 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem * (Generally the same as GetAllChannelMetadata just using raw strings as Include and Sort inputs) * * @Note Requires the *App Context* add-on to be enabled for your key in the PubNub Admin Portal - * + * + * @param OnGetAllChannelMetadataResponse The callback function used to handle the result. * @param Include (Optional) A comma-separated list of property names to include in the response. * @param Limit (Optional) The maximum number of results to return (default: 100). * @param Filter (Optional) Expression used to filter the results. Check online documentation to see exact filter formulas; @@ -1681,10 +1696,125 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem #pragma endregion +#pragma region ENTITIES + + /** + * Creates a PubNub Channel entity for the specified channel name. + * + * The returned channel entity provides access to channel-specific operations + * such as publishing messages, sending signals, and managing presence information. + * It also allows creating subscriptions to receive real-time updates from the channel. + * + * @param Channel The name of the channel to create an entity for. + * @return A new channel entity configured for the specified channel. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelEntity* CreateChannelEntity(FString Channel); + + /** + * Creates a PubNub Channel Group entity for the specified channel group name. + * + * The returned channel group entity provides access to channel group-specific operations + * such as adding/removing channels, listing channels in the group, and managing the + * group lifecycle. It also allows creating subscriptions to receive real-time updates + * from all channels in the group. + * + * @param ChannelGroup The name of the channel group to create an entity for. + * @return A new channel group entity configured for the specified channel group. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelGroupEntity* CreateChannelGroupEntity(FString ChannelGroup); + + /** + * Creates a PubNub Channel Metadata entity for the specified channel. + * + * The returned channel metadata entity provides access to App Context operations + * for managing metadata associated with the specified channel. This includes + * setting, retrieving, and removing channel metadata information. + * + * @note Requires the App Context add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param Channel The name of the channel to create a metadata entity for. + * @return A new channel metadata entity configured for the specified channel. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelMetadataEntity* CreateChannelMetadataEntity(FString Channel); + + /** + * Creates a PubNub User Metadata entity for the specified user. + * + * The returned user metadata entity provides access to App Context operations + * for managing metadata associated with the specified user. This includes + * setting, retrieving, and removing user metadata information. + * + * @note Requires the App Context add-on to be enabled for your key in the PubNub Admin Portal. + * + * @param User The user identifier to create a metadata entity for. + * @return A new user metadata entity configured for the specified user. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubUserMetadataEntity* CreateUserMetadataEntity(FString User); + + /** + * Creates a subscription set for multiple channels and channel groups. + * + * The returned subscription set allows you to manage subscriptions to multiple + * entities as a single unit, enabling efficient subscribe/unsubscribe operations + * across all specified channels and channel groups simultaneously. + * + * @note At least one Channel or ChannelGroup is needed to create SubscriptionSet. + * + * @param Channels Array of channel names to include in the subscription set. + * @param ChannelGroups Array of channel group names to include in the subscription set. + * @param SubscriptionSettings Optional settings to configure the subscription behavior. + * @return A new subscription set configured for the specified channels and channel groups. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + UPubnubSubscriptionSet* CreateSubscriptionSet(TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); + + /** + * Creates a subscription set from an array of existing PubNub entities. + * + * The returned subscription set allows you to manage subscriptions to multiple + * entities as a single unit. This is useful when you already have entity objects + * and want to group their subscriptions together. + * + * @param Entities Array of PubNub entity objects to include in the subscription set. + * @param SubscriptionSettings Optional settings to configure the subscription behavior. + * @return A new subscription set configured for the specified entities. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + UPubnubSubscriptionSet* CreateSubscriptionSetFromEntities(TArray Entities, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); + + /** + * Gets all currently active individual subscriptions. + * + * Returns an array of all subscription objects that are currently active + * and receiving real-time updates from PubNub. This includes subscriptions + * created directly from entities, but excludes those managed by subscription sets. + * + * @return Array of all active individual subscriptions. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + TArray GetActiveSubscriptions(); + + /** + * Gets all currently active subscription sets. + * + * Returns an array of all subscription set objects that are currently active + * and managing multiple subscriptions as a single unit. Each subscription set + * may contain multiple individual subscriptions. + * + * @return Array of all active subscription sets. + */ + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + TArray GetActiveSubscriptionSets(); + +#pragma endregion private: - //Thread for quick operations, generally everything except subscribe + //Thread for all PubNub operations, this thread will queue all PubNub calls and trigger them one by one FPubnubFunctionThread* QuickActionThread = nullptr; //Pubnub context for the most of the pubnub operations @@ -1695,7 +1825,8 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem //Auth token has to be kept alive for the lifetime of the sdk, so this is the container for it char* AuthTokenBuffer = nullptr; size_t AuthTokenLength = 0; - + + //Storage for global subscriptions (not from Entities) TMap ChannelSubscriptions; TMap ChannelGroupSubscriptions; @@ -1716,8 +1847,6 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem private: //Error when the response was not OK void PubnubResponseError(pubnub_res PubnubResponse, FString ErrorMessage); - //Error during publishing a message - void PubnubPublishError(); #pragma endregion @@ -1804,6 +1933,11 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem void RemoveMessageAction_priv(FString Channel, FString MessageTimetoken, FString ActionTimetoken, FOnRemoveMessageActionResponseNative OnRemoveMessageActionResponse); void GetMessageActions_priv(FString Channel, FOnGetMessageActionsResponseNative OnGetMessageActionsResponse, FString Start, FString End, int Limit); + + void SubscribeWithSubscription(UPubnubSubscription* Subscription, FPubnubSubscriptionCursor Cursor, FOnSubscribeOperationResponseNative OnSubscribeResponse); + void SubscribeWithSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet, FPubnubSubscriptionCursor Cursor, FOnSubscribeOperationResponseNative OnSubscribeResponse); + void UnsubscribeWithSubscription(UPubnubSubscription* Subscription, FOnSubscribeOperationResponseNative OnUnsubscribeResponse); + void UnsubscribeWithSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet, FOnSubscribeOperationResponseNative OnUnsubscribeResponse); #pragma endregion @@ -1813,7 +1947,6 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem void HereNowUESettingsToPubnubHereNowOptions(FPubnubListUsersFromChannelSettings &HereNowSettings, pubnub_here_now_options &PubnubHereNowOptions); void SetStateUESettingsToPubnubSetStateOptions(FPubnubSetStateSettings &SetStateSettings, pubnub_set_state_options &PubnubSetStateOptions); void FetchHistoryUESettingsToPbFetchHistoryOptions(FPubnubFetchHistorySettings &FetchHistorySettings, pubnub_fetch_history_options &PubnubFetchHistoryOptions); - static FPubnubMessageData UEMessageFromPubnub(pubnub_v2_message PubnubMessage); void DecryptHistoryMessages(TArray& Messages); diff --git a/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelEntity.cpp b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelEntity.cpp new file mode 100644 index 0000000..7ab49a8 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelEntity.cpp @@ -0,0 +1,607 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Entities/Sample_ChannelEntity.h" +// snippet.includes +#include "Kismet/GameplayStatics.h" +#include "Engine/GameInstance.h" + +// snippet.end + +/** + * NOTE: Each sample is designed to be fully self-contained and portable. + * You can copy-paste any individual sample into a new project, and it should compile and run without errors + * — as long as you also include the necessary `#include` statements. + * + * To ensure independence, each sample retrieves the PubnubSubsystem and explicitly calls `SetUserID()` + * before performing any PubNub operations. + * + * In a real project, however, you only need to call `SetUserID()` once — typically during initialization + * (e.g., in GameInstance or at login) before making your first PubNub request. + * + * The samples assume that in Pubnub SDK settings sections in ProjectSettings following fields are set: + * PublishKey and SubscribeKey have correct keys, InitializeAutomatically is true. + * + * ENTITY SAMPLES demonstrate the new Entity-based approach to PubNub operations, which provides + * a more object-oriented and intuitive way to work with PubNub channels, channel groups, and metadata. + */ + +// NOTE: Comments marked with `ACTION REQUIRED` indicate lines you must change. + + +ASample_ChannelEntity::ASample_ChannelEntity() +{ + SamplesName = "ChannelEntity"; +} + +void ASample_ChannelEntity::RunSamples() +{ + Super::RunSamples(); + + CreateChannelEntitySample(); + PublishSimpleSample(); + PublishWithSettingsSample(); + PublishWithResultSample(); + PublishWithResultLambdaSample(); + SimpleSignalSample(); + SignalWithSettingsSample(); + SignalWithResultSample(); + SignalWithResultLambdaSample(); + ListUsersFromChannelSample(); + ListUsersFromChannelWithSettingsSample(); + ListUsersFromChannelWithLambdaSample(); + SubscribeWithChannelEntitySample(); + ChannelEntityAllListenersSample(); + ChannelEntityPublishMessageSample(); +} + + +/* CHANNEL ENTITY SAMPLE FUNCTIONS */ + + +// snippet.create_channel_entity +void ASample_ChannelEntity::CreateChannelEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("game_lobby"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); +} + +// snippet.publish_simple_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::PublishSimpleSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Publish simple text message using the channel entity + FString SimpleMessage = TEXT("Ready to start the mission!"); + ChannelEntity->PublishMessage(SimpleMessage); +} + +// snippet.publish_with_settings_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::PublishWithSettingsSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Set Message to Publish + FString JsonMessage = R"({"event": "PowerUpUsed", "powerup": "Invisibility Cloak", "duration": 10})"; + + // Create additional PublishSettings + FPubnubPublishSettings PublishSettings; + PublishSettings.MetaData = R"({"map": "DesertStrike", "match_id": "MATCH-42"})"; + PublishSettings.CustomMessageType = "game-event"; + + // Publish message with settings using the channel entity + ChannelEntity->PublishMessage(JsonMessage, PublishSettings); +} + +// snippet.publish_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::PublishWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind PublishedMessageResponse to be fired with PublishMessage result + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + FOnPublishMessageResponse OnPublishMessageResponse; + OnPublishMessageResponse.BindDynamic(this, &ASample_ChannelEntity::PublishedMessageResponse); + + // Publish simple text message using the channel entity + FString SimpleMessage = TEXT("Ready to start the mission!"); + ChannelEntity->PublishMessage(SimpleMessage, OnPublishMessageResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::PublishedMessageResponse(FPubnubOperationResult Result, FPubnubMessageData Message) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to publish message. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Message published successfully. Published message timetoken: %s"), *Message.Timetoken); + } +} + +// snippet.publish_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::PublishWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind lambda function to PublishMessageResponse delegate + FOnPublishMessageResponseNative OnPublishMessageResponse; + OnPublishMessageResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubMessageData& Message) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to publish message. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Message published successfully. Published message timetoken: %s"), *Message.Timetoken); + } + }); + + // Publish simple text message using the channel entity + FString SimpleMessage = TEXT("Ready to start the mission!"); + ChannelEntity->PublishMessage(SimpleMessage, OnPublishMessageResponse); +} + +// snippet.simple_signal_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SimpleSignalSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Send signal message using the channel entity + FString SimpleMessage = TEXT("Ready to start the mission!"); + ChannelEntity->Signal(SimpleMessage); +} + +// snippet.signal_with_settings_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SignalWithSettingsSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("guild_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Set Message to Signal + FString Message = TEXT("PlayerTyping"); + + // Create additional SignalSettings + FPubnubSignalSettings SignalSettings; + SignalSettings.CustomMessageType = "typing-indicator"; + + // Send signal with settings using the channel entity + ChannelEntity->Signal(Message, SignalSettings); +} + +// snippet.signal_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SignalWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind SignalMessageResponse to be fired with Signal result + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + FOnSignalResponse OnSignalResponse; + OnSignalResponse.BindDynamic(this, &ASample_ChannelEntity::SignalMessageResponse); + + // Send signal message using the channel entity + FString SimpleMessage = TEXT("Player is aiming"); + ChannelEntity->Signal(SimpleMessage, OnSignalResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SignalMessageResponse(FPubnubOperationResult Result, FPubnubMessageData Message) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to send signal. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Signal sent successfully. Signal timetoken: %s"), *Message.Timetoken); + } +} + +// snippet.signal_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SignalWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("global_chat"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind lambda function to SignalResponse delegate + FOnSignalResponseNative OnSignalResponse; + OnSignalResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubMessageData& Message) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to send signal. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Signal sent successfully. Signal timetoken: %s"), *Message.Timetoken); + } + }); + + // Send signal message using the channel entity + FString SimpleMessage = TEXT("Player is aiming"); + ChannelEntity->Signal(SimpleMessage, OnSignalResponse); +} + +// snippet.list_users_from_channel_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::ListUsersFromChannelSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to list users from + FString ChannelName = TEXT("guild-channel"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + FOnListUsersFromChannelResponse OnListUsersFromChannelResponse; + OnListUsersFromChannelResponse.BindDynamic(this, &ASample_ChannelEntity::OnListUsersFromChannelResponse_Simple); + + // List users from the channel using the channel entity + ChannelEntity->ListUsersFromChannel(OnListUsersFromChannelResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnListUsersFromChannelResponse_Simple(FPubnubOperationResult Result, FPubnubListUsersFromChannelWrapper Data) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to list users from channel. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Users successfully listed from channel. Occupancy: %d"), Data.Occupancy); + } +} + +// snippet.list_users_from_channel_with_settings_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::ListUsersFromChannelWithSettingsSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to list users from + FString ChannelName = TEXT("guild-channel"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + FOnListUsersFromChannelResponse OnListUsersFromChannelResponse; + OnListUsersFromChannelResponse.BindDynamic(this, &ASample_ChannelEntity::OnListUsersFromChannelResponse_WithSettings); + + // Create additional settings + FPubnubListUsersFromChannelSettings Settings; + Settings.State = true; + Settings.DisableUserID = false; + + // List users from the channel with settings using the channel entity + ChannelEntity->ListUsersFromChannel(OnListUsersFromChannelResponse, Settings); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnListUsersFromChannelResponse_WithSettings(FPubnubOperationResult Result, FPubnubListUsersFromChannelWrapper Data) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to list users from channel. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Users successfully listed from channel. Total occupancy: %d"), Data.Occupancy); + // List all users with theirs states + for (auto const& [UserID, UserState] : Data.UsersState) + { + UE_LOG(LogTemp, Log, TEXT("UserID: %s, User State: %s"), *UserID, *UserState); + } + } +} + +// snippet.list_users_from_channel_with_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::ListUsersFromChannelWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to list users from + FString ChannelName = TEXT("guild-channel"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Bind lambda to response delegate + FOnListUsersFromChannelResponseNative OnListUsersFromChannelResponse; + OnListUsersFromChannelResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubListUsersFromChannelWrapper& Data) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to list users from channel. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Users successfully listed from channel. Occupancy: %d"), Data.Occupancy); + } + }); + + // List users from the channel using the channel entity + ChannelEntity->ListUsersFromChannel(OnListUsersFromChannelResponse); +} + +// snippet.subscribe_with_channel_entity +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::SubscribeWithChannelEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the channel you want to work with + FString ChannelName = TEXT("game_lobby"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Create a subscription from the channel entity + UPubnubSubscription* ChannelSubscription = ChannelEntity->CreateSubscription(); + + // Add message listener to the subscription + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + ChannelSubscription->OnPubnubMessage.AddDynamic(this, &ASample_ChannelEntity::OnMessageReceived_ChannelEntitySample); + + // Subscribe to start receiving messages + ChannelSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); +} + +// snippet.channel_entity_all_listeners +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::ChannelEntityAllListenersSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity + FString ChannelName = TEXT("comprehensive_channel"); + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(ChannelName); + + // Create a subscription with presence events enabled to receive all event types + FPubnubSubscribeSettings SubscriptionSettings; + SubscriptionSettings.ReceivePresenceEvents = true; + UPubnubSubscription* Subscription = ChannelEntity->CreateSubscription(SubscriptionSettings); + + // Add ALL listener types to handle different PubNub events + + // 1. Message Listener - Fires when regular messages are published to the channel + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->OnPubnubMessage.AddDynamic(this, &ASample_ChannelEntity::OnMessage_AllListenersSample); + + // 2. Signal Listener - Fires when signals are sent to the channel + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->OnPubnubSignal.AddDynamic(this, &ASample_ChannelEntity::OnSignal_AllListenersSample); + + // 3. Presence Event Listener - Fires when users join/leave/timeout on the channel + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->OnPubnubPresenceEvent.AddDynamic(this, &ASample_ChannelEntity::OnPresenceEvent_AllListenersSample); + + // 4. Object Event Listener - Fires when App Context metadata changes occur + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_ChannelEntity::OnObjectEvent_AllListenersSample); + + // 5. Message Action Listener - Fires when message actions/reactions are added or removed + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->OnPubnubMessageAction.AddDynamic(this, &ASample_ChannelEntity::OnMessageAction_AllListenersSample); + + // 6. Universal Listener - Fires for ANY type of PubNub event (catch-all) + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + Subscription->FOnPubnubAnyMessageType.AddDynamic(this, &ASample_ChannelEntity::OnAnyEvent_AllListenersSample); + + // Subscribe to start receiving all event types + Subscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnMessage_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("MESSAGE LISTENER - Content: %s, Channel: %s, User: %s"), + *Message.Message, *Message.Channel, *Message.UserID); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnSignal_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("SIGNAL LISTENER - Content: %s, Channel: %s, User: %s"), + *Message.Message, *Message.Channel, *Message.UserID); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnPresenceEvent_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("PRESENCE LISTENER - Event: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnObjectEvent_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("OBJECT EVENT LISTENER - Event: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnMessageAction_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("MESSAGE ACTION LISTENER - Action: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnAnyEvent_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("UNIVERSAL LISTENER - Type: %d, Content: %s, Channel: %s"), + (int32)Message.MessageType, *Message.Message, *Message.Channel); +} + +// snippet.channel_entity_publish_message +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::ChannelEntityPublishMessageSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel entity for the game chat channel + FString GameChatChannel = TEXT("team_alpha_chat"); + UPubnubChannelEntity* ChatChannelEntity = PubnubSubsystem->CreateChannelEntity(GameChatChannel); + + // Set up callback to handle publish result + // ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class + FOnPublishMessageResponse OnPublishResult; + OnPublishResult.BindDynamic(this, &ASample_ChannelEntity::OnPublishResult_ChannelEntitySample); + + // Publish a tactical message to the team using the channel entity + FString TacticalMessage = TEXT("Enemy spotted at coordinates B-7, requesting backup!"); + ChatChannelEntity->PublishMessage(TacticalMessage, OnPublishResult); +} + +// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class +void ASample_ChannelEntity::OnPublishResult_ChannelEntitySample(FPubnubOperationResult Result, FPubnubMessageData Message) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to send tactical message. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Tactical message sent successfully to team. Message timetoken: %s"), *Message.Timetoken); + } +} + +// snippet.end diff --git a/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelGroupEntity.cpp b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelGroupEntity.cpp new file mode 100644 index 0000000..27c1cd6 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelGroupEntity.cpp @@ -0,0 +1,467 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Entities/Sample_ChannelGroupEntity.h" +// snippet.includes +#include "Kismet/GameplayStatics.h" +#include "Engine/GameInstance.h" + +// snippet.end + +/** + * NOTE: Each sample is designed to be fully self-contained and portable. + * You can copy-paste any individual sample into a new project, and it should compile and run without errors + * — as long as you also include the necessary `#include` statements. + * + * To ensure independence, each sample retrieves the PubnubSubsystem and explicitly calls `SetUserID()` + * before performing any PubNub operations. + * + * In a real project, however, you only need to call `SetUserID()` once — typically during initialization + * (e.g., in GameInstance or at login) before making your first PubNub request. + * + * The samples assume that in Pubnub SDK settings sections in ProjectSettings following fields are set: + * PublishKey and SubscribeKey have correct keys, InitializeAutomatically is true. + * + * CHANNEL GROUP ENTITY SAMPLES demonstrate working with PubNub channel groups through the entity-based approach. + */ + +// NOTE: Comments marked with `ACTION REQUIRED` indicate lines you must change. + +ASample_ChannelGroupEntity::ASample_ChannelGroupEntity() +{ + SamplesName = "ChannelGroupEntity"; +} + +void ASample_ChannelGroupEntity::RunSamples() +{ + Super::RunSamples(); + + CreateChannelGroupEntitySample(); + SubscribeWithChannelGroupEntitySample(); + AddChannelToGroupSample(); + AddChannelToGroupWithResultSample(); + AddChannelToGroupWithResultLambdaSample(); + ListChannelsFromGroupSample(); + ListChannelsFromGroupWithLambdaSample(); + RemoveChannelFromGroupSample(); + RemoveChannelFromGroupWithResultSample(); + RemoveChannelFromGroupWithResultLambdaSample(); + RemoveChannelGroupSample(); + RemoveChannelGroupWithResultSample(); + RemoveChannelGroupWithResultLambdaSample(); +} + + +/* CHANNEL GROUP ENTITY SAMPLE FUNCTIONS */ + +// snippet.create_channel_group_entity +void ASample_ChannelGroupEntity::CreateChannelGroupEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("game_rooms"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); +} + +// snippet.subscribe_with_channel_group_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::SubscribeWithChannelGroupEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("game_rooms"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Create a subscription from the channel group entity + UPubnubSubscription* GroupSubscription = ChannelGroupEntity->CreateSubscription(); + + // Add message listener to the subscription + // ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class + GroupSubscription->OnPubnubMessage.AddDynamic(this, &ASample_ChannelGroupEntity::OnMessageReceived_ChannelGroupEntitySample); + + // Subscribe to start receiving messages from all channels in the group + GroupSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Group Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); +} + +// snippet.add_channel_to_group_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::AddChannelToGroupSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Add channel to the channel group using the channel group entity + FString Channel = TEXT("global_chat"); + ChannelGroupEntity->AddChannelToGroup(Channel); +} + +// snippet.add_channel_to_group_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::AddChannelToGroupWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class + FOnAddChannelToGroupResponse OnAddChannelToGroupResponse; + OnAddChannelToGroupResponse.BindDynamic(this, &ASample_ChannelGroupEntity::OnAddChannelToGroupResponse); + + // Add channel to the channel group using the channel group entity + FString Channel = TEXT("trade_chat"); + ChannelGroupEntity->AddChannelToGroup(Channel, OnAddChannelToGroupResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::OnAddChannelToGroupResponse(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to add channel to group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel successfully added to group.")); + } +} + +// snippet.add_channel_to_group_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::AddChannelToGroupWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind lambda to response delegate + FOnAddChannelToGroupResponseNative OnAddChannelToGroupResponse; + OnAddChannelToGroupResponse.BindLambda([](const FPubnubOperationResult& Result) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to add channel to group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel successfully added to group.")); + } + }); + + // Add channel to the channel group using the channel group entity + FString Channel = TEXT("guild_chat"); + ChannelGroupEntity->AddChannelToGroup(Channel, OnAddChannelToGroupResponse); +} + +// snippet.list_channels_from_group_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::ListChannelsFromGroupSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class + FOnListChannelsFromGroupResponse OnListChannelsFromGroupResponse; + OnListChannelsFromGroupResponse.BindDynamic(this, &ASample_ChannelGroupEntity::OnListChannelsFromGroupResponse); + + // List channels from the channel group using the channel group entity + ChannelGroupEntity->ListChannelsFromGroup(OnListChannelsFromGroupResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::OnListChannelsFromGroupResponse(FPubnubOperationResult Result, const TArray& Channels) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to list channels from group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channels successfully listed from group. Listed channels:")); + for (const FString& Channel : Channels) + { + UE_LOG(LogTemp, Log, TEXT("- %s"), *Channel); + } + } +} + +// snippet.list_channels_from_group_with_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::ListChannelsFromGroupWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind lambda to response delegate + FOnListChannelsFromGroupResponseNative OnListChannelsFromGroupResponse; + OnListChannelsFromGroupResponse.BindLambda([](const FPubnubOperationResult& Result, const TArray& Channels) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to list channels from group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channels successfully listed from group. Listed channels:")); + for (const FString& Channel : Channels) + { + UE_LOG(LogTemp, Log, TEXT("- %s"), *Channel); + } + } + }); + + // List channels from the channel group using the channel group entity + ChannelGroupEntity->ListChannelsFromGroup(OnListChannelsFromGroupResponse); +} + +// snippet.remove_channel_from_group_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelFromGroupSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Remove channel from the channel group using the channel group entity + FString Channel = TEXT("global_chat"); + ChannelGroupEntity->RemoveChannelFromGroup(Channel); +} + +// snippet.remove_channel_from_group_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelFromGroupWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class + FOnRemoveChannelFromGroupResponse OnRemoveChannelFromGroupResponse; + OnRemoveChannelFromGroupResponse.BindDynamic(this, &ASample_ChannelGroupEntity::OnRemoveChannelFromGroupResponse); + + // Remove channel from the channel group using the channel group entity + FString Channel = TEXT("trade_chat"); + ChannelGroupEntity->RemoveChannelFromGroup(Channel, OnRemoveChannelFromGroupResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::OnRemoveChannelFromGroupResponse(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel from group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel successfully removed from group.")); + } +} + +// snippet.remove_channel_from_group_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelFromGroupWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind lambda to response delegate + FOnRemoveChannelFromGroupResponseNative OnRemoveChannelFromGroupResponse; + OnRemoveChannelFromGroupResponse.BindLambda([](const FPubnubOperationResult& Result) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel from group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel successfully removed from group.")); + } + }); + + // Remove channel from the channel group using the channel group entity + FString Channel = TEXT("guild_chat"); + ChannelGroupEntity->RemoveChannelFromGroup(Channel, OnRemoveChannelFromGroupResponse); +} + +// snippet.remove_channel_group_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelGroupSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Remove the entire channel group using the channel group entity + ChannelGroupEntity->RemoveChannelGroup(); +} + +// snippet.remove_channel_group_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelGroupWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class + FOnRemoveChannelGroupResponse OnRemoveChannelGroupResponse; + OnRemoveChannelGroupResponse.BindDynamic(this, &ASample_ChannelGroupEntity::OnRemoveChannelGroupResponse); + + // Remove the entire channel group using the channel group entity + ChannelGroupEntity->RemoveChannelGroup(OnRemoveChannelGroupResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::OnRemoveChannelGroupResponse(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel group successfully removed.")); + } +} + +// snippet.remove_channel_group_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelGroupEntity with name of your Actor class +void ASample_ChannelGroupEntity::RemoveChannelGroupWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for the group you want to work with + FString ChannelGroupName = TEXT("all-chats"); + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(ChannelGroupName); + + // Bind lambda to response delegate + FOnRemoveChannelGroupResponseNative OnRemoveChannelGroupResponse; + OnRemoveChannelGroupResponse.BindLambda([](const FPubnubOperationResult& Result) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel group. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Channel group successfully removed.")); + } + }); + + // Remove the entire channel group using the channel group entity + ChannelGroupEntity->RemoveChannelGroup(OnRemoveChannelGroupResponse); +} + +// snippet.end diff --git a/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelMetadataEntity.cpp b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelMetadataEntity.cpp new file mode 100644 index 0000000..4d9bdf8 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelMetadataEntity.cpp @@ -0,0 +1,423 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Entities/Sample_ChannelMetadataEntity.h" +// snippet.includes +#include "Kismet/GameplayStatics.h" +#include "Engine/GameInstance.h" + +// snippet.end + +/** + * NOTE: Each sample is designed to be fully self-contained and portable. + * You can copy-paste any individual sample into a new project, and it should compile and run without errors + * — as long as you also include the necessary `#include` statements. + * + * To ensure independence, each sample retrieves the PubnubSubsystem and explicitly calls `SetUserID()` + * before performing any PubNub operations. + * + * In a real project, however, you only need to call `SetUserID()` once — typically during initialization + * (e.g., in GameInstance or at login) before making your first PubNub request. + * + * The samples assume that in Pubnub SDK settings sections in ProjectSettings following fields are set: + * PublishKey and SubscribeKey have correct keys, InitializeAutomatically is true. + * + * CHANNEL METADATA ENTITY SAMPLES demonstrate working with PubNub channel metadata through the entity-based approach. + */ + +// NOTE: Comments marked with `ACTION REQUIRED` indicate lines you must change. + +ASample_ChannelMetadataEntity::ASample_ChannelMetadataEntity() +{ + SamplesName = "ChannelMetadataEntity"; +} + +void ASample_ChannelMetadataEntity::RunSamples() +{ + Super::RunSamples(); + + CreateChannelMetadataEntitySample(); + SubscribeWithChannelMetadataEntitySample(); + ChannelMetadataEntitySetMetadataSample(); + SetChannelMetadataSample(); + SetChannelMetadataWithResultSample(); + SetChannelMetadataWithLambdaSample(); + GetChannelMetadataSample(); + GetChannelMetadataWithLambdaSample(); + RemoveChannelMetadataSample(); + RemoveChannelMetadataWithResultSample(); + RemoveChannelMetadataWithResultLambdaSample(); +} + + +/* CHANNEL METADATA ENTITY SAMPLE FUNCTIONS */ + +// snippet.create_channel_metadata_entity +void ASample_ChannelMetadataEntity::CreateChannelMetadataEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity for the channel you want to monitor metadata changes + FString ChannelName = TEXT("lobby_settings"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); +} + +// snippet.subscribe_with_channel_metadata_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::SubscribeWithChannelMetadataEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity for the channel you want to monitor metadata changes + FString ChannelName = TEXT("lobby_settings"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Create a subscription from the channel metadata entity + UPubnubSubscription* MetadataSubscription = ChannelMetadataEntity->CreateSubscription(); + + // Add object event listener to receive App Context metadata change notifications + // ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class + MetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_ChannelMetadataEntity::OnObjectEvent_ChannelMetadataEntitySample); + + // Subscribe to start receiving metadata change events + MetadataSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Metadata Entity - Object event received: %s"), *Message.Message); +} + +// snippet.channel_metadata_entity_set_metadata +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::ChannelMetadataEntitySetMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Game_Master"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity for a tournament lobby + FString TournamentLobby = TEXT("tournament_lobby_finals"); + UPubnubChannelMetadataEntity* LobbyMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(TournamentLobby); + + // Set up callback to handle metadata set result + // ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class + FOnSetChannelMetadataResponse OnSetMetadataResult; + OnSetMetadataResult.BindDynamic(this, &ASample_ChannelMetadataEntity::OnSetChannelMetadataResult_Sample); + + // Set tournament lobby information and rules + FPubnubChannelData TournamentLobbyInfo; + TournamentLobbyInfo.ChannelID = TournamentLobby; + TournamentLobbyInfo.ChannelName = "Championship Finals Lobby"; + TournamentLobbyInfo.Description = "Final tournament matches - best of 5 rounds"; + TournamentLobbyInfo.Custom = "{\"max_players\":10,\"tournament_tier\":\"championship\",\"prize_pool\":\"50000\"}"; + + LobbyMetadataEntity->SetChannelMetadata(TournamentLobbyInfo, OnSetMetadataResult); +} + +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::OnSetChannelMetadataResult_Sample(FPubnubOperationResult Result, FPubnubChannelData ChannelData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to set tournament lobby info. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Tournament lobby info set successfully: %s - %s"), *ChannelData.ChannelName, *ChannelData.Description); + } +} + +// snippet.set_channel_metadata_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::SetChannelMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("general-chat-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Create channel metadata object + FPubnubChannelData ChannelMetadata; + ChannelMetadata.ChannelName = "General Chat"; + ChannelMetadata.Description = "Channel for all players to chat."; + ChannelMetadata.Custom = "{\"topic\": \"welcomes\"}"; + + // Set channel metadata using the channel metadata entity + ChannelMetadataEntity->SetChannelMetadata(ChannelMetadata); +} + +// snippet.set_channel_metadata_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::SetChannelMetadataWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("trade-chat-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class + FOnSetChannelMetadataResponse OnSetChannelMetadataResponse; + OnSetChannelMetadataResponse.BindDynamic(this, &ASample_ChannelMetadataEntity::OnSetChannelMetadataResponse); + + // Create channel metadata object + FPubnubChannelData ChannelMetadata; + ChannelMetadata.ChannelName = "Trade Chat"; + ChannelMetadata.Status = "active"; + ChannelMetadata.Custom = "{\"rules\": \"wts_wtt_only\"}"; + + // Set channel metadata with all available data included in response using the channel metadata entity + FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude::FromValue(true); + ChannelMetadataEntity->SetChannelMetadata(ChannelMetadata, OnSetChannelMetadataResponse, Include); +} + +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::OnSetChannelMetadataResponse(FPubnubOperationResult Result, FPubnubChannelData ChannelData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to set channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully set channel metadata. ChannelID: %s, Name: %s, Custom: %s, Status: %s"), *ChannelData.ChannelID, *ChannelData.ChannelName, *ChannelData.Custom, *ChannelData.Status); + } +} + +// snippet.set_channel_metadata_with_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::SetChannelMetadataWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("guild-hall-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind lambda to response delegate + FOnSetChannelMetadataResponseNative OnSetChannelMetadataResponse; + OnSetChannelMetadataResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubChannelData& ChannelData) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to set channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully set channel metadata. ChannelID: %s, Name: %s, Custom: %s, Status: %s"), *ChannelData.ChannelID, *ChannelData.ChannelName, *ChannelData.Custom, *ChannelData.Status); + } + }); + + // Create channel metadata object + FPubnubChannelData ChannelMetadata; + ChannelMetadata.ChannelName = "Guild Hall"; + ChannelMetadata.Status = "archived"; + ChannelMetadata.Custom = "{\"motd\": \"Raid tonight at 8!\"}"; + + // Set channel metadata with all available data included in response using the channel metadata entity + FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude::FromValue(true); + ChannelMetadataEntity->SetChannelMetadata(ChannelMetadata, OnSetChannelMetadataResponse, Include); +} + +// snippet.get_channel_metadata_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::GetChannelMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("general-chat-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class + FOnGetChannelMetadataResponse OnGetChannelMetadataResponse; + OnGetChannelMetadataResponse.BindDynamic(this, &ASample_ChannelMetadataEntity::OnGetChannelMetadataResponse_Simple); + + // Get channel metadata using the channel metadata entity + ChannelMetadataEntity->GetChannelMetadata(OnGetChannelMetadataResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::OnGetChannelMetadataResponse_Simple(FPubnubOperationResult Result, FPubnubChannelData ChannelData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to get channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully got channel metadata. ChannelID: %s, Name: %s"), *ChannelData.ChannelID, *ChannelData.ChannelName); + } +} + +// snippet.get_channel_metadata_with_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::GetChannelMetadataWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("guild-hall-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind lambda to response delegate + FOnGetChannelMetadataResponseNative OnGetChannelMetadataResponse; + OnGetChannelMetadataResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubChannelData& ChannelData) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to get channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully got channel metadata. ChannelID: %s, Name: %s"), *ChannelData.ChannelID, *ChannelData.ChannelName); + } + }); + + // Get channel metadata using the channel metadata entity + ChannelMetadataEntity->GetChannelMetadata(OnGetChannelMetadataResponse); +} + +// snippet.remove_channel_metadata_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::RemoveChannelMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("general-chat-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Remove channel metadata using the channel metadata entity + ChannelMetadataEntity->RemoveChannelMetadata(); +} + +// snippet.remove_channel_metadata_with_result_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::RemoveChannelMetadataWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("trade-chat-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class + FOnRemoveChannelMetadataResponse OnRemoveChannelMetadataResponse; + OnRemoveChannelMetadataResponse.BindDynamic(this, &ASample_ChannelMetadataEntity::OnRemoveChannelMetadataResponse); + + // Remove channel metadata using the channel metadata entity + ChannelMetadataEntity->RemoveChannelMetadata(OnRemoveChannelMetadataResponse); +} + +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::OnRemoveChannelMetadataResponse(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully removed channel metadata.")); + } +} + +// snippet.remove_channel_metadata_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_ChannelMetadataEntity with name of your Actor class +void ASample_ChannelMetadataEntity::RemoveChannelMetadataWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel metadata entity + FString ChannelName = TEXT("guild-hall-channel"); + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(ChannelName); + + // Bind lambda to response delegate + FOnRemoveChannelMetadataResponseNative OnRemoveChannelMetadataResponse; + OnRemoveChannelMetadataResponse.BindLambda([](const FPubnubOperationResult& Result) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove channel metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully removed channel metadata.")); + } + }); + + // Remove channel metadata using the channel metadata entity + ChannelMetadataEntity->RemoveChannelMetadata(OnRemoveChannelMetadataResponse); +} + +// snippet.end diff --git a/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_SubscriptionSet.cpp b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_SubscriptionSet.cpp new file mode 100644 index 0000000..9a7951d --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_SubscriptionSet.cpp @@ -0,0 +1,223 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Entities/Sample_SubscriptionSet.h" +// snippet.includes +#include "Kismet/GameplayStatics.h" +#include "Engine/GameInstance.h" + +// snippet.end + +/** + * NOTE: Each sample is designed to be fully self-contained and portable. + * You can copy-paste any individual sample into a new project, and it should compile and run without errors + * — as long as you also include the necessary `#include` statements. + * + * To ensure independence, each sample retrieves the PubnubSubsystem and explicitly calls `SetUserID()` + * before performing any PubNub operations. + * + * In a real project, however, you only need to call `SetUserID()` once — typically during initialization + * (e.g., in GameInstance or at login) before making your first PubNub request. + * + * The samples assume that in Pubnub SDK settings sections in ProjectSettings following fields are set: + * PublishKey and SubscribeKey have correct keys, InitializeAutomatically is true. + * + * SUBSCRIPTION SET SAMPLES demonstrate the powerful subscription set functionality, which allows + * managing multiple subscriptions as a single unit for complex monitoring and communication scenarios. + */ + +// NOTE: Comments marked with `ACTION REQUIRED` indicate lines you must change. + + +//Internal function, don't copy it with the samples +void ASample_SubscriptionSet::RunSamples() +{ + Super::RunSamples(); + + CreateSubscriptionSetFromNamesSample(); + CreateSubscriptionSetFromEntitiesSample(); + SubscriptionSetAddRemoveSubscriptionsSample(); + SubscriptionSetMergeOperationsSample(); +} + +//Internal function, don't copy it with the samples +ASample_SubscriptionSet::ASample_SubscriptionSet() +{ + SamplesName = "SubscriptionSet"; +} + + +/* SUBSCRIPTION SET SAMPLE FUNCTIONS */ + +// snippet.create_subscription_set_from_names +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::CreateSubscriptionSetFromNamesSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Squad_Leader"); + PubnubSubsystem->SetUserID(UserID); + + // Define multiple channels and channel groups to monitor for team coordination + TArray TeamChannels = { + TEXT("squad_alpha_chat"), // Alpha team communication + TEXT("squad_bravo_chat"), // Bravo team communication + TEXT("tactical_announcements") // Mission updates and announcements + }; + + TArray OperationChannelGroups = { + TEXT("mission_channels"), // All mission-related channels + TEXT("support_channels") // Support and logistics channels + }; + + // Create subscription set from channel and group names - monitors all team communications + UPubnubSubscriptionSet* TeamCommSubscriptionSet = PubnubSubsystem->CreateSubscriptionSet(TeamChannels, OperationChannelGroups); + + // Add message listener to monitor all team communications + // ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class + TeamCommSubscriptionSet->OnPubnubMessage.AddDynamic(this, &ASample_SubscriptionSet::OnMessage_SubscriptionSetFromNamesSample); + + // Subscribe to start monitoring all team channels and groups simultaneously + TeamCommSubscriptionSet->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::OnMessage_SubscriptionSetFromNamesSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("TEAM COMMS - Message on %s: %s (from %s)"), + *Message.Channel, *Message.Message, *Message.UserID); +} + +// snippet.create_subscription_set_from_entities +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::CreateSubscriptionSetFromEntitiesSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Command_Center"); + PubnubSubsystem->SetUserID(UserID); + + // Create individual entities for different aspects of game monitoring + UPubnubChannelEntity* PlayerStatsChannel = PubnubSubsystem->CreateChannelEntity(TEXT("player_statistics")); + UPubnubChannelEntity* GameEventsChannel = PubnubSubsystem->CreateChannelEntity(TEXT("game_events_feed")); + UPubnubChannelGroupEntity* ServerStatusGroup = PubnubSubsystem->CreateChannelGroupEntity(TEXT("server_monitoring")); + UPubnubChannelMetadataEntity* MatchConfigEntity = PubnubSubsystem->CreateChannelMetadataEntity(TEXT("match_configuration")); + + // Combine different entity types into a comprehensive monitoring subscription set + TArray MonitoringEntities = { + PlayerStatsChannel, // Monitor player performance data + GameEventsChannel, // Monitor in-game events (kills, objectives, etc.) + ServerStatusGroup, // Monitor all server status channels + MatchConfigEntity // Monitor match configuration changes + }; + + // Create subscription set from existing entities - provides unified monitoring dashboard + UPubnubSubscriptionSet* GameMonitoringSet = PubnubSubsystem->CreateSubscriptionSetFromEntities(MonitoringEntities); + + // Add message listener to capture all monitoring data + // ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class + GameMonitoringSet->OnPubnubMessage.AddDynamic(this, &ASample_SubscriptionSet::OnMessage_SubscriptionSetFromEntitiesSample); + + // Add object event listener to monitor configuration changes + // ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class + GameMonitoringSet->OnPubnubObjectEvent.AddDynamic(this, &ASample_SubscriptionSet::OnMessage_SubscriptionSetFromEntitiesSample); + + // Subscribe to start comprehensive game monitoring + GameMonitoringSet->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::OnMessage_SubscriptionSetFromEntitiesSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("GAME MONITORING - Message on %s: %s (from %s)"), + *Message.Channel, *Message.Message, *Message.UserID); +} + +// snippet.subscription_set_add_remove_subscriptions +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::SubscriptionSetAddRemoveSubscriptionsSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Match_Coordinator"); + PubnubSubsystem->SetUserID(UserID); + + // Create a subscription set for tournament management + TArray TournamentChannels = {TEXT("tournament_lobby"), TEXT("match_results")}; + UPubnubSubscriptionSet* TournamentSet = PubnubSubsystem->CreateSubscriptionSet(TournamentChannels, TArray()); + + // Create individual subscriptions for different game areas + UPubnubChannelEntity* PlayerFeedbackChannel = PubnubSubsystem->CreateChannelEntity(TEXT("player_feedback")); + UPubnubSubscription* FeedbackSubscription = PlayerFeedbackChannel->CreateSubscription(); + + UPubnubChannelEntity* AdminNoticesChannel = PubnubSubsystem->CreateChannelEntity(TEXT("admin_notices")); + UPubnubSubscription* AdminSubscription = AdminNoticesChannel->CreateSubscription(); + + // Add individual subscriptions to the tournament set + TournamentSet->AddSubscription(FeedbackSubscription); + TournamentSet->AddSubscription(AdminSubscription); + + // Check current subscriptions in the set + TArray CurrentSubscriptions = TournamentSet->GetSubscriptions(); + UE_LOG(LogTemp, Log, TEXT("Tournament set now contains %d subscriptions"), CurrentSubscriptions.Num()); + + // Remove a subscription when no longer needed + TournamentSet->RemoveSubscription(FeedbackSubscription); + + // Check subscriptions after removal + TArray SubscriptionsAfterRemoval = TournamentSet->GetSubscriptions(); + UE_LOG(LogTemp, Log, TEXT("Tournament set now contains %d subscriptions after removal"), SubscriptionsAfterRemoval.Num()); +} + +// snippet.subscription_set_merge_operations +// ACTION REQUIRED: Replace ASample_SubscriptionSet with name of your Actor class +void ASample_SubscriptionSet::SubscriptionSetMergeOperationsSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Event_Manager"); + PubnubSubsystem->SetUserID(UserID); + + // Create main subscription set for core game channels + TArray CoreChannels = {TEXT("game_lobby"), TEXT("general_chat")}; + UPubnubSubscriptionSet* CoreGameSet = PubnubSubsystem->CreateSubscriptionSet(CoreChannels, TArray()); + + // Create additional subscription set for special events + TArray EventChannels = {TEXT("special_events"), TEXT("tournament_updates")}; + UPubnubSubscriptionSet* SpecialEventsSet = PubnubSubsystem->CreateSubscriptionSet(EventChannels, TArray()); + + // Create VIP subscription set for premium features + TArray VipChannels = {TEXT("vip_lounge"), TEXT("premium_support")}; + UPubnubSubscriptionSet* VipSet = PubnubSubsystem->CreateSubscriptionSet(VipChannels, TArray()); + + UE_LOG(LogTemp, Log, TEXT("Created core game set, special events set, and VIP set")); + + // Merge special events into core game monitoring + CoreGameSet->AddSubscriptionSet(SpecialEventsSet); + UE_LOG(LogTemp, Log, TEXT("Merged special events into core game monitoring")); + + // Merge VIP channels for comprehensive monitoring + CoreGameSet->AddSubscriptionSet(VipSet); + UE_LOG(LogTemp, Log, TEXT("Merged VIP channels for comprehensive event management")); + + // Later, remove special events if no longer needed + CoreGameSet->RemoveSubscriptionSet(SpecialEventsSet); + UE_LOG(LogTemp, Log, TEXT("Removed special events from monitoring (event ended)")); + + // Subscribe to the final combined set + CoreGameSet->Subscribe(); + UE_LOG(LogTemp, Log, TEXT("Now monitoring core game channels + VIP channels")); +} + +// snippet.end diff --git a/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_UserMetadataEntity.cpp b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_UserMetadataEntity.cpp new file mode 100644 index 0000000..7b4f7a7 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Entities/Sample_UserMetadataEntity.cpp @@ -0,0 +1,417 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Entities/Sample_UserMetadataEntity.h" +// snippet.includes +#include "Kismet/GameplayStatics.h" +#include "Engine/GameInstance.h" + +// snippet.end + +/** + * NOTE: Each sample is designed to be fully self-contained and portable. + * You can copy-paste any individual sample into a new project, and it should compile and run without errors + * — as long as you also include the necessary `#include` statements. + * + * To ensure independence, each sample retrieves the PubnubSubsystem and explicitly calls `SetUserID()` + * before performing any PubNub operations. + * + * In a real project, however, you only need to call `SetUserID()` once — typically during initialization + * (e.g., in GameInstance or at login) before making your first PubNub request. + * + * The samples assume that in Pubnub SDK settings sections in ProjectSettings following fields are set: + * PublishKey and SubscribeKey have correct keys, InitializeAutomatically is true. + * + * USER METADATA ENTITY SAMPLES demonstrate working with PubNub user metadata through the entity-based approach. + */ + +// NOTE: Comments marked with `ACTION REQUIRED` indicate lines you must change. + +ASample_UserMetadataEntity::ASample_UserMetadataEntity() +{ + SamplesName = "UserMetadataEntity"; +} + +void ASample_UserMetadataEntity::RunSamples() +{ + Super::RunSamples(); + + CreateUserMetadataEntitySample(); + SubscribeWithUserMetadataEntitySample(); + UserMetadataEntitySetMetadataSample(); + SetUserMetadataSample(); + SetUserMetadataWithResultSample(); + SetUserMetadataWithLambdaSample(); + GetUserMetadataSample(); + GetUserMetadataWithLambdaSample(); + RemoveUserMetadataSample(); + RemoveUserMetadataWithResultSample(); + RemoveUserMetadataWithResultLambdaSample(); +} + + +/* USER METADATA ENTITY SAMPLE FUNCTIONS */ + +// snippet.create_user_metadata_entity +void ASample_UserMetadataEntity::CreateUserMetadataEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity for the user you want to monitor metadata changes + FString UserToMonitor = TEXT("Player_002"); + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserToMonitor); +} + +// snippet.subscribe_with_user_metadata_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::SubscribeWithUserMetadataEntitySample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity for the user you want to monitor metadata changes + FString UserToMonitor = TEXT("Player_002"); + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserToMonitor); + + // Create a subscription from the user metadata entity + UPubnubSubscription* UserMetadataSubscription = UserMetadataEntity->CreateSubscription(); + + // Add object event listener to receive App Context user metadata change notifications + // ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class + UserMetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_UserMetadataEntity::OnObjectEvent_UserMetadataEntitySample); + + // Subscribe to start receiving user metadata change events + UserMetadataSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("User Metadata Entity - Object event received: %s"), *Message.Message); +} + +// snippet.user_metadata_entity_set_metadata +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::UserMetadataEntitySetMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity for updating player profile + FString PlayerToUpdate = TEXT("Champion_Alex"); + UPubnubUserMetadataEntity* PlayerMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(PlayerToUpdate); + + // Set up callback to handle metadata set result + // ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class + FOnSetUserMetadataResponse OnSetPlayerInfoResult; + OnSetPlayerInfoResult.BindDynamic(this, &ASample_UserMetadataEntity::OnSetUserMetadataResult_Sample); + + // Set champion player profile and statistics + FPubnubUserData ChampionPlayerProfile; + ChampionPlayerProfile.UserID = PlayerToUpdate; + ChampionPlayerProfile.UserName = "Alex 'Lightning' Rodriguez"; + ChampionPlayerProfile.Email = "alex.lightning@esports.com"; + ChampionPlayerProfile.Custom = "{\"rank\":\"Champion\",\"wins\":127,\"losses\":23,\"favorite_weapon\":\"Plasma Rifle\",\"achievements\":[\"First Blood\",\"Triple Kill Master\"]}"; + + PlayerMetadataEntity->SetUserMetadata(ChampionPlayerProfile, OnSetPlayerInfoResult); +} + +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::OnSetUserMetadataResult_Sample(FPubnubOperationResult Result, FPubnubUserData UserData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to update champion player profile. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Champion player profile updated successfully: %s (%s)"), *UserData.UserName, *UserData.UserID); + } +} + +// snippet.set_user_metadata_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::SetUserMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Create user metadata object + FPubnubUserData UserMetadata; + UserMetadata.UserName = "Player One"; + UserMetadata.Email = "player.one@pubnub.com"; + UserMetadata.Custom = "{\"level\": 5, \"rank\": \"gold\"}"; + + // Set user metadata using the user metadata entity + UserMetadataEntity->SetUserMetadata(UserMetadata); +} + +// snippet.set_user_metadata_with_result_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::SetUserMetadataWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_002"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class + FOnSetUserMetadataResponse OnSetUserMetadataResponse; + OnSetUserMetadataResponse.BindDynamic(this, &ASample_UserMetadataEntity::OnSetUserMetadataResponse); + + // Create user metadata object + FPubnubUserData UserMetadata; + UserMetadata.UserName = "Player Two"; + UserMetadata.Status = "active"; + UserMetadata.Custom = "{\"inventory_slots\": 20, \"guild_id\": \"G2\"}"; + + // Set user metadata with all available data included in response using the user metadata entity + FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude::FromValue(true); + UserMetadataEntity->SetUserMetadata(UserMetadata, OnSetUserMetadataResponse, Include); +} + +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::OnSetUserMetadataResponse(FPubnubOperationResult Result, FPubnubUserData UserData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to set user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully set user metadata. UserID: %s, Name: %s, Custom: %s, Status: %s"), *UserData.UserID, *UserData.UserName, *UserData.Custom, *UserData.Status); + } +} + +// snippet.set_user_metadata_with_lambda_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::SetUserMetadataWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_003"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Bind lambda to response delegate + FOnSetUserMetadataResponseNative OnSetUserMetadataResponse; + OnSetUserMetadataResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubUserData& UserData) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to set user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully set user metadata. UserID: %s, Name: %s, Custom: %s, Status: %s"), *UserData.UserID, *UserData.UserName, *UserData.Custom, *UserData.Status); + } + }); + + // Create user metadata object + FPubnubUserData UserMetadata; + UserMetadata.UserName = "Player Three"; + UserMetadata.Status = "inactive"; + UserMetadata.Custom = "{\"last_seen\": \"2024-01-01\"}"; + + // Set user metadata with all available data included in response using the user metadata entity + FPubnubGetMetadataInclude Include = FPubnubGetMetadataInclude::FromValue(true); + UserMetadataEntity->SetUserMetadata(UserMetadata, OnSetUserMetadataResponse, Include); +} + +// snippet.get_user_metadata_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::GetUserMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class + FOnGetUserMetadataResponse OnGetUserMetadataResponse; + OnGetUserMetadataResponse.BindDynamic(this, &ASample_UserMetadataEntity::OnGetUserMetadataResponse_Simple); + + // Get user metadata using the user metadata entity + UserMetadataEntity->GetUserMetadata(OnGetUserMetadataResponse); +} + +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::OnGetUserMetadataResponse_Simple(FPubnubOperationResult Result, FPubnubUserData UserData) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to get user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully got user metadata. UserID: %s, Name: %s"), *UserData.UserID, *UserData.UserName); + } +} + +// snippet.get_user_metadata_with_lambda_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::GetUserMetadataWithLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_003"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Bind lambda to response delegate + FOnGetUserMetadataResponseNative OnGetUserMetadataResponse; + OnGetUserMetadataResponse.BindLambda([](const FPubnubOperationResult& Result, const FPubnubUserData& UserData) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to get user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully got user metadata. UserID: %s, Name: %s"), *UserData.UserID, *UserData.UserName); + } + }); + + // Get user metadata using the user metadata entity + UserMetadataEntity->GetUserMetadata(OnGetUserMetadataResponse); +} + +// snippet.remove_user_metadata_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::RemoveUserMetadataSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserID); + + // Remove user metadata using the user metadata entity + UserMetadataEntity->RemoveUserMetadata(); +} + +// snippet.remove_user_metadata_with_result_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::RemoveUserMetadataWithResultSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + FString UserToRemove = TEXT("Player_002"); + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserToRemove); + + // Bind response delegate + // ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class + FOnRemoveUserMetadataResponse OnRemoveUserMetadataResponse; + OnRemoveUserMetadataResponse.BindDynamic(this, &ASample_UserMetadataEntity::OnRemoveUserMetadataResponse); + + // Remove user metadata using the user metadata entity + UserMetadataEntity->RemoveUserMetadata(OnRemoveUserMetadataResponse); +} + +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::OnRemoveUserMetadataResponse(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully removed user metadata.")); + } +} + +// snippet.remove_user_metadata_with_result_lambda_entity +// ACTION REQUIRED: Replace ASample_UserMetadataEntity with name of your Actor class +void ASample_UserMetadataEntity::RemoveUserMetadataWithResultLambdaSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Player_001"); + PubnubSubsystem->SetUserID(UserID); + + // Create a user metadata entity + FString UserToRemove = TEXT("Player_003"); + UPubnubUserMetadataEntity* UserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(UserToRemove); + + // Bind lambda to response delegate + FOnRemoveUserMetadataResponseNative OnRemoveUserMetadataResponse; + OnRemoveUserMetadataResponse.BindLambda([](const FPubnubOperationResult& Result) + { + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to remove user metadata. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Successfully removed user metadata.")); + } + }); + + // Remove user metadata using the user metadata entity + UserMetadataEntity->RemoveUserMetadata(OnRemoveUserMetadataResponse); +} + +// snippet.end diff --git a/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp new file mode 100644 index 0000000..93f9b3f --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp @@ -0,0 +1,1125 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "PubnubSubsystem.h" +#include "PubnubEnumLibrary.h" +#include "Kismet/GameplayStatics.h" +#include "FunctionLibraries/PubnubJsonUtilities.h" +#include "Entities/PubnubChannelEntity.h" +#include "Entities/PubnubChannelGroupEntity.h" +#include "Entities/PubnubChannelMetadataEntity.h" +#include "Entities/PubnubUserMetadataEntity.h" +#include "Entities/PubnubSubscription.h" +#include "FunctionLibraries/PubnubTimetokenUtilities.h" + +#if WITH_DEV_AUTOMATION_TESTS + +#include "Tests/PubnubTestsUtils.h" +#include "Tests/AutomationCommon.h" +#include "Misc/AutomationTest.h" + +using namespace PubnubTests; + +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubChannelEntityPublishSubscribeTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.ChannelEntity.PublishAndSubscribe", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubChannelGroupEntityTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.ChannelGroupEntity.CreateAndSubscribe", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubSubscriptionSetTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.SubscriptionSet.MultipleChannels", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubSubscriptionListenersTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.Subscription.AllListeners", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubSubscriptionSetManagementTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.SubscriptionSet.DynamicManagement", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); +IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FPubnubEntityCreationAndTrackingTest, FPubnubAutomationTestBase, "Pubnub.Integration.Entities.Subsystem.EntityCreationAndTracking", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter); + +bool FPubnubChannelEntityPublishSubscribeTest::RunTest(const FString& Parameters) +{ + // Initial variables + const FString TestMessage = "\"Entity test message\""; + const FString TestUser = SDK_PREFIX + "entity_test_user"; + const FString TestChannel = SDK_PREFIX + "entity_test_channel"; + TSharedPtr TestMessageReceived = MakeShared(false); + TSharedPtr TestPublishResultReceived = MakeShared(false); + TSharedPtr TestSubscribeResultReceived = MakeShared(false); + TSharedPtr PublishResult = MakeShared(); + TSharedPtr ReceivedMessageData = MakeShared(); + + if(!InitTest()) + { + AddError("TestInitialization failed"); + return false; + } + + // Check for any errors on the way + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(ErrorMessage); + }); + + // Set User ID - it's necessary for any operation + PubnubSubsystem->SetUserID(TestUser); + + // Create channel entity immediately - no delayed actions needed + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(TestChannel); + TestNotNull("Channel entity should be created", ChannelEntity); + if (!ChannelEntity) + { + AddError("Failed to create channel entity"); + return false; + } + TestEqual("Channel entity should have correct ID", TestChannel, ChannelEntity->EntityID); + TestEqual("Channel entity should have correct type", EPubnubEntityType::PEnT_Channel, ChannelEntity->EntityType); + + // Create subscription from entity immediately + UPubnubSubscription* Subscription = ChannelEntity->CreateSubscription(); + TestNotNull("Subscription should be created from channel entity", Subscription); + if (!Subscription) + { + AddError("Failed to create subscription from channel entity"); + return false; + } + + // Set up message listener on the subscription immediately + Subscription->OnPubnubMessageNative.AddLambda([this, TestMessage, TestChannel, TestUser, TestMessageReceived, ReceivedMessageData](const FPubnubMessageData& ReceivedMessage) + { + *TestMessageReceived = true; + *ReceivedMessageData = ReceivedMessage; + + // Verify message content + TestEqual("Received message content", TestMessage, ReceivedMessage.Message); + TestEqual("Received message channel", TestChannel, ReceivedMessage.Channel); + TestEqual("Received message UserID", TestUser, ReceivedMessage.UserID); + TestEqual("Received message MessageType", EPubnubMessageType::PMT_Published, ReceivedMessage.MessageType); + TestFalse("Received message timetoken should not be empty", ReceivedMessage.Timetoken.IsEmpty()); + }); + + // Create subscribe callback + FOnSubscribeOperationResponseNative SubscribeCallback; + SubscribeCallback.BindLambda([this, TestSubscribeResultReceived](const FPubnubOperationResult& Result) + { + *TestSubscribeResultReceived = true; + TestFalse("Subscribe operation should not have failed", Result.Error); + TestEqual("Subscribe HTTP status should be 200", Result.Status, 200); + + if (Result.Error) + { + AddError(FString::Printf(TEXT("Subscribe failed with error: %s"), *Result.ErrorMessage)); + } + }); + + // Subscribe using the subscription + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, Subscription, SubscribeCallback]() + { + Subscription->Subscribe(SubscribeCallback); + }, 0.1f)); + + // Wait until subscribe result is received + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestSubscribeResultReceived]() { + return *TestSubscribeResultReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestSubscribeResultReceived]() + { + if (!*TestSubscribeResultReceived) + { + AddError("Subscribe result callback was not received"); + } + }, 0.1f)); + + // Create publish callback + FOnPublishMessageResponseNative PublishCallback; + PublishCallback.BindLambda([this, TestMessage, TestChannel, TestUser, TestPublishResultReceived, PublishResult] + (const FPubnubOperationResult& Result, const FPubnubMessageData& PublishedMessage) + { + *TestPublishResultReceived = true; + *PublishResult = Result; + + // Verify publish result + TestFalse("Publish operation should not have failed", Result.Error); + TestEqual("Publish HTTP status should be 200", Result.Status, 200); + + if (!Result.Error) + { + // Verify published message data matches what we sent + TestEqual("Published message data - content", TestMessage, PublishedMessage.Message); + TestEqual("Published message data - channel", TestChannel, PublishedMessage.Channel); + TestEqual("Published message data - user ID", TestUser, PublishedMessage.UserID); + TestEqual("Published message data - message type", EPubnubMessageType::PMT_Published, PublishedMessage.MessageType); + TestFalse("Published message timetoken should not be empty", PublishedMessage.Timetoken.IsEmpty()); + } + else + { + AddError(FString::Printf(TEXT("Publish failed with error: %s"), *Result.ErrorMessage)); + } + }); + + // Publish message using the channel entity + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, ChannelEntity, TestMessage, PublishCallback]() + { + ChannelEntity->PublishMessage(TestMessage, PublishCallback); + }, 0.5f)); + + // Wait until publish result is received + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestPublishResultReceived]() { + return *TestPublishResultReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestPublishResultReceived]() + { + if (!*TestPublishResultReceived) + { + AddError("Publish result callback was not received"); + } + }, 0.1f)); + + // Wait until message is received + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestMessageReceived]() { + return *TestMessageReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestMessageReceived]() + { + if (!*TestMessageReceived) + { + AddError("Test message was not received through entity subscription"); + } + }, 0.1f)); + + // Clean up + CleanUp(); + + return true; +} + +bool FPubnubChannelGroupEntityTest::RunTest(const FString& Parameters) +{ + // Initial variables + const FString TestMessage = "\"Channel group test message\""; + const FString TestUser = SDK_PREFIX + "channel_group_test_user"; + const FString TestChannelGroup = SDK_PREFIX + "test_channel_group"; + const FString TestChannel1 = SDK_PREFIX + "test_channel_1"; + const FString TestChannel2 = SDK_PREFIX + "test_channel_2"; + TSharedPtr TestChannelGroupCreated = MakeShared(false); + TSharedPtr TestChannelAdded = MakeShared(false); + TSharedPtr TestSubscriptionCreated = MakeShared(false); + TSharedPtr TestMessageReceived = MakeShared(false); + TSharedPtr TestPublishResultReceived = MakeShared(false); + TSharedPtr TestSubscribeResultReceived = MakeShared(false); + TSharedPtr ReceivedMessageData = MakeShared(); + + if(!InitTest()) + { + AddError("TestInitialization failed"); + return false; + } + + // Check for any errors on the way + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(ErrorMessage); + }); + + // Set User ID - it's necessary for any operation + PubnubSubsystem->SetUserID(TestUser); + + + /*PubnubSubsystem->OnSubscriptionStatusChangedNative.AddLambda([this](EPubnubSubscriptionStatus Status, const FPubnubSubscriptionStatusData& StatusData) + { + AddInfo("Subscription listener."); + for(auto ch : StatusData.ChannelGroups) + { + + AddInfo(FString::Printf(TEXT("Subscription listener: %s"), *ch)); + } + + });*/ + + + + // Create channel group entity immediately - no delayed actions needed + UPubnubChannelGroupEntity* ChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(TestChannelGroup); + TestNotNull("Channel group entity should be created", ChannelGroupEntity); + if (!ChannelGroupEntity) + { + AddError("Failed to create channel group entity"); + return false; + } + TestEqual("Channel group entity should have correct ID", TestChannelGroup, ChannelGroupEntity->EntityID); + TestEqual("Channel group entity should have correct type", EPubnubEntityType::PEnT_ChannelGroup, ChannelGroupEntity->EntityType); + *TestChannelGroupCreated = true; + + // Create subscription from channel group entity immediately + UPubnubSubscription* Subscription = ChannelGroupEntity->CreateSubscription(); + TestNotNull("Subscription should be created from channel group entity", Subscription); + if (!Subscription) + { + AddError("Failed to create subscription from channel group entity"); + return false; + } + *TestSubscriptionCreated = true; + + // Set up message listener on the subscription immediately + Subscription->OnPubnubMessageNative.AddLambda([this, TestMessage, TestChannel1, TestUser, TestMessageReceived, ReceivedMessageData](const FPubnubMessageData& ReceivedMessage) + { + *TestMessageReceived = true; + *ReceivedMessageData = ReceivedMessage; + + // Verify message content + TestEqual("Received message content", TestMessage, ReceivedMessage.Message); + TestEqual("Received message channel", TestChannel1, ReceivedMessage.Channel); + TestEqual("Received message UserID", TestUser, ReceivedMessage.UserID); + TestEqual("Received message MessageType", EPubnubMessageType::PMT_Published, ReceivedMessage.MessageType); + TestFalse("Received message timetoken should not be empty", ReceivedMessage.Timetoken.IsEmpty()); + }); + + // Create callback for adding channel to group + FOnAddChannelToGroupResponseNative AddChannelCallback; + AddChannelCallback.BindLambda([this, TestChannelAdded](const FPubnubOperationResult& Result) + { + TestFalse("Add channel to group operation should not have failed", Result.Error); + if (!Result.Error) + { + *TestChannelAdded = true; + } + else + { + AddError(FString::Printf(TEXT("Add channel to group failed with error: %s"), *Result.ErrorMessage)); + } + }); + + // Add a channel to the channel group + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, ChannelGroupEntity, TestChannel1, AddChannelCallback]() + { + ChannelGroupEntity->AddChannelToGroup(TestChannel1, AddChannelCallback); + }, 0.1f)); + + // Wait for channel to be added + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestChannelAdded]() { + return *TestChannelAdded; + }, MAX_WAIT_TIME)); + + // Create subscribe callback + FOnSubscribeOperationResponseNative SubscribeCallback; + SubscribeCallback.BindLambda([this, TestSubscribeResultReceived](const FPubnubOperationResult& Result) + { + *TestSubscribeResultReceived = true; + TestFalse("Subscribe operation should not have failed", Result.Error); + TestEqual("Subscribe HTTP status should be 200", Result.Status, 200); + + if (Result.Error) + { + AddError(FString::Printf(TEXT("Subscribe to channel group failed with error: %s"), *Result.ErrorMessage)); + } + }); + + // Subscribe using the subscription + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, Subscription, SubscribeCallback]() + { + Subscription->Subscribe(SubscribeCallback); + }, 0.1f)); + + // Wait until subscribe result is received + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestSubscribeResultReceived]() { + return *TestSubscribeResultReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestSubscribeResultReceived]() + { + if (!*TestSubscribeResultReceived) + { + AddError("Subscribe result callback was not received for channel group"); + } + }, 0.1f)); + + // Create publish callback + FOnPublishMessageResponseNative PublishCallback; + PublishCallback.BindLambda([this, TestMessage, TestChannel1, TestUser, TestPublishResultReceived] + (const FPubnubOperationResult& Result, const FPubnubMessageData& PublishedMessage) + { + *TestPublishResultReceived = true; + + // Verify publish result + TestFalse("Publish operation should not have failed", Result.Error); + TestEqual("Publish HTTP status should be 200", Result.Status, 200); + + if (!Result.Error) + { + // Verify published message data matches what we sent + TestEqual("Published message data - content", TestMessage, PublishedMessage.Message); + TestEqual("Published message data - channel", TestChannel1, PublishedMessage.Channel); + TestEqual("Published message data - user ID", TestUser, PublishedMessage.UserID); + TestEqual("Published message data - message type", EPubnubMessageType::PMT_Published, PublishedMessage.MessageType); + TestFalse("Published message timetoken should not be empty", PublishedMessage.Timetoken.IsEmpty()); + } + else + { + AddError(FString::Printf(TEXT("Publish to channel in group failed with error: %s"), *Result.ErrorMessage)); + } + }); + + // Publish message to channel that's in the group + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestChannel1, TestMessage, PublishCallback]() + { + // Create a temporary channel entity to publish to the channel that's part of the group + UPubnubChannelEntity* TempChannelEntity = PubnubSubsystem->CreateChannelEntity(TestChannel1); + if (TempChannelEntity) + { + TempChannelEntity->PublishMessage(TestMessage, PublishCallback); + } + else + { + AddError("Failed to create temporary channel entity for publishing"); + } + }, 0.5f)); + + // Wait until publish result is received + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestPublishResultReceived]() { + return *TestPublishResultReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestPublishResultReceived]() + { + if (!*TestPublishResultReceived) + { + AddError("Publish result callback was not received"); + } + }, 0.1f)); + + // Wait until message is received through channel group subscription + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([TestMessageReceived]() { + return *TestMessageReceived; + }, MAX_WAIT_TIME)); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestMessageReceived]() + { + if (!*TestMessageReceived) + { + AddError("Test message was not received through channel group subscription"); + } + }, 0.1f)); + + // Verify test completion + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestChannelGroupCreated, TestChannelAdded, TestSubscriptionCreated, TestMessageReceived]() + { + TestTrue("Channel group entity should be created", *TestChannelGroupCreated); + TestTrue("Channel should be added to group", *TestChannelAdded); + TestTrue("Subscription should be created from channel group", *TestSubscriptionCreated); + TestTrue("Message should be received through channel group subscription", *TestMessageReceived); + }, 0.1f)); + + // Clean up + CleanUp(); + + return true; +} + +bool FPubnubSubscriptionSetTest::RunTest(const FString& Parameters) +{ + // Initial variables + const FString TestUser = SDK_PREFIX + "subscription_set_test_user"; + const FString TestChannel1 = SDK_PREFIX + "set_test_channel_1"; + const FString TestChannel2 = SDK_PREFIX + "set_test_channel_2"; + const FString TestChannelGroup = SDK_PREFIX + "set_test_group"; + TSharedPtr TestSubscriptionSetCreated = MakeShared(false); + TSharedPtr TestSubscriptionSetFromEntitiesCreated = MakeShared(false); + TSharedPtr TestActiveSubscriptionsRetrieved = MakeShared(false); + + if(!InitTest()) + { + AddError("TestInitialization failed"); + return false; + } + + // Check for any errors on the way + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(ErrorMessage); + }); + + // Set User ID - it's necessary for any operation + PubnubSubsystem->SetUserID(TestUser); + + // Create subscription set from channel names immediately + TArray Channels = {TestChannel1, TestChannel2}; + TArray ChannelGroups = {TestChannelGroup}; + + UPubnubSubscriptionSet* SubscriptionSet = PubnubSubsystem->CreateSubscriptionSet(Channels, ChannelGroups); + TestNotNull("Subscription set should be created from channel/group names", SubscriptionSet); + if (!SubscriptionSet) + { + AddError("Failed to create subscription set from channel/group names"); + return false; + } + *TestSubscriptionSetCreated = true; + + // Create subscription set from entities immediately + UPubnubChannelEntity* Entity1 = PubnubSubsystem->CreateChannelEntity(TestChannel1); + UPubnubChannelEntity* Entity2 = PubnubSubsystem->CreateChannelEntity(TestChannel2); + + TestNotNull("Entity 1 should be created", Entity1); + TestNotNull("Entity 2 should be created", Entity2); + + if (!Entity1 || !Entity2) + { + AddError("Failed to create entities for subscription set test"); + return false; + } + + TArray Entities = {Entity1, Entity2}; + UPubnubSubscriptionSet* SubscriptionSetFromEntities = PubnubSubsystem->CreateSubscriptionSetFromEntities(Entities); + TestNotNull("Subscription set should be created from entities", SubscriptionSetFromEntities); + if (!SubscriptionSetFromEntities) + { + AddError("Failed to create subscription set from entities"); + return false; + } + *TestSubscriptionSetFromEntitiesCreated = true; + + // Test getting active subscriptions and subscription sets immediately + TArray ActiveSubscriptions = PubnubSubsystem->GetActiveSubscriptions(); + TArray ActiveSubscriptionSets = PubnubSubsystem->GetActiveSubscriptionSets(); + + // Note: These arrays might be empty if no subscriptions are currently active + // The important thing is that the functions don't crash and return valid arrays + TestTrue("GetActiveSubscriptions should return valid array", ActiveSubscriptions.Num() >= 0); + TestTrue("GetActiveSubscriptionSets should return valid array", ActiveSubscriptionSets.Num() >= 0); + *TestActiveSubscriptionsRetrieved = true; + + // Verify test completion immediately + TestTrue("Subscription set should be created from names", *TestSubscriptionSetCreated); + TestTrue("Subscription set should be created from entities", *TestSubscriptionSetFromEntitiesCreated); + TestTrue("Active subscriptions should be retrievable", *TestActiveSubscriptionsRetrieved); + + // Clean up + CleanUp(); + + return true; +} + +bool FPubnubSubscriptionListenersTest::RunTest(const FString& Parameters) +{ + // Test data for different PubNub event types + const FString TestMessageContent = "\"Listener test message\""; + const FString TestSignalContent = "\"Test signal\""; + const FString TestUser = SDK_PREFIX + "listeners_test_user"; + const FString TestChannel = SDK_PREFIX + "listeners_test_channel"; + + // Counters to track how many times each listener type fires + TSharedPtr MessageListenerFireCount = MakeShared(0); + TSharedPtr SignalListenerFireCount = MakeShared(0); + TSharedPtr PresenceListenerFireCount = MakeShared(0); + TSharedPtr ObjectEventListenerFireCount = MakeShared(0); + TSharedPtr MessageActionListenerFireCount = MakeShared(0); + TSharedPtr UniversalListenerFireCount = MakeShared(0); + + // Flags to track completion of async operations + TSharedPtr SubscriptionActivated = MakeShared(false); + TSharedPtr MessagePublishCompleted = MakeShared(false); + TSharedPtr SignalSendCompleted = MakeShared(false); + TSharedPtr MessageActionAddCompleted = MakeShared(false); + TSharedPtr PresenceEventSentCompleted = MakeShared(false); + TSharedPtr ChannelMetadataSetCompleted = MakeShared(false); + + // Store the timetoken of the published message (needed for valid message actions) + TSharedPtr PublishedMessageTimetoken = MakeShared(""); + + if(!InitTest()) + { + AddError("TestInitialization failed"); + return false; + } + + // Check for any errors on the way + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(ErrorMessage); + }); + + // Set User ID - required for all PubNub operations + PubnubSubsystem->SetUserID(TestUser); + + // Create channel entity for testing all subscription listener types + UPubnubChannelEntity* ChannelEntity = PubnubSubsystem->CreateChannelEntity(TestChannel); + TestNotNull("Channel entity creation should succeed", ChannelEntity); + if (!ChannelEntity) + { + AddError("Failed to create channel entity for listener testing"); + return false; + } + + // Create subscription with presence events enabled to test all listener types + FPubnubSubscribeSettings SubscriptionSettings; + SubscriptionSettings.ReceivePresenceEvents = true; + + UPubnubSubscription* Subscription = ChannelEntity->CreateSubscription(SubscriptionSettings); + TestNotNull("Subscription creation should succeed", Subscription); + if (!Subscription) + { + AddError("Failed to create subscription from channel entity"); + return false; + } + + // Set up all 6 listener types to verify proper event routing and isolation + + // 1. Message Listener - Should only fire for published messages (EPubnubMessageType::PMT_Published) + Subscription->OnPubnubMessageNative.AddLambda([this, TestMessageContent, TestChannel, TestUser, MessageListenerFireCount, PublishedMessageTimetoken](const FPubnubMessageData& ReceivedMessage) + { + (*MessageListenerFireCount)++; + + // Verify this listener received a proper published message + TestEqual("Message listener - correct content received", TestMessageContent, ReceivedMessage.Message); + TestEqual("Message listener - correct channel received", TestChannel, ReceivedMessage.Channel); + TestEqual("Message listener - correct user received", TestUser, ReceivedMessage.UserID); + TestEqual("Message listener - correct message type", EPubnubMessageType::PMT_Published, ReceivedMessage.MessageType); + TestFalse("Message listener - valid timetoken received", ReceivedMessage.Timetoken.IsEmpty()); + + // Store the message timetoken for later message action testing + *PublishedMessageTimetoken = ReceivedMessage.Timetoken; + }); + + // 2. Signal Listener - Should only fire for signals (EPubnubMessageType::PMT_Signal) + Subscription->OnPubnubSignalNative.AddLambda([this, TestSignalContent, TestChannel, TestUser, SignalListenerFireCount](const FPubnubMessageData& ReceivedMessage) + { + (*SignalListenerFireCount)++; + + // Verify this listener received a proper signal + TestEqual("Signal listener - correct content received", TestSignalContent, ReceivedMessage.Message); + TestEqual("Signal listener - correct channel received", TestChannel, ReceivedMessage.Channel); + TestEqual("Signal listener - correct user received", TestUser, ReceivedMessage.UserID); + TestEqual("Signal listener - correct message type", EPubnubMessageType::PMT_Signal, ReceivedMessage.MessageType); + TestFalse("Signal listener - valid timetoken received", ReceivedMessage.Timetoken.IsEmpty()); + }); + + // 3. Presence Listener - Should only fire for presence events (messages sent to "-pnpres" channels) + Subscription->OnPubnubPresenceEventNative.AddLambda([this, TestChannel, PresenceListenerFireCount](const FPubnubMessageData& ReceivedMessage) + { + (*PresenceListenerFireCount)++; + + // Verify this listener received a proper presence event + TestTrue("Presence listener - channel has pnpres suffix", ReceivedMessage.Channel.Contains("-pnpres")); + TestEqual("Presence listener - correct message type", EPubnubMessageType::PMT_Published, ReceivedMessage.MessageType); + TestTrue("Presence listener - contains join action", ReceivedMessage.Message.Contains("\"action\":\"join\"")); + TestTrue("Presence listener - contains user uuid", ReceivedMessage.Message.Contains("\"uuid\":\"Jack-device\"")); + }); + + // 4. Object Event Listener - Should only fire for App Context object events (EPubnubMessageType::PMT_Objects) + Subscription->OnPubnubObjectEventNative.AddLambda([this, TestChannel, ObjectEventListenerFireCount](const FPubnubMessageData& ReceivedMessage) + { + (*ObjectEventListenerFireCount)++; + + // Verify this listener received a proper App Context object event + TestEqual("Object event listener - correct message type", EPubnubMessageType::PMT_Objects, ReceivedMessage.MessageType); + TestTrue("Object event listener - channel relates to test channel", ReceivedMessage.Channel.Contains(TestChannel) || ReceivedMessage.Channel.Equals(TestChannel)); + TestTrue("Object event listener - event data not empty", !ReceivedMessage.Message.IsEmpty()); + }); + + // 5. Message Action Listener - Should only fire for message actions/reactions (EPubnubMessageType::PMT_Action) + Subscription->OnPubnubMessageActionNative.AddLambda([this, TestChannel, MessageActionListenerFireCount](const FPubnubMessageData& ReceivedMessage) + { + (*MessageActionListenerFireCount)++; + + // Verify this listener received a proper message action event + TestEqual("Message action listener - correct message type", EPubnubMessageType::PMT_Action, ReceivedMessage.MessageType); + TestEqual("Message action listener - correct channel", TestChannel, ReceivedMessage.Channel); + TestTrue("Message action listener - action data present", !ReceivedMessage.Message.IsEmpty()); + TestTrue("Message action listener - contains reaction data", ReceivedMessage.Message.Contains("reaction") || ReceivedMessage.Message.Contains("action")); + }); + + // 6. Universal Listener - Should fire for ALL event types (messages, signals, presence, objects, actions) + Subscription->FOnPubnubAnyMessageTypeNative.AddLambda([this, UniversalListenerFireCount](const FPubnubMessageData& ReceivedMessage) + { + (*UniversalListenerFireCount)++; + + // Verify this universal listener receives valid events of any type + TestTrue("Universal listener - receives valid event types", + ReceivedMessage.MessageType == EPubnubMessageType::PMT_Published || + ReceivedMessage.MessageType == EPubnubMessageType::PMT_Signal || + ReceivedMessage.MessageType == EPubnubMessageType::PMT_Action || + ReceivedMessage.MessageType == EPubnubMessageType::PMT_Objects); + TestFalse("Universal listener - channel information present", ReceivedMessage.Channel.IsEmpty()); + }); + + // Create callback to verify subscription activation + FOnSubscribeOperationResponseNative SubscribeCallback; + SubscribeCallback.BindLambda([this, SubscriptionActivated](const FPubnubOperationResult& Result) + { + *SubscriptionActivated = true; + TestFalse("Subscription activation must succeed", Result.Error); + TestEqual("Subscription HTTP response must be 200", Result.Status, 200); + + if (Result.Error) + { + AddError(FString::Printf(TEXT("Subscription activation failed: %s"), *Result.ErrorMessage)); + } + }); + + // Activate the subscription to start receiving all event types + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, Subscription, SubscribeCallback]() + { + Subscription->Subscribe(SubscribeCallback); + }, 0.1f)); + + // Wait for subscription to be fully active before sending events + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([SubscriptionActivated]() { + return *SubscriptionActivated; + }, MAX_WAIT_TIME)); + + // Reset all listener fire counts to ensure clean testing + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([MessageListenerFireCount, SignalListenerFireCount, PresenceListenerFireCount, ObjectEventListenerFireCount, MessageActionListenerFireCount, UniversalListenerFireCount]() + { + *MessageListenerFireCount = 0; + *SignalListenerFireCount = 0; + *PresenceListenerFireCount = 0; + *ObjectEventListenerFireCount = 0; + *MessageActionListenerFireCount = 0; + *UniversalListenerFireCount = 0; + }, 0.1f)); + + // === EVENT SEQUENCE: Test each event type to verify proper listener routing === + + // EVENT 1: Publish a regular message (should trigger Message + Universal listeners) + FOnPublishMessageResponseNative PublishMessageCallback; + PublishMessageCallback.BindLambda([this, MessagePublishCompleted](const FPubnubOperationResult& Result, const FPubnubMessageData& PublishedMessage) + { + *MessagePublishCompleted = true; + TestFalse("Message publish operation must succeed", Result.Error); + if (Result.Error) + { + AddError(FString::Printf(TEXT("Message publish failed: %s"), *Result.ErrorMessage)); + } + }); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, ChannelEntity, TestMessageContent, PublishMessageCallback]() + { + ChannelEntity->PublishMessage(TestMessageContent, PublishMessageCallback); + }, 0.5f)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([MessagePublishCompleted]() { + return *MessagePublishCompleted; + }, MAX_WAIT_TIME)); + + // Allow time for message event to be processed and listeners to fire + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([]() + { + // Processing delay to ensure message listener has fired + }, 1.0f)); + + // EVENT 2: Send a signal (should trigger Signal + Universal listeners) + FOnSignalResponseNative SendSignalCallback; + SendSignalCallback.BindLambda([this, SignalSendCompleted](const FPubnubOperationResult& Result, const FPubnubMessageData& SentSignal) + { + *SignalSendCompleted = true; + TestFalse("Signal send operation must succeed", Result.Error); + if (Result.Error) + { + AddError(FString::Printf(TEXT("Signal send failed: %s"), *Result.ErrorMessage)); + } + }); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, ChannelEntity, TestSignalContent, SendSignalCallback]() + { + ChannelEntity->Signal(TestSignalContent, SendSignalCallback); + }, 0.5f)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([SignalSendCompleted]() { + return *SignalSendCompleted; + }, MAX_WAIT_TIME)); + + // Allow time for signal event to be processed and listeners to fire + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([]() + { + // Processing delay to ensure signal listener has fired + }, 1.0f)); + + // EVENT 3: Add message action/reaction (should trigger MessageAction + Universal listeners) + FOnAddMessageActionResponseNative AddMessageActionCallback; + AddMessageActionCallback.BindLambda([this, MessageActionAddCompleted](const FPubnubOperationResult& Result, const FPubnubMessageActionData& ActionData) + { + *MessageActionAddCompleted = true; + TestFalse("Add message action operation must succeed", Result.Error); + if (Result.Error) + { + AddError(FString::Printf(TEXT("Add message action failed: %s"), *Result.ErrorMessage)); + } + }); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestChannel, AddMessageActionCallback, PublishedMessageTimetoken]() + { + // Add a reaction to the published message using its real timetoken + if (!PublishedMessageTimetoken->IsEmpty()) + { + PubnubSubsystem->AddMessageAction(TestChannel, *PublishedMessageTimetoken, "reaction", "👍", AddMessageActionCallback); + } + else + { + AddError("Cannot add message action - published message timetoken not captured"); + } + }, 0.5f)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([MessageActionAddCompleted]() { + return *MessageActionAddCompleted; + }, MAX_WAIT_TIME)); + + // Allow time for message action event to be processed and listeners to fire + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([]() + { + // Processing delay to ensure message action listener has fired + }, 1.0f)); + + // EVENT 4: Send presence event (should trigger Presence + Universal listeners) + const FString PresenceChannelName = TestChannel + "-pnpres"; + const FString PresenceEventData = "{\"action\":\"join\",\"uuid\":\"Jack-device\",\"timestamp\":1756891149,\"precise_timestamp\":1756891149913,\"occupancy\":2}"; + + FOnPublishMessageResponseNative SendPresenceEventCallback; + SendPresenceEventCallback.BindLambda([this, PresenceEventSentCompleted](const FPubnubOperationResult& Result, const FPubnubMessageData& PublishedMessage) + { + *PresenceEventSentCompleted = true; + TestFalse("Presence event send operation must succeed", Result.Error); + if (Result.Error) + { + AddError(FString::Printf(TEXT("Presence event send failed: %s"), *Result.ErrorMessage)); + } + }); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, PresenceChannelName, PresenceEventData, SendPresenceEventCallback]() + { + // Send simulated presence event to trigger presence listener + PubnubSubsystem->PublishMessage(PresenceChannelName, PresenceEventData, SendPresenceEventCallback); + }, 0.5f)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([PresenceEventSentCompleted]() { + return *PresenceEventSentCompleted; + }, MAX_WAIT_TIME)); + + // Allow time for presence event to be processed and listeners to fire + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([]() + { + // Processing delay to ensure presence listener has fired + }, 1.0f)); + + // EVENT 5: Set channel metadata (should trigger Object + Universal listeners) + FOnSetChannelMetadataResponseNative SetChannelMetadataCallback; + SetChannelMetadataCallback.BindLambda([this, ChannelMetadataSetCompleted](const FPubnubOperationResult& Result, const FPubnubChannelData& ChannelData) + { + *ChannelMetadataSetCompleted = true; + TestFalse("Set channel metadata operation must succeed", Result.Error); + if (Result.Error) + { + AddError(FString::Printf(TEXT("Set channel metadata failed: %s"), *Result.ErrorMessage)); + } + }); + + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, TestChannel, SetChannelMetadataCallback]() + { + // Create channel metadata entity and set metadata to trigger object event + UPubnubChannelMetadataEntity* ChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(TestChannel); + if (ChannelMetadataEntity) + { + // Use unique metadata to ensure object event is triggered (same metadata won't trigger event) + FPubnubChannelData UniqueChannelMetadata; + UniqueChannelMetadata.ChannelID = TestChannel; + UniqueChannelMetadata.ChannelName = UPubnubTimetokenUtilities::GetCurrentUnixTimetoken(); // Unique name ensures event triggers + UniqueChannelMetadata.Description = "Listener test metadata"; + + ChannelMetadataEntity->SetChannelMetadata(UniqueChannelMetadata, SetChannelMetadataCallback); + } + }, 0.5f)); + + ADD_LATENT_AUTOMATION_COMMAND(FWaitUntilLatentCommand([ChannelMetadataSetCompleted]() { + return *ChannelMetadataSetCompleted; + }, MAX_WAIT_TIME)); + + // Allow time for object event to be processed and listeners to fire, then verify all results + ADD_LATENT_AUTOMATION_COMMAND(FDelayedFunctionLatentCommand([this, MessageListenerFireCount, SignalListenerFireCount, PresenceListenerFireCount, ObjectEventListenerFireCount, MessageActionListenerFireCount, UniversalListenerFireCount]() + { + // === COMPREHENSIVE LISTENER VERIFICATION === + // This test verifies that the subscription system properly routes events to the correct listeners + // and that the universal listener captures all events without duplication or loss. + + // Verify each specific listener fired exactly once for its designated event type + TestEqual("Message listener isolation: fires only for published messages", *MessageListenerFireCount, 1); + TestEqual("Signal listener isolation: fires only for signals", *SignalListenerFireCount, 1); + TestEqual("Presence listener isolation: fires only for presence events", *PresenceListenerFireCount, 1); + TestEqual("Object event listener isolation: fires only for App Context object events", *ObjectEventListenerFireCount, 1); + TestEqual("Message action listener isolation: fires only for message actions", *MessageActionListenerFireCount, 1); + + // Verify universal listener captured all events (sum of all specific listeners) + int32 ExpectedUniversalFireCount = *MessageListenerFireCount + *SignalListenerFireCount + + *PresenceListenerFireCount + *ObjectEventListenerFireCount + + *MessageActionListenerFireCount; + TestEqual("Universal listener completeness: captures all events exactly", *UniversalListenerFireCount, ExpectedUniversalFireCount); + TestEqual("Universal listener total count: exactly 5 events", *UniversalListenerFireCount, 5); + + // Final verification: perfect listener behavior with complete isolation and universal coverage + bool bListenerSystemWorking = + (*MessageListenerFireCount == 1) && // Message listener: exactly 1 fire + (*SignalListenerFireCount == 1) && // Signal listener: exactly 1 fire + (*PresenceListenerFireCount == 1) && // Presence listener: exactly 1 fire + (*ObjectEventListenerFireCount == 1) && // Object listener: exactly 1 fire + (*MessageActionListenerFireCount == 1) && // Action listener: exactly 1 fire + (*UniversalListenerFireCount == 5); // Universal listener: exactly 5 fires (sum) + + TestTrue("Complete listener system verification: isolation + universal coverage", bListenerSystemWorking); + + // Provide detailed diagnostics if any listener failed to behave correctly + if (!bListenerSystemWorking) + { + AddError(FString::Printf(TEXT("Listener system failure - Fire counts: Message=%d, Signal=%d, Presence=%d, Object=%d, Action=%d, Universal=%d"), + *MessageListenerFireCount, *SignalListenerFireCount, *PresenceListenerFireCount, + *ObjectEventListenerFireCount, *MessageActionListenerFireCount, *UniversalListenerFireCount)); + } + }, 1.0f)); + + // Clean up + CleanUp(); + + return true; +} + +bool FPubnubSubscriptionSetManagementTest::RunTest(const FString& Parameters) +{ + // Test data for subscription set management operations + const FString TestUser = SDK_PREFIX + "subscription_management_user"; + const FString TestChannel1 = SDK_PREFIX + "management_channel_1"; + const FString TestChannel2 = SDK_PREFIX + "management_channel_2"; + const FString TestChannel3 = SDK_PREFIX + "management_channel_3"; + const FString TestChannelGroup = SDK_PREFIX + "management_group"; + + // Flags to track operation completions + TSharedPtr InitializationCompleted = MakeShared(false); + + if(!InitTest()) + { + AddError("Test initialization failed for subscription set management test"); + return false; + } + + // Error handling setup + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(FString::Printf(TEXT("PubNub error during subscription management test: %s"), *ErrorMessage)); + }); + + // Set User ID required for operations + PubnubSubsystem->SetUserID(TestUser); + + // Create multiple channel entities for subscription set testing + UPubnubChannelEntity* ChannelEntity1 = PubnubSubsystem->CreateChannelEntity(TestChannel1); + UPubnubChannelEntity* ChannelEntity2 = PubnubSubsystem->CreateChannelEntity(TestChannel2); + UPubnubChannelEntity* ChannelEntity3 = PubnubSubsystem->CreateChannelEntity(TestChannel3); + + TestNotNull("Channel entity 1 creation must succeed", ChannelEntity1); + TestNotNull("Channel entity 2 creation must succeed", ChannelEntity2); + TestNotNull("Channel entity 3 creation must succeed", ChannelEntity3); + + if (!ChannelEntity1 || !ChannelEntity2 || !ChannelEntity3) + { + AddError("Failed to create required channel entities for subscription management test"); + return false; + } + + // Create individual subscriptions from entities + UPubnubSubscription* IndividualSubscription1 = ChannelEntity1->CreateSubscription(); + UPubnubSubscription* IndividualSubscription2 = ChannelEntity2->CreateSubscription(); + UPubnubSubscription* IndividualSubscription3 = ChannelEntity3->CreateSubscription(); + + TestNotNull("Individual subscription 1 creation must succeed", IndividualSubscription1); + TestNotNull("Individual subscription 2 creation must succeed", IndividualSubscription2); + TestNotNull("Individual subscription 3 creation must succeed", IndividualSubscription3); + + // Create initial subscription set from channel names + TArray InitialChannels = {TestChannel1, TestChannel2}; + TArray InitialChannelGroups = {TestChannelGroup}; + UPubnubSubscriptionSet* InitialSubscriptionSet = PubnubSubsystem->CreateSubscriptionSet(InitialChannels, InitialChannelGroups); + TestNotNull("Initial subscription set creation must succeed", InitialSubscriptionSet); + + // Verify initial subscription set state + TArray InitialSubscriptions = InitialSubscriptionSet->GetSubscriptions(); + TestEqual("Initial subscription set should be empty (no individual subscriptions added yet)", InitialSubscriptions.Num(), 0); + + // TEST 1: Add individual subscription to subscription set + InitialSubscriptionSet->AddSubscription(IndividualSubscription1); + TArray SubscriptionsAfterAdd = InitialSubscriptionSet->GetSubscriptions(); + TestEqual("Subscription set should contain 1 subscription after adding", SubscriptionsAfterAdd.Num(), 1); + TestEqual("Added subscription should match the one we added", SubscriptionsAfterAdd[0], IndividualSubscription1); + + // TEST 2: Add second individual subscription to same set + InitialSubscriptionSet->AddSubscription(IndividualSubscription2); + TArray SubscriptionsAfterSecondAdd = InitialSubscriptionSet->GetSubscriptions(); + TestEqual("Subscription set should contain 2 subscriptions after adding second", SubscriptionsAfterSecondAdd.Num(), 2); + TestTrue("Set should contain first subscription", SubscriptionsAfterSecondAdd.Contains(IndividualSubscription1)); + TestTrue("Set should contain second subscription", SubscriptionsAfterSecondAdd.Contains(IndividualSubscription2)); + + // TEST 3: Create second subscription set and combine individual subscription to create third set + UPubnubSubscriptionSet* CombinedSubscriptionSet = IndividualSubscription1->AddSubscription(IndividualSubscription3); + TestNotNull("Combined subscription set creation must succeed", CombinedSubscriptionSet); + + TArray CombinedSubscriptions = CombinedSubscriptionSet->GetSubscriptions(); + TestEqual("Combined subscription set should contain 2 subscriptions", CombinedSubscriptions.Num(), 2); + TestTrue("Combined set should contain first subscription", CombinedSubscriptions.Contains(IndividualSubscription1)); + TestTrue("Combined set should contain third subscription", CombinedSubscriptions.Contains(IndividualSubscription3)); + + // TEST 4: Merge subscription sets using AddSubscriptionSet + UPubnubSubscriptionSet* MainSet = PubnubSubsystem->CreateSubscriptionSet(TArray{TestChannel1}, TArray()); + UPubnubSubscriptionSet* SetToMerge = PubnubSubsystem->CreateSubscriptionSet(TArray{TestChannel2}, TArray()); + + TestNotNull("Main subscription set for merging must be created", MainSet); + TestNotNull("Set to merge must be created", SetToMerge); + + // Add subscription to main set before merging + MainSet->AddSubscription(IndividualSubscription1); + TArray MainSetBeforeMerge = MainSet->GetSubscriptions(); + TestEqual("Main set should have 1 subscription before merge", MainSetBeforeMerge.Num(), 1); + + // Perform the merge operation + MainSet->AddSubscriptionSet(SetToMerge); + TArray MainSetAfterMerge = MainSet->GetSubscriptions(); + TestEqual("Main set should still have 1 subscription after merge (sets merge channels, not subscriptions)", MainSetAfterMerge.Num(), 1); + + // TEST 5: Remove subscription from subscription set + InitialSubscriptionSet->RemoveSubscription(IndividualSubscription1); + TArray SubscriptionsAfterRemove = InitialSubscriptionSet->GetSubscriptions(); + TestEqual("Subscription set should have 1 subscription after removing first", SubscriptionsAfterRemove.Num(), 1); + TestFalse("Set should not contain removed subscription", SubscriptionsAfterRemove.Contains(IndividualSubscription1)); + TestTrue("Set should still contain second subscription", SubscriptionsAfterRemove.Contains(IndividualSubscription2)); + + // TEST 6: Remove subscription set from another subscription set + UPubnubSubscriptionSet* SetForSubtraction = PubnubSubsystem->CreateSubscriptionSet(TArray{TestChannel2}, TArray()); + MainSet->RemoveSubscriptionSet(SetForSubtraction); + TArray MainSetAfterSubtraction = MainSet->GetSubscriptions(); + TestEqual("Main set should still have 1 subscription after subtraction", MainSetAfterSubtraction.Num(), 1); + + // Final verification of subscription set state + TestTrue("All subscription set management operations completed successfully", + (SubscriptionsAfterRemove.Num() == 1) && + (CombinedSubscriptions.Num() == 2) && + (MainSetAfterMerge.Num() == 1)); + + CleanUp(); + return true; +} + +bool FPubnubEntityCreationAndTrackingTest::RunTest(const FString& Parameters) +{ + // Test data for entity creation and tracking operations + const FString TestUser = SDK_PREFIX + "entity_tracking_user"; + const FString TestChannel = SDK_PREFIX + "tracking_channel"; + const FString TestChannelGroup = SDK_PREFIX + "tracking_group"; + const FString TestUserID = SDK_PREFIX + "tracking_user_metadata"; + const FString TestChannelForMetadata = SDK_PREFIX + "tracking_channel_metadata"; + + if(!InitTest()) + { + AddError("Test initialization failed for entity creation and tracking test"); + return false; + } + + // Error handling setup + PubnubSubsystem->OnPubnubErrorNative.AddLambda([this](FString ErrorMessage, EPubnubErrorType ErrorType) + { + AddError(FString::Printf(TEXT("PubNub error during entity tracking test: %s"), *ErrorMessage)); + }); + + // Set User ID required for operations + PubnubSubsystem->SetUserID(TestUser); + + // Get baseline active counts before creating entities + TArray ActiveSubscriptionsBaseline = PubnubSubsystem->GetActiveSubscriptions(); + TArray ActiveSubscriptionSetsBaseline = PubnubSubsystem->GetActiveSubscriptionSets(); + int32 BaselineActiveSubscriptionCount = ActiveSubscriptionsBaseline.Num(); + int32 BaselineActiveSubscriptionSetCount = ActiveSubscriptionSetsBaseline.Num(); + + // TEST 1: Create all entity types and verify they work correctly + UPubnubChannelEntity* CreatedChannelEntity = PubnubSubsystem->CreateChannelEntity(TestChannel); + TestNotNull("Channel entity creation must succeed", CreatedChannelEntity); + TestEqual("Channel entity must have correct ID", CreatedChannelEntity->EntityID, TestChannel); + TestEqual("Channel entity must have correct type", CreatedChannelEntity->EntityType, EPubnubEntityType::PEnT_Channel); + + UPubnubChannelGroupEntity* CreatedChannelGroupEntity = PubnubSubsystem->CreateChannelGroupEntity(TestChannelGroup); + TestNotNull("Channel group entity creation must succeed", CreatedChannelGroupEntity); + TestEqual("Channel group entity must have correct ID", CreatedChannelGroupEntity->EntityID, TestChannelGroup); + TestEqual("Channel group entity must have correct type", CreatedChannelGroupEntity->EntityType, EPubnubEntityType::PEnT_ChannelGroup); + + UPubnubChannelMetadataEntity* CreatedChannelMetadataEntity = PubnubSubsystem->CreateChannelMetadataEntity(TestChannelForMetadata); + TestNotNull("Channel metadata entity creation must succeed", CreatedChannelMetadataEntity); + TestEqual("Channel metadata entity must have correct ID", CreatedChannelMetadataEntity->EntityID, TestChannelForMetadata); + TestEqual("Channel metadata entity must have correct type", CreatedChannelMetadataEntity->EntityType, EPubnubEntityType::PEnT_ChannelMetadata); + + UPubnubUserMetadataEntity* CreatedUserMetadataEntity = PubnubSubsystem->CreateUserMetadataEntity(TestUserID); + TestNotNull("User metadata entity creation must succeed", CreatedUserMetadataEntity); + TestEqual("User metadata entity must have correct ID", CreatedUserMetadataEntity->EntityID, TestUserID); + TestEqual("User metadata entity must have correct type", CreatedUserMetadataEntity->EntityType, EPubnubEntityType::PEnT_UserMetadata); + + // TEST 2: Create subscriptions and subscription sets, then verify tracking + UPubnubSubscription* TrackedSubscription1 = CreatedChannelEntity->CreateSubscription(); + UPubnubSubscription* TrackedSubscription2 = CreatedChannelGroupEntity->CreateSubscription(); + TestNotNull("Tracked subscription 1 creation must succeed", TrackedSubscription1); + TestNotNull("Tracked subscription 2 creation must succeed", TrackedSubscription2); + + UPubnubSubscriptionSet* TrackedSubscriptionSet1 = PubnubSubsystem->CreateSubscriptionSet(TArray{TestChannel}, TArray()); + UPubnubSubscriptionSet* TrackedSubscriptionSet2 = PubnubSubsystem->CreateSubscriptionSetFromEntities(TArray{CreatedChannelEntity, CreatedChannelGroupEntity}); + TestNotNull("Tracked subscription set 1 creation must succeed", TrackedSubscriptionSet1); + TestNotNull("Tracked subscription set 2 creation must succeed", TrackedSubscriptionSet2); + + // TEST 3: Verify GetActiveSubscriptions tracks individual subscriptions correctly + TArray CurrentActiveSubscriptions = PubnubSubsystem->GetActiveSubscriptions(); + TestTrue("Active subscriptions count should increase after creating subscriptions", + CurrentActiveSubscriptions.Num() >= BaselineActiveSubscriptionCount); + + // Verify that our created subscriptions are being tracked (if they're in active state) + bool bSubscription1IsTracked = CurrentActiveSubscriptions.Contains(TrackedSubscription1); + bool bSubscription2IsTracked = CurrentActiveSubscriptions.Contains(TrackedSubscription2); + TestTrue("GetActiveSubscriptions must return valid array", CurrentActiveSubscriptions.Num() >= 0); + + // TEST 4: Verify GetActiveSubscriptionSets tracks subscription sets correctly + TArray CurrentActiveSubscriptionSets = PubnubSubsystem->GetActiveSubscriptionSets(); + TestTrue("Active subscription sets count should increase after creating sets", + CurrentActiveSubscriptionSets.Num() >= BaselineActiveSubscriptionSetCount); + + // Verify that our created subscription sets are being tracked (if they're in active state) + bool bSubscriptionSet1IsTracked = CurrentActiveSubscriptionSets.Contains(TrackedSubscriptionSet1); + bool bSubscriptionSet2IsTracked = CurrentActiveSubscriptionSets.Contains(TrackedSubscriptionSet2); + TestTrue("GetActiveSubscriptionSets must return valid array", CurrentActiveSubscriptionSets.Num() >= 0); + + // TEST 5: Verify subscription set manipulation functions work correctly + TrackedSubscriptionSet1->AddSubscription(TrackedSubscription1); + TArray SetContentsAfterAdd = TrackedSubscriptionSet1->GetSubscriptions(); + TestEqual("Subscription set should contain 1 subscription after adding", SetContentsAfterAdd.Num(), 1); + TestEqual("Added subscription should be the correct one", SetContentsAfterAdd[0], TrackedSubscription1); + + TrackedSubscriptionSet1->AddSubscription(TrackedSubscription2); + TArray SetContentsAfterSecondAdd = TrackedSubscriptionSet1->GetSubscriptions(); + TestEqual("Subscription set should contain 2 subscriptions after adding second", SetContentsAfterSecondAdd.Num(), 2); + TestTrue("Set should contain both subscriptions", SetContentsAfterSecondAdd.Contains(TrackedSubscription1) && SetContentsAfterSecondAdd.Contains(TrackedSubscription2)); + + TrackedSubscriptionSet1->RemoveSubscription(TrackedSubscription1); + TArray SetContentsAfterRemove = TrackedSubscriptionSet1->GetSubscriptions(); + TestEqual("Subscription set should contain 1 subscription after removing first", SetContentsAfterRemove.Num(), 1); + TestFalse("Set should not contain removed subscription", SetContentsAfterRemove.Contains(TrackedSubscription1)); + TestTrue("Set should still contain second subscription", SetContentsAfterRemove.Contains(TrackedSubscription2)); + + // TEST 6: Test subscription set merging and subtraction + TrackedSubscriptionSet1->AddSubscriptionSet(TrackedSubscriptionSet2); + TArray SetContentsAfterMerge = TrackedSubscriptionSet1->GetSubscriptions(); + TestTrue("Subscription set contents should be valid after merge operation", SetContentsAfterMerge.Num() >= 1); + + TrackedSubscriptionSet1->RemoveSubscriptionSet(TrackedSubscriptionSet2); + TArray SetContentsAfterSubtraction = TrackedSubscriptionSet1->GetSubscriptions(); + TestTrue("Subscription set contents should be valid after subtraction operation", SetContentsAfterSubtraction.Num() >= 0); + + // Final verification of all entity creation and tracking functionality + TestTrue("All entity types created successfully", + IsValid(CreatedChannelEntity) && + IsValid(CreatedChannelGroupEntity) && + IsValid(CreatedChannelMetadataEntity) && + IsValid(CreatedUserMetadataEntity)); + + TestTrue("All subscription objects created successfully", + IsValid(TrackedSubscription1) && + IsValid(TrackedSubscription2) && + IsValid(TrackedSubscriptionSet1) && + IsValid(TrackedSubscriptionSet2)); + + TestTrue("Active tracking functions work correctly", + (CurrentActiveSubscriptions.Num() >= BaselineActiveSubscriptionCount) && + (CurrentActiveSubscriptionSets.Num() >= BaselineActiveSubscriptionSetCount)); + + CleanUp(); + return true; +} + +#endif // WITH_DEV_AUTOMATION_TESTS \ No newline at end of file diff --git a/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelEntity.h b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelEntity.h new file mode 100644 index 0000000..0eb650d --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelEntity.h @@ -0,0 +1,129 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +// snippet.includes +#include "PubnubSubsystem.h" +#include "Entities/PubnubChannelEntity.h" +#include "Entities/PubnubSubscription.h" + +// snippet.end + +#include "CoreMinimal.h" +#include "Samples/PubnubSampleBase.h" +#include "Sample_ChannelEntity.generated.h" + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_ChannelEntity : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_ChannelEntity(); + + + /* CHANNEL ENTITY SAMPLE FUNCTIONS */ + + // snippet.create_channel_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void CreateChannelEntitySample(); + + // snippet.publish_simple_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void PublishSimpleSample(); + + // snippet.publish_with_settings_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void PublishWithSettingsSample(); + + // snippet.publish_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void PublishWithResultSample(); + + UFUNCTION() + void PublishedMessageResponse(FPubnubOperationResult Result, FPubnubMessageData Message); + + // snippet.publish_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void PublishWithResultLambdaSample(); + + // snippet.simple_signal_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void SimpleSignalSample(); + + // snippet.signal_with_settings_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void SignalWithSettingsSample(); + + // snippet.signal_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void SignalWithResultSample(); + + UFUNCTION() + void SignalMessageResponse(FPubnubOperationResult Result, FPubnubMessageData Message); + + // snippet.signal_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void SignalWithResultLambdaSample(); + + // snippet.list_users_from_channel_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void ListUsersFromChannelSample(); + + UFUNCTION() + void OnListUsersFromChannelResponse_Simple(FPubnubOperationResult Result, FPubnubListUsersFromChannelWrapper Data); + + // snippet.list_users_from_channel_with_settings_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void ListUsersFromChannelWithSettingsSample(); + + UFUNCTION() + void OnListUsersFromChannelResponse_WithSettings(FPubnubOperationResult Result, FPubnubListUsersFromChannelWrapper Data); + + // snippet.list_users_from_channel_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void ListUsersFromChannelWithLambdaSample(); + + // snippet.subscribe_with_channel_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void SubscribeWithChannelEntitySample(); + + UFUNCTION() + void OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message); + + // snippet.channel_entity_all_listeners + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void ChannelEntityAllListenersSample(); + + UFUNCTION() + void OnMessage_AllListenersSample(FPubnubMessageData Message); + + UFUNCTION() + void OnSignal_AllListenersSample(FPubnubMessageData Message); + + UFUNCTION() + void OnPresenceEvent_AllListenersSample(FPubnubMessageData Message); + + UFUNCTION() + void OnObjectEvent_AllListenersSample(FPubnubMessageData Message); + + UFUNCTION() + void OnMessageAction_AllListenersSample(FPubnubMessageData Message); + + UFUNCTION() + void OnAnyEvent_AllListenersSample(FPubnubMessageData Message); + + // snippet.channel_entity_publish_message + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelEntity") + void ChannelEntityPublishMessageSample(); + + UFUNCTION() + void OnPublishResult_ChannelEntitySample(FPubnubOperationResult Result, FPubnubMessageData Message); + + + // snippet.end +}; diff --git a/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelGroupEntity.h b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelGroupEntity.h new file mode 100644 index 0000000..dd110f0 --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelGroupEntity.h @@ -0,0 +1,101 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +// snippet.includes +#include "PubnubSubsystem.h" +#include "Entities/PubnubChannelGroupEntity.h" +#include "Entities/PubnubSubscription.h" + +// snippet.end + +#include "CoreMinimal.h" +#include "Samples/PubnubSampleBase.h" +#include "Sample_ChannelGroupEntity.generated.h" + + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_ChannelGroupEntity : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_ChannelGroupEntity(); + + + /* CHANNEL GROUP ENTITY SAMPLE FUNCTIONS */ + + // snippet.create_channel_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void CreateChannelGroupEntitySample(); + + // snippet.subscribe_with_channel_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void SubscribeWithChannelGroupEntitySample(); + + UFUNCTION() + void OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message); + + // snippet.add_channel_to_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void AddChannelToGroupSample(); + + // snippet.add_channel_to_group_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void AddChannelToGroupWithResultSample(); + + UFUNCTION() + void OnAddChannelToGroupResponse(FPubnubOperationResult Result); + + // snippet.add_channel_to_group_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void AddChannelToGroupWithResultLambdaSample(); + + // snippet.list_channels_from_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void ListChannelsFromGroupSample(); + + UFUNCTION() + void OnListChannelsFromGroupResponse(FPubnubOperationResult Result, const TArray& Channels); + + // snippet.list_channels_from_group_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void ListChannelsFromGroupWithLambdaSample(); + + // snippet.remove_channel_from_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelFromGroupSample(); + + // snippet.remove_channel_from_group_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelFromGroupWithResultSample(); + + UFUNCTION() + void OnRemoveChannelFromGroupResponse(FPubnubOperationResult Result); + + // snippet.remove_channel_from_group_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelFromGroupWithResultLambdaSample(); + + // snippet.remove_channel_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelGroupSample(); + + // snippet.remove_channel_group_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelGroupWithResultSample(); + + UFUNCTION() + void OnRemoveChannelGroupResponse(FPubnubOperationResult Result); + + // snippet.remove_channel_group_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelGroupEntity") + void RemoveChannelGroupWithResultLambdaSample(); + + + // snippet.end +}; diff --git a/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelMetadataEntity.h b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelMetadataEntity.h new file mode 100644 index 0000000..85ffca6 --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelMetadataEntity.h @@ -0,0 +1,93 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +// snippet.includes +#include "PubnubSubsystem.h" +#include "Entities/PubnubChannelMetadataEntity.h" +#include "Entities/PubnubSubscription.h" + +// snippet.end + +#include "CoreMinimal.h" +#include "Samples/PubnubSampleBase.h" +#include "Sample_ChannelMetadataEntity.generated.h" + + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_ChannelMetadataEntity : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_ChannelMetadataEntity(); + + + /* CHANNEL METADATA ENTITY SAMPLE FUNCTIONS */ + + // snippet.create_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void CreateChannelMetadataEntitySample(); + + // snippet.subscribe_with_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void SubscribeWithChannelMetadataEntitySample(); + + UFUNCTION() + void OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message); + + // snippet.channel_metadata_entity_set_metadata + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void ChannelMetadataEntitySetMetadataSample(); + + UFUNCTION() + void OnSetChannelMetadataResult_Sample(FPubnubOperationResult Result, FPubnubChannelData ChannelData); + + // snippet.set_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void SetChannelMetadataSample(); + + // snippet.set_channel_metadata_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void SetChannelMetadataWithResultSample(); + + UFUNCTION() + void OnSetChannelMetadataResponse(FPubnubOperationResult Result, FPubnubChannelData ChannelData); + + // snippet.set_channel_metadata_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void SetChannelMetadataWithLambdaSample(); + + // snippet.get_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void GetChannelMetadataSample(); + + UFUNCTION() + void OnGetChannelMetadataResponse_Simple(FPubnubOperationResult Result, FPubnubChannelData ChannelData); + + // snippet.get_channel_metadata_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void GetChannelMetadataWithLambdaSample(); + + // snippet.remove_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void RemoveChannelMetadataSample(); + + // snippet.remove_channel_metadata_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void RemoveChannelMetadataWithResultSample(); + + UFUNCTION() + void OnRemoveChannelMetadataResponse(FPubnubOperationResult Result); + + // snippet.remove_channel_metadata_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|ChannelMetadataEntity") + void RemoveChannelMetadataWithResultLambdaSample(); + + + // snippet.end +}; diff --git a/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_SubscriptionSet.h b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_SubscriptionSet.h new file mode 100644 index 0000000..4dea9f0 --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_SubscriptionSet.h @@ -0,0 +1,59 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +// snippet.includes +#include "PubnubSubsystem.h" +#include "Entities/PubnubChannelEntity.h" +#include "Entities/PubnubChannelGroupEntity.h" +#include "Entities/PubnubChannelMetadataEntity.h" +#include "Entities/PubnubUserMetadataEntity.h" +#include "Entities/PubnubSubscription.h" + +// snippet.end + +#include "CoreMinimal.h" +#include "Samples/PubnubSampleBase.h" +#include "Sample_SubscriptionSet.generated.h" + + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_SubscriptionSet : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_SubscriptionSet(); + + + /* SUBSCRIPTION SET SAMPLE FUNCTIONS */ + + // snippet.create_subscription_set_from_names + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|SubscriptionSet") + void CreateSubscriptionSetFromNamesSample(); + + UFUNCTION() + void OnMessage_SubscriptionSetFromNamesSample(FPubnubMessageData Message); + + // snippet.create_subscription_set_from_entities + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|SubscriptionSet") + void CreateSubscriptionSetFromEntitiesSample(); + + UFUNCTION() + void OnMessage_SubscriptionSetFromEntitiesSample(FPubnubMessageData Message); + + // snippet.subscription_set_add_remove_subscriptions + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|SubscriptionSet") + void SubscriptionSetAddRemoveSubscriptionsSample(); + + // snippet.subscription_set_merge_operations + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|SubscriptionSet") + void SubscriptionSetMergeOperationsSample(); + + + // snippet.end +}; diff --git a/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_UserMetadataEntity.h b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_UserMetadataEntity.h new file mode 100644 index 0000000..52941d1 --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Entities/Sample_UserMetadataEntity.h @@ -0,0 +1,93 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +// snippet.includes +#include "PubnubSubsystem.h" +#include "Entities/PubnubUserMetadataEntity.h" +#include "Entities/PubnubSubscription.h" + +// snippet.end + +#include "CoreMinimal.h" +#include "Samples/PubnubSampleBase.h" +#include "Sample_UserMetadataEntity.generated.h" + + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_UserMetadataEntity : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_UserMetadataEntity(); + + + /* USER METADATA ENTITY SAMPLE FUNCTIONS */ + + // snippet.create_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void CreateUserMetadataEntitySample(); + + // snippet.subscribe_with_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void SubscribeWithUserMetadataEntitySample(); + + UFUNCTION() + void OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message); + + // snippet.user_metadata_entity_set_metadata + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void UserMetadataEntitySetMetadataSample(); + + UFUNCTION() + void OnSetUserMetadataResult_Sample(FPubnubOperationResult Result, FPubnubUserData UserData); + + // snippet.set_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void SetUserMetadataSample(); + + // snippet.set_user_metadata_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void SetUserMetadataWithResultSample(); + + UFUNCTION() + void OnSetUserMetadataResponse(FPubnubOperationResult Result, FPubnubUserData UserData); + + // snippet.set_user_metadata_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void SetUserMetadataWithLambdaSample(); + + // snippet.get_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void GetUserMetadataSample(); + + UFUNCTION() + void OnGetUserMetadataResponse_Simple(FPubnubOperationResult Result, FPubnubUserData UserData); + + // snippet.get_user_metadata_with_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void GetUserMetadataWithLambdaSample(); + + // snippet.remove_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void RemoveUserMetadataSample(); + + // snippet.remove_user_metadata_with_result_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void RemoveUserMetadataWithResultSample(); + + UFUNCTION() + void OnRemoveUserMetadataResponse(FPubnubOperationResult Result); + + // snippet.remove_user_metadata_with_result_lambda_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|UserMetadataEntity") + void RemoveUserMetadataWithResultLambdaSample(); + + + // snippet.end +}; diff --git a/Source/ThirdParty/sdk/lib/IOS/libpubnub.a b/Source/ThirdParty/sdk/lib/IOS/libpubnub.a index 7cff674..5e50ca8 100644 Binary files a/Source/ThirdParty/sdk/lib/IOS/libpubnub.a and b/Source/ThirdParty/sdk/lib/IOS/libpubnub.a differ diff --git a/Source/ThirdParty/sdk/lib/MacOS/libpubnub.a b/Source/ThirdParty/sdk/lib/MacOS/libpubnub.a index 116f536..a71321f 100644 Binary files a/Source/ThirdParty/sdk/lib/MacOS/libpubnub.a and b/Source/ThirdParty/sdk/lib/MacOS/libpubnub.a differ diff --git a/Source/ThirdParty/sdk/lib/arm64/libpubnub.a b/Source/ThirdParty/sdk/lib/arm64/libpubnub.a index 8e48be7..3432ce0 100644 Binary files a/Source/ThirdParty/sdk/lib/arm64/libpubnub.a and b/Source/ThirdParty/sdk/lib/arm64/libpubnub.a differ diff --git a/Source/ThirdParty/sdk/lib/linux/libpubnub.a b/Source/ThirdParty/sdk/lib/linux/libpubnub.a index add3967..5a61710 100644 Binary files a/Source/ThirdParty/sdk/lib/linux/libpubnub.a and b/Source/ThirdParty/sdk/lib/linux/libpubnub.a differ diff --git a/Source/ThirdParty/sdk/lib/win64/pubnub.lib b/Source/ThirdParty/sdk/lib/win64/pubnub.lib index 399af08..4775c8e 100644 Binary files a/Source/ThirdParty/sdk/lib/win64/pubnub.lib and b/Source/ThirdParty/sdk/lib/win64/pubnub.lib differ