Skip to content
51 changes: 50 additions & 1 deletion testproject/Assets/Scripts/CommandLineHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ public class CommandLineProcessor
private string m_SceneToLoad;
private Dictionary<string, string> m_CommandLineArguments = new Dictionary<string, string>();
private ConnectionModeScript m_ConnectionModeScript;

public string TransportAddress;
public string TransportPort;

public CommandLineProcessor(string[] args)
{
Debug.Log("CommandLineProcessor constructor called");
try
{
if (s_Singleton != null)
Expand Down Expand Up @@ -54,7 +59,6 @@ public CommandLineProcessor(string[] args)
m_CommandLineArguments.Add(arg, value);
}
}

ProcessCommandLine();
}

Expand Down Expand Up @@ -90,6 +94,13 @@ public void ProcessCommandLine()
}
}

if (m_CommandLineArguments.TryGetValue("-transport", out string transportName))
{
Debug.Log($"Setting transport to {transportName}");
SetTransport(transportName);
m_CommandLineArguments.Remove("-transport");
}

if (m_CommandLineArguments.TryGetValue("-ip", out string ipValue))
{
SetTransportAddress(ipValue);
Expand All @@ -98,6 +109,7 @@ public void ProcessCommandLine()

if (m_CommandLineArguments.TryGetValue("-p", out string port))
{
TransportPort = port;
SetPort(ushort.Parse(port));
m_CommandLineArguments.Remove("-p");
}
Expand All @@ -110,6 +122,7 @@ public void ProcessCommandLine()

if (m_CommandLineArguments.TryGetValue("-m", out string netcodeValue))
{
Debug.Log($"Starting {netcodeValue}");
switch (netcodeValue)
{
case "server":
Expand Down Expand Up @@ -197,8 +210,44 @@ private void StartClient()
}
}

private void SetTransport(string transportName)
{
if (transportName.Equals("utp", StringComparison.CurrentCultureIgnoreCase))
{
AddUnityTransport(NetworkManager.Singleton);
}
#if UNITY_UNET_PRESENT
else if (transportName.Equals("unet", StringComparison.CurrentCultureIgnoreCase))
{
// Do nothing, this is the default
}
#endif

}

private static void AddUnityTransport(NetworkManager networkManager)
{
// Create transport
var unityTransport = networkManager.gameObject.AddComponent<UnityTransport>();
// We need to increase this buffer size for tests that spawn a bunch of things
unityTransport.MaxPayloadSize = 256000;
unityTransport.MaxSendQueueSize = 1024 * 1024;

// Allow 4 connection attempts that each will time out after 500ms
unityTransport.MaxConnectAttempts = 4;
unityTransport.ConnectTimeoutMS = 500;

// Set the NetworkConfig
networkManager.NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = unityTransport
};
}

private void SetTransportAddress(string address)
{
TransportAddress = address;
var transport = NetworkManager.Singleton.NetworkConfig.NetworkTransport;
switch (transport)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public static string StartWorkerNode()
workerProcess.StartInfo.UseShellExecute = false;
workerProcess.StartInfo.RedirectStandardError = true;
workerProcess.StartInfo.RedirectStandardOutput = true;
workerProcess.StartInfo.Arguments = $"{IsWorkerArg} {extraArgs} -logFile {logPath} -s {BuildMultiprocessTestPlayer.MainSceneName}";
workerProcess.StartInfo.Arguments = $"{IsWorkerArg} {extraArgs} -logFile {logPath}";

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,57 @@ public void Awake()
MultiprocessLogger.Log($"Awake {s_ProcessId}");
ReadGitHashFile();

// There are two possible ways for configuration data to be set
// 1. From a webapi
// 2. From a text file resource
// Configuration via command line (supported for many but not all platforms)
bool isClient = Environment.GetCommandLineArgs().Any(value => value == MultiprocessOrchestration.IsWorkerArg);
if (isClient)
{
MultiprocessLogger.Log("Setting up via command line - client");
m_IsClient = isClient;
var cli = new CommandLineProcessor(Environment.GetCommandLineArgs());
if (Environment.GetCommandLineArgs().Any(value => value == "-ip"))
{
m_ConnectAddress = cli.TransportAddress;
}
if (Environment.GetCommandLineArgs().Any(value => value == "-p"))
{
Port = cli.TransportPort;
}
SetConfigurationTypeAndConnect(ConfigurationType.CommandLine);
}

if (ConfigurationType == ConfigurationType.Unknown)
{
bool isHost = Environment.GetCommandLineArgs().Any(value => value == "host");
if (isHost)
{
MultiprocessLogger.Log("Setting up via command line - host");
var cli = new CommandLineProcessor(Environment.GetCommandLineArgs());
ConfigurationType = ConfigurationType.CommandLine;
}
}


// Configuration via configuration file - all platform support but set at build time
if (ConfigurationType == ConfigurationType.Unknown)
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't we add some form of debug log or warning that notifies the configuration type is unknown?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh yeah, that's right. Previously if things weren't set up correctly we'd assert and fail the test but this looks like it'll continue on with no indication of what went wrong.

I'll make an appropriate message and submit an update shortly.

//TODO: For next PR
}

