From 3303882e1e51a6277d636ec65330d1fe5d8a1247 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 27 Aug 2025 14:09:03 +0200 Subject: [PATCH 01/16] Add base implementation of Entities and Subscriptions. --- .../Private/Entities/PubnubBaseEntity.cpp | 24 +++ .../Private/Entities/PubnubChannelEntity.cpp | 39 ++++ .../Private/Entities/PubnubSubscription.cpp | 123 ++++++++++++ .../FunctionLibraries/PubnubUtilities.cpp | 190 +++++++++++------- .../PubnubLibrary/Private/PubnubSubsystem.cpp | 38 ++-- .../Public/Entities/PubnubBaseEntity.h | 37 ++++ .../Public/Entities/PubnubChannelEntity.h | 83 ++++++++ .../Public/Entities/PubnubSubscription.h | 103 ++++++++++ .../FunctionLibraries/PubnubUtilities.h | 14 +- .../PubnubLibrary/Public/PubnubEnumLibrary.h | 28 ++- .../Public/PubnubStructLibrary.h | 11 + Source/PubnubLibrary/Public/PubnubSubsystem.h | 12 +- 12 files changed, 592 insertions(+), 110 deletions(-) create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubSubscription.h diff --git a/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp new file mode 100644 index 0000000..3e188c6 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp @@ -0,0 +1,24 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubBaseEntity.h" +#include "Entities/PubnubSubscription.h" +#include "PubnubSubsystem.h" + +UPubnubSubscription* UPubnubBaseEntity::CreateSubscription(FPubnubSubscribeSettings SubscribeSettings) +{ + 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")); + } + + PubnubSubsystem = InPubnubSubsystem; +} diff --git a/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp new file mode 100644 index 0000000..9372457 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp @@ -0,0 +1,39 @@ +// 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) +{ + PubnubSubsystem->PublishMessage(EntityID, Message, OnPublishMessageResponse, PublishSettings); +} + +void UPubnubChannelEntity::PublishMessage(FString Message, FOnPublishMessageResponseNative NativeCallback, FPubnubPublishSettings PublishSettings) +{ + PubnubSubsystem->PublishMessage(EntityID, Message, NativeCallback, PublishSettings); +} + +void UPubnubChannelEntity::PublishMessage(FString Message, FPubnubPublishSettings PublishSettings) +{ + PubnubSubsystem->PublishMessage(EntityID, Message, PublishSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FOnSignalResponse OnSignalResponse, FPubnubSignalSettings SignalSettings) +{ + PubnubSubsystem->Signal(EntityID, Message, OnSignalResponse, SignalSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FOnSignalResponseNative NativeCallback, FPubnubSignalSettings SignalSettings) +{ + PubnubSubsystem->Signal(EntityID, Message, NativeCallback, SignalSettings); +} + +void UPubnubChannelEntity::Signal(FString Message, FPubnubSignalSettings SignalSettings) +{ + PubnubSubsystem->Signal(EntityID, Message, SignalSettings); +} diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp new file mode 100644 index 0000000..0239d52 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -0,0 +1,123 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubSubscription.h" +#include "PubnubSubsystem.h" +#include "Entities/PubnubBaseEntity.h" +#include "FunctionLibraries/PubnubUtilities.h" + + +void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings) +{ + if(!Entity) + { + UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, Entity is invalid")); + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscription = UPubnubUtilities::EEGetSubscriptionForEntity(InPubnubSubsystem->ctx_ee, Entity->EntityID, Entity->EntityType, InSubscribeSettings); + + // 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->OnPubnubMessage.Broadcast(MessageData); + S->OnPubnubMessageNative.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::EEAddListenerOfType(CCoreSubscription, CallbackMessages, EPubnubListenerType::PLT_Message, this); + UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackSignals, EPubnubListenerType::PLT_Signal, this); + UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackObjects, EPubnubListenerType::PLT_Objects, this); + UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackMessageActions, EPubnubListenerType::PLT_MessageAction, this); +} + +void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) +{ + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, subscription is invalid")); + } + + UPubnubUtilities::EESubscribeWithSubscription(CCoreSubscription, Cursor); +} + +void UPubnubSubscription::Unsubscribe() +{ + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, subscription is invalid")); + } + + UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); +} diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index 5ae3a24..c87019d 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -3,6 +3,7 @@ #include "FunctionLibraries/PubnubUtilities.h" #include "Config/PubnubSettings.h" +#include "FunctionLibraries/PubnubJsonUtilities.h" #include "Kismet/KismetMathLibrary.h" @@ -120,6 +121,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,34 +337,27 @@ FString UPubnubUtilities::GetAllSortToString(const FPubnubGetAllSort& GetAllIncl return FinalString; } -pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForChannel(pubnub_t* Context, FString Channel, 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); - - pubnub_entity_free((void**)&PubnubChannel); - - return Subscription; -} - -pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForChannelGroup(pubnub_t* Context, FString ChannelGroup, 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 ChannelGroupHolder(ChannelGroup); - - pubnub_channel_group_t* PubnubChannelGroup = pubnub_channel_group_alloc(Context, ChannelGroupHolder.Get()); - - pubnub_subscription_t* Subscription = pubnub_subscription_alloc((pubnub_entity_t*)PubnubChannelGroup, &PnOptions); + FUTF8StringHolder EntityIDHolder(EntityID); + pubnub_entity_t* PubnubEntity = nullptr; + switch (EntityType) + { + case EPubnubEntityType::PEnT_Channel: + PubnubEntity = reinterpret_cast(pubnub_channel_alloc(Context, EntityIDHolder.Get())); + case EPubnubEntityType::PEnT_ChannelGroup: + PubnubEntity = reinterpret_cast(pubnub_channel_group_alloc(Context, EntityIDHolder.Get())); + case EPubnubEntityType::PEnT_ChannelMetadata: + PubnubEntity = reinterpret_cast(pubnub_channel_metadata_alloc(Context, EntityIDHolder.Get())); + case EPubnubEntityType::PEnT_UserMetadata: + PubnubEntity = reinterpret_cast(pubnub_user_metadata_alloc(Context, EntityIDHolder.Get())); + } + pubnub_subscription_t* Subscription = pubnub_subscription_alloc(PubnubEntity, &PnOptions); - pubnub_entity_free((void**)&PubnubChannelGroup); + pubnub_entity_free((void**)&PubnubEntity); return Subscription; } @@ -354,103 +369,122 @@ 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) + + EEAddListenersOfAllTypes(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) + EERemoveListenersOfAllTypes(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); } - - enum pubnub_res AddObjectsListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PBSL_LISTENER_ON_OBJECTS, Callback, PubnubSubsystem); - if(PNR_OK != AddObjectsListenerResult) + else { - FString ResultString(pubnub_res_2_string(AddObjectsListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Add_subscription_listener (Objects) failed with error: " + ResultString); - return false; + SubscribeResult = pubnub_subscribe_with_subscription(Subscription, nullptr); } - - enum pubnub_res SubscribeResult = pubnub_subscribe_with_subscription(Subscription, 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 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::EEUnsubscribeWithSubscription(pubnub_subscription_t** SubscriptionPtr) { - if(!PubnubSubsystem) + enum pubnub_res UnsubscribeResult = pubnub_unsubscribe_with_subscription(SubscriptionPtr); + 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::EEAddListenerOfType(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"); + EEAddListenersOfAllTypes(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) - { - FString ResultString(pubnub_res_2_string(RemoveMessageListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Message) failed with error: " + ResultString); - return false; - } + pubnub_subscribe_listener_type PubnubListenerType = static_cast(static_cast(ListenerType)); + enum pubnub_res AddMessageListenerResult = pubnub_subscribe_add_subscription_listener(Subscription, PubnubListenerType, Callback, Caller); - enum pubnub_res RemoveSignalListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_SIGNAL, Callback, PubnubSubsystem); - if(PNR_OK != RemoveSignalListenerResult) + if(PNR_OK != AddMessageListenerResult) { - FString ResultString(pubnub_res_2_string(RemoveSignalListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Signal) 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; +} - enum pubnub_res RemoveMessageActionListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_MESSAGE_ACTION, Callback, PubnubSubsystem); - if(PNR_OK != RemoveMessageActionListenerResult) +void UPubnubUtilities::EEAddListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) { - FString ResultString(pubnub_res_2_string(RemoveMessageActionListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Message) failed with error: " + ResultString); - return false; + EEAddListenerOfType(Subscription, Callback, Type, Caller); } +} - enum pubnub_res RemoveObjectsListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PBSL_LISTENER_ON_OBJECTS, Callback, PubnubSubsystem); - if(PNR_OK != RemoveObjectsListenerResult) +bool UPubnubUtilities::EERemoveListenerOfType(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(RemoveObjectsListenerResult)); - PubnubSubsystem->PubnubError("Failed to subscribe. Remove_subscription_listener (Objects) failed with error: " + ResultString); + EERemoveListenersOfAllTypes(SubscriptionPtr, 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 RemoveMessageActionListenerResult = pubnub_subscribe_remove_subscription_listener(*SubscriptionPtr, PubnubListenerType, Callback, Caller); + if(PNR_OK != RemoveMessageActionListenerResult) { - 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(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::EERemoveListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +{ + for(EPubnubListenerType Type : TEnumRange()) + { + EERemoveListenerOfType(SubscriptionPtr, Callback, Type, Caller); + } +} diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index f879059..aefd05b 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -11,6 +11,7 @@ #include "FunctionLibraries/PubnubJsonUtilities.h" #include "FunctionLibraries/PubnubUtilities.h" #include "Threads/PubnubFunctionThread.h" +#include "Entities/PubnubChannelEntity.h" DEFINE_LOG_CATEGORY(PubnubLog) @@ -1151,6 +1152,14 @@ TScriptInterface UPubnubSubsystem::GetCryptoModu return nullptr; } +UPubnubChannelEntity* UPubnubSubsystem::CreateChannel(FString ChannelID) +{ + UPubnubChannelEntity* Channel = NewObject(this); + Channel->InitEntity(this); + Channel->EntityID = ChannelID; + return Channel; +} + FString UPubnubSubsystem::GetLastResponse(pubnub_t* context) { FString Response; @@ -1469,7 +1478,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 +1492,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 +1534,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 +1548,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) @@ -2665,27 +2674,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..4cac54f --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h @@ -0,0 +1,37 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PubnubSubsystem.h" +#include "PubnubBaseEntity.generated.h" + + +class UPubnubSubscription; + +/** + * + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubBaseEntity : public UObject +{ + GENERATED_BODY() + + friend class UPubnubSubsystem; + + +public: + + 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..53e482f --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h @@ -0,0 +1,83 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity +{ + GENERATED_BODY() + +public: + + UPubnubChannelEntity(); + + /** + * Publishes a message to a specified 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 a specified 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 a specified 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 specified 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 a specified 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 a specified 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); + + //UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel") + + +}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h new file mode 100644 index 0000000..18d6452 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -0,0 +1,103 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PubnubSubsystem.h" +#include "PubnubEnumLibrary.h" +#include "PubnubStructLibrary.h" +#include "PubnubSubscription.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubMessage, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageNative, const FPubnubMessageData& Message); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubSignal, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubSignalNative, const FPubnubMessageData& Message); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubPresenceEvent, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubPresenceEventNative, const FPubnubMessageData& Message); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubObjectEvent, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubObjectEventNative, const FPubnubMessageData& Message); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageAction, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubMessageActionNative, const FPubnubMessageData& Message); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPubnubAnyMessageType, FPubnubMessageData, Message); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubAnyMessageTypeNative, const FPubnubMessageData& Message); + + +/** + * + */ +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject +{ + GENERATED_BODY() + + friend class UPubnubBaseEntity; + +public: + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubMessage OnPubnubMessage; + FOnPubnubMessageNative OnPubnubMessageNative; + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubSignal OnPubnubSignal; + FOnPubnubSignalNative OnPubnubSignalNative; + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubPresenceEvent OnPubnubPresenceEvent; + FOnPubnubPresenceEventNative OnPubnubPresenceEventNative; + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubObjectEvent OnPubnubObjectEvent; + FOnPubnubObjectEventNative OnPubnubObjectEventNative; + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubMessageAction OnPubnubMessageAction; + FOnPubnubMessageActionNative OnPubnubMessageActionNative; + + UPROPERTY(BlueprintAssignable, Category="Pubnub|Subscription") + FOnPubnubAnyMessageType FOnPubnubAnyMessageType; + FOnPubnubAnyMessageTypeNative FOnPubnubAnyMessageTypeNative; + + virtual void Subscribe(FPubnubSubscriptionCursor Cursor){}; + virtual void Unsubscribe(){}; + +protected: + + UPROPERTY() + UPubnubSubsystem* PubnubSubsystem = nullptr; + +}; + + +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase +{ + GENERATED_BODY() + + friend class UPubnubBaseEntity; + +public: + + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; + + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + virtual void Unsubscribe() override; + +private: + + pubnub_subscription_t* CCoreSubscription = nullptr; + + void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings); + +}; + + +UCLASS(Blueprintable) +class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase +{ + GENERATED_BODY() + + + +}; \ No newline at end of file diff --git a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h index b2c04fc..faeef5e 100644 --- a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h +++ b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h @@ -6,6 +6,7 @@ #include "CoreMinimal.h" #include "Async/Async.h" #include "PubnubStructLibrary.h" +#include "PubnubEnumLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "PubnubUtilities.generated.h" @@ -31,6 +32,7 @@ struct FUTF8StringHolder } }; + /** * */ @@ -65,6 +67,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,11 +87,15 @@ 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 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 EEAddListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EEAddListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller); + static bool EERemoveListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); + static void EERemoveListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller); /* TEMPLATES TO CALL PUBNUB DELEGATES */ diff --git a/Source/PubnubLibrary/Public/PubnubEnumLibrary.h b/Source/PubnubLibrary/Public/PubnubEnumLibrary.h index 3d74603..27cc6ac 100644 --- a/Source/PubnubLibrary/Public/PubnubEnumLibrary.h +++ b/Source/PubnubLibrary/Public/PubnubEnumLibrary.h @@ -42,10 +42,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 +106,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..d8c8133 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -20,6 +20,7 @@ class UPubnubSettings; class FPubnubFunctionThread; class UPubnubChatSystem; class UPubnubAesCryptor; +class UPubnubChannelEntity; struct CCoreSubscriptionData { @@ -33,8 +34,6 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnPubnubError, FString, ErrorMessa 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); @@ -109,6 +108,8 @@ UCLASS() class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() + + friend class UPubnubSubscription; public: @@ -1681,6 +1682,12 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem #pragma endregion +#pragma region ENTITIES + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelEntity* CreateChannel(FString ChannelID); + +#pragma endregion private: @@ -1813,7 +1820,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); From 2f6dcb94d4d4e19d17a467b404d60e8e718c8e25 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Thu, 28 Aug 2025 11:20:23 +0200 Subject: [PATCH 02/16] Add functions to all entities. Add support for SubscruptionSet. --- .../Private/Entities/PubnubChannelEntity.cpp | 10 ++ .../Entities/PubnubChannelGroupEntity.cpp | 50 ++++++++ .../Entities/PubnubChannelMetadataEntity.cpp | 40 +++++++ .../Private/Entities/PubnubSubscription.cpp | 113 +++++++++++++++++- .../Entities/PubnubUserMetadataEntity.cpp | 40 +++++++ .../FunctionLibraries/PubnubUtilities.cpp | 107 +++++++++++++++-- .../PubnubLibrary/Private/PubnubSubsystem.cpp | 71 ++++++++++- .../Public/Entities/PubnubBaseEntity.h | 1 + .../Public/Entities/PubnubChannelEntity.h | 34 ++++-- .../Entities/PubnubChannelGroupEntity.h | 107 +++++++++++++++++ .../Entities/PubnubChannelMetadataEntity.h | 90 ++++++++++++++ .../Public/Entities/PubnubSubscription.h | 15 +++ .../Entities/PubnubUserMetadataEntity.h | 91 ++++++++++++++ .../FunctionLibraries/PubnubUtilities.h | 14 ++- Source/PubnubLibrary/Public/PubnubSubsystem.h | 27 ++++- 15 files changed, 779 insertions(+), 31 deletions(-) create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp create mode 100644 Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h create mode 100644 Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h diff --git a/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp index 9372457..56ce046 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp @@ -37,3 +37,13 @@ void UPubnubChannelEntity::Signal(FString Message, FPubnubSignalSettings SignalS { PubnubSubsystem->Signal(EntityID, Message, SignalSettings); } + +void UPubnubChannelEntity::ListUsersFromChannel(FOnListUsersFromChannelResponse ListUsersFromChannelResponse, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings) +{ + PubnubSubsystem->ListUsersFromChannel(EntityID, ListUsersFromChannelResponse, ListUsersFromChannelSettings); +} + +void UPubnubChannelEntity::ListUsersFromChannel(FOnListUsersFromChannelResponseNative NativeCallback, FPubnubListUsersFromChannelSettings ListUsersFromChannelSettings) +{ + 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..8617b2c --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp @@ -0,0 +1,50 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Entities/PubnubChannelGroupEntity.h" + + +UPubnubChannelGroupEntity::UPubnubChannelGroupEntity() +{ + EntityType = EPubnubEntityType::PEnT_ChannelGroup; +} + +void UPubnubChannelGroupEntity::AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponse OnAddChannelToGroupResponse) +{ + PubnubSubsystem->AddChannelToGroup(Channel, EntityID, OnAddChannelToGroupResponse); +} + +void UPubnubChannelGroupEntity::AddChannelToGroup(FString Channel, FOnAddChannelToGroupResponseNative NativeCallback) +{ + PubnubSubsystem->AddChannelToGroup(Channel, EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponse OnRemoveChannelFromGroupResponse) +{ + PubnubSubsystem->RemoveChannelFromGroup(Channel, EntityID, OnRemoveChannelFromGroupResponse); +} + +void UPubnubChannelGroupEntity::RemoveChannelFromGroup(FString Channel, FOnRemoveChannelFromGroupResponseNative NativeCallback) +{ + PubnubSubsystem->RemoveChannelFromGroup(Channel, EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::ListChannelsFromGroup(FOnListChannelsFromGroupResponse OnListChannelsResponse) +{ + PubnubSubsystem->ListChannelsFromGroup(EntityID, OnListChannelsResponse); +} + +void UPubnubChannelGroupEntity::ListChannelsFromGroup(FOnListChannelsFromGroupResponseNative NativeCallback) +{ + PubnubSubsystem->ListChannelsFromGroup(EntityID, NativeCallback); +} + +void UPubnubChannelGroupEntity::RemoveChannelGroup(FOnRemoveChannelGroupResponse OnRemoveChannelGroupResponse) +{ + PubnubSubsystem->RemoveChannelGroup(EntityID, OnRemoveChannelGroupResponse); +} + +void UPubnubChannelGroupEntity::RemoveChannelGroup(FOnRemoveChannelGroupResponseNative NativeCallback) +{ + 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..66338ba --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp @@ -0,0 +1,40 @@ +// 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) +{ + PubnubSubsystem->SetChannelMetadata(EntityID, ChannelMetadata, OnSetChannelMetadataResponse, Include); +} + +void UPubnubChannelMetadataEntity::SetChannelMetadata(FPubnubChannelData ChannelMetadata, FOnSetChannelMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->SetChannelMetadata(EntityID, ChannelMetadata, NativeCallback, Include); +} + +void UPubnubChannelMetadataEntity::GetChannelMetadata(FOnGetChannelMetadataResponse OnGetChannelMetadataResponse, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->GetChannelMetadata(EntityID, OnGetChannelMetadataResponse, Include); +} + +void UPubnubChannelMetadataEntity::GetChannelMetadata(FOnGetChannelMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->GetChannelMetadata(EntityID, NativeCallback, Include); +} + +void UPubnubChannelMetadataEntity::RemoveChannelMetadata(FOnRemoveChannelMetadataResponse OnRemoveChannelMetadataResponse) +{ + PubnubSubsystem->RemoveChannelMetadata(EntityID, OnRemoveChannelMetadataResponse); +} + +void UPubnubChannelMetadataEntity::RemoveChannelMetadata(FOnRemoveChannelMetadataResponseNative NativeCallback) +{ + PubnubSubsystem->RemoveChannelMetadata(EntityID, NativeCallback); +} + diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 0239d52..343bda7 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -96,10 +96,10 @@ void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, }; // Register created callback in subscription - UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackMessages, EPubnubListenerType::PLT_Message, this); - UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackSignals, EPubnubListenerType::PLT_Signal, this); - UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackObjects, EPubnubListenerType::PLT_Objects, this); - UPubnubUtilities::EEAddListenerOfType(CCoreSubscription, CallbackMessageActions, EPubnubListenerType::PLT_MessageAction, this); + 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); } void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) @@ -121,3 +121,108 @@ void UPubnubSubscription::Unsubscribe() UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); } + +void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) +{ +} + +void UPubnubSubscriptionSet::Unsubscribe() +{ +} + +void UPubnubSubscriptionSet::InitSubscription(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.")); + } + PubnubSubsystem = InPubnubSubsystem; + CCoreSubscriptionSet = UPubnubUtilities::EEGetSubscriptionSetForEntities(InPubnubSubsystem->ctx_ee, Channels, ChannelGroups, InSubscribeSettings); + + + // 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->OnPubnubMessage.Broadcast(MessageData); + S->OnPubnubMessageNative.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); +} \ No newline at end of file diff --git a/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp new file mode 100644 index 0000000..efb0d23 --- /dev/null +++ b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp @@ -0,0 +1,40 @@ +// 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) +{ + PubnubSubsystem->SetUserMetadata(EntityID, UserMetadata, OnSetUserMetadataResponse, Include); +} + +void UPubnubUserMetadataEntity::SetUserMetadata(FPubnubUserData UserMetadata, FOnSetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->SetUserMetadata(EntityID, UserMetadata, NativeCallback, Include); +} + +void UPubnubUserMetadataEntity::GetUserMetadata(FOnGetUserMetadataResponse OnGetUserMetadataResponse, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->GetUserMetadata(EntityID, OnGetUserMetadataResponse, Include); +} + +void UPubnubUserMetadataEntity::GetUserMetadata(FOnGetUserMetadataResponseNative NativeCallback, FPubnubGetMetadataInclude Include) +{ + PubnubSubsystem->GetUserMetadata(EntityID, NativeCallback, Include); +} + +void UPubnubUserMetadataEntity::RemoveUserMetadata(FOnRemoveUserMetadataResponse OnRemoveUserMetadataResponse) +{ + PubnubSubsystem->RemoveUserMetadata(EntityID, OnRemoveUserMetadataResponse); +} + +void UPubnubUserMetadataEntity::RemoveUserMetadata(FOnRemoveUserMetadataResponseNative NativeCallback) +{ + PubnubSubsystem->RemoveUserMetadata(EntityID, NativeCallback); +} + diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index c87019d..a5c73bf 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -362,6 +362,35 @@ pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForEntity(pubnub_t* Co return Subscription; } +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; + + TArray PubnubEntities; + PubnubEntities.Reserve(Channels.Num() + ChannelGroups.Num()); + + 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_alloc(Context, EntityIDHolder.Get()))); + } + + 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((void**)&Entity); + } + + return SubscriptionSet; +} + bool UPubnubUtilities::EEAddListenerAndSubscribe(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UPubnubSubsystem* PubnubSubsystem) { if(!PubnubSubsystem) @@ -370,7 +399,7 @@ bool UPubnubUtilities::EEAddListenerAndSubscribe(pubnub_subscription_t* Subscrip return false; } - EEAddListenersOfAllTypes(Subscription, Callback, PubnubSubsystem); + EEAddSubscriptionListenersOfAllTypes(Subscription, Callback, PubnubSubsystem); return EESubscribeWithSubscription(Subscription, FPubnubSubscriptionCursor()); } @@ -389,7 +418,7 @@ bool UPubnubUtilities::EERemoveListenerAndUnsubscribe(pubnub_subscription_t** Su return false; } - EERemoveListenersOfAllTypes(SubscriptionPtr, Callback, PubnubSubsystem); + EERemoveSubscriptionListenersOfAllTypes(SubscriptionPtr, Callback, PubnubSubsystem); return EEUnsubscribeWithSubscription(SubscriptionPtr); } @@ -432,11 +461,11 @@ bool UPubnubUtilities::EEUnsubscribeWithSubscription(pubnub_subscription_t** Sub return true; } -bool UPubnubUtilities::EEAddListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +bool UPubnubUtilities::EEAddSubscriptionListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) { if(ListenerType == EPubnubListenerType::PLT_All) { - EEAddListenersOfAllTypes(Subscription, Callback, Caller); + EEAddSubscriptionListenersOfAllTypes(Subscription, Callback, Caller); return false; } @@ -453,19 +482,19 @@ bool UPubnubUtilities::EEAddListenerOfType(pubnub_subscription_t* Subscription, return true; } -void UPubnubUtilities::EEAddListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +void UPubnubUtilities::EEAddSubscriptionListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller) { for(EPubnubListenerType Type : TEnumRange()) { - EEAddListenerOfType(Subscription, Callback, Type, Caller); + EEAddSubscriptionListenerOfType(Subscription, Callback, Type, Caller); } } -bool UPubnubUtilities::EERemoveListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) +bool UPubnubUtilities::EERemoveSubscriptionListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) { if(ListenerType == EPubnubListenerType::PLT_All) { - EERemoveListenersOfAllTypes(SubscriptionPtr, Callback, Caller); + EERemoveSubscriptionListenersOfAllTypes(SubscriptionPtr, Callback, Caller); return false; } @@ -481,10 +510,68 @@ bool UPubnubUtilities::EERemoveListenerOfType(pubnub_subscription_t** Subscripti return true; } -void UPubnubUtilities::EERemoveListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller) +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) + { + EEAddSubscriptionSetListenersOfAllTypes(SubscriptionSet, Callback, Caller); + return false; + } + + 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(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; +} + +void UPubnubUtilities::EERemoveSubscriptionSetListenersOfAllTypes(pubnub_subscription_set_t** SubscriptionSetPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller) { for(EPubnubListenerType Type : TEnumRange()) { - EERemoveListenerOfType(SubscriptionPtr, Callback, Type, Caller); + EERemoveSubscriptionSetListenerOfType(SubscriptionSetPtr, Callback, Type, Caller); } } + diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index aefd05b..d756784 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -5,13 +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) @@ -1152,14 +1157,76 @@ TScriptInterface UPubnubSubsystem::GetCryptoModu return nullptr; } -UPubnubChannelEntity* UPubnubSubsystem::CreateChannel(FString ChannelID) +UPubnubChannelEntity* UPubnubSubsystem::CreateChannelEntity(FString ChannelID) { + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + UPubnubChannelEntity* Channel = NewObject(this); Channel->InitEntity(this); Channel->EntityID = ChannelID; return Channel; } +UPubnubChannelGroupEntity* UPubnubSubsystem::CreateChannelGroupEntity(FString ChannelID) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + + UPubnubChannelGroupEntity* ChannelGroup = NewObject(this); + ChannelGroup->InitEntity(this); + ChannelGroup->EntityID = ChannelID; + return ChannelGroup; +} + +UPubnubChannelMetadataEntity* UPubnubSubsystem::CreateChannelMetadataEntity(FString ChannelID) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + + UPubnubChannelMetadataEntity* ChannelMetadata = NewObject(this); + ChannelMetadata->InitEntity(this); + ChannelMetadata->EntityID = ChannelID; + return ChannelMetadata; +} + +UPubnubUserMetadataEntity* UPubnubSubsystem::CreateUserMetadataEntity(FString ChannelID) +{ + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + + UPubnubUserMetadataEntity* UserMetadata = NewObject(this); + UserMetadata->InitEntity(this); + UserMetadata->EntityID = ChannelID; + return UserMetadata; +} + +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->InitSubscription(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->InitSubscription(this, Channels, ChannelGroups, SubscriptionSettings); + return SubscriptionSet; +} + FString UPubnubSubsystem::GetLastResponse(pubnub_t* context) { FString Response; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h index 4cac54f..e5394c8 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h @@ -29,6 +29,7 @@ class PUBNUBLIBRARY_API UPubnubBaseEntity : public UObject EPubnubEntityType EntityType = EPubnubEntityType::PEnT_Channel; protected: + UPROPERTY() UPubnubSubsystem* PubnubSubsystem = nullptr; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h index 53e482f..95bc38e 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h @@ -22,7 +22,7 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity UPubnubChannelEntity(); /** - * Publishes a message to a specified channel. + * 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. @@ -32,7 +32,7 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity void PublishMessage(FString Message, FOnPublishMessageResponse OnPublishMessageResponse, FPubnubPublishSettings PublishSettings = FPubnubPublishSettings()); /** - * Publishes a message to a specified channel. + * 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. @@ -42,7 +42,7 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity void PublishMessage(FString Message, FOnPublishMessageResponseNative NativeCallback = nullptr, FPubnubPublishSettings PublishSettings = FPubnubPublishSettings()); /** - * Publishes a message to a specified channel. Overload without delegate to get publish result. + * 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. @@ -50,7 +50,7 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity void PublishMessage(FString Message, FPubnubPublishSettings PublishSettings); /** - * Sends a signal to a specified channel. + * 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. @@ -60,7 +60,7 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity void Signal(FString Message, FOnSignalResponse OnSignalResponse, FPubnubSignalSettings SignalSettings = FPubnubSignalSettings()); /** - * Sends a signal to a specified channel. + * 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. @@ -70,14 +70,32 @@ class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity void Signal(FString Message, FOnSignalResponseNative NativeCallback = nullptr, FPubnubSignalSettings SignalSettings = FPubnubSignalSettings()); /** - * Sends a signal to a specified channel. Overload without delegate to get signal result. + * 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); - //UFUNCTION(BlueprintCallable, Category = "Pubnub|Channel") - + /** + * 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..e61dc2a --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h @@ -0,0 +1,107 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelGroupEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * + */ +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..c74c701 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h @@ -0,0 +1,90 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubChannelMetadataEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * + */ +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 index 18d6452..56cf25b 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -98,6 +98,21 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase { GENERATED_BODY() + friend class UPubnubSubsystem; + +public: + + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; + + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + virtual void Unsubscribe() override; + +private: + + pubnub_subscription_set_t* CCoreSubscriptionSet = nullptr; + + void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings); }; \ No newline at end of file diff --git a/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h new file mode 100644 index 0000000..2197d19 --- /dev/null +++ b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h @@ -0,0 +1,91 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Entities/PubnubBaseEntity.h" +#include "PubnubUserMetadataEntity.generated.h" + +class UPubnubSubsystem; + + +/** + * + */ +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 faeef5e..521cabc 100644 --- a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h +++ b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h @@ -88,15 +88,21 @@ class PUBNUBLIBRARY_API UPubnubUtilities : public UBlueprintFunctionLibrary /* C-CORE EVENT ENGINE HELPERS */ 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 EEAddListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); - static void EEAddListenersOfAllTypes(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, UObject* Caller); - static bool EERemoveListenerOfType(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller); - static void EERemoveListenersOfAllTypes(pubnub_subscription_t** SubscriptionPtr, pubnub_subscribe_message_callback_t Callback, UObject* Caller); + 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/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index d8c8133..c47c941 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -20,7 +20,12 @@ class UPubnubSettings; class FPubnubFunctionThread; class UPubnubChatSystem; class UPubnubAesCryptor; +class UPubnubBaseEntity; class UPubnubChannelEntity; +class UPubnubChannelGroupEntity; +class UPubnubChannelMetadataEntity; +class UPubnubUserMetadataEntity; +class UPubnubSubscriptionSet; struct CCoreSubscriptionData { @@ -110,6 +115,7 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem GENERATED_BODY() friend class UPubnubSubscription; + friend class UPubnubSubscriptionSet; public: @@ -858,6 +864,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. @@ -904,8 +911,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. @@ -1685,7 +1691,22 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem #pragma region ENTITIES UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") - UPubnubChannelEntity* CreateChannel(FString ChannelID); + UPubnubChannelEntity* CreateChannelEntity(FString ChannelID); + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelGroupEntity* CreateChannelGroupEntity(FString ChannelID); + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubChannelMetadataEntity* CreateChannelMetadataEntity(FString ChannelID); + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") + UPubnubUserMetadataEntity* CreateUserMetadataEntity(FString ChannelID); + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + UPubnubSubscriptionSet* CreateSubscriptionSet(TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + UPubnubSubscriptionSet* CreateSubscriptionSetFromEntities(TArray Entities, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); #pragma endregion From d93b05c6d1114fb7f27c9cd4054b3e2f68303abe Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Thu, 28 Aug 2025 13:06:49 +0200 Subject: [PATCH 03/16] Ensure proper clean of subscriptions when PubnubSubsystem is deinitialized. --- .../Private/Entities/PubnubSubscription.cpp | 111 ++++++++++++++++-- .../FunctionLibraries/PubnubUtilities.cpp | 38 ++++++ .../PubnubLibrary/Private/PubnubSubsystem.cpp | 13 ++ .../Public/Entities/PubnubSubscription.h | 13 +- .../FunctionLibraries/PubnubUtilities.h | 2 + Source/PubnubLibrary/Public/PubnubSubsystem.h | 11 +- 6 files changed, 172 insertions(+), 16 deletions(-) diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 343bda7..1271e4e 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -6,11 +6,49 @@ #include "FunctionLibraries/PubnubUtilities.h" +void UPubnubSubscriptionBase::BeginDestroy() +{ + CleanUpSubscription(); + + Super::BeginDestroy(); +} + +void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + } + + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, internal C-Core subscription is invalid")); + } + + UPubnubUtilities::EESubscribeWithSubscription(CCoreSubscription, Cursor); +} + +void UPubnubSubscription::Unsubscribe() +{ + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + } + + if(!CCoreSubscription) + { + UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, internal C-Core subscription is invalid")); + } + + UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); +} + void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings) { 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); @@ -100,34 +138,59 @@ void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, 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::Subscribe(FPubnubSubscriptionCursor Cursor) +void UPubnubSubscription::CleanUpSubscription() { - if(!CCoreSubscription) + if(!IsInitialized) {return;} + + if(CCoreSubscription) { - UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, subscription is invalid")); + pubnub_subscription_free(&CCoreSubscription); } - UPubnubUtilities::EESubscribeWithSubscription(CCoreSubscription, Cursor); -} - -void UPubnubSubscription::Unsubscribe() -{ - if(!CCoreSubscription) + if(PubnubSubsystem) { - UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, subscription is invalid")); + PubnubSubsystem->OnPubnubSubsystemDeinitialized.RemoveDynamic(this, &UPubnubSubscription::CleanUpSubscription); } - UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); + IsInitialized = false; } void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) { + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, internal C-Core subscription set is invalid")); + } + + UPubnubUtilities::EESubscribeWithSubscriptionSet(CCoreSubscriptionSet, Cursor); } void UPubnubSubscriptionSet::Unsubscribe() { + if(!IsInitialized) + { + UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + } + + if(!CCoreSubscriptionSet) + { + UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, internal C-Core subscription set is invalid")); + } + + UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(&CCoreSubscriptionSet); } void UPubnubSubscriptionSet::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings) @@ -135,6 +198,7 @@ void UPubnubSubscriptionSet::InitSubscription(UPubnubSubsystem* InPubnubSubsyste 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); @@ -225,4 +289,27 @@ void UPubnubSubscriptionSet::InitSubscription(UPubnubSubsystem* InPubnubSubsyste UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackSignals, EPubnubListenerType::PLT_Signal, this); UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackObjects, EPubnubListenerType::PLT_Objects, this); UPubnubUtilities::EEAddSubscriptionSetListenerOfType(CCoreSubscriptionSet, CallbackMessageActions, EPubnubListenerType::PLT_MessageAction, this); -} \ No newline at end of file + + //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/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index a5c73bf..6a1f489 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -461,6 +461,44 @@ bool UPubnubUtilities::EEUnsubscribeWithSubscription(pubnub_subscription_t** Sub return true; } +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)); + UE_LOG(PubnubLog, Error, TEXT("Failed to subscribe. Subscribe_with_subscription_set failed with error: %s"), *ResultString); + return false; + } + + return true; +} + +bool UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(pubnub_subscription_set_t** SubscriptionSetPtr) +{ + enum pubnub_res UnsubscribeResult = pubnub_unsubscribe_with_subscription_set(SubscriptionSetPtr); + if(PNR_OK != UnsubscribeResult) + { + 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; +} + bool UPubnubUtilities::EEAddSubscriptionListenerOfType(pubnub_subscription_t* Subscription, pubnub_subscribe_message_callback_t Callback, EPubnubListenerType ListenerType, UObject* Caller) { if(ListenerType == EPubnubListenerType::PLT_All) diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index d756784..3c8673e 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -101,6 +101,9 @@ void UPubnubSubsystem::DeinitPubnub() ChannelGroupSubscriptions.Empty(); IsUserIDSet = false; delete[] AuthTokenBuffer; + + //Notify that Deinitialization is finished + OnPubnubSubsystemDeinitialized.Broadcast(); } void UPubnubSubsystem::SetUserID(FString UserID) @@ -1719,6 +1722,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(); diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h index 56cf25b..3bade2a 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -61,10 +61,16 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject virtual void Subscribe(FPubnubSubscriptionCursor Cursor){}; virtual void Unsubscribe(){}; + //Do proper clean up when object is being destroyed + virtual void BeginDestroy() override; + protected: UPROPERTY() UPubnubSubsystem* PubnubSubsystem = nullptr; + + bool IsInitialized = false; + virtual void CleanUpSubscription(){}; }; @@ -78,6 +84,7 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase public: + UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; @@ -89,7 +96,8 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase pubnub_subscription_t* CCoreSubscription = nullptr; void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, UPubnubBaseEntity* Entity, FPubnubSubscribeSettings InSubscribeSettings); - + UFUNCTION() + virtual void CleanUpSubscription() override; }; @@ -114,5 +122,6 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase pubnub_subscription_set_t* CCoreSubscriptionSet = nullptr; void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings); - + UFUNCTION() + virtual void CleanUpSubscription() override; }; \ No newline at end of file diff --git a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h index 521cabc..8561944 100644 --- a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h +++ b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h @@ -93,6 +93,8 @@ class PUBNUBLIBRARY_API UPubnubUtilities : public UBlueprintFunctionLibrary 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); diff --git a/Source/PubnubLibrary/Public/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index c47c941..e72d4ae 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -33,6 +33,9 @@ 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); @@ -100,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); @@ -122,7 +124,11 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem 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; @@ -356,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. */ From 492fee3bdfaaf691eba68b16dc94ae1b60a55584 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Fri, 29 Aug 2025 11:49:14 +0200 Subject: [PATCH 04/16] Add Subscription and SubscriptionSet operations. --- .../Private/Entities/PubnubSubscription.cpp | 232 +++++++++++++++++- .../FunctionLibraries/PubnubUtilities.cpp | 1 + .../PubnubLibrary/Private/PubnubSubsystem.cpp | 115 +++++++-- .../Public/Entities/PubnubSubscription.h | 41 +++- .../FunctionLibraries/PubnubUtilities.h | 1 + Source/PubnubLibrary/Public/PubnubSubsystem.h | 19 +- 6 files changed, 362 insertions(+), 47 deletions(-) diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 1271e4e..59d84ba 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -17,12 +17,14 @@ void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + 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("Can't subscribe, internal C-Core subscription is invalid")); + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: internal C-Core subscription is invalid.")); + return; } UPubnubUtilities::EESubscribeWithSubscription(CCoreSubscription, Cursor); @@ -32,27 +34,79 @@ void UPubnubSubscription::Unsubscribe() { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + 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("Can't unsubscribe, internal C-Core subscription is invalid")); + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: internal C-Core subscription is invalid.")); + return; } UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); } +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(!Entity) { - UE_LOG(PubnubLog, Error, TEXT("Can't initialize subscription, Entity is invalid")); + 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(!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 @@ -167,12 +221,14 @@ void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("Can't subscribe, This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + 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("Can't subscribe, internal C-Core subscription set is invalid")); + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: internal C-Core subscription set is invalid.")); + return; } UPubnubUtilities::EESubscribeWithSubscriptionSet(CCoreSubscriptionSet, Cursor); @@ -182,18 +238,140 @@ void UPubnubSubscriptionSet::Unsubscribe() { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("Can't unsubscribe, This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription")); + 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("Can't unsubscribe, internal C-Core subscription set is invalid")); + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: internal C-Core subscription set is invalid.")); + return; } UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(&CCoreSubscriptionSet); } -void UPubnubSubscriptionSet::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings) +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()) { @@ -203,7 +381,41 @@ void UPubnubSubscriptionSet::InitSubscription(UPubnubSubsystem* InPubnubSubsyste 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 diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index 6a1f489..f50085f 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -3,6 +3,7 @@ #include "FunctionLibraries/PubnubUtilities.h" #include "Config/PubnubSettings.h" +#include "Entities/PubnubSubscription.h" #include "FunctionLibraries/PubnubJsonUtilities.h" #include "Kismet/KismetMathLibrary.h" diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index 3c8673e..1214a0f 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -1160,44 +1160,44 @@ TScriptInterface UPubnubSubsystem::GetCryptoModu return nullptr; } -UPubnubChannelEntity* UPubnubSubsystem::CreateChannelEntity(FString ChannelID) +UPubnubChannelEntity* UPubnubSubsystem::CreateChannelEntity(FString Channel) { - PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + PUBNUB_RETURN_IF_FIELD_EMPTY(Channel, nullptr); - UPubnubChannelEntity* Channel = NewObject(this); - Channel->InitEntity(this); - Channel->EntityID = ChannelID; - return Channel; + UPubnubChannelEntity* ChannelEntity = NewObject(this); + ChannelEntity->InitEntity(this); + ChannelEntity->EntityID = Channel; + return ChannelEntity; } -UPubnubChannelGroupEntity* UPubnubSubsystem::CreateChannelGroupEntity(FString ChannelID) +UPubnubChannelGroupEntity* UPubnubSubsystem::CreateChannelGroupEntity(FString ChannelGroup) { - PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelGroup, nullptr); - UPubnubChannelGroupEntity* ChannelGroup = NewObject(this); - ChannelGroup->InitEntity(this); - ChannelGroup->EntityID = ChannelID; - return ChannelGroup; + UPubnubChannelGroupEntity* ChannelGroupEntity = NewObject(this); + ChannelGroupEntity->InitEntity(this); + ChannelGroupEntity->EntityID = ChannelGroup; + return ChannelGroupEntity; } -UPubnubChannelMetadataEntity* UPubnubSubsystem::CreateChannelMetadataEntity(FString ChannelID) +UPubnubChannelMetadataEntity* UPubnubSubsystem::CreateChannelMetadataEntity(FString Channel) { - PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + PUBNUB_RETURN_IF_FIELD_EMPTY(Channel, nullptr); - UPubnubChannelMetadataEntity* ChannelMetadata = NewObject(this); - ChannelMetadata->InitEntity(this); - ChannelMetadata->EntityID = ChannelID; - return ChannelMetadata; + UPubnubChannelMetadataEntity* ChannelMetadataEntity = NewObject(this); + ChannelMetadataEntity->InitEntity(this); + ChannelMetadataEntity->EntityID = Channel; + return ChannelMetadataEntity; } -UPubnubUserMetadataEntity* UPubnubSubsystem::CreateUserMetadataEntity(FString ChannelID) +UPubnubUserMetadataEntity* UPubnubSubsystem::CreateUserMetadataEntity(FString User) { - PUBNUB_RETURN_IF_FIELD_EMPTY(ChannelID, nullptr); + PUBNUB_RETURN_IF_FIELD_EMPTY(User, nullptr); - UPubnubUserMetadataEntity* UserMetadata = NewObject(this); - UserMetadata->InitEntity(this); - UserMetadata->EntityID = ChannelID; - return UserMetadata; + UPubnubUserMetadataEntity* UserMetadataEntity = NewObject(this); + UserMetadataEntity->InitEntity(this); + UserMetadataEntity->EntityID = User; + return UserMetadataEntity; } UPubnubSubscriptionSet* UPubnubSubsystem::CreateSubscriptionSet(TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings SubscriptionSettings) @@ -1207,7 +1207,7 @@ UPubnubSubscriptionSet* UPubnubSubsystem::CreateSubscriptionSet(TArray PubnubError("[CreateSubscriptionSet]: at least one Channel or ChannelGroup is needed to create SubscriptionSet.", EPubnubErrorType::PET_Warning); } UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); - SubscriptionSet->InitSubscription(this, Channels, ChannelGroups, SubscriptionSettings); + SubscriptionSet->InitSubscriptionSet(this, Channels, ChannelGroups, SubscriptionSettings); return SubscriptionSet; } @@ -1226,10 +1226,73 @@ UPubnubSubscriptionSet* UPubnubSubsystem::CreateSubscriptionSetFromEntities(TArr } UPubnubSubscriptionSet* SubscriptionSet = NewObject(this); - SubscriptionSet->InitSubscription(this, Channels, ChannelGroups, SubscriptionSettings); + 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; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h index 3bade2a..9b6a52f 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -4,7 +4,6 @@ #include "CoreMinimal.h" #include "PubnubSubsystem.h" -#include "PubnubEnumLibrary.h" #include "PubnubStructLibrary.h" #include "PubnubSubscription.generated.h" @@ -80,7 +79,9 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase { GENERATED_BODY() + friend class UPubnubSubsystem; friend class UPubnubBaseEntity; + friend class UPubnubSubscriptionSet; public: @@ -91,11 +92,17 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") virtual void Unsubscribe() override; + 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; }; @@ -107,21 +114,45 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase GENERATED_BODY() friend class UPubnubSubsystem; + friend class UPubnubSubscription; public: - UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; - UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") virtual void Unsubscribe() override; + + //This will subscribe automatically if set is subscribed + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void AddSubscription(UPubnubSubscription* Subscription); + + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void RemoveSubscription(UPubnubSubscription* Subscription); + + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void AddSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet); + + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") + void RemoveSubscriptionSet(UPubnubSubscriptionSet* SubscriptionSet); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Pubnub|SubscriptionSet") + TArray GetSubscriptions() { return Subscriptions;}; private: + + UPROPERTY() + TArray Subscriptions; pubnub_subscription_set_t* CCoreSubscriptionSet = nullptr; - void InitSubscription(UPubnubSubsystem* InPubnubSubsystem, TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings InSubscribeSettings); + 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; -}; \ No newline at end of file +}; + diff --git a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h index 8561944..9109c7c 100644 --- a/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h +++ b/Source/PubnubLibrary/Public/FunctionLibraries/PubnubUtilities.h @@ -12,6 +12,7 @@ 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. diff --git a/Source/PubnubLibrary/Public/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index e72d4ae..c8f7b5f 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -1698,16 +1698,16 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem #pragma region ENTITIES UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") - UPubnubChannelEntity* CreateChannelEntity(FString ChannelID); + UPubnubChannelEntity* CreateChannelEntity(FString Channel); UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") - UPubnubChannelGroupEntity* CreateChannelGroupEntity(FString ChannelID); + UPubnubChannelGroupEntity* CreateChannelGroupEntity(FString ChannelGroup); UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") - UPubnubChannelMetadataEntity* CreateChannelMetadataEntity(FString ChannelID); + UPubnubChannelMetadataEntity* CreateChannelMetadataEntity(FString Channel); UFUNCTION(BlueprintCallable, Category = "Pubnub|Entities") - UPubnubUserMetadataEntity* CreateUserMetadataEntity(FString ChannelID); + UPubnubUserMetadataEntity* CreateUserMetadataEntity(FString User); UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") UPubnubSubscriptionSet* CreateSubscriptionSet(TArray Channels, TArray ChannelGroups, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); @@ -1715,11 +1715,17 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") UPubnubSubscriptionSet* CreateSubscriptionSetFromEntities(TArray Entities, FPubnubSubscribeSettings SubscriptionSettings = FPubnubSubscribeSettings()); + UFUNCTION(BlueprintCallable, Category = "Pubnub|Subscriptions") + TArray GetActiveSubscriptions(); + + 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 @@ -1730,7 +1736,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; From b5bd90c82d7aceeef6fbdc5e1cbc80bf4740f5da Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Fri, 29 Aug 2025 13:51:41 +0200 Subject: [PATCH 05/16] Add delegates called on subscribe operation responses for Subscription and SubscriptionSet. --- .../Private/Entities/PubnubSubscription.cpp | 78 ++++++++++++++++--- .../PubnubLibrary/Private/PubnubSubsystem.cpp | 72 +++++++++++++++++ .../Public/Entities/PubnubSubscription.h | 30 ++++--- Source/PubnubLibrary/Public/PubnubSubsystem.h | 10 ++- 4 files changed, 164 insertions(+), 26 deletions(-) diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 59d84ba..314d2e8 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -13,38 +13,65 @@ void UPubnubSubscriptionBase::BeginDestroy() Super::BeginDestroy(); } -void UPubnubSubscription::Subscribe(FPubnubSubscriptionCursor Cursor) +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.")); + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: This SubscriptionSet 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 is invalid.")); + 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); + }); - UPubnubUtilities::EESubscribeWithSubscription(CCoreSubscription, Cursor); + Unsubscribe(NativeCallback); } -void UPubnubSubscription::Unsubscribe() +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.")); + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: This SubscriptionSet 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 is invalid.")); + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: internal C-Core subscription set is invalid.")); return; } - UPubnubUtilities::EEUnsubscribeWithSubscription(&CCoreSubscription); + PubnubSubsystem->UnsubscribeWithSubscription(this, NativeCallback); } UPubnubSubscriptionSet* UPubnubSubscription::AddSubscription(UPubnubSubscription* Subscription) @@ -217,7 +244,18 @@ void UPubnubSubscription::CleanUpSubscription() IsInitialized = false; } -void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) +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) { @@ -230,11 +268,27 @@ void UPubnubSubscriptionSet::Subscribe(FPubnubSubscriptionCursor Cursor) 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); + }); - UPubnubUtilities::EESubscribeWithSubscriptionSet(CCoreSubscriptionSet, Cursor); + Unsubscribe(NativeCallback); } -void UPubnubSubscriptionSet::Unsubscribe() +void UPubnubSubscriptionSet::Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback) { if(!IsInitialized) { @@ -248,7 +302,7 @@ void UPubnubSubscriptionSet::Unsubscribe() return; } - UPubnubUtilities::EEUnsubscribeWithSubscriptionSet(&CCoreSubscriptionSet); + PubnubSubsystem->UnsubscribeWithSubscriptionSet(this, NativeCallback); } void UPubnubSubscriptionSet::AddSubscription(UPubnubSubscription* Subscription) diff --git a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp index 1214a0f..22bb4bf 100644 --- a/Source/PubnubLibrary/Private/PubnubSubsystem.cpp +++ b/Source/PubnubLibrary/Private/PubnubSubsystem.cpp @@ -2779,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; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h index 9b6a52f..e3dd089 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -57,8 +57,8 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject FOnPubnubAnyMessageType FOnPubnubAnyMessageType; FOnPubnubAnyMessageTypeNative FOnPubnubAnyMessageTypeNative; - virtual void Subscribe(FPubnubSubscriptionCursor Cursor){}; - virtual void Unsubscribe(){}; + //virtual void Subscribe(FPubnubSubscriptionCursor Cursor){}; + //virtual void Unsubscribe(){}; //Do proper clean up when object is being destroyed virtual void BeginDestroy() override; @@ -85,12 +85,16 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase public: + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnSubscribeResponse")) + virtual void Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); - UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") - virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; + void Subscribe(FOnSubscribeOperationResponseNative NativeCallback = nullptr, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + void Subscribe(FPubnubSubscriptionCursor Cursor); - UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") - virtual void Unsubscribe() override; + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnUnsubscribeResponse")) + void Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse); + + void Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback); UFUNCTION(BlueprintCallable, Category="Pubnub|Subscription") UPubnubSubscriptionSet* AddSubscription(UPubnubSubscription* Subscription); @@ -118,11 +122,16 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase public: - UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") - virtual void Subscribe(FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()) override; + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnSubscribeResponse")) + virtual void Subscribe(FOnSubscribeOperationResponse OnSubscribeResponse, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + + void Subscribe(FOnSubscribeOperationResponseNative NativeCallback = nullptr, FPubnubSubscriptionCursor Cursor = FPubnubSubscriptionCursor()); + void Subscribe(FPubnubSubscriptionCursor Cursor); - UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") - virtual void Unsubscribe() override; + UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet", meta = (AutoCreateRefTerm = "OnUnsubscribeResponse")) + void Unsubscribe(FOnSubscribeOperationResponse OnUnsubscribeResponse); + + void Unsubscribe(FOnSubscribeOperationResponseNative NativeCallback); //This will subscribe automatically if set is subscribed UFUNCTION(BlueprintCallable, Category="Pubnub|SubscriptionSet") @@ -155,4 +164,3 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase UFUNCTION() virtual void CleanUpSubscription() override; }; - diff --git a/Source/PubnubLibrary/Public/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index c8f7b5f..81759f5 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -947,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; @@ -1758,8 +1759,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 @@ -1846,6 +1845,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 From 9196ea04502046909be9c26c8fd4ca7f76384af0 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Tue, 2 Sep 2025 13:10:56 +0200 Subject: [PATCH 06/16] Add comments to all public methods. --- .../Public/Entities/PubnubBaseEntity.h | 22 ++- .../Public/Entities/PubnubChannelEntity.h | 8 +- .../Entities/PubnubChannelGroupEntity.h | 7 +- .../Entities/PubnubChannelMetadataEntity.h | 6 +- .../Public/Entities/PubnubSubscription.h | 169 +++++++++++++++++- .../Entities/PubnubUserMetadataEntity.h | 6 +- Source/PubnubLibrary/Public/PubnubSubsystem.h | 88 +++++++++ 7 files changed, 293 insertions(+), 13 deletions(-) diff --git a/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h index e5394c8..4bd5bd4 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubBaseEntity.h @@ -10,7 +10,12 @@ 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 @@ -22,6 +27,21 @@ class PUBNUBLIBRARY_API UPubnubBaseEntity : public UObject 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()); diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h index 95bc38e..becf837 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h @@ -10,9 +10,13 @@ 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) +UCLASS(Blueprintable, Abstract) class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity { GENERATED_BODY() diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h index e61dc2a..6f73c4f 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelGroupEntity.h @@ -10,7 +10,12 @@ 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 diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h index c74c701..f404949 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelMetadataEntity.h @@ -10,7 +10,11 @@ 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 diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h index e3dd089..b229ed7 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -7,24 +7,41 @@ #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) +UCLASS(Blueprintable, Abstract) class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject { GENERATED_BODY() @@ -33,34 +50,48 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject 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; - //virtual void Subscribe(FPubnubSubscriptionCursor Cursor){}; - //virtual void Unsubscribe(){}; - - //Do proper clean up when object is being destroyed + /** + * 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: @@ -74,6 +105,14 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject }; +/** + * 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 { @@ -85,17 +124,60 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase 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); @@ -112,6 +194,14 @@ class PUBNUBLIBRARY_API UPubnubSubscription: public UPubnubSubscriptionBase }; +/** + * 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 { @@ -122,30 +212,95 @@ class PUBNUBLIBRARY_API UPubnubSubscriptionSet: public UPubnubSubscriptionBase 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); - //This will subscribe automatically if set is subscribed + /** + * 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;}; diff --git a/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h index 2197d19..a10cd85 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubUserMetadataEntity.h @@ -10,7 +10,11 @@ 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 diff --git a/Source/PubnubLibrary/Public/PubnubSubsystem.h b/Source/PubnubLibrary/Public/PubnubSubsystem.h index 81759f5..7989fe6 100644 --- a/Source/PubnubLibrary/Public/PubnubSubsystem.h +++ b/Source/PubnubLibrary/Public/PubnubSubsystem.h @@ -1698,27 +1698,115 @@ class PUBNUBLIBRARY_API UPubnubSubsystem : public UGameInstanceSubsystem #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(); From 0a3b8616eaaad3394ec8dbf7c574472a6cd3a79c Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Tue, 2 Sep 2025 16:00:31 +0200 Subject: [PATCH 07/16] Tests WIP --- .../Public/Entities/PubnubChannelEntity.h | 2 +- .../Public/Entities/PubnubSubscription.h | 2 +- .../Private/Tests/PubnubEntitiesTests.cpp | 489 ++++++++++++++++++ 3 files changed, 491 insertions(+), 2 deletions(-) create mode 100644 Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp diff --git a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h index becf837..4eb103f 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubChannelEntity.h @@ -16,7 +16,7 @@ class UPubnubSubsystem; * It enables channel-specific operations such as publishing messages, sending signals, * and managing presence information through Blueprint-compatible functions and delegates. */ -UCLASS(Blueprintable, Abstract) +UCLASS(Blueprintable) class PUBNUBLIBRARY_API UPubnubChannelEntity : public UPubnubBaseEntity { GENERATED_BODY() diff --git a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h index b229ed7..040c119 100644 --- a/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h +++ b/Source/PubnubLibrary/Public/Entities/PubnubSubscription.h @@ -41,7 +41,7 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FOnPubnubAnyMessageTypeNative, const FPubnub * and events from PubNub channels. It manages the lifecycle of subscriptions * and provides both Blueprint-compatible and native C++ event handling. */ -UCLASS(Blueprintable, Abstract) +UCLASS(Blueprintable) class PUBNUBLIBRARY_API UPubnubSubscriptionBase : public UObject { GENERATED_BODY() diff --git a/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp new file mode 100644 index 0000000..cdf8c27 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp @@ -0,0 +1,489 @@ +// 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" + +#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); + +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; +} + +#endif // WITH_DEV_AUTOMATION_TESTS \ No newline at end of file From e27c020bc4c298d3292d9d25c86b54decd7507f6 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 3 Sep 2025 09:05:51 +0200 Subject: [PATCH 08/16] Fix subscription to group. --- .../FunctionLibraries/PubnubUtilities.cpp | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index f50085f..4cafe55 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -347,14 +347,21 @@ pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForEntity(pubnub_t* Co pubnub_entity_t* PubnubEntity = nullptr; switch (EntityType) { - case EPubnubEntityType::PEnT_Channel: - PubnubEntity = reinterpret_cast(pubnub_channel_alloc(Context, EntityIDHolder.Get())); - case EPubnubEntityType::PEnT_ChannelGroup: - PubnubEntity = reinterpret_cast(pubnub_channel_group_alloc(Context, EntityIDHolder.Get())); - case EPubnubEntityType::PEnT_ChannelMetadata: - PubnubEntity = reinterpret_cast(pubnub_channel_metadata_alloc(Context, EntityIDHolder.Get())); - case EPubnubEntityType::PEnT_UserMetadata: - PubnubEntity = reinterpret_cast(pubnub_user_metadata_alloc(Context, EntityIDHolder.Get())); + 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); From 051d433fdc8de4728b50b551c749a091bbc347d7 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 3 Sep 2025 12:45:52 +0200 Subject: [PATCH 09/16] Add remaining Entities tests. --- .../Private/Entities/PubnubSubscription.cpp | 4 +- .../Private/Tests/PubnubEntitiesTests.cpp | 636 ++++++++++++++++++ 2 files changed, 638 insertions(+), 2 deletions(-) diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 314d2e8..3d02a97 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -189,8 +189,8 @@ void UPubnubSubscription::InternalInit() { if (UPubnubSubscription* S = SubscriptionWeak.Get(); IsValid(S)) { - S->OnPubnubMessage.Broadcast(MessageData); - S->OnPubnubMessageNative.Broadcast(MessageData); + S->OnPubnubObjectEvent.Broadcast(MessageData); + S->OnPubnubObjectEventNative.Broadcast(MessageData); S->FOnPubnubAnyMessageType.Broadcast(MessageData); S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); } diff --git a/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp index cdf8c27..93f9b3f 100644 --- a/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp +++ b/Source/PubnubLibraryTests/Private/Tests/PubnubEntitiesTests.cpp @@ -9,6 +9,7 @@ #include "Entities/PubnubChannelMetadataEntity.h" #include "Entities/PubnubUserMetadataEntity.h" #include "Entities/PubnubSubscription.h" +#include "FunctionLibraries/PubnubTimetokenUtilities.h" #if WITH_DEV_AUTOMATION_TESTS @@ -21,6 +22,9 @@ 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) { @@ -486,4 +490,636 @@ bool FPubnubSubscriptionSetTest::RunTest(const FString& Parameters) 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 From cfb8d402507feabd28b237112a63b9c9cccfabb8 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 3 Sep 2025 13:28:56 +0200 Subject: [PATCH 10/16] Add Entities samples. --- .../Private/Samples/Sample_Entities.cpp | 623 ++++++++++++++++++ .../Public/Samples/Sample_Entities.h | 137 ++++ 2 files changed, 760 insertions(+) create mode 100644 Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp create mode 100644 Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h diff --git a/Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp b/Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp new file mode 100644 index 0000000..78b7174 --- /dev/null +++ b/Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp @@ -0,0 +1,623 @@ +// Copyright 2025 PubNub Inc. All Rights Reserved. + +#include "Samples/Sample_Entities.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. + + +//Internal function, don't copy it with the samples +void ASample_Entities::RunSamples() +{ + Super::RunSamples(); + + SubscribeWithChannelEntitySample(); + SubscribeWithChannelGroupEntitySample(); + SubscribeWithChannelMetadataEntitySample(); + SubscribeWithUserMetadataEntitySample(); + ChannelEntityAllListenersSample(); + ChannelEntityPublishMessageSample(); + ChannelGroupEntityAddChannelSample(); + ChannelMetadataEntitySetMetadataSample(); + UserMetadataEntitySetMetadataSample(); + CreateSubscriptionSetFromNamesSample(); + CreateSubscriptionSetFromEntitiesSample(); + SubscriptionSetAddRemoveSubscriptionsSample(); + SubscriptionSetMergeOperationsSample(); +} + +//Internal function, don't copy it with the samples +ASample_Entities::ASample_Entities() +{ + SamplesName = "Entities"; +} + + +/* ENTITY SAMPLE FUNCTIONS */ + +// snippet.subscribe_with_channel_entity +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + ChannelSubscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessageReceived_ChannelEntitySample); + + // Subscribe to start receiving messages + ChannelSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); +} + +// snippet.subscribe_with_channel_group_entity +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + GroupSubscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessageReceived_ChannelGroupEntitySample); + + // Subscribe to start receiving messages from all channels in the group + GroupSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Group Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); +} + +// snippet.subscribe_with_channel_metadata_entity +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + MetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_ChannelMetadataEntitySample); + + // Subscribe to start receiving metadata change events + MetadataSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("Channel Metadata Entity - Object event received: %s"), *Message.Message); +} + +// snippet.subscribe_with_user_metadata_entity +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + UserMetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_UserMetadataEntitySample); + + // Subscribe to start receiving user metadata change events + UserMetadataSubscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("User Metadata Entity - Object event received: %s"), *Message.Message); +} + +// snippet.channel_entity_all_listeners +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + Subscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_AllListenersSample); + + // 2. Signal Listener - Fires when signals are sent to the channel + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + Subscription->OnPubnubSignal.AddDynamic(this, &ASample_Entities::OnSignal_AllListenersSample); + + // 3. Presence Event Listener - Fires when users join/leave/timeout on the channel + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + Subscription->OnPubnubPresenceEvent.AddDynamic(this, &ASample_Entities::OnPresenceEvent_AllListenersSample); + + // 4. Object Event Listener - Fires when App Context metadata changes occur + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + Subscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_AllListenersSample); + + // 5. Message Action Listener - Fires when message actions/reactions are added or removed + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + Subscription->OnPubnubMessageAction.AddDynamic(this, &ASample_Entities::OnMessageAction_AllListenersSample); + + // 6. Universal Listener - Fires for ANY type of PubNub event (catch-all) + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + Subscription->FOnPubnubAnyMessageType.AddDynamic(this, &ASample_Entities::OnAnyEvent_AllListenersSample); + + // Subscribe to start receiving all event types + Subscription->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::OnPresenceEvent_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("PRESENCE LISTENER - Event: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnObjectEvent_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("OBJECT EVENT LISTENER - Event: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnMessageAction_AllListenersSample(FPubnubMessageData Message) +{ + UE_LOG(LogTemp, Log, TEXT("MESSAGE ACTION LISTENER - Action: %s, Channel: %s"), + *Message.Message, *Message.Channel); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + FOnPublishMessageResponse OnPublishResult; + OnPublishResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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.channel_group_entity_add_channel +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::ChannelGroupEntityAddChannelSample() +{ + // Get PubnubSubsystem from GameInstance + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); + UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); + + // Set UserID + FString UserID = TEXT("Server_Admin"); + PubnubSubsystem->SetUserID(UserID); + + // Create a channel group entity for managing multiplayer game rooms + FString GameRoomsGroup = TEXT("multiplayer_game_rooms"); + UPubnubChannelGroupEntity* GameRoomsEntity = PubnubSubsystem->CreateChannelGroupEntity(GameRoomsGroup); + + // Set up callback to handle the add channel operation result + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + FOnAddChannelToGroupResponse OnAddChannelResult; + OnAddChannelResult.BindDynamic(this, &ASample_Entities::OnAddChannelResult_GroupEntitySample); + + // Add a new battle arena channel to the multiplayer rooms group + FString NewBattleArena = TEXT("battle_arena_storm"); + GameRoomsEntity->AddChannelToGroup(NewBattleArena, OnAddChannelResult); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::OnAddChannelResult_GroupEntitySample(FPubnubOperationResult Result) +{ + if(Result.Error) + { + UE_LOG(LogTemp, Error, TEXT("Failed to add battle arena to game rooms. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); + } + else + { + UE_LOG(LogTemp, Log, TEXT("Battle arena successfully added to multiplayer game rooms group!")); + } +} + +// snippet.channel_metadata_entity_set_metadata +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + FOnSetChannelMetadataResponse OnSetMetadataResult; + OnSetMetadataResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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.user_metadata_entity_set_metadata +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + FOnSetUserMetadataResponse OnSetPlayerInfoResult; + OnSetPlayerInfoResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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.create_subscription_set_from_names +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + TeamCommSubscriptionSet->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromNamesSample); + + // Subscribe to start monitoring all team channels and groups simultaneously + TeamCommSubscriptionSet->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class + GameMonitoringSet->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromEntitiesSample); + + // Add object event listener to monitor configuration changes + // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class + GameMonitoringSet->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromEntitiesSample); + + // Subscribe to start comprehensive game monitoring + GameMonitoringSet->Subscribe(); +} + +// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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_Entities with name of your Actor class +void ASample_Entities::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/Public/Samples/Sample_Entities.h b/Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h new file mode 100644 index 0000000..5ed98b5 --- /dev/null +++ b/Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h @@ -0,0 +1,137 @@ +// 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 "PubnubSampleBase.h" +#include "Sample_Entities.generated.h" + + +UCLASS() +class PUBNUBLIBRARYTESTS_API ASample_Entities : public APubnubSampleBase +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") + void RunSamples() override; + + ASample_Entities(); + + + /* ENTITY SAMPLE FUNCTIONS */ + + // snippet.subscribe_with_channel_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscribeWithChannelEntitySample(); + + UFUNCTION() + void OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message); + + // snippet.subscribe_with_channel_group_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscribeWithChannelGroupEntitySample(); + + UFUNCTION() + void OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message); + + // snippet.subscribe_with_channel_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscribeWithChannelMetadataEntitySample(); + + UFUNCTION() + void OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message); + + // snippet.subscribe_with_user_metadata_entity + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscribeWithUserMetadataEntitySample(); + + UFUNCTION() + void OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message); + + // snippet.channel_entity_all_listeners + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + 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|Entities") + void ChannelEntityPublishMessageSample(); + + UFUNCTION() + void OnPublishResult_ChannelEntitySample(FPubnubOperationResult Result, FPubnubMessageData Message); + + // snippet.channel_group_entity_add_channel + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void ChannelGroupEntityAddChannelSample(); + + UFUNCTION() + void OnAddChannelResult_GroupEntitySample(FPubnubOperationResult Result); + + // snippet.channel_metadata_entity_set_metadata + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void ChannelMetadataEntitySetMetadataSample(); + + UFUNCTION() + void OnSetChannelMetadataResult_Sample(FPubnubOperationResult Result, FPubnubChannelData ChannelData); + + // snippet.user_metadata_entity_set_metadata + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void UserMetadataEntitySetMetadataSample(); + + UFUNCTION() + void OnSetUserMetadataResult_Sample(FPubnubOperationResult Result, FPubnubUserData UserData); + + // snippet.create_subscription_set_from_names + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void CreateSubscriptionSetFromNamesSample(); + + UFUNCTION() + void OnMessage_SubscriptionSetFromNamesSample(FPubnubMessageData Message); + + // snippet.create_subscription_set_from_entities + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void CreateSubscriptionSetFromEntitiesSample(); + + UFUNCTION() + void OnMessage_SubscriptionSetFromEntitiesSample(FPubnubMessageData Message); + + // snippet.subscription_set_add_remove_subscriptions + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscriptionSetAddRemoveSubscriptionsSample(); + + // snippet.subscription_set_merge_operations + UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") + void SubscriptionSetMergeOperationsSample(); + + + // snippet.end +}; From 1e0fe4d70725d2248c076d9783652811e24ca007 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 3 Sep 2025 13:41:17 +0200 Subject: [PATCH 11/16] Convert C style casts to C++. --- .../Private/FunctionLibraries/PubnubUtilities.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp index 4cafe55..e834678 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -365,7 +365,7 @@ pubnub_subscription_t* UPubnubUtilities::EEGetSubscriptionForEntity(pubnub_t* Co } pubnub_subscription_t* Subscription = pubnub_subscription_alloc(PubnubEntity, &PnOptions); - pubnub_entity_free((void**)&PubnubEntity); + pubnub_entity_free(reinterpret_cast(&PubnubEntity)); return Subscription; } @@ -393,7 +393,7 @@ pubnub_subscription_set_t* UPubnubUtilities::EEGetSubscriptionSetForEntities(pub for(pubnub_entity_t*& Entity : PubnubEntities) { - pubnub_entity_free((void**)&Entity); + pubnub_entity_free(reinterpret_cast(&Entity)); } return SubscriptionSet; From 6e96f4b075574ed7eac9b3158aba0ededf967535 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 3 Sep 2025 14:35:58 +0200 Subject: [PATCH 12/16] Small fixes to Entity related operations. --- .../Private/Entities/PubnubBaseEntity.cpp | 7 +++ .../Private/Entities/PubnubChannelEntity.cpp | 45 +++++++++++++++++++ .../Entities/PubnubChannelGroupEntity.cpp | 40 +++++++++++++++++ .../Entities/PubnubChannelMetadataEntity.cpp | 30 +++++++++++++ .../Private/Entities/PubnubSubscription.cpp | 14 +++++- .../Entities/PubnubUserMetadataEntity.cpp | 30 +++++++++++++ .../FunctionLibraries/PubnubUtilities.cpp | 2 +- 7 files changed, 165 insertions(+), 3 deletions(-) diff --git a/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp index 3e188c6..b878bb5 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubBaseEntity.cpp @@ -6,6 +6,12 @@ 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); @@ -18,6 +24,7 @@ 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 index 56ce046..e9082c3 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelEntity.cpp @@ -10,40 +10,85 @@ UPubnubChannelEntity::UPubnubChannelEntity() 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 index 8617b2c..8f4080d 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelGroupEntity.cpp @@ -10,41 +10,81 @@ UPubnubChannelGroupEntity::UPubnubChannelGroupEntity() 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 index 66338ba..569dc02 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubChannelMetadataEntity.cpp @@ -10,31 +10,61 @@ UPubnubChannelMetadataEntity::UPubnubChannelMetadataEntity() 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 index 3d02a97..0b0d79e 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -28,7 +28,7 @@ void UPubnubSubscription::Subscribe(FOnSubscribeOperationResponseNative NativeCa { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + UE_LOG(PubnubLog, Error, TEXT("[Subscribe]: This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); return; } @@ -61,7 +61,7 @@ void UPubnubSubscription::Unsubscribe(FOnSubscribeOperationResponseNative Native { if(!IsInitialized) { - UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: This SubscriptionSet is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); + UE_LOG(PubnubLog, Error, TEXT("[Unsubscribe]: This Subscription is invalid. Probably PubnubSubsystem was deinitialized. Initialize it again and create new subscription.")); return; } @@ -108,6 +108,11 @@ UPubnubSubscriptionSet* UPubnubSubscription::AddSubscription(UPubnubSubscription 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.")); @@ -121,6 +126,11 @@ void UPubnubSubscription::InitSubscription(UPubnubSubsystem* InPubnubSubsystem, 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.")); diff --git a/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp index efb0d23..259e73f 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubUserMetadataEntity.cpp @@ -10,31 +10,61 @@ UPubnubUserMetadataEntity::UPubnubUserMetadataEntity() 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 e834678..3043c97 100644 --- a/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp +++ b/Source/PubnubLibrary/Private/FunctionLibraries/PubnubUtilities.cpp @@ -386,7 +386,7 @@ pubnub_subscription_set_t* UPubnubUtilities::EEGetSubscriptionSetForEntities(pub for(FString ChannelGroup : ChannelGroups) { FUTF8StringHolder EntityIDHolder(ChannelGroup); - PubnubEntities.Add(reinterpret_cast(pubnub_channel_alloc(Context, EntityIDHolder.Get()))); + PubnubEntities.Add(reinterpret_cast(pubnub_channel_group_alloc(Context, EntityIDHolder.Get()))); } pubnub_subscription_set_t* SubscriptionSet = pubnub_subscription_set_alloc_with_entities(PubnubEntities.GetData(), PubnubEntities.Num(), &PnOptions); From c07b0e785db141d5e08a5f27d30f40232b35afb5 Mon Sep 17 00:00:00 2001 From: Kamil Gronek Date: Wed, 10 Sep 2025 11:29:56 +0200 Subject: [PATCH 13/16] Add new samples for all functions in entities. --- .../Samples/Entities/Sample_ChannelEntity.cpp | 607 +++++++++++++++++ .../Entities/Sample_ChannelGroupEntity.cpp | 467 +++++++++++++ .../Entities/Sample_ChannelMetadataEntity.cpp | 423 ++++++++++++ .../Entities/Sample_SubscriptionSet.cpp | 223 +++++++ .../Entities/Sample_UserMetadataEntity.cpp | 417 ++++++++++++ .../Private/Samples/Sample_Entities.cpp | 623 ------------------ .../Samples/Entities/Sample_ChannelEntity.h | 129 ++++ .../Entities/Sample_ChannelGroupEntity.h | 101 +++ .../Entities/Sample_ChannelMetadataEntity.h | 93 +++ .../Samples/Entities/Sample_SubscriptionSet.h | 59 ++ .../Entities/Sample_UserMetadataEntity.h | 93 +++ .../Public/Samples/Sample_Entities.h | 137 ---- 12 files changed, 2612 insertions(+), 760 deletions(-) create mode 100644 Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelEntity.cpp create mode 100644 Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelGroupEntity.cpp create mode 100644 Source/PubnubLibraryTests/Private/Samples/Entities/Sample_ChannelMetadataEntity.cpp create mode 100644 Source/PubnubLibraryTests/Private/Samples/Entities/Sample_SubscriptionSet.cpp create mode 100644 Source/PubnubLibraryTests/Private/Samples/Entities/Sample_UserMetadataEntity.cpp delete mode 100644 Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp create mode 100644 Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelEntity.h create mode 100644 Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelGroupEntity.h create mode 100644 Source/PubnubLibraryTests/Public/Samples/Entities/Sample_ChannelMetadataEntity.h create mode 100644 Source/PubnubLibraryTests/Public/Samples/Entities/Sample_SubscriptionSet.h create mode 100644 Source/PubnubLibraryTests/Public/Samples/Entities/Sample_UserMetadataEntity.h delete mode 100644 Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h 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/Samples/Sample_Entities.cpp b/Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp deleted file mode 100644 index 78b7174..0000000 --- a/Source/PubnubLibraryTests/Private/Samples/Sample_Entities.cpp +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright 2025 PubNub Inc. All Rights Reserved. - -#include "Samples/Sample_Entities.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. - - -//Internal function, don't copy it with the samples -void ASample_Entities::RunSamples() -{ - Super::RunSamples(); - - SubscribeWithChannelEntitySample(); - SubscribeWithChannelGroupEntitySample(); - SubscribeWithChannelMetadataEntitySample(); - SubscribeWithUserMetadataEntitySample(); - ChannelEntityAllListenersSample(); - ChannelEntityPublishMessageSample(); - ChannelGroupEntityAddChannelSample(); - ChannelMetadataEntitySetMetadataSample(); - UserMetadataEntitySetMetadataSample(); - CreateSubscriptionSetFromNamesSample(); - CreateSubscriptionSetFromEntitiesSample(); - SubscriptionSetAddRemoveSubscriptionsSample(); - SubscriptionSetMergeOperationsSample(); -} - -//Internal function, don't copy it with the samples -ASample_Entities::ASample_Entities() -{ - SamplesName = "Entities"; -} - - -/* ENTITY SAMPLE FUNCTIONS */ - -// snippet.subscribe_with_channel_entity -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - ChannelSubscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessageReceived_ChannelEntitySample); - - // Subscribe to start receiving messages - ChannelSubscription->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("Channel Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); -} - -// snippet.subscribe_with_channel_group_entity -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - GroupSubscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessageReceived_ChannelGroupEntitySample); - - // Subscribe to start receiving messages from all channels in the group - GroupSubscription->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("Channel Group Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel); -} - -// snippet.subscribe_with_channel_metadata_entity -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - MetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_ChannelMetadataEntitySample); - - // Subscribe to start receiving metadata change events - MetadataSubscription->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("Channel Metadata Entity - Object event received: %s"), *Message.Message); -} - -// snippet.subscribe_with_user_metadata_entity -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - UserMetadataSubscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_UserMetadataEntitySample); - - // Subscribe to start receiving user metadata change events - UserMetadataSubscription->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("User Metadata Entity - Object event received: %s"), *Message.Message); -} - -// snippet.channel_entity_all_listeners -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - Subscription->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_AllListenersSample); - - // 2. Signal Listener - Fires when signals are sent to the channel - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - Subscription->OnPubnubSignal.AddDynamic(this, &ASample_Entities::OnSignal_AllListenersSample); - - // 3. Presence Event Listener - Fires when users join/leave/timeout on the channel - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - Subscription->OnPubnubPresenceEvent.AddDynamic(this, &ASample_Entities::OnPresenceEvent_AllListenersSample); - - // 4. Object Event Listener - Fires when App Context metadata changes occur - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - Subscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnObjectEvent_AllListenersSample); - - // 5. Message Action Listener - Fires when message actions/reactions are added or removed - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - Subscription->OnPubnubMessageAction.AddDynamic(this, &ASample_Entities::OnMessageAction_AllListenersSample); - - // 6. Universal Listener - Fires for ANY type of PubNub event (catch-all) - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - Subscription->FOnPubnubAnyMessageType.AddDynamic(this, &ASample_Entities::OnAnyEvent_AllListenersSample); - - // Subscribe to start receiving all event types - Subscription->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::OnPresenceEvent_AllListenersSample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("PRESENCE LISTENER - Event: %s, Channel: %s"), - *Message.Message, *Message.Channel); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnObjectEvent_AllListenersSample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("OBJECT EVENT LISTENER - Event: %s, Channel: %s"), - *Message.Message, *Message.Channel); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnMessageAction_AllListenersSample(FPubnubMessageData Message) -{ - UE_LOG(LogTemp, Log, TEXT("MESSAGE ACTION LISTENER - Action: %s, Channel: %s"), - *Message.Message, *Message.Channel); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - FOnPublishMessageResponse OnPublishResult; - OnPublishResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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.channel_group_entity_add_channel -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::ChannelGroupEntityAddChannelSample() -{ - // Get PubnubSubsystem from GameInstance - UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); - UPubnubSubsystem* PubnubSubsystem = GameInstance->GetSubsystem(); - - // Set UserID - FString UserID = TEXT("Server_Admin"); - PubnubSubsystem->SetUserID(UserID); - - // Create a channel group entity for managing multiplayer game rooms - FString GameRoomsGroup = TEXT("multiplayer_game_rooms"); - UPubnubChannelGroupEntity* GameRoomsEntity = PubnubSubsystem->CreateChannelGroupEntity(GameRoomsGroup); - - // Set up callback to handle the add channel operation result - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - FOnAddChannelToGroupResponse OnAddChannelResult; - OnAddChannelResult.BindDynamic(this, &ASample_Entities::OnAddChannelResult_GroupEntitySample); - - // Add a new battle arena channel to the multiplayer rooms group - FString NewBattleArena = TEXT("battle_arena_storm"); - GameRoomsEntity->AddChannelToGroup(NewBattleArena, OnAddChannelResult); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::OnAddChannelResult_GroupEntitySample(FPubnubOperationResult Result) -{ - if(Result.Error) - { - UE_LOG(LogTemp, Error, TEXT("Failed to add battle arena to game rooms. Status: %d, Reason: %s"), Result.Status, *Result.ErrorMessage); - } - else - { - UE_LOG(LogTemp, Log, TEXT("Battle arena successfully added to multiplayer game rooms group!")); - } -} - -// snippet.channel_metadata_entity_set_metadata -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - FOnSetChannelMetadataResponse OnSetMetadataResult; - OnSetMetadataResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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.user_metadata_entity_set_metadata -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - FOnSetUserMetadataResponse OnSetPlayerInfoResult; - OnSetPlayerInfoResult.BindDynamic(this, &ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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.create_subscription_set_from_names -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - TeamCommSubscriptionSet->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromNamesSample); - - // Subscribe to start monitoring all team channels and groups simultaneously - TeamCommSubscriptionSet->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class - GameMonitoringSet->OnPubnubMessage.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromEntitiesSample); - - // Add object event listener to monitor configuration changes - // ACTION REQUIRED: Replace ASample_Entities with name of your Actor class - GameMonitoringSet->OnPubnubObjectEvent.AddDynamic(this, &ASample_Entities::OnMessage_SubscriptionSetFromEntitiesSample); - - // Subscribe to start comprehensive game monitoring - GameMonitoringSet->Subscribe(); -} - -// ACTION REQUIRED: Replace ASample_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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_Entities with name of your Actor class -void ASample_Entities::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/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/PubnubLibraryTests/Public/Samples/Sample_Entities.h b/Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h deleted file mode 100644 index 5ed98b5..0000000 --- a/Source/PubnubLibraryTests/Public/Samples/Sample_Entities.h +++ /dev/null @@ -1,137 +0,0 @@ -// 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 "PubnubSampleBase.h" -#include "Sample_Entities.generated.h" - - -UCLASS() -class PUBNUBLIBRARYTESTS_API ASample_Entities : public APubnubSampleBase -{ - GENERATED_BODY() - -public: - - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples") - void RunSamples() override; - - ASample_Entities(); - - - /* ENTITY SAMPLE FUNCTIONS */ - - // snippet.subscribe_with_channel_entity - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscribeWithChannelEntitySample(); - - UFUNCTION() - void OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message); - - // snippet.subscribe_with_channel_group_entity - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscribeWithChannelGroupEntitySample(); - - UFUNCTION() - void OnMessageReceived_ChannelGroupEntitySample(FPubnubMessageData Message); - - // snippet.subscribe_with_channel_metadata_entity - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscribeWithChannelMetadataEntitySample(); - - UFUNCTION() - void OnObjectEvent_ChannelMetadataEntitySample(FPubnubMessageData Message); - - // snippet.subscribe_with_user_metadata_entity - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscribeWithUserMetadataEntitySample(); - - UFUNCTION() - void OnObjectEvent_UserMetadataEntitySample(FPubnubMessageData Message); - - // snippet.channel_entity_all_listeners - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - 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|Entities") - void ChannelEntityPublishMessageSample(); - - UFUNCTION() - void OnPublishResult_ChannelEntitySample(FPubnubOperationResult Result, FPubnubMessageData Message); - - // snippet.channel_group_entity_add_channel - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void ChannelGroupEntityAddChannelSample(); - - UFUNCTION() - void OnAddChannelResult_GroupEntitySample(FPubnubOperationResult Result); - - // snippet.channel_metadata_entity_set_metadata - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void ChannelMetadataEntitySetMetadataSample(); - - UFUNCTION() - void OnSetChannelMetadataResult_Sample(FPubnubOperationResult Result, FPubnubChannelData ChannelData); - - // snippet.user_metadata_entity_set_metadata - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void UserMetadataEntitySetMetadataSample(); - - UFUNCTION() - void OnSetUserMetadataResult_Sample(FPubnubOperationResult Result, FPubnubUserData UserData); - - // snippet.create_subscription_set_from_names - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void CreateSubscriptionSetFromNamesSample(); - - UFUNCTION() - void OnMessage_SubscriptionSetFromNamesSample(FPubnubMessageData Message); - - // snippet.create_subscription_set_from_entities - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void CreateSubscriptionSetFromEntitiesSample(); - - UFUNCTION() - void OnMessage_SubscriptionSetFromEntitiesSample(FPubnubMessageData Message); - - // snippet.subscription_set_add_remove_subscriptions - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscriptionSetAddRemoveSubscriptionsSample(); - - // snippet.subscription_set_merge_operations - UFUNCTION(BlueprintCallable, Category = "Pubnub|Samples|Entities") - void SubscriptionSetMergeOperationsSample(); - - - // snippet.end -}; From 9fc3f2c07eae78734d0ed44794497738af04ca2b Mon Sep 17 00:00:00 2001 From: Kamil Date: Wed, 10 Sep 2025 14:43:55 +0200 Subject: [PATCH 14/16] Add mac and ios libs --- Source/ThirdParty/sdk/lib/IOS/libpubnub.a | Bin 865304 -> 865304 bytes Source/ThirdParty/sdk/lib/MacOS/libpubnub.a | Bin 1681856 -> 1681856 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Source/ThirdParty/sdk/lib/IOS/libpubnub.a b/Source/ThirdParty/sdk/lib/IOS/libpubnub.a index 7cff674984420e5d06bd320cb5980b4bba586010..5e50ca808c2f3afbe28e34123ffa63c00f804d35 100644 GIT binary patch delta 1909 zcmY+FZD<@t7{|Gpy}g&6GjHh5UGdDBh)GW+8`7Ff5!@IgqDZ1r69eVafLg277|>FD zYf?&@e6W{%*c7#Jk%SrrO|Ir?6N){r6s%u*_Dd@&CZb%BLed~a#p3MT&SdxF{PzEU z=9y=neP*iEGF56hxkG(;S5vFeY;Ck6I-mL{U0yd?{g;rQ>*}w&Lx5r!(_H5Ddmj>D zuoFjfKYUVb5P(}QUM@H>o2;tah~~1ZSNh!w813*C-Vwx>z=6DPVn`74G}eLcLf5c| z1XE~I6QnqUDV*;>V~QZ$m3{*C#XRKt0NSh-guB*`p=s4cqA)j(LXz+}S2O5fjnHt? zGlQ-nK?X~T4xMMvCUFr8yGD9A@;Wo;JJesWl z_Uuk!EsR&?*_n%x3Ow8@TxQZw;Y26Cb-#<~F!csrPPhn#!3q3~%#gcQPT?11WJYx8 zIgR&h--R7{K8s^+4cmPNw{6$Zc0Kw9{!~`Xj6tzvG%pxu($l7hZ7MgMFuv@F~^L%I~bq&nY8O*LwXmWu!u`DP-PN z7Q%9^LwZ!1Y;mnCFDQLUXKiKUK^;a*O2Ns|D3h0ODo=`>%H-;Q%E1RcuAZcS$Q!4$ z*Z-%;+;&me*zf<*X#gj3=~aL3V^5rOAvO1ahm7u4C!H%WEE7UHv_7IH#ThL#sYPM< zDYfx&;aWMQQTT2`4LhyQTuDYYX4K(kpKkk%$z z(5hNvr^plSNL;wG9kwf}4L>G`Ss$VB_aSYrN{~c65`)4A+Lm9C`~Y*^IIbaQ#u!%d z7>dEavs$ewG%Eodg|?DrI)mHbgBwm^(kwHyXFHS#>66#aNxFj6$jaLQuY)vfxa!F~c5dYOi=sf+Y3XBRxARURS{7 z@20C1F#h}G@*?1j^#A)Om@azIt_IKK=fx7bdBT(Zw20W$iKA&ONG9S?Ujk(yjiFT~4NvAIR)>My4`oOQ^`V zWdKd1qPTGg%@f5Vbq34wIC(9vsPwtO;;nHe?=j^JMsC5LUv~HI;gO}0XcNG54VD| zCys2|@;$DTcaYba<7(E@$Lhhhwfm+p%IT(x=w1^FEY~no_4rQ zlGQ_u+}oxdE|DbogptevZP+hK@*E@WU0SwAA;Ak;Yg8fK1?@MeIawKS=;piHTTmhQ znR={kStYYN8nHuDQSUDBVZ^3|_1-(M3r^-f^!^1Mv84ILJMyjKGwRcaUsFi8t7qX( zcvkINJqkNz#G!}Y)>pn!lx5RDvbq&o8U${PULDZ;sv+8cMNgR!UHwO&K@g4I&{Hl% z;jppqBZ$5}WOM`|Dm`XoY=}+`8i{A?-5xc%E0^kVa~%b(jApMJv*N_uT=QLx-3~GR zr9~Q>{!(t%Z%~M{+uZuRLXM|R+^mrCYv%L|3OO@xrY9wdtkqfXQ{N@gKW>f@*4LJQ z_@JU$cGTbbl0wd$^rzoZNXvqMUfdw}vFXRisuMjX$T+b+D_66kR%g8=`7B%g|LxYP zpd^ccY%HT<2`oa-Ipa1{2AG3vkOfPp;L*rF`7g+N*aPgAv&Z)LnSZ? zKb$HACm4ctyD4vkBn`2dk!WKnj<Eq(fLEy4XQz^&8T(mWW_QcS;Lo0kD%X=uoQ^Q3OzQ*P)xAOb`#v*emI&0*%Q| zLZ9uDhEoL4zrjyv&c~7;o3Zj}@i8eYC#eu^|EE-#mJD3EB4y#)vAENq4}B+H#Y54W zQV2C}NDJ~r3IL^9@(A8U?HQ*|SFD!v=O;((sgftKF-U2wk*{Gt^-lR|Tm^u!nB0ncxpTqIE@x1F$0t^4ImWi1HYy*$yI=sj;7NI?SVT;%&oJnx zi|jvS3IJ*h7TULZI1q~xTC~=F8K){1l-OjJ*z?sJMeWJPOaRy6KB6&5MB8?0#i8E0V` z0$;K|rm|B8_z_1;i1^F$6gwxT051GIrQR=6n4ajgqBP}jb^;)DX;VrtB!JqL?CvW+ zO9^sfM$G<6)<^fTlhSbX^ zL>OpM^e1U?tPFtd-87|ML@?RV>7z4g`41-mLK}%|WQ_nU{iLOpt`NUZc5IUx8^qUL z5k6=U6KdS9cb$JIxyHrcx$0I4fC&T}feF_tJV(%!(JQX4T&>vUfQ0pyHD+D&^Ik@g=0n=&?LkS2 zE@u-rbkzI%S)D3jQ~T>d?-o9BVUrO=r`IRE|KqG z6Jmfto0j<^2*BWu=KDtG*yX|JeW$Tm82o0NC1JhW(gjvHMrSRzSC&ReE4Ox3>yQW(WP$2qA>jTyy|Ma35Y07R~KLe0Q)o5 zRd{Sxig|LL>ZQ(y)sL}VD8$gi0{uvjfY$Uz z1OMhn$i&;S$-bQde>!%`Uf%R#EU@mEi4^t*xDlvF{{!I|e-KcjA_8RL-ujHM`I{-i zb>ena#s?!J0#bT&GvgZt0D2YOnK}Gh0dR+Wka>Qd0KgEnNA&m$0suoiwN3wFjzeC0 zOMiNfL#}Ai+a5_2WqbLf-hmDocCa^f%QbyqR@1Flcl53<<7I;;Y@gBH_^{y06C|M` zQ51w`Y7<$<#aKAxng$}C;ldC?z+yf_TWB@m=H)F3Tf6B7STPxLy1O znZz>%VC5OITG*S#P{VZXl@$8t2_pVRux2bGPhBPx}~a*t z6^2akK_cDOJ3^959y2IyEWz;vaI6oUiZX5JGSm8)1Wl$R)=4GC8JW#EJ@-53xR*cA zZ@=^V9_RbM-`#VGs&k2|!z*n+$zQlAte^i@K|R&UC>#rQ-#w)DHpTAB>|8rt(RvST zJusJ@Bdwy^yP!j!Z9cENa45vi23@b^%g_dP?mTHlcu}#XV~@!IPn@=NY+^w6T4HfL zB;;DBSWFWtTny-0Cu?|fj_{q00kwCmDz0B3Y*5X>{pW=hb`s7{8qiFSlvI3f%< zOlCOi10hI*q4#jvX(2P)Y|-*Bp(M==jExDI)OV5Ii2@pa>KoxUElQEz7AIb`abZRQ zcWVIUnc_wI5@4a?@G^1XbhE|&Dlx$WBODsuy;U@ml9cPj8txXnSaym9Q`|-x#II=` znU}IA@t%vtwqua38X#OS_^*6EG9noAiTfXD$Zg6SR6lLeZXTS90>QFv{rah z78=e!Yc0-UKu?sA?!RMw@GJv5n*+C8*+Q;V&$1=oH*+)& z+Tu@0lOE#be>N9?iAcgl8sMR=fX5M==hg5D(Z2fy#xc<MLGaq{M@l4z^-I36A_M;JG!${qzKR1?Fi+j02N0+ab(izEYf>MMN-1kj`mf| z!dOJO%YX>wGBaai;Yd+hoHGMpc{@$o z!7SP;H48!8m9esRA=ky_W{&chE0@1#Y}BXW4>!B=d85I~y1&CU{1m%F0EDl- z>#AGIfXHPZX@P`mDX$|~42`+opew6L2adA;xb|e4ImQJkPz*pAHWWtBNzHWiFyQmG z(x2!9Ghkn(G)QL=0HjyYXLoMo(8tDEB>l0td2#=hUa_K-bK>MroQ>r!$sJ$wE#JvDV-gW5>o+0dj zzMxz{M=1;=@63{iSw8V47Z9G9E;rD(B2nxOl3`pd$7v@S7f4jA;focTlH*=w#lPhCL?{MgVRRjTpDPhZs;Ul{gs?EAIedvlRhI2>vmtK zO8@{BA1-tco23%(TJ286*cH&q>QnBkQ?xRY=jo_p7PZZ!Y0^IN+@RxG2(e+yaZd*= z2k|nWG+zHH&+q8EB7qrN`Kvz9CfaxmxRdbwK!*E3_Pmmr}S4c&;mN&32E*&0fD^cnBPCxRpz zf;7*Y&jCYW@#Tfy!70+&^^$jxZwjzz+3G#RCk+7N`z99`kk0iD-n)EZLl4FFc@Ku6 zilAYnihbVX>=dBltXtkeF3?m5MWuoV2EZy}rQ5MTH(EsLs@0WLfM__iw*YiQ~)J}sA&Lz?Nl;49@29nm5k6!YKTDrfj213m1S zR5}*2;&q-P!nAq5?=`-=0k~W4yTapyi5jW&t>IyTMd`17+vhP3P|T6{d<~4FEkb(s zvhRBy_W3te(kP?8+x*o*FL!ovSGZ1r`1PZ}f&7$(hF^Ozu!8HQf00>$ zGIr)<#9v_mY^=!MjDg=WfPBoW_}Rx9!_&-Hd{3+Km(9TW8a2tE5~T9_n`$ZVFxXh( zMz#5Ab_Im++8@<}Po&cLc5nPz?d4wwU=ew!iu|#GV_Q;{`Q8AFqal>L1W37lZ9psO zI%dG~dc;1;8E~u-ZK7`**7Iy+fU}!Xh!#1l`?~xZUVj9gpl-Rv`zXM*1GZDyr)ZSc z6i78(dI^jf{g#1K!s~Xl`* Date: Thu, 11 Sep 2025 09:29:22 +0200 Subject: [PATCH 15/16] Add Linux, Win and Android libraries. Fix plugin compilation error. Fix subscription calling incorrect delegate. --- PubnubLibrary.uplugin | 2 +- .../Private/Entities/PubnubSubscription.cpp | 4 ++-- .../PubnubLibrary/Public/PubnubEnumLibrary.h | 1 + Source/ThirdParty/sdk/lib/arm64/libpubnub.a | Bin 3612832 -> 3610672 bytes Source/ThirdParty/sdk/lib/linux/libpubnub.a | Bin 1023952 -> 1023952 bytes Source/ThirdParty/sdk/lib/win64/pubnub.lib | Bin 5405322 -> 5405274 bytes 6 files changed, 4 insertions(+), 3 deletions(-) diff --git a/PubnubLibrary.uplugin b/PubnubLibrary.uplugin index a7bda1b..fbd437d 100644 --- a/PubnubLibrary.uplugin +++ b/PubnubLibrary.uplugin @@ -1,6 +1,6 @@ { "FileVersion": 3, - "Version": 11, + "Version": 12, "VersionName": "1.0.0", "FriendlyName": "Pubnub Unreal SDK", "Description": "Quickly add interactive features to your game that scale without building your backend infrastructure.", diff --git a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp index 0b0d79e..8569a6e 100644 --- a/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp +++ b/Source/PubnubLibrary/Private/Entities/PubnubSubscription.cpp @@ -535,8 +535,8 @@ void UPubnubSubscriptionSet::InternalInit() { if (UPubnubSubscriptionSet* S = SubscriptionWeak.Get(); IsValid(S)) { - S->OnPubnubMessage.Broadcast(MessageData); - S->OnPubnubMessageNative.Broadcast(MessageData); + S->OnPubnubObjectEvent.Broadcast(MessageData); + S->OnPubnubObjectEventNative.Broadcast(MessageData); S->FOnPubnubAnyMessageType.Broadcast(MessageData); S->FOnPubnubAnyMessageTypeNative.Broadcast(MessageData); } diff --git a/Source/PubnubLibrary/Public/PubnubEnumLibrary.h b/Source/PubnubLibrary/Public/PubnubEnumLibrary.h index 27cc6ac..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) diff --git a/Source/ThirdParty/sdk/lib/arm64/libpubnub.a b/Source/ThirdParty/sdk/lib/arm64/libpubnub.a index 8e48be77538ada311adb129860008cdd46a69703..3432ce07156d16bcd77c18daa6c3788e449e5f64 100644 GIT binary patch delta 9086 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 WF&7YX12GQ}^8zv7_A{~kEKUGWX(0~) delta 81 zcmcbx-|oVGyM`9V7N!>FEiA#Y%m#V}(=Y0<=}+H#oz=blOe_l!vjQ<25VHd@2M}`t WF&7YX12GQ}^8zv7_A{~kEKUGWDj^O4 diff --git a/Source/ThirdParty/sdk/lib/win64/pubnub.lib b/Source/ThirdParty/sdk/lib/win64/pubnub.lib index 399af080342b687ac9c9d08b505f705f4f10f366..4775c8edff386a65d03a5ff7bdf5b72fd54aa8d9 100644 GIT binary patch delta 11814 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 O+WTLlvlCK}8U8<9ib5&? delta 11798 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 Mt3;b-CZ5#%FYfJ$hyVZp From 2bed6b3cfa53fa71c987c35b3bdeade2aa6cc379 Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Thu, 11 Sep 2025 07:53:18 +0000 Subject: [PATCH 16/16] PubNub SDK 1.1.0 release. --- .pubnub.yml | 11 +++++++++++ PubnubLibrary.uplugin | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) 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 fbd437d..d1985c2 100644 --- a/PubnubLibrary.uplugin +++ b/PubnubLibrary.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 12, - "VersionName": "1.0.0", + "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",