# OddSockets Unreal Engine SDK

A comprehensive real-time messaging SDK for Unreal Engine, providing seamless integration with the OddSockets platform. Features full Blueprint support, C++ API, and automatic manager discovery with worker load balancing.

## ✨ Features

- **🎮 Full Unreal Engine Integration**: Native C++ and Blueprint support
- **🔄 Auto-Reconnection**: Exponential backoff with configurable retry limits
- **⚖️ Load Balancing**: Automatic worker assignment and session stickiness
- **📡 Real-time Messaging**: WebSocket-based pub/sub with 32KB message limit
- **👥 Presence Tracking**: Real-time user presence and state management
- **📚 Message History**: Cached and server-side message history
- **🛡️ Error Handling**: Comprehensive error handling with Blueprint events
- **🎯 Blueprint Visual Scripting**: Full visual scripting support
- **🔧 Editor Integration**: Configure everything in the Unreal Editor

## 🚀 Quick Start

### 1. Installation

1. Copy the `OddSockets` folder to your project's `Plugins` directory
2. Add `OddSockets` to your project's `.uproject` file:
```json
{
  "Plugins": [
    {
      "Name": "OddSockets",
      "Enabled": true
    }
  ]
}
```
3. Regenerate project files and compile

### 2. C++ Usage

```cpp
#include "OddSocketsClient.h"

// In your actor's header file
UCLASS()
class MYGAME_API AMyGameActor : public AActor
{
    GENERATED_BODY()

public:
    AMyGameActor();

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OddSockets")
    AOddSocketsClient* OddSocketsClient;

    UFUNCTION()
    void OnConnected();

    UFUNCTION()
    void OnChannelMessage(const FOddSocketsChannelMessageData& MessageData);
};

// In your actor's implementation
AMyGameActor::AMyGameActor()
{
    PrimaryActorTick.bCanEverTick = false;
}

void AMyGameActor::BeginPlay()
{
    Super::BeginPlay();

    // Spawn OddSockets client
    OddSocketsClient = GetWorld()->SpawnActor<AOddSocketsClient>();

    // Configure the client
    FOddSocketsConfig Config;
    Config.ApiKey = TEXT("your-api-key-here");
    Config.UserId = TEXT("player-123");
    Config.bAutoConnect = true;
    Config.ReconnectAttempts = 5;
    Config.Timeout = 10;

    // Initialize and connect
    OddSocketsClient->Initialize(Config);
    
    // Bind events
    OddSocketsClient->OnConnected.AddDynamic(this, &AMyGameActor::OnConnected);
    
    // Connect
    OddSocketsClient->ConnectAsync();
}

void AMyGameActor::OnConnected()
{
    // Get a channel and subscribe
    UOddSocketsChannel* GameChannel = OddSocketsClient->GetChannel(TEXT("game-lobby"));
    
    // Set subscription options
    FOddSocketsSubscriptionOptions Options;
    Options.MaxHistory = 50;
    Options.bEnablePresence = true;
    Options.bRetainHistory = true;
    
    // Bind channel events
    GameChannel->OnMessage.AddDynamic(this, &AMyGameActor::OnChannelMessage);
    
    // Subscribe to channel
    GameChannel->SubscribeAsync(Options);
}

void AMyGameActor::OnChannelMessage(const FOddSocketsChannelMessageData& MessageData)
{
    UE_LOG(LogTemp, Log, TEXT("Received message from %s: %s"), 
           *MessageData.Sender, *MessageData.Message);
    
    // Parse JSON message if needed
    // Handle game logic based on message content
}
```

### 3. Blueprint Usage

1. **Create OddSockets Client Actor**:
   - In your Blueprint, use "Spawn Actor from Class"
   - Select "OddSockets Client" as the class

2. **Configure and Connect**:
   - Create an "OddSockets Config" struct
   - Set your API Key and other settings
   - Call "Initialize" with the config
   - Bind to "On Connected" event
   - Call "Connect Async"

3. **Channel Operations**:
   - Use "Get Channel" to get a channel reference
   - Call "Subscribe Async" with subscription options
   - Bind to "On Message" event to receive messages
   - Use "Publish Async" to send messages

## 📖 API Reference

### AOddSocketsClient

Main client class for connecting to OddSockets.

#### Methods

- `Initialize(Config)` - Initialize client with configuration
- `ConnectAsync()` - Connect to OddSockets platform
- `Disconnect()` - Disconnect from platform
- `GetChannel(ChannelName)` - Get or create a channel
- `PublishBulkAsync(Messages)` - Publish multiple messages at once

#### Properties

- `Config` - Client configuration (editable in editor)
- `ConnectionState` - Current connection state
- `ClientIdentifier` - Unique client identifier
- `WorkerInfo` - Information about assigned worker
- `SessionInfo` - Current session information

#### Events

- `OnConnecting` - Fired when connection starts
- `OnConnected` - Fired when successfully connected
- `OnDisconnected` - Fired when disconnected
- `OnError` - Fired when an error occurs
- `OnWorkerAssigned` - Fired when assigned to a worker
- `OnReconnecting` - Fired during reconnection attempts
- `OnMaxReconnectAttemptsReached` - Fired when max reconnects reached