// configuration via WebApi - works on all platforms and is set at run time
if (ConfigurationType == ConfigurationType.Unknown)
{
MultiprocessLogger.Log($"Awake {s_ProcessId} - Calling ConfigureViewWebApi");
ConfigureViaWebApi();
MultiprocessLogger.Log($"Awake {s_ProcessId} - Calling ConfigureViewWebApi completed");
}


// if we've tried all the configuration types and none of them are correct then we should throw an exception
if (ConfigurationType == ConfigurationType.Unknown)
{
throw new Exception("Unable to determine configuration for NetworkManager via commandline, webapi or config file");
}

if (Instance != null)
{
MultiprocessLogger.LogError("Multiple test coordinator, destroying this instance");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void Update()
t.text = $"On Update -\ntestCoordinator.isActiveAndEnabled:{testCoordinator.isActiveAndEnabled} {testCoordinator.ConfigurationType}\n" +
$"Transport: {transportString}\n" +
$"{CommandLineArguments}\n" +
$"IsHost: {NetworkManager.Singleton.IsHost} IsClient: {NetworkManager.Singleton.IsClient}\n" +
$"IsHost: {NetworkManager.Singleton.IsHost} IsClient: {NetworkManager.Singleton.IsClient} {NetworkManager.Singleton.IsConnectedClient}\n" +
$"{m_UpdateCounter}\n";
}
}
Expand Down
29 changes: 28 additions & 1 deletion testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,10 @@ Tests when launched locally will simply create new OS processes for each worker
![](readme-ressources/OrchestrationOverview.jpg)
*Note that this diagram is still WIP for the CI part*
### Bokken orchestration
todo
Bokken Orchestration can be performed with the support of the following tool:
[Multiplayer Multiprocess Test Tools](https://github.cds.internal.unity3d.com/unity/multiplayer-multiprocess-test-tools)
[Documentation](https://backstage.corp.unity3d.com/catalog/default/component/multiplayer-multiprocess-test-tools/docs/)

### CI
todo
#### Performance report dashboards
Expand All @@ -247,6 +250,30 @@ The test coordinator in client mode will automatically try to connect to a serve
Test methods are executed twice. Once in "registration" mode, to have all the steps register themselves using a unique ID. This ID is deterministic between client and server processes, so that when a server calls a step during actual test execution, the clients have the same ID associated with the same lambda.
During test execution, the main node's step will call an RPC on clients to trigger their pre-registered lambda. The main node's step will then yield until it receives a "client done" RPC from all clients. The main node's test will then be able to continue execution to the next step.

## The MultiprocessTestPlayer
The MultiprocessTestPlayer, which is built by the sub-menu items under "Netcode" -> "Multiprocess Test", supports many workflows and configurations.

### Command Line
Currently the MultiprocessTestPlayer can be configured via the command line on all platforms that support parsing of command line arguments in the C# layer.
For example, this means that command line configuration is not available on Android.

#### Setting the transport address
Default Values on Client: 127.0.0.1, 3076

In order to set the transport address for either the server/host or the client, the options of "-ip" and "-p" can be used. For example:

-ip 127.0.0.1 -p 3076

These options can be passed when starting the client, for example, in order to let it know where the host is to connect to.

#### Setting the transport
Default Values: UNET

The default transport is UNET but this can be switched to UTP by using

-transport utp


# Future considerations
- Integrate with local MultiInstance tests?
- Have ExecuteStepInContext a game facing feature for sequencing client-server actions?
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
{
"name": "TestProject.MultiprocessTests",
"rootNamespace": "",
"references": [
"Unity.Netcode.Runtime",
"TestProject",
"Unity.Netcode.Editor",
"Unity.Netcode.Components",
"ScriptsForAutomatedTesting",
"Unity.PerformanceTesting"
"Unity.PerformanceTesting",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"optionalUnityReferences": [
"TestAssemblies"
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [
"nunit.framework.dll"
],
"includePlatforms": [
"Editor",
"macOSStandalone",
"LinuxStandalone64",
"WindowsStandalone32",
"WindowsStandalone64"
]
}
"autoReferenced": true,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}