# GameLift Fleet Management

**URL:** https://heroiclabs.com/docs/nakama/guides/concepts/gamelift-integration/
**Summary:** This guide explains how to integrate Nakama's matchmaking system with Amazon GameLift for efficient multiplayer game scaling, covering AWS configuration, queue setup, plugin installation, and session management.
**Keywords:** gamelift, fleet management, matchmaking, multiplayer, gamelift integration
**Categories:** nakama, gamelift-integration, concepts

---


# Amazon GameLift Fleet Management using the Nakama GameLift Plugin

This guide details the integration process between Nakama's powerful social matchmaking system and Amazon GameLift's session management, enabling developers to efficiently scale multiplayer games.

Follow along to learn how to configure AWS services, set up SQS and SNS queues, install and configure the Nakama-GameLift plugin, and manage game sessions. By the end, you'll be equipped to deploy a robust multiplayer environment powered by Nakama and Amazon GameLift.

## Prerequisites

{{< note "important" "Nakama Version" >}}
The Nakama-GameLift plugin is compatible with Nakama 3.21 and above.
{{< /note >}}

Before proceeding ensure that you have:

- Created an [Amazon AWS Account](https://aws.amazon.com/)
- [Installed Nakama server](/docs/nakama/getting-started/install/docker/)
- Installed the [Nakama Unity SDK](/docs/nakama/client-libraries/unity/index.html#installation)



## Architecture overview

![An overview of how Nakama orchestrates the GameLift fleet.]({{< fingerprint_image "/images/pages/nakama/guides/concepts/gamelift-integration/sequence-diagram.png" >}})

## Maintaining state synchronization

In order to facilitate integration between Nakama and Amazon GameLift, a number of components need to be setup and configured within your AWS account: an AWS GameLift Fleet with a Placement Queue, an SNS Topic, and an SQS Queue.

Additionally, it is required that each GameLift Game Session updates Nakama on certain status changes to allow it to maintain it's own state representation of the active Amazon GameLift instances. This is required so that Nakama knows in near real-time the current state of your fleet and all of the instances within it, allowing this data to be queryable and drive your matchmaking needs.

{{< note important "Important!" >}}
These updates to Nakama are to be sent via two RPC endpoints (Update/Delete) exposed by the Nakama-GameLift Plugin. For further information please refer to the section titled ["Notifying Nakama of game session updates"](/docs/nakama/guides/concepts/gamelift-integration/#notifying-nakama-of-game-session-updates). 
{{< / note >}}

## Creating the Headless Unity server

This guide covers just the required steps to get your headless Unity server configured to work with Nakama and GameLift. It does not cover synchronizing game state between the headless server and the client, for this please see the documentation for your chosen networking framework (e.g. Nakama, Unity Netcode for GameObjects, Mirror, etc).

### Installing the Nakama SDK

To install the Nakama SDK into the Unity project:

1. Download the latest [Nakama Unity SDK package](https://github.com/heroiclabs/nakama-unity/releases).
2. Double-click the `Nakama.unitypackage` and install it in your Unity project.

### Install the GameLift Managed Servers SDK

To install the GameLift SDK into the Unity project:

1. Download the [GameLift Managed Servers SDK v5.0.0 or higher](https://aws.amazon.com/gamelift/getting-started/).
2. Extract the zip file.
3. Create a `Plugins` folder in your Unity project's `Assets` folder.
4. Copy the `.DLL` files from the extracted zip's `GameLift-CSharp-ServerSDK-5.0.0/src/GameLiftServerSDK/bin/Release` folder into it. (You may need to remove `Newtonsoft.Json.dll` if it is already in use in your project).

### Initialize GameLift

Where you choose to initialize GameLift depends on your game. It is recommended that you place the following code snippets somewhere within the initialization script of your game.

It is important that during the **OnProcessTerminate** lifecycle event, Nakama must be informed that the game session is ending via the RPC defined by the Nakama-GameLift plugin as shown in the code example below.

1. Create an `InitializeGameLift` method that will handle initializing the GameLift lifecycle events:

```csharp
private void InitializeGameLift()
{
  Debug.Log("Initializing GameLift");

  // Pass null values here as these are configured automatically by AWS GameLift; only used when using GameLift Anywhere
  var serverParameters = new ServerParameters(null, null, null, null, null);

  // Initialize the GameLift SDK
  var initOutcome = GameLiftServerAPI.InitSDK(serverParameters);

  if (!initOutcome.Success)
  {
    Debug.LogError($"Failed to initialize GameLift: {initOutcome.Error}");
    return;
  }

  // Configure process callbacks (methods defined below)
  var processParameters = new ProcessParameters(
    OnStartGameSession,
    OnUpdateGameSession,
    OnProcessTerminate,
    OnHealthcheck,
    7778, // Port depends on your networking framework (Netcode for GameObjects uses 7778)
    new LogParameters(new List<string>
    {
      "/local/game/logs/server.log" // Define where GameLift should grab the logs from
    })
  );

  // Tell GameLift the server is ready
  var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParameters);

  if (!processReadyOutcome.Success)
  {
    Debug.LogError($"GameLift ProcessReady failed: {processReadyOutcome.Error}");
  }
  else
  {
    Debug.Log("GameLift initialized successfully");
  }
}
```

2. Create the methods to handle the various GameLift callbacks defined above, making sure to call the appropriate Nakama RPCs to maintain game session state integrity between Nakama and Amazon GameLift.

```csharp
// Called when GameLift creates a game session and sends an activation request, here you can get information about the particular game session
private void OnStartGameSession(GameSession gameSession)
{
  Debug.Log($"Game session started.\nIP: {gameSession.IpAddress}\nPort: {gameSession.Port}");

  // You must call ActivateGameSession once the server is ready to accept incoming player connections
  GameLiftServerAPI.ActivateGameSession();

  // It is recommended that you cache a copy of the game session so that you can reference metadata later on.
  _gameSession = gameSession;

  Debug.Log("Game session activated");
}

// Called when a game session is updated
private void OnUpdateGameSession(UpdateGameSession updateGameSession)
{
  // Not used in this example
}

// Called before shutting down an instance hosting this server
private void OnProcessTerminate()
{
  // Tell Nakama that the GameLift session is ending
  var payload = new Dictionary<string, string> {{ "id", GameLiftServerAPI.GetGameSessionId().Result}};
  _nakamaClient.RpcAsync(_httpKey, "delete_instance_info", payload.ToJson());

  // Tell GameLift this process is ending
  GameLiftServerAPI.ProcessEnding();
}

// Called periodically (every ~60 seconds) to check the health of the server
private bool OnHealthCheck()
{
  // Return true here to confirm the server is healthy
  return true;
}
```

3. Call the `InitializeGameLift` method from the `Start` method of your chosen script:

```csharp
private void Start()
{
  InitializeGameLift();
}
```

### Accepting GameLift player sessions

Any player session created in GameLift needs to be validated by the server. This can be achieved by calling the `ApprovePlayerSession` GameLift API method. You should call this when a player has connected.

```csharp
// Accept the player session in GameLift and update Nakama
var acceptOutcome = GameLiftServerAPI.AcceptPlayerSession(playerSessionId);
UpdateNakamaGameSessionData();
``` 


### Removing GameLift player sessions

When a player disconnects from the server, the GameLift player session should be removed.

```csharp
// Remove the player session in GameLift and update Nakama
GameLiftServerAPI.RemovePlayerSession(playerSessionId);
UpdateNakamaGameSessionData();
```

### Notifying Nakama of game session updates

When a player connects or disconnects from the game server, or when game session meta data has changed, Nakama **must** be informed via the Update RPC that the Nakama-GameLift plugin exposes.

```csharp
public void UpdateNakamaGameSessionData()
{
  // Class used as a payload to the Nakama Fleetmanager Update RPC
  private class NakamaUpdateRequest
  {
      public string id;
      public int player_count;
      public Dictionary<string, object> metadata;
  }

  // Get the total current player count from GameLift
  var req = new DescribePlayerSessionsRequest()
  {
    GameSessionId = gameSessionId
  };

  var sessions = GameLiftServerAPI.DescribePlayerSessions(req);
  if (sessions.Error != null)
  {
    Debug.Log($"Error getting sessions from GameLiftServerAPI: {sessions.Error?.ErrorMessage}");  
  }

  var playerCount = 0;
  foreach (var s in sessions.Result.PlayerSessions)
  {
    if (s.Status == PlayerSessionStatus.ACTIVE || s.Status == PlayerSessionStatus.RESERVED)
    {
      playerCount++;
    }
  }

  // Notify Nakama of the updated information about this game session.
  // These values can be retrieved from the Game Session object cached earlier if required.
  // If these values are omitted, the existing values will persist.
  var metadata = new Dictionary<string, object> {
    { "GameSessionData", "<game_session_data>" },
    { "GameSessionName", "<game_session_name>" },
    { "GameProperties": new Dictionary<string, string> {
      { "key1", "value1" },
      { "key2", "value2" }
    }
  };

  var payload = new NakamaUpdateRequest()
  {
    id = GameLiftServerAPI.GetGameSessionId().Result,
    player_count = playerCount,
    metadata = metadata
  };

  _nakamaClient.RpcAsync(_httpKey, "update_instance_info", payload.ToJson());
}
```

### Ending the GameLift session

If your headless server process exits for any reason (e.g. after a match) you should explicitly tell GameLift that the process is ending to ensure the game session ends appropriately.

```csharp
GameLiftServerAPI.ProcessEnding();
```

You should also explicitly call `Destroy` when the application quits.

```csharp
private void OnApplicationQuit()
{
  GameLiftServerAPI.Destroy();
}
```

### Creating the build

Create a build that you can upload to AWS.

1. Go to the **File** -> **Build Settings** menu.
2. Choose the **Dedicated Server** option.
3. For **Target Platform** select either **Windows** or **Linux**.
4. Build your project.
5. Zip up the contents of your build and name it appropriately (e.g. `1.0.0.zip`) Do not include the **_DoNotShip** folder.

## Configuring AWS Services

The AWS requirements for setting up the GameLift service are as follows:

- An SNS Topic for publishing events to
- An SQS Queue for reading events from
- An S3 bucket for storing server builds
- An IAM Role to allow GameLift access to the S3 bucket
- An IAM Role to allow the Nakama server to authenticate and access GameLift APIs
- The GameLift service including placement queue, builds, fleets, and an alias

### Creating the Topic in Amazon Simple Notification Service (SNS)

A Topic must be created for notifications/events to be published to via Amazon Simple Notification Service (SNS). This Topic will be used by the Fleet Placement Queue to publish placement status updates. Nakama will receive these events through a Simple Queue System (SQS) subscribed to this topic.

To create the Topic:

1. Navigate to the [Amazon SNS Console](https://aws.amazon.com/sns/).
2. Go to **Topics** and click **Create topic**.
3. Choose **FIFO** for the Type.
4. For the Name enter: `fleet-game-session-placement.fifo`. 
5. Enter a Display Name such as `Fleet Game Session Placement`.
6. Make sure you configure your Access Policy to allow the SNS Topic to be published to from GameLift.

```json
{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "SNS:GetTopicAttributes",
        "SNS:SetTopicAttributes",
        "SNS:AddPermission",
        "SNS:RemovePermission",
        "SNS:DeleteTopic",
        "SNS:Subscribe",
        "SNS:ListSubscriptionsByTopic",
        "SNS:Publish"
      ],
      "Resource": "arn:aws:sns:your_region:your_account:your_topic_name",
      "Condition": {
        "StringEquals": {
          "AWS:SourceAccount": "your_account"
        }
      }
    },
    {
      "Sid": "__console_pub_0",
      "Effect": "Allow",
      "Principal": { 
        "Service": "gamelift.amazonaws.com" 
      },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:your_region:your_account:your_topic_name",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:gamelift:your_region:your_account:gamesessionqueue/your_queue_name"
        }
      }
    }
  ]
}
```

7. Click **Create topic** and note down the **ARN** that you are given.

For more information please see the official AWS documentation for [setting up an SNS topic](https://docs.aws.amazon.com/gamelift/latest/developerguide/queue-notification.html).

### Creating the Queue in Amazon Simple Queue Service (SQS)

This queue will subscribe to the SNS topic created above in order to make GameLift fleet events available to Nakama. This is necessary as SNS events are unavailable outside of AWS without first pushing them to an accessible SQS queue.

To create the SQS queue that will be read from via Nakama:

1. Navigate to [Amazon SQS](https://aws.amazon.com/sqs/).
2. Click on **Create queue**.
3. For **Type** choose **FIFO**.
4. For **Name** enter: `fleet-placement-events.fifo`.
5. **Configuration** and **Encryption** can be left at default values.
6. Ensure the **Access policy** is configured to allow access to the SNS topic.

```json
{
  "Version": "2012-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": {
        "Service": "sns.amazonaws.com"
      },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:your_region:your_account:fleet-placement-events.fifo"
    }
  ]
}
```
7. Click **Create queue**.
8. Click **Subscribe to Amazon SNS Topic** and choose the `fleet-game-session-placement.fifo` topic created earlier.

You can confirm that everything is configured correctly by verifying that the subscription to the SNS Topic shows up under the SNS Subscriptions section.

### Creating the S3 bucket

There are two options for uploading game server builds to AWS GameLift: via the CLI or an S3 bucket. For simplicity this guide uses the S3 approach.

1. Navigate to the **S3** service.
2. Create a new bucket with a unique name (such as `my-organisation-gamelift-builds`) and give it default permissions.
3. Upload the server build Zip file you created in the previous section.

{{< note important "Important!" >}}
When creating the S3 bucket, ensure that it is created in the same region as the GameLift Fleet you intend to create.
{{< / note >}}

### Creating the IAM Role for S3 access

For GameLift to be able to access the S3 bucket storing the server builds it needs to be given the appropriate permissions via a Role.

1. Navigate to the **IAM** service and go to **Policies**.
2. Create a new Policy called `GameLiftS3AccessPolicy`.
3. For permissions give it the following JSON (where the resource name matches your S3 bucket ARN):

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-organisation-gamelift-builds/*"
    }
  ]
}
```

4. Navigate to **Roles**.
5. Create a new Role called `GameLiftS3AccessRole`.
6. Attach the `GameLiftS3AccessPolicy` to the role via the **Permissions** tab.
7. In the **Trust relationships** tab edit the **Trust Policy** with the following:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Principle": {
        "Service": "gamelift.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

This role will now allow the AWS GameLift service to access the S3 bucket and download the headless server builds stored there when creating a new GameLift Build.

### Creating the IAM Role for GameLift access

In order for Nakama to access GameLift services there needs to be an IAM User created with an access token. This access token (and secret access token) will be passed to Nakama via environment variables so that it can interact with the GameLift APIs to search for and create new game sessions.

1. In the **IAM** section navigate to **Policies**.
2. Create a new Policy called `GameLiftAPIAccess`.
3. For the permissions JSON enter the following:
    
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Action": [
        "gamelift:CreateGameSession",
        "gamelift:DescribeGameSessionDetails",
        "gamelift:UpdateGameSession",
        "gamelift:CreatePlayerSession",
        "gamelift:DescribePlayerSessions",
        "gamelift:SearchGameSessions",
        "gamelift:GetGameSessionLogUrl",
        "gamelift:CreatePlayerSessions",
        "gamelift:DescribeGameSessions",
        "gamelift:StartGameSessionPlacement"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Statement2",
      "Effect": "Allow",
      "Action": [
        "sqs:ReceiveMessage",
        "sqs:DeleteMessage"
      ],
      "Resource": "arn:aws:sqs:your_region:your_account:fleet-placement-events.fifo"
    }
  ]
}
```

This Policy will allow the IAM User to interact with various GameLift APIs. You can add or remove API functions from this policy depending on your needs.

1. Navigate to the **Users** section.
2. Create a new User called `NakamaGameLiftUser`.
3. Attach the `GameLiftAPIAccess` policy under the **Permissions** tab.

Now create access tokens for this user:

1. Go to the **Security Credentials** tab.
2. Under the **Access Keys** section click **Create access key**.
3. Follow the instructions and then note down the **Access Key** and **Secret Access Key** for later.

### Creating a GameLift Build

The first step in launching a fleet of headless game servers with GameLift is to create a Build. A Build defines the executable binary that GameLift will spin up as part of a fleet. You should specify the operating system that you expect the build to run on as well as the GameLift Server SDK version your build is using.

1. Navigate to the **GameLift** service and go to the **Builds** section.
2. Give your build a **Name** (e.g. `my-server-1.0.0`).
3. Specify a Version number (e.g. `1.0.0`).
4. Choose the Operating System of your build (e.g. `Linux 2`).
    - Note that the `Linux` option does not support the `5.0.0` server SDK
5. Choose `5.0.0` for the Server SDK Version.
6. Under the **Game Server Build** option, choose your server build Zip file.
7. From the **IAM Role** dropdown choose the `GameLiftS3AccessRole`.
8. Click **Create** to create your build.

### Creating a GameLift Fleet

Once you have created a build you're ready to create a fleet. A fleet is a group of game servers that can be configured to scale up and down as demand calls for it.

To create your fleet:

1. Navigate to the **Fleets** section and click **Create Fleet**.
2. Choose `Managed EC2` for **Compute Type**.
3. Give your fleet a **Name** (e.g. `build_1_0_0_fleet_1`).
4. Keep the **Binary Type** as `Build`.
5. Select the Build you just created from the **Build** dropdown.
6. Leave the **Additional Details** section blank and click **Next**.
7. Choose a **Location** for your Fleet and click **Next**.
8. For **Instance Type** choose **On Demand**.
9. Select the most appropriate instance type for your needs (for this guide the recommendation is `c4.large`) and click **Next** .
10. For **Launch Path** enter the path to your server binary in the Zip file you uploaded (e.g. `server.x86_64`)
11. For **Launch parameters** enter at least the following:

```bash
-batchmode -nographics -logFile /local/game/logs/server.log
```

12. Go to the **EC2 Port Settings** section.
13. Add an entry for both **TCP** and **UDP** with the following:
  - **Port Range**: enter `7000-8000`
  - **IP Address Range**: enter `0.0.0.0/0`
14. Click on **Next**.
15. Leave the **Tags** section blank unless you wish to add them to your fleet.
16. Finally click on **Submit**.

Once your fleet is created it will go through several stages which can be viewed via the **Events** tab. If any errors occur during these stages you will be notified here. 

When your fleet status changes to **FLEET_STATE_ACTIVE** it will begin scaling up your fleet as per the scaling configuration, which by default will spin up 1 instance.

### Creating a GameLift Alias

An alias is a pointer to a fleet that can be changed at any point. This allows you to dynamically change which fleet a client connects to without needing to rebuild and republish the client itself.

To create an alias:

1. Navigate to the **Aliases** section and click **Create Alias**.
2. Give your alias a **Name** (e.g. `alias-1`).
3. Select the **Simple** option for **Routing Type**.
4. Select the **Fleet** the alias should point to.
5. Click **Create** and take note of the **Alias ID**.

### Creating the GameLift Game Session Placement Queue

In order for Nakama to be able to effectively create game sessions within Amazon GameLift, a game session placement queue must be configured. Requests made to this queue signals to Amazon GameLift that a new game session is required and allows them to be asynchronously processed. Whenever a new game session is required, Nakama will make a request to this queue, and once the request is fulfilled by Amazon GameLift, a callback will be triggered within Nakama containing the information about the newly created game session.

To create a game session placement queue:

1. Navigate to [Amazon GameLift](https://aws.amazon.com/gamelift/).
2. Click on **Queues**.
3. Click **Create queue**.
4. For **Name** enter: `game-session-placement`
5. Change the **Timeout** value to `30` (This ensures Nakama does not wait for more than 30 seconds when requesting a new game session).
6. For **Destination order**, be sure to select the **Region** of the **Alias** created earlier and then select the alias name from the dropdown.
7. For **Event notifications**, choose the **Region** of the SNS Topic created earlier and then choose `fleet-game-session-placement.fifo`.


## Installing the Nakama-GameLift Plugin

This guide assumes you:
  * Have a Nakama server project up and running using the Go runtime. Follow the [Introduction to Nakama Go Runtime](https://heroiclabs.com/docs/nakama/server-framework/go-runtime/) documentation to get started, if needed.
  * Are familiar with hosting a Nakama instance. See our [Heroic Cloud documentation](https://heroiclabs.com/docs/heroic-cloud/introduction/) for details on how to launch an instance of Nakama in the cloud.

This guide focuses on implementing the necessary functionality to communicate with AWS GameLift in order to search for and create game sessions and receive match results.

The Nakama-GameLift plugin provides an Amazon GameLift implementation of Nakama's Fleet Manager interface and is available as an [Open-Source plugin via GitHub](https://github.com/heroiclabs/nakama-gamelift/) and 

To install the plugin in your Nakama project, run the following command in the project folder:

```bash
go get github.com/heroiclabs/nakama-gamelift/fleetmanager
```

Once installed, a new `FleetManager` instance can be created within the `InitModule` function.

```go
// It is recommended that you pass in the necessary AWS configuration values via ENV vars.
// e.g.
// 
// env, ok := ctx.Value(runtime.RUNTIME_CTX_ENV).(map[string]string)
// if !ok {
//   return fmt.Errorf("expects env ctx value to be a map[string]string")
// }
// awsAccessKey, ok := env["GL_AWS_ACCESS_KEY"]
// if !ok {
//   return runtime.NewError("missing GL_AWS_ACCESS_KEY environment variable", 3)
// }

// Create the GameLift configuration object using appropropriate string values for AWS resources.
cfg := fleetmanager.NewGameLiftConfig(awsAccessKey, awsSecretAccessKey, awsRegion, awsAliasId, awsPlacementQueueName, awsGameLiftPlacementEventsQueueUrl)

// Create a new Fleet Manager instance.
fm, err := fleetmanager.NewGameLiftFleetManager(ctx, logger, db, initializer, nk, cfg)
if err != nil {
    return err
}

// Register the Fleet Manager with Nakama.
if err = initializer.RegisterFleetManager(fm); err != nil {
    logger.WithField("error", err).Error("failed to register aws gamelift fleet manager")
    return err
}
```

To access the fleet manager elsewhere after it has been registered:

```go
fm := nk.GetFleetManager()
```

## Using the Fleet Manager API

The Fleet Manager API exposes several functions that can be used to Create, List, Retrieve, Join and Delete game sessions within Amazon GameLift. These are covered below. For full documentation please visit the [nakama-gamelift repository on GitHub](https://github.com/heroiclabs/nakama-gamelift).

### Create

The `Create` function is used to create a new game session within the Amazon GameLift fleet. The process is asynchronous, so the function takes a callback as part of the signature. This callback is invoked once the creation process succeeds, times-out or fails. The callback can be used to notify any interested parties on the status of the creation process:

```go
var callback runtime.FmCreateCallbackFn = func(status runtime.FmCreateStatus, instanceInfo *runtime.InstanceInfo, sessionInfo []*runtime.SessionInfo, metadata map[string]any, createErr error) {
// createErr is not nil only if status is != runtime.CreateSuccess.
// the original AWS Placement Event can be retrieved from `metadata` under the 'event' key.
switch status {
    case runtime.CreateSuccess:
        // Create was successful, instanceInfo contains the instance information for player connection.
        // sessionInfo contains Player Session info if a list of userIds is passed to the Create function.
        info, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", info)
        // Notify any interested party. 
        return
    case runtime.CreateTimeout:
        // AWS GameLift was not able to successfully create the placed Game Session request within the timeout
        // (configurable in the placement queue).
        // The client should be notified to either reattempt to find an available Game Session or retry creation.
        info, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", info)
        // Notify any interested party. 
        return
    default:
        // The request failed to be placed.
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance")
        // Notify any interested party.
        return
    }
}

maxPlayers := 10 // Maximum number of players that will be able to connect to the Game Session
playerIds := []string{userId} // Optional - Reserves a Player Session for each userId. The reservation expires after 60s if the client doesn't connect.
metadata := map[string]any // Optional - Metadata containing GameProperties or 
err = fm.Create(ctx, maxPlayers, playerIds, metadata, callback)
```

### List

The `list` function enables querying of existing game sessions and uses the same [query syntax](https://heroiclabs.com/docs/nakama/concepts/multiplayer/query-syntax/) found elsewhere in Nakama:

```go
query := "+value.playerCount:2" // Query to list Game Sessions currently containing 2 Player Sessions. An empty query will list all Game Sessions.
limit := 10 // Number of results per page (does not apply if a query is != ""
cursor := "" // Pagination cursor
instances, nextCursor, err := fm.List(ctx, query, limit, cursor)
```

#### A note on querying game sessions

The following properties can be queried for game sessions:

- `values.create_time` a **date** value
- `values.player_count` a **numeric** value
- `values.metadata.GameSessionData` a **string** value
- `values.metadata.GameSessionName` a **string** value
- `values.metadata.GameProperties` a **string:string** dictionary value

An example query which looks for game sessions with a name prefix of `season` and a game mode of `freeforall` would be:

```go
query := "+values.metadata.GameSessionName:/(season).+/ +values.metadata.GameProperties.mode:freeforall"
```

### Get

The `get` function gets information for a specific game session:

```go
id := "<Game Session ARN>"
instance, err := fm.Get(ctx, id)
```

### Join

The `join` function reserves a seat in an existing game session and retrieves the corresponding player session data needed for a client to connect:

```go
id := "<game session ARN>"
userIds := []string{userId}
metadata := map[string]string{
  userId: "<player data>",
} // metadata is optional. It can be used to set an arbitrary string that can be retrieved from the Game Session. Each key needs to match an userId present in userIds, otherwise it is ignored.
joinInfo, err := fm.Join(ctx, id string, userIds []string, metadata map[string]string)
```

### Delete

Delete should be used to delete an InstanceInfo data regarding a Game Session that was terminated on GameLift:

```go
id := "<game session ARN>"
err := fm.Delete(ctx, id)
```


## Example: Finding/Creating a Game Session via Nakama Matchmaking

One common use case for creating an Amazon GameLift game session via Nakama is to combine it with Nakama's powerful social matchmaking features. In the following client/server code examples, a player will use Nakama's matchmaking to find a match from their friends list and Nakama will then find or create an Amazon GameLift game session in the configured fleet before sending the connection details to the matched players.

1. On the server, register a `MatchmakerMatched` hook that will find or create an Amazon GameLift game session for the matched players and, when the game session is available, send the connection details to the matched players.

```go
// Define notification codes
notificationConnectionInfo := 111
notificationCreateTimeout := 112
notificationCreateFailed := 113

// Register the Matchmaker Matched hook to find/create a GameLift game session
if err := initializer.RegisterMatchmakerMatched(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry) (string, error) {
		// Define the maximum amount of players per game
    maxPlayers := 10

    // Find existing GameLift game sessions
    fm := nk.GetFleetManager()
    query := fmt.Sprintf("+values.playerCount:<=%v", maxPlayers - len(entries)) // Assuming a max match size of 10, find a match that has enough player spaces for the matched players
    limit := 1
    cursor := ""
    instances, _, err := fm.List(ctx, query, limit, cursor)
    if err != nil {
      logger.WithField("error", err.Error()).Error("failed to list gamelift instances")
		  return "", ErrInternalError
    }

    // If an instance was found, tell GameLift the players are joining the instance and then notify players with the connection details
    if len(instances) > 0 {
      instance := instances[0]

      userIds := make([]string, 0, len(entries))
      for _, entry := range entries {
        userIds = append(userIds, entry.Presence.UserId)
      }

      joinInfo, err := fm.Join(ctx, instance.Id, userIds, nil)
      if err != nil {
        logger.WithField("error", err.Error()).Error("failed to join gamelift instance")
			  return "", ErrInternalError
      }

      // Send connection details notifications to players
      for _, userId := range userIds {
        // Get the user's GameLift session ID
        sessionId := ""
        for _, sessionInfo := range joinInfo.SessionInfo {
          if sessionInfo.UserId == userId {
            sessionId = sessionInfo.SessionId
            break
          }
        }

        subject := "connection-info"
        content := map[string]interface{}{
          "IpAddress": joinInfo.InstanceInfo.IpAddress,
          "DnsName": joinInfo.InstanceInfo.DnsName,
          "Port": joinInfo.InstanceInfo.Port,
          "SessionId": sessionId,
        }
        code := notificationConnectionInfo
        senderId := "" // System sender
        persistent := false
        nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
      }

      // We don't pass a Match ID back to the user as we are not creating a Nakama match
      return "", nil
    }

    // If no instance was found, ask GameLift to create a new one and, when it is available, notify the players with the connection details
    // First establish the creation callback
    var callback runtime.FmCreateCallbackFn = func(status runtime.FmCreateStatus, instanceInfo *runtime.InstanceInfo, sessionInfo []*runtime.SessionInfo, metadata map[string]any, createErr error) {
      switch status {
      case runtime.CreateSuccess:
        joinInfo, _ := json.Marshal(instanceInfo)
        logger.Info("GameLift instance created: %s", joinInfo)

        // Send connection details notifications to players
        for _, userId := range userIds {
          // Get the user's GameLift session ID
          sessionId := ""
          for _, sessionInfo := range joinInfo.SessionInfo {
            if sessionInfo.UserId == userId {
              sessionId = sessionInfo.SessionId
              break
            }
          }

          subject := "connection-info"
          content := map[string]interface{}{
            "IpAddress": joinInfo.InstanceInfo.IpAddress,
            "DnsName": joinInfo.InstanceInfo.DnsName,
            "Port": joinInfo.InstanceInfo.Port,
            "SessionId": sessionId,
          }
          code := notificationConnectionInfo
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
        return
      case runtime.CreateTimeout:
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance, timed out")

        // Send notification to client that game session creation timed out
        for _, userId := range userIds {
          subject := "create-timeout"
          content := map[string]interface{}{}
          code := notificationCreateTimeout
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
      default:
        logger.WithField("error", createErr.Error()).Error("Failed to create GameLift instance")

        // Send notification to client that game session couldn't be created
        for _, userId := range userIds {
          subject := "create-timeout"
          content := map[string]interface{}{}
          code := notificationCreateFailed
          senderId := "" // System sender
          persistent := false
          nk.NotificationSend(ctx, userId, subject, content, code, senderId, persistent)
        }
        return
      }
    }

    // Game session metadata as described by AWS GameLift Documentation
    // https://docs.aws.amazon.com/gamelift/latest/apireference/API_GameSession.html
    // These properties can be queried by the Fleet Manager when listing existing game sessions.
    // These properties can also be updated by calling the Update RPC from the headless server.
    metadata := map[string]interface{}{
      "GameSessionData": "<game_session_data>",
      "GameSessionName": "<game_session_name>",
      "GameProperties": map[string]string {
        "key1": "value1",
        "key2": "value2",
      },
    }

    if err = fm.Create(ctx, maxPlayers, userIds, metadata, callback); err != nil {
      logger.WithField("error", err.Error()).Error("failed to create new fleet game session")
      return "", ErrInternalError
    }

    // We don't pass a Match ID back to the user as we are not creating a Nakama match
		return "", nil
	}); err != nil {
		logger.Error("unable to register matchmaker matched hook: %v", err)
		return err
	}
```

2. On the client, use matchmaking to match with friends:

```csharp
var minPlayers = 2;
var maxPlayers = 10;
var query = "properties.player:/(<friendId1>|<friendId2>|<friendId3>)/";
var stringProperties = new Dictionary<string, string>() {
  {"player", "<userId>"}
};
var matchmakerTicket = await socket.AddMatchmakerAsync(query, minPlayers, maxPlayers, stringProperties);
```

3. On the client, register to listen for notifications from the server:

```csharp
socket.ReceivedNotification += notification => {
  const int notificationConnectionInfo = 111;
  const int notificationCreateTimeout = 112;
  const int notificationCreateFailed = 113;

  switch (notification.Code)
  {
    case notificationConnectionInfo:
      var data = notification.Content.FromJson<Dictionary<string, string>>();
      Debug.Log("Game session created, connection info:");
      Debug.Log($"IP: {data["IpAddress"]}");
      Debug.Log($"DnsName: {data["DnsName"]}");
      Debug.Log($"Port: {data["Port"]}");
      Debug.Log($"SessionId: {data["SessionId"]}");

      // Connect to the Amazon GameLift headless server with your chosen networking framework and the connection info.
      break;
    case notificationCreateTimeout:
      Debug.LogError("Game session creation timed out");
      break;
    case notificationCreateFailed:
      Debug.LogError("Failed to create game session");
      break;
  }
};
```

## FAQ

**My SNS/SQS/GameLift instance cannot access X/Y/Z**

Be sure to check the **Access Policies** and **Permissions** associated with each AWS service to make sure it has been configured to correctly have access to the relevant ARNs.

**My Nakama instance is not receiving events from the SQS queue**

Similarly to above, double check that the policies and permissions are correct, particularly within the SNS Topic and SQS queue.

**My Nakama instance cannot interact with the AWS APIs**

As above, ensure that the policies and permissions within AWS are correct.

**Game sessions aren't being created**

Check the Fleet configuration to ensure scaling has been configured correctly, in particular check the max instances setting. If you are still experiencing issues, please refer to the [Amazon GameLift documentation](https://docs.aws.amazon.com/gamelift/).

**I cannot connect to the headless game server**

Be sure to check that the appropriate **Ports** have been configured when creating the fleet. This will depend on your chosen networking framework, for example, Unity Netcode For GameObjects uses port 7778 by default.

If this does not resolve the issue, be sure to check the Debug logs of both the client and the server for more information.

**I'm having an issue with X/Y/Z**

If the issue you're experiencing is not covered above, then there are a few things you can do to help diagnose the issue:

1. Check the Nakama server logs
2. Check the Game Session logs to look for client side issues
3. Check the Nakama Console for any potential issues