### UOddSocketsChannel

Channel class for pub/sub messaging.

#### Methods

- `SubscribeAsync(Options)` - Subscribe to channel
- `UnsubscribeAsync()` - Unsubscribe from channel
- `PublishAsync(Message, Options)` - Publish a message
- `GetHistoryAsync(Options)` - Get message history
- `GetPresenceAsync()` - Get current presence information
- `UpdateStateAsync(State)` - Update user state

#### Properties

- `Name` - Channel name
- `IsSubscribed` - Whether currently subscribed
- `PresenceMap` - Current presence information
- `CachedHistory` - Cached message history

#### Events

- `OnMessage` - Fired when message received
- `OnSubscribed` - Fired when subscription confirmed
- `OnUnsubscribed` - Fired when unsubscription confirmed
- `OnPublished` - Fired when message published
- `OnPresence` - Fired when presence information received
- `OnPresenceChange` - Fired when user joins/leaves
- `OnHistory` - Fired when history received

## 🔧 Configuration

### FOddSocketsConfig

```cpp
USTRUCT(BlueprintType)
struct FOddSocketsConfig
{
    // Required Settings
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Required Settings")
    FString ApiKey; // Your OddSockets API key

    // Optional Settings
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Optional Settings")
    FString UserId; // User identifier (auto-generated if empty)

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Optional Settings")
    bool bAutoConnect = true; // Automatically connect on initialization

    // Connection Settings
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings")
    int32 ReconnectAttempts = 5; // Maximum reconnection attempts (0-10)

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings")
    int32 Timeout = 10; // Connection timeout in seconds (5-60)

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Connection Settings")
    int32 HeartbeatInterval = 30; // Heartbeat interval in seconds (10-300)

    // Logging
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Logging")
    EOddSocketsLogLevel LogLevel = EOddSocketsLogLevel::Info; // Logging level
};
```

### Subscription Options

```cpp
USTRUCT(BlueprintType)
struct FOddSocketsSubscriptionOptions
{
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Options")
    int32 MaxHistory = 100; // Maximum history messages to retain

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Options")
    bool bRetainHistory = true; // Whether to retain message history

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Options")
    bool bEnablePresence = false; // Whether to enable presence tracking
};
```

## 🎯 Examples

### Basic Chat System

```cpp
// Chat manager class
UCLASS()
class MYGAME_API AChatManager : public AActor
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, Category = "Chat")
    void SendChatMessage(const FString& Message);

    UFUNCTION(BlueprintCallable, Category = "Chat")
    void JoinChatRoom(const FString& RoomName);

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OddSockets")
    AOddSocketsClient* OddSocketsClient;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Chat")
    UOddSocketsChannel* ChatChannel;

    UFUNCTION()
    void OnConnected();

    UFUNCTION()
    void OnChatMessage(const FOddSocketsChannelMessageData& MessageData);

    UFUNCTION()
    void OnPresenceChange(const FOddSocketsChannelPresenceChangeData& PresenceData);
};

void AChatManager::BeginPlay()
{
    Super::BeginPlay();

    // Initialize OddSockets
    OddSocketsClient = GetWorld()->SpawnActor<AOddSocketsClient>();
    
    FOddSocketsConfig Config;
    Config.ApiKey = TEXT("your-api-key");
    Config.UserId = FString::Printf(TEXT("player_%d"), FMath::RandRange(1000, 9999));
    
    OddSocketsClient->Initialize(Config);
    OddSocketsClient->OnConnected.AddDynamic(this, &AChatManager::OnConnected);
    OddSocketsClient->ConnectAsync();
}

void AChatManager::OnConnected()
{
    JoinChatRoom(TEXT("global-chat"));
}

void AChatManager::JoinChatRoom(const FString& RoomName)
{
    ChatChannel = OddSocketsClient->GetChannel(RoomName);
    
    FOddSocketsSubscriptionOptions Options;
    Options.MaxHistory = 50;
    Options.bEnablePresence = true;
    Options.bRetainHistory = true;
    
    ChatChannel->OnMessage.AddDynamic(this, &AChatManager::OnChatMessage);
    ChatChannel->OnPresenceChange.AddDynamic(this, &AChatManager::OnPresenceChange);
    
    ChatChannel->SubscribeAsync(Options);
}

void AChatManager::SendChatMessage(const FString& Message)
{
    if (ChatChannel && ChatChannel->IsSubscribed())
    {
        // Create chat message JSON
        FString ChatJson = FString::Printf(TEXT("{\"type\":\"chat\",\"message\":\"%s\",\"timestamp\":\"%s\"}"), 
                                          *Message, *FDateTime::UtcNow().ToIso8601());
        
        FOddSocketsPublishOptions Options;
        Options.Ttl = 3600; // 1 hour TTL
        
        ChatChannel->PublishAsync(ChatJson, Options);
    }
}

void AChatManager::OnChatMessage(const FOddSocketsChannelMessageData& MessageData)
{
    // Parse JSON and display chat message
    UE_LOG(LogTemp, Log, TEXT("Chat from %s: %s"), *MessageData.Sender, *MessageData.Message);
    
    // Update UI with new chat message
    // OnChatMessageReceived.Broadcast(MessageData);
}

void AChatManager::OnPresenceChange(const FOddSocketsChannelPresenceChangeData& PresenceData)
{
    if (PresenceData.Action == TEXT("join"))
    {
        UE_LOG(LogTemp, Log, TEXT("%s joined the chat"), *PresenceData.User.UserId);
    }
    else if (PresenceData.Action == TEXT("leave"))
    {
        UE_LOG(LogTemp, Log, TEXT("%s left the chat"), *PresenceData.User.UserId);
    }
}
```

