2025-11-26 18:33:09 -06:00

9.1 KiB

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:

[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

// 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

// 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

// 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

// 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:

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:

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:

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
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