# Redis Module

This module provides comprehensive Redis operations for the Forge extension, enabling persistent data storage and retrieval from SQF scripts.

## Architecture

The Redis module is organized into specialized operation groups:

- **Common**: Basic key-value operations
- **Hash**: Structured data storage (field-value pairs)
- **List**: Ordered collections and queues
- **Set**: Unique collections and membership tracking

## Connection Management

### Connection Pool

The module uses `bb8` for connection pooling, providing:
- **Automatic connection reuse**: Reduces overhead
- **Configurable pool size**: Control max/min connections
- **Idle timeout**: Prevents stale connections
- **Lazy initialization**: Pool created on first use

### Configuration

Redis connection settings are loaded from `@forge_server/config.toml`:

```toml
[redis]
host = "127.0.0.1"
port = 6379
password = ""  # Optional
max_connections = 10
min_connections = 2
idle_timeout = 300  # seconds
```

## Common Operations

Basic key-value operations for simple data storage.

### Available Commands

| Command | Description | Returns |
|---------|-------------|---------|
| `redis:common:set` | Set a string value | "OK" |
| `redis:common:get` | Get a string value | Value or empty string |
| `redis:common:incr` | Increment a numeric value | New value |
| `redis:common:decr` | Decrement a numeric value | New value |
| `redis:common:del` | Delete a key | Number of keys removed |
| `redis:common:keys` | List all keys | Comma-separated keys |

### SQF Examples

```sqf
// Set a value
"forge_server" callExtension ["redis:common:set", ["player_count", "42"]];

// Get a value
private _result = "forge_server" callExtension ["redis:common:get", ["player_count"]];
private _count = _result select 0;  // "42"

// Increment
"forge_server" callExtension ["redis:common:incr", ["player_count", 1]];

// Delete
"forge_server" callExtension ["redis:common:del", ["player_count"]];
```

## Hash Operations

Hash operations store structured data as field-value pairs, ideal for objects and entities.

### Available Commands

| Command | Description | Returns |
|---------|-------------|---------|
| `redis:hash:set` | Set a single field | 1 if new, 0 if updated |
| `redis:hash:mset` | Set multiple fields atomically | "OK" |
| `redis:hash:get` | Get a field value | Value or empty string |
| `redis:hash:getall` | Get all fields and values | Comma-separated pairs |
| `redis:hash:del` | Delete a field | Number of fields removed |
| `redis:hash:keys` | Get all field names | Comma-separated keys |
| `redis:hash:vals` | Get all values | Comma-separated values |
| `redis:hash:len` | Get number of fields | Field count |
| `redis:hash:exists` | Check if field exists | "1" or "0" |

### SQF Examples

```sqf
// Set a single field
"forge_server" callExtension ["redis:hash:set", ["actor:76561198123456789", "name", "John Doe"]];

// Set multiple fields atomically
private _fields = [
    ["name", "John Doe"],
    ["bank", "1000"],
    ["level", "5"]
];
"forge_server" callExtension ["redis:hash:mset", ["actor:76561198123456789", _fields]];

// Get a field
private _result = "forge_server" callExtension ["redis:hash:get", ["actor:76561198123456789", "name"]];
private _name = _result select 0;  // "John Doe"

// Get all fields
private _result = "forge_server" callExtension ["redis:hash:getall", ["actor:76561198123456789"]];
// Returns: "name, John Doe, bank, 1000, level, 5"

// Check if field exists
private _result = "forge_server" callExtension ["redis:hash:exists", ["actor:76561198123456789", "name"]];
private _exists = (_result select 0) == "1";
```

## List Operations

List operations manage ordered collections, useful for queues, logs, and sequential data.

### Available Commands

| Command | Description | Returns |
|---------|-------------|---------|
| `redis:list:set` | Set element at index | "OK" |
| `redis:list:get` | Get element at index | Value (base64 decoded) |
| `redis:list:len` | Get list length | Element count |
| `redis:list:range` | Get range of elements | JSON array |
| `redis:list:lpush` | Prepend to list | New length |
| `redis:list:rpush` | Append to list | New length |
| `redis:list:lpop` | Remove from beginning | JSON array of removed elements |
| `redis:list:rpop` | Remove from end | JSON array of removed elements |
| `redis:list:trim` | Trim to range | "OK" |
| `redis:list:del` | Remove by value | Number removed |

### SQF Examples

