Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unreal SDK add Allocate + Reserve and changes to the plugin settings #1345

Merged
merged 5 commits into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion sdks/unreal/Agones/Source/Agones/Agones.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ void FAgonesModule::OnWorldInitialized(UWorld* World, const UWorld::Initializati
if (World != nullptr && World->GetNetMode() == ENetMode::NM_DedicatedServer)
{
HookPtr = MakeShareable(new FAgonesHook());
HookPtr->Ready();
}
}

Expand Down
69 changes: 45 additions & 24 deletions sdks/unreal/Agones/Source/Agones/AgonesHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "JsonObjectConverter.h"
#include "Serialization/JsonReader.h"
#include "GenericPlatform/GenericPlatformMisc.h"
#include "Model/Duration.h"
#include "Model/KeyValuePair.h"
#include "Runtime/Online/HTTP/Public/Http.h"
#include "Serialization/JsonSerializer.h"
Expand All @@ -43,6 +44,8 @@ FAgonesHook::FAgonesHook()
, SetLabelSuffix(FString(TEXT("/metadata/label")))
, SetAnnotationSuffix(FString(TEXT("/metadata/annotation")))
, GetGameServerSuffix(FString(TEXT("/gameserver")))
, AllocateSuffix(FString(TEXT("/allocate")))
, ReserveSuffix(FString(TEXT("/reserve")))
{
Settings = GetDefault<UAgonesSettings>();
check(Settings != nullptr);
Expand All @@ -52,12 +55,17 @@ FAgonesHook::FAgonesHook()
FHttpRetrySystem::FRetryLimitCountSetting(RetryLimitCount),
FHttpRetrySystem::FRetryTimeoutRelativeSecondsSetting());

UE_LOG(LogAgonesHook, Log, TEXT("Initialized Agones Hook, Sidecar address: %s, Health Enabled: %s, Health Ping: %f, Debug: %s, Request Retry Limit: %d")
UE_LOG(LogAgonesHook, Log, TEXT("Initialized Agones Hook, Sidecar address: %s, Health Enabled: %s, Health Ping: %f, Request Retry Limit: %d, Send Ready at Startup: %s")
, *SidecarAddress
, (Settings->bHealthPingEnabled ? TEXT("True") : TEXT("False"))
, Settings->HealthPingSeconds
, (Settings->bDebugLogEnabled ? TEXT("True") : TEXT("False"))
, Settings->RequestRetryLimit);
, Settings->RequestRetryLimit
, (Settings->bSendReadyAtStartup ? TEXT("True") : TEXT("False")));

if (Settings->bSendReadyAtStartup)
markmandel marked this conversation as resolved.
Show resolved Hide resolved
{
Ready();
}
}

FAgonesHook::~FAgonesHook()
Expand Down Expand Up @@ -115,28 +123,26 @@ void FAgonesHook::SetLabel(const FString& Key, const FString& Value)
{
FKeyValuePair Label = { Key, Value };
FString Json;
if (FJsonObjectConverter::UStructToJsonObjectString(Label, Json))
if (!FJsonObjectConverter::UStructToJsonObjectString(Label, Json))
{
SendRequest(SidecarAddress + SetLabelSuffix, Json, FHttpVerb::PUT, true);
}
else
{
UE_LOG(LogAgonesHook, Error, TEXT("Failed to send request, error serializing key-value pair (%s: %s)"), *Key, *Value);
UE_LOG(LogAgonesHook, Error, TEXT("Failed to set label, error serializing key-value pair (%s: %s)"), *Key, *Value);
return;
}

SendRequest(SidecarAddress + SetLabelSuffix, Json, FHttpVerb::PUT, true);
}