### Multiplayer Game State Sync

```cpp
// Game state synchronization
UCLASS()
class MYGAME_API AGameStateSyncer : public AActor
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, Category = "Game Sync")
    void SyncPlayerPosition(const FVector& Position, const FRotator& Rotation);

    UFUNCTION(BlueprintCallable, Category = "Game Sync")
    void SyncGameEvent(const FString& EventType, const FString& EventData);

protected:
    virtual void BeginPlay() override;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "OddSockets")
    AOddSocketsClient* OddSocketsClient;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Game Sync")
    UOddSocketsChannel* GameChannel;

    UFUNCTION()
    void OnGameMessage(const FOddSocketsChannelMessageData& MessageData);
};

void AGameStateSyncer::SyncPlayerPosition(const FVector& Position, const FRotator& Rotation)
{
    if (GameChannel && GameChannel->IsSubscribed())
    {
        FString PositionJson = FString::Printf(
            TEXT("{\"type\":\"position\",\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,\"pitch\":%.2f,\"yaw\":%.2f,\"roll\":%.2f}"),
            Position.X, Position.Y, Position.Z, Rotation.Pitch, Rotation.Yaw, Rotation.Roll
        );
        
        GameChannel->PublishAsync(PositionJson, FOddSocketsPublishOptions());
    }
}

void AGameStateSyncer::OnGameMessage(const FOddSocketsChannelMessageData& MessageData)
{
    // Parse game state updates and apply to local game objects
    // Handle position updates, game events, etc.
}
```

## 🔍 Troubleshooting

### Common Issues

1. **Connection Fails**
   - Verify your API key is correct
   - Check network connectivity
   - Ensure manager endpoint is reachable

2. **Messages Not Received**
   - Confirm channel subscription was successful
   - Check if client is still connected
   - Verify message size is under 32KB limit

3. **Blueprint Compilation Errors**
   - Ensure OddSockets plugin is enabled
   - Regenerate project files
   - Check Blueprint node connections

### Debug Logging

Enable detailed logging by setting the log level:

```cpp
Config.LogLevel = EOddSocketsLogLevel::Debug;
```

Or in Blueprint, set the "Log Level" property to "Debug".

## 📚 Advanced Usage

### Custom Message Validation

```cpp
// Custom message size validation
bool ValidateCustomMessage(const FString& Message)
{
    int32 MessageSize = FTCHARToUTF8(Message.GetCharArray().GetData()).Length();
    return MessageSize <= FOddSocketsMessageSizeLimits::MAX_MESSAGE_SIZE;
}
```

### Bulk Message Publishing

```cpp
// Publish multiple messages at once
TArray<FOddSocketsBulkMessage> Messages;

FOddSocketsBulkMessage Msg1;
Msg1.Channel = TEXT("channel1");
Msg1.Message = TEXT("{\"type\":\"update\",\"data\":\"value1\"}");

FOddSocketsBulkMessage Msg2;
Msg2.Channel = TEXT("channel2");
Msg2.Message = TEXT("{\"type\":\"update\",\"data\":\"value2\"}");

Messages.Add(Msg1);
Messages.Add(Msg2);

OddSocketsClient->PublishBulkAsync(Messages);
```

### State Management

```cpp
// Update user state with custom data
TMap<FString, FString> UserState;
UserState.Add(TEXT("status"), TEXT("online"));
UserState.Add(TEXT("level"), TEXT("42"));
UserState.Add(TEXT("location"), TEXT("lobby"));

GameChannel->UpdateStateAsync(UserState);
```

## 🤝 Support

- **Documentation**: [OddSockets Docs](https://docs.oddsockets.com)
- **GitHub Issues**: [Report Issues](https://github.com/oddsockets/unreal-sdk/issues)
- **Discord**: [Join Community](https://discord.gg/oddsockets)
- **Email**: support@oddsockets.com

## 📄 License

This SDK is licensed under the MIT License. See LICENSE file for details.

## 🔄 Changelog

### v1.0.0
- Initial release
- Full JavaScript SDK pattern compliance
- Blueprint and C++ support
- Auto-reconnection with exponential backoff
- Message size validation (32KB limit)
- Presence tracking
- Message history
- Bulk publishing
- Comprehensive error handling
