Skip to main content
Beta features require the CountsAndLists feature gate to be enabled in your Agones installation. These APIs are stable but may still evolve based on feedback.

Overview

The Beta SDK provides Counters and Lists for advanced capacity management. These features enable sophisticated tracking of game server resources beyond simple player counts. Package: agones.dev.sdk.beta

Use Cases

Counters

Counters track numeric values with capacity limits:
  • Player slots by class: Track warriors, mages, healers separately
  • Resource consumption: Monitor memory, CPU, or bandwidth usage
  • Match statistics: Count rounds played, objectives completed
  • Session types: Track ranked matches, casual matches, etc.

Lists

Lists track string values with capacity limits:
  • Player IDs by team: Separate red team, blue team players
  • Active sessions: Track match IDs or room names
  • Feature flags: Store enabled features for the server
  • Map rotation: Track available or played maps

Counters

Counters are named numeric values with a count and capacity. They support get, set, increment, and decrement operations.

Counter Structure

message Counter {
  string name = 1;      // Counter name
  int64 count = 2;      // Current count value
  int64 capacity = 3;   // Maximum capacity
}

GetCounter

Retrieves the current state of a counter.
rpc GetCounter(GetCounterRequest) returns (Counter);

Request

name
string
required
The name of the counter to retrieve.

Response

name
string
The counter name.
count
int64
The current count value.
capacity
int64
The maximum capacity.

Example Usage

import (
    "context"
    "log"
    betaSdk "agones.dev/agones/sdks/go/beta"
)

func getPlayerCounter(beta *betaSdk.Beta) error {
    req := &betaSdk.GetCounterRequest{Name: "players"}
    
    counter, err := beta.GetCounter(context.Background(), req)
    if err != nil {
        return fmt.Errorf("could not get counter: %v", err)
    }
    
    log.Printf("Counter %s: %d/%d", counter.Name, counter.Count, counter.Capacity)
    return nil
}

UpdateCounter

Updates a counter’s count and/or capacity. This method can set absolute values or increment/decrement.
rpc UpdateCounter(UpdateCounterRequest) returns (Counter);

Request

counterUpdateRequest
CounterUpdateRequest
required
The counter update specification.

Response

name
string
The counter name.
count
int64
The updated count value.
capacity
int64
The updated capacity.

Error Codes

  • NOT_FOUND: Counter doesn’t exist
  • OUT_OF_RANGE: Count would be outside [0, capacity]

Example Usage