void FAgonesHook::SetAnnotation(const FString& Key, const FString& Value)
{
FKeyValuePair Annotation = { Key, Value };
FString Json;
if (FJsonObjectConverter::UStructToJsonObjectString(Annotation, Json))
if (!FJsonObjectConverter::UStructToJsonObjectString(Annotation, Json))
{
SendRequest(SidecarAddress + SetAnnotationSuffix, Json, FHttpVerb::PUT, true);
}
else
{
UE_LOG(LogAgonesHook, Error, TEXT("Failed to send request, error serializing key-value pair (%s: %s)"), *Key, *Value);
UE_LOG(LogAgonesHook, Error, TEXT("Failed to set annotation, error serializing key-value pair (%s: %s)"), *Key, *Value);
return;
}

SendRequest(SidecarAddress + SetAnnotationSuffix, Json, FHttpVerb::PUT, true);
}

void FAgonesHook::GetGameServer(const FGameServerRequestCompleteDelegate& Delegate)
Expand Down Expand Up @@ -167,6 +173,24 @@ void FAgonesHook::GetGameServer(const FGameServerRequestCompleteDelegate& Delega
});
}

void FAgonesHook::Allocate()
{
SendRequest(SidecarAddress + AllocateSuffix, TEXT("{}"), FHttpVerb::POST, true);
}

