Libraries

s&box libraries list

Extend RpcCallback

extend.extend_rpc

Async RPC Callback for s&box – Call server methods with await, [RpcCallback] handles the round-trip automatically.

About

# Extend RPC Library

A library for **s&box** that makes it easy to send RPC calls and receive **asynchronous callbacks**.

It simplifies **client ↔ server** communication while taking full advantage of async/await.

## πŸš€ Features

- Asynchronous RPC calls

- Receive callbacks using `async/await`

- Simplified error and timeout handling

- Supported return types:

βœ… **Primitives** (int, float, bool, string, etc.)

βœ… **structs**

βœ… **records**
βœ… **List<>**

## πŸ“¦ Installation

Clone or download this repository and add its content directly into your **s&box** project at Libraries/extend.extend_rpc folder.

## πŸ›  Basic Usage Example

[RpcCallback]
public async Task<int> Compute(int a, int b)
{
    return a + b;
}

protected override void OnStart()
{
    Task.RunInThreadAsync(async () =>
    {
        var result = await Compute(1, 4);
        Log.Info("Result is: " + result);
    });
}


## πŸ›  List Usage Example

[RpcCallback]
public async Task<List<int>> Compute(int a, int b)
{
    return [a + b];
}

protected override void OnStart()
{
    Task.RunInThreadAsync(async () =>
    {
        var results = await Compute(1, 4);
        Log.Info("Result is: " + string.Join(", ", results));
    });
}

## πŸ›  Using a struct as return type

protected override void OnStart()
{
    Task.RunInThreadAsync(async () =>
    {
        var stats = await GetPlayerStats("Player_42");
        Log.Info($"Kills: {stats.Kills}, Deaths: {stats.Deaths}, Accuracy: {stats.Accuracy}");
    });
}

[RpcCallback]
public async Task<PlayerStats> GetPlayerStats(string playerId)
{
    // Simulate database fetch
    await Task.Delay(100); 

    return new PlayerStats
    {
        Kills = 10,
        Deaths = 3,
        Accuracy = 0.75f
    };
}

public struct PlayerStats
{
    public int Kills { get; set; }
    public int Deaths { get; set; }
    public float Accuracy { get; set; }
}

## πŸ›  Using a record as return type

// Primary constructors are not supported yet when serializing / deserializing in s&box
public record InventoryItem( string Name, int Quantity );

// Instead, use this way
public record InventoryItem
{
    public string Name { get; set; }
    public int Quantity { get; set; } 

    public InventoryItem( string name, int quantity )
    {
        Name = name;
        Quantity = quantity;
    }
}

[RpcCallback]
public async Task<InventoryItem> GetInventoryItem(string itemId)
{
    await Task.Delay(50);

    return new InventoryItem("Health Potion", 5);
}

protected override void OnStart()
{
    Task.RunInThreadAsync(async () =>
    {
        var item = await GetInventoryItem("potion_health");
        Log.Info($"Item: {item.Name}, Quantity: {item.Quantity}");
    });
}

## βš™οΈ How it works

Functions marked with the [RpcCallback] attribute are **wrapped automatically**.

When you call them:

1. The wrapper sends an **RPC to the server**.

2. The server executes the **original method**.

3. The return value is captured.

4. Another **RPC is sent back to the caller (client)** with the result.

This makes it possible to await the call just like a local async function.

The caller receives the result as a Task<T> (async/await).

Any return type is supported as long as it’s a primitive, a struct, or a record that can be serialized.

Collections (List<T>, arrays, etc.) are also supported if T is a valid type.

The library handles serialization, network transport, and automatic value return.

Configurable timeout (default: 5s).

## πŸ“– Quick Reference

[RpcCallback] β†’ Marks a method as an asynchronous RPC.

Task.RunInThreadAsync(...) β†’ Runs a task outside the main game thread.

Exceptions are automatically propagated back to the caller.

## 🀝 Contributing

Pull Requests are welcome!

Please follow C# best practices. Unit tests is optional.