```sqf
// Append to list
"forge_server" callExtension ["redis:list:rpush", ["event_log", "Player joined"]];
"forge_server" callExtension ["redis:list:rpush", ["event_log", "Player spawned"]];

// Get range
private _result = "forge_server" callExtension ["redis:list:range", ["event_log", 0, -1]];
private _events = parseJSON (_result select 0);  // Array of all events

// Pop from end
private _result = "forge_server" callExtension ["redis:list:rpop", ["event_log", 1]];
private _lastEvent = parseJSON (_result select 0);  // ["Player spawned"]

// Trim to last 100 entries
"forge_server" callExtension ["redis:list:trim", ["event_log", -100, -1]];
```

> [!NOTE]
> List values are automatically base64 encoded/decoded to handle special characters safely.

## Set Operations

Set operations manage unique collections, perfect for membership tracking and preventing duplicates.

### Available Commands

| Command | Description | Returns |
|---------|-------------|---------|
| `redis:set:add` | Add member to set | 1 if new, 0 if exists |
| `redis:set:members` | Get all members | Comma-separated members |
| `redis:set:card` | Get member count | Cardinality |
| `redis:set:ismember` | Check membership | "1" or "0" |
| `redis:set:randmember` | Get random member | Member value |
| `redis:set:randmembers` | Get N random members | Comma-separated members |
| `redis:set:pop` | Remove random member | Removed member |
| `redis:set:del` | Remove specific member | 1 if removed, 0 if not found |

### SQF Examples

```sqf
// Add members to a set
"forge_server" callExtension ["redis:set:add", ["org:elite_squad:members", "76561198123456789"]];
"forge_server" callExtension ["redis:set:add", ["org:elite_squad:members", "76561198987654321"]];

// Check membership
private _result = "forge_server" callExtension ["redis:set:ismember", ["org:elite_squad:members", "76561198123456789"]];
private _isMember = (_result select 0) == "1";

// Get all members
private _result = "forge_server" callExtension ["redis:set:members", ["org:elite_squad:members"]];
private _memberUIDs = (_result select 0) splitString ",";

// Get member count
private _result = "forge_server" callExtension ["redis:set:card", ["org:elite_squad:members"]];
private _memberCount = parseNumber (_result select 0);

// Remove member
"forge_server" callExtension ["redis:set:del", ["org:elite_squad:members", "76561198123456789"]];
```

## Helper Utilities

### Base64 Encoding

List operations use base64 encoding to safely store complex strings:

```rust
use crate::redis::helpers::{encode_b64, decode_b64};

let encoded = encode_b64("Complex [string] with {special} chars");
let decoded = decode_b64(&encoded)?;  // Original string
```

### Value Parsing

The `parse_redis_value` function intelligently converts Redis strings to JSON types:

```rust
use crate::redis::helpers::parse_redis_value;

parse_redis_value("42");           // Number(42)
parse_redis_value("true");         // Bool(true)
parse_redis_value("{\"key\":1}");  // Object
parse_redis_value("text");         // String("text")
```

## Macro Usage

The `redis_operation!` macro handles all connection and async boilerplate:

```rust
use crate::redis_operation;
use bb8_redis::redis::AsyncCommands;

pub fn my_redis_command(key: String) -> String {
    redis_operation!(conn => {
        match conn.get::<_, String>(&key).await {
            Ok(value) => value,
            Err(e) => format!("Error: {}", e),
        }
    })
}
```

The macro automatically:
- Acquires a connection from the pool
- Handles lazy initialization if needed
- Executes the operation asynchronously
- Returns the result to SQF

## Error Handling

All Redis operations return strings:
- **Success**: Operation result (e.g., "OK", value, count)
- **Error**: String starting with "Error: " followed by the error message

```sqf
private _result = "forge_server" callExtension ["redis:common:get", ["mykey"]];
private _value = _result select 0;

if (_value find "Error:" == 0) then {
    diag_log format ["Redis error: %1", _value];
} else {
    // Use the value
    systemChat format ["Value: %1", _value];
};
```

## Performance Considerations

- **Connection Pooling**: Reuses connections to minimize overhead
- **Async Operations**: Non-blocking I/O prevents server lag
- **Atomic Operations**: `hash:mset` sets multiple fields in one operation
- **Batch Operations**: Use lists and sets for bulk data

## Best Practices

1. **Use Hashes for Objects**: Store actor/org data as hash fields
2. **Use Sets for Membership**: Track organization members, online players
3. **Use Lists for Logs**: Event logs, chat history, audit trails
4. **Prefix Keys**: Use namespaces like `actor:`, `org:`, `vehicle:`
5. **Handle Errors**: Always check for "Error:" prefix in results
6. **Atomic Updates**: Use `hash:mset` instead of multiple `hash:set` calls