void FAgonesHook::Reserve(const int64 Seconds)
{
FDuration Duration = { Seconds };
FString Json;
if (!FJsonObjectConverter::UStructToJsonObjectString(Duration, Json))
{
UE_LOG(LogAgonesHook, Error, TEXT("Failed to send reserve request, error serializing duration (%d)"), Seconds);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dunno the style for cpp well but would prefer to return early in the conditional with this one

eg. if the duration doesnt parse return and log ... if it does then normal flow no ident sendrequest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't believe unreal has a specific guideline for this but agree it looks better, will change


SendRequest(SidecarAddress + ReserveSuffix, Json, FHttpVerb::POST, true);
}

TSharedRef<IHttpRequest> FAgonesHook::MakeRequest(const FString& URL, const FString& JsonContent, const FHttpVerb Verb, const bool bRetryOnFailure)
{
TSharedRef<IHttpRequest> Req = bRetryOnFailure
Expand All @@ -184,16 +208,13 @@ TSharedRef<IHttpRequest> FAgonesHook::SendRequest(const FString& URL, const FStr
{
TSharedRef<IHttpRequest> Req = MakeRequest(URL, JsonContent, Verb, bRetryOnFailure);
bool bSuccess = Req->ProcessRequest();
if (Settings->bDebugLogEnabled)
if (bSuccess)
{
if (bSuccess)
{
UE_LOG(LogAgonesHook, Log, TEXT("Send: %s"), *URL);
}
else
{
UE_LOG(LogAgonesHook, Error, TEXT("Failed sending: %s"), *URL);
}
UE_LOG(LogAgonesHook, Verbose, TEXT("Send: %s"), *URL);
}
else
{
UE_LOG(LogAgonesHook, Error, TEXT("Failed sending: %s"), *URL);
}

return Req;
Expand Down
6 changes: 6 additions & 0 deletions sdks/unreal/Agones/Source/Agones/AgonesHook.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ class AGONES_API FAgonesHook : public FTickableGameObject
void SetAnnotation(const FString& Key, const FString& Value);
/** Retrieve the GameServer details from the sidecar */
void GetGameServer(const FGameServerRequestCompleteDelegate& Delegate);
/** Sends a request to allocate the GameServer **/
void Allocate();
/** Sends a request to mark the GameServer as reserved for the specified duration */
void Reserve(const int64 Seconds);

private:

Expand All @@ -117,4 +121,6 @@ class AGONES_API FAgonesHook : public FTickableGameObject
const FString SetLabelSuffix;
const FString SetAnnotationSuffix;
const FString GetGameServerSuffix;
const FString AllocateSuffix;
const FString ReserveSuffix;
};
2 changes: 1 addition & 1 deletion sdks/unreal/Agones/Source/Agones/AgonesSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ UAgonesSettings::UAgonesSettings()
: Super()
, bHealthPingEnabled(true)
, HealthPingSeconds(5.0f)
, bDebugLogEnabled(false)
, RequestRetryLimit(30)
, bSendReadyAtStartup(true)
{
}

Expand Down
27 changes: 27 additions & 0 deletions sdks/unreal/Agones/Source/Agones/Model/Duration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "CoreMinimal.h"
#include "Duration.generated.h"

USTRUCT()
struct FDuration
{
GENERATED_BODY()

UPROPERTY()
int64 Seconds;
};
6 changes: 3 additions & 3 deletions sdks/unreal/Agones/Source/Agones/Public/AgonesSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ class AGONES_API UAgonesSettings : public UObject
UPROPERTY(EditAnywhere, config, Category = "Agones", meta = (DisplayName = "Health Ping Seconds"))
float HealthPingSeconds;

UPROPERTY(EditAnywhere, config, Category = "Agones", meta = (DisplayName = "Debug Logging Enabled"))
bool bDebugLogEnabled;

UPROPERTY(EditAnywhere, config, Category = "Agones", meta = (DisplayName = "Request Retry Limit"))
uint32 RequestRetryLimit;

UPROPERTY(EditAnywhere, config, Category = "Agones", meta = (DisplayName = "Send Ready at Startup"))
bool bSendReadyAtStartup;
};
36 changes: 35 additions & 1 deletion site/content/en/docs/Guides/Client SDKs/unreal.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,40 @@ At this moment we do not provide binaries for the plugin. This requires you to c
2. Copy {{< ghlink href="sdks/unreal" >}}the Agones plugin directory{{< /ghlink >}} into the Plugins directory.
3. Build the project.

### Agones Hook

To manually call the Agones SDK methods add the plugin as a dependency inside the `<project>.Build.cs` file:

```
PublicDependencyModuleNames.AddRange(
new string[]
{
...
"Agones"
});
```

Then use `FAgonesModule::GetHook()` to get a reference to the Agones hook and call the SDK methods using the hook:

```
#include "Agones.h"

...

// Get a reference to the Agones hook.
FAgonesHook& Hook = FAgonesModule::GetHook();

Hook.Ready();
Hook.SetLabel(TEXT("key"), TEXT("value"));

// GetGameServerDelegate here is a class member of type FGameServerRequestCompleteDelegate.
Hook.GetGameServer(GetGameServerDelegate);
GetGameServerDelegate.BindLambda([](TSharedPtr<FGameServer> GameServer, bool bSuccess)
{
// ...
});
```

## Settings

The settings for the Agones Plugin can be found in the Unreal Engine editor `Edit > Project Settings > Plugins > Agones`
Expand All @@ -39,6 +73,6 @@ Available settings:

- Health Ping Enabled. Whether the server sends a health ping to the Agones sidecar. (default: `true`)
- Health Ping Seconds. Interval of the server sending a health ping to the Agones sidecar. (default: `5`)
- Debug Logging Enabled. Debug logging for development of this Plugin. (default: `false`)
- Request Retry Limit. Maximum number of times a failed request to the Agones sidecar is retried. Health requests are not retried. (default: `30`)
- Send Ready at Startup. Automatically send a Ready request when the server starts. Disable this to manually control when the game server should be marked as ready. (default: `true`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does there need to be more docs around how to use the the supplied methods, such as Allocate, Ready, SetLabel etc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree, it might be worth adding a paragraph on how these mehtods / structs are available to be set from within UE4Editor?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be worth adding a paragraph around using this locally ... eg. how we can turn off the healthcheck/ready checks when just launching via UE4Editor that makes it easier for most GameDevs to iterate fast before pushing into our clusters.

Copy link
Contributor Author

@WVerlaek WVerlaek Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added some docs on how to use the sdk methods, please lmk if it makes any sense 😄

Copy link
Contributor Author

@WVerlaek WVerlaek Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For local dev (or for running outside an agones environment) I think the plugin could use some more work to support disabling executing the sdk methods / auto health checks. Was thinking about an additional config value bAgonesEnabled which can be set to default, forceEnabled, or forceDisabled, where default will disable calling out to the agones sidecar when running locally inside the unreal editor - but that's not something for this pr