// Set absolute count
func setPlayerCount(beta *betaSdk.Beta, count int64) error {
    req := &betaSdk.UpdateCounterRequest{
        CounterUpdateRequest: &betaSdk.CounterUpdateRequest{
            Name:  "players",
            Count: &wrapperspb.Int64Value{Value: count},
        },
    }
    
    counter, err := beta.UpdateCounter(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Set player count to %d", counter.Count)
    return nil
}

// Increment count
func incrementPlayers(beta *betaSdk.Beta, amount int64) error {
    req := &betaSdk.UpdateCounterRequest{
        CounterUpdateRequest: &betaSdk.CounterUpdateRequest{
            Name:      "players",
            CountDiff: amount,
        },
    }
    
    counter, err := beta.UpdateCounter(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Player count: %d", counter.Count)
    return nil
}

// Update capacity
func setPlayerCapacity(beta *betaSdk.Beta, capacity int64) error {
    req := &betaSdk.UpdateCounterRequest{
        CounterUpdateRequest: &betaSdk.CounterUpdateRequest{
            Name:     "players",
            Capacity: &wrapperspb.Int64Value{Value: capacity},
        },
    }
    
    counter, err := beta.UpdateCounter(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Set capacity to %d", counter.Capacity)
    return nil
}

Lists

Lists are named collections of string values with a capacity limit. They support get, update, add, and remove operations.

List Structure

message List {
  string name = 1;              // List name
  int64 capacity = 2;           // Maximum capacity
  repeated string values = 3;   // Array of string values
}

GetList

Retrieves the current state of a list.
rpc GetList(GetListRequest) returns (List);

Request

name
string
required
The name of the list to retrieve.

Response

name
string
The list name.
capacity
int64
The maximum capacity.
values
string[]
Array of string values currently in the list.

Example Usage

func getPlayerList(beta *betaSdk.Beta) error {
    req := &betaSdk.GetListRequest{Name: "players"}
    
    list, err := beta.GetList(context.Background(), req)
    if err != nil {
        return fmt.Errorf("could not get list: %v", err)
    }
    
    log.Printf("List %s (%d/%d): %v", 
        list.Name, len(list.Values), list.Capacity, list.Values)
    return nil
}

UpdateList

Updates a list’s values and/or capacity. This overwrites all existing values.
rpc UpdateList(UpdateListRequest) returns (List);
UpdateList overwrites all existing values. Use AddListValue or RemoveListValue to modify individual values.

Request

list
List
required
The list specification with updated values.
update_mask
FieldMask
required
Field mask specifying which fields to update (e.g., “capacity”, “values”). If a field is in the mask but not set in the request, it’s set to the default value (0 for capacity, empty array for values).

Response

name
string
The list name.
capacity
int64
The updated capacity.
values
string[]
The updated array of values.

Error Codes

  • NOT_FOUND: List doesn’t exist
  • INVALID_ARGUMENT: Invalid field mask path

Example Usage

import "google.golang.org/protobuf/types/known/fieldmaskpb"

// Update capacity only
func updateListCapacity(beta *betaSdk.Beta, capacity int64) error {
    req := &betaSdk.UpdateListRequest{
        List: &betaSdk.List{
            Name:     "players",
            Capacity: capacity,
        },
        UpdateMask: &fieldmaskpb.FieldMask{
            Paths: []string{"capacity"},
        },
    }
    
    list, err := beta.UpdateList(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Updated capacity to %d", list.Capacity)
    return nil
}

// Replace all values
func replaceListValues(beta *betaSdk.Beta, values []string) error {
    req := &betaSdk.UpdateListRequest{
        List: &betaSdk.List{
            Name:   "players",
            Values: values,
        },
        UpdateMask: &fieldmaskpb.FieldMask{
            Paths: []string{"values"},
        },
    }
    
    list, err := beta.UpdateList(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Replaced values: %v", list.Values)
    return nil
}

AddListValue

Adds a single value to a list.
rpc AddListValue(AddListValueRequest) returns (List);

Request

name
string
required
The name of the list to add a value to.
value
string
required
The value to add to the list.

Response

name
string
The list name.
capacity
int64
The list capacity.
values
string[]
The updated array including the new value.

Error Codes

  • NOT_FOUND: List doesn’t exist
  • ALREADY_EXISTS: Value already in the list
  • OUT_OF_RANGE: List is at capacity

Example Usage

func addPlayerToList(beta *betaSdk.Beta, playerID string) error {
    req := &betaSdk.AddListValueRequest{
        Name:  "players",
        Value: playerID,
    }
    
    list, err := beta.AddListValue(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Added %s. List now has %d values", playerID, len(list.Values))
    return nil
}

RemoveListValue

Removes a single value from a list.
rpc RemoveListValue(RemoveListValueRequest) returns (List);

Request

name
string
required
The name of the list to remove a value from.
value
string
required
The value to remove from the list.

Response

name
string
The list name.
capacity
int64
The list capacity.
values
string[]
The updated array excluding the removed value.

Error Codes

  • NOT_FOUND: List doesn’t exist or value not in list

Example Usage

func removePlayerFromList(beta *betaSdk.Beta, playerID string) error {
    req := &betaSdk.RemoveListValueRequest{
        Name:  "players",
        Value: playerID,
    }
    
    list, err := beta.RemoveListValue(context.Background(), req)
    if err != nil {
        return err
    }
    
    log.Printf("Removed %s. List now has %d values", playerID, len(list.Values))
    return nil
}

HTTP Gateway

Beta SDK methods are available via HTTP/JSON:
curl http://localhost:9357/v1beta1/counters/players

Complete Integration Example

package main

import (
    "context"
    "log"
    
    "agones.dev/agones/sdks/go"
    betaSdk "agones.dev/agones/sdks/go/beta"
    "google.golang.org/protobuf/types/known/wrapperspb"
)

type GameServer struct {
    sdk  *sdk.SDK
    beta *betaSdk.Beta
}

func NewGameServer() (*GameServer, error) {
    s, err := sdk.NewSDK()
    if err != nil {
        return nil, err
    }
    
    b := betaSdk.NewBeta(s)
    
    return &GameServer{sdk: s, beta: b}, nil
}

func (gs *GameServer) OnPlayerJoin(playerID, team string) error {
    ctx := context.Background()
    
    // Increment total player counter
    _, err := gs.beta.UpdateCounter(ctx, &betaSdk.UpdateCounterRequest{
        CounterUpdateRequest: &betaSdk.CounterUpdateRequest{
            Name:      "players",
            CountDiff: 1,
        },
    })
    if err != nil {
        return err
    }
    
    // Add player to team list
    listName := team + "-team"
    _, err = gs.beta.AddListValue(ctx, &betaSdk.AddListValueRequest{
        Name:  listName,
        Value: playerID,
    })
    if err != nil {
        return err
    }
    
    log.Printf("Player %s joined %s", playerID, team)
    return nil
}

func (gs *GameServer) OnPlayerLeave(playerID, team string) error {
    ctx := context.Background()
    
    // Decrement total player counter
    _, err := gs.beta.UpdateCounter(ctx, &betaSdk.UpdateCounterRequest{
        CounterUpdateRequest: &betaSdk.CounterUpdateRequest{
            Name:      "players",
            CountDiff: -1,
        },
    })
    if err != nil {
        return err
    }
    
    // Remove player from team list
    listName := team + "-team"
    _, err = gs.beta.RemoveListValue(ctx, &betaSdk.RemoveListValueRequest{
        Name:  listName,
        Value: playerID,
    })
    if err != nil {
        return err
    }
    
    log.Printf("Player %s left %s", playerID, team)
    return nil
}

func (gs *GameServer) GetTeamBalance() (int, int, error) {
    ctx := context.Background()
    
    redTeam, err := gs.beta.GetList(ctx, &betaSdk.GetListRequest{Name: "red-team"})
    if err != nil {
        return 0, 0, err
    }
    
    blueTeam, err := gs.beta.GetList(ctx, &betaSdk.GetListRequest{Name: "blue-team"})
    if err != nil {
        return 0, 0, err
    }
    
    return len(redTeam.Values), len(blueTeam.Values), nil
}

Best Practices

Counter Usage

  1. Use counters for numeric capacity: Track things that have numeric limits (player slots, resources)
  2. Prefer countDiff for increments: Use relative changes instead of absolute values when possible
  3. Check capacity before incrementing: Handle OUT_OF_RANGE errors gracefully

List Usage

  1. Use lists for identifiers: Track player IDs, session IDs, or other unique strings
  2. Set appropriate capacity: Lists have a maximum capacity of 1000 items
  3. Use AddListValue/RemoveListValue: Avoid UpdateList which overwrites all values
  4. Handle duplicates: AddListValue returns ALREADY_EXISTS if the value exists

Integration with Allocation

Counters and Lists can be used in allocation requests:
{
  "namespace": "default",
  "gameServerSelectors": [{
    "matchLabels": {"game": "my-game"},
    "counters": {
      "players": {
        "minAvailable": 1
      }
    },
    "lists": {
      "red-team": {
        "minAvailable": 1
      }
    }
  }],
  "counters": {
    "players": {
      "action": "Increment",
      "amount": 1
    }
  },
  "lists": {
    "red-team": {
      "addValues": ["player-123"]
    }
  }
}

Build docs developers (and LLMs) love