Skip to content

Commit

Permalink
Use SemVer to check communication compatibility between C# and Python (
Browse files Browse the repository at this point in the history
…#3760)

* [communication] Use semantic versioning to test communication compatibility between C# and Python.

- Add tests for the change.

Co-authored-by: Chris Elion <chris.elion@unity3d.com>
  • Loading branch information
surfnerd and Chris Elion committed Apr 9, 2020
1 parent b6759f1 commit fe6a63f
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 19 deletions.
1 change: 1 addition & 0 deletions com.unity.ml-agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added ability to start training (initialize model weights) from a previous run ID. (#3710)
- The internal event `Academy.AgentSetStatus` was renamed to `Academy.AgentPreStep` and made public.
- The offset logic was removed from DecisionRequester.
- The communication API version has been bumped up to 1.0.0 and will use [Semantic Versioning](https://semver.org/) to do compatibility checks for communication between Unity and the Python process.

### Minor Changes
- Format of console output has changed slightly and now matches the name of the model/summary directory. (#3630, #3616)
Expand Down
51 changes: 46 additions & 5 deletions com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,41 @@ public RpcCommunicator(CommunicatorInitParameters communicatorInitParameters)

#region Initialization

internal static bool CheckCommunicationVersionsAreCompatible(
string unityCommunicationVersion,
string pythonApiVersion,
string pythonLibraryVersion)
{
var unityVersion = new Version(unityCommunicationVersion);
var pythonVersion = new Version(pythonApiVersion);
if (unityVersion.Major == 0)
{
if (unityVersion.Major != pythonVersion.Major || unityVersion.Minor != pythonVersion.Minor)
{
return false;
}

}
else if (unityVersion.Major != pythonVersion.Major)
{
return false;
}
else if (unityVersion.Minor != pythonVersion.Minor)
{
// Even if we initialize, we still want to check to make sure that we inform users of minor version
// changes. This will surface any features that may not work due to minor version incompatibilities.
Debug.LogWarningFormat(
"WARNING: The communication API versions between Unity and python differ at the minor version level. " +
"Python API: {0}, Unity API: {1} Python Library Version: {2} .\n" +
"This means that some features may not work unless you upgrade the package with the lower version." +
"Please find the versions that work best together from our release page.\n" +
"https://github.com/Unity-Technologies/ml-agents/releases",
pythonApiVersion, unityCommunicationVersion, pythonLibraryVersion
);
}
return true;
}

/// <summary>
/// Sends the initialization parameters through the Communicator.
/// Is used by the academy to send initialization parameters to the communicator.
Expand All @@ -87,17 +122,23 @@ public UnityRLInitParameters Initialize(CommunicatorInitParameters initParameter
},
out input);

var pythonCommunicationVersion = initializationInput.RlInitializationInput.CommunicationVersion;
var pythonPackageVersion = initializationInput.RlInitializationInput.PackageVersion;
var unityCommunicationVersion = initParameters.unityCommunicationVersion;

var communicationIsCompatible = CheckCommunicationVersionsAreCompatible(unityCommunicationVersion,
pythonCommunicationVersion,
pythonPackageVersion);

// Initialization succeeded part-way. The most likely cause is a mismatch between the communicator
// API strings, so log an explicit warning if that's the case.
if (initializationInput != null && input == null)
{
var pythonCommunicationVersion = initializationInput.RlInitializationInput.CommunicationVersion;
var pythonPackageVersion = initializationInput.RlInitializationInput.PackageVersion;
if (pythonCommunicationVersion != initParameters.unityCommunicationVersion)
if (!communicationIsCompatible)
{
Debug.LogWarningFormat(
"Communication protocol between python ({0}) and Unity ({1}) don't match. " +
"Python library version: {2}.",
"Communication protocol between python ({0}) and Unity ({1}) have different " +
"versions which make them incompatible. Python library version: {2}.",
pythonCommunicationVersion, initParameters.unityCommunicationVersion,
pythonPackageVersion
);
Expand Down
3 changes: 3 additions & 0 deletions com.unity.ml-agents/Tests/Editor/Communicator.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections;
using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace MLAgents.Tests.Communicator
{
[TestFixture]
public class RpcCommunicatorTests
{

[Test]
public void TestCheckCommunicationVersionsAreCompatible()
{
var unityVerStr = "1.0.0";
var pythonVerStr = "1.0.0";
var pythonPackageVerStr = "0.16.0";

Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));
LogAssert.NoUnexpectedReceived();

pythonVerStr = "1.1.0";
Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));

// Ensure that a warning was printed.
LogAssert.Expect(LogType.Warning, new Regex("(.\\s)+"));

unityVerStr = "2.0.0";
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));

unityVerStr = "0.15.0";
pythonVerStr = "0.15.0";
Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));
unityVerStr = "0.16.0";
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));
unityVerStr = "1.15.0";
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
pythonVerStr,
pythonPackageVerStr));

}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 49 additions & 14 deletions ml-agents-envs/mlagents_envs/environment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import atexit
from distutils.version import StrictVersion
import glob
import uuid
import numpy as np
Expand Down Expand Up @@ -45,7 +46,6 @@
import signal
import struct


logger = get_logger(__name__)


Expand All @@ -71,6 +71,47 @@ class UnityEnvironment(BaseEnv):
# Command line argument used to pass the port to the executable environment.
PORT_COMMAND_LINE_ARG = "--mlagents-port"

@staticmethod
def _raise_version_exception(unity_com_ver: str) -> None:
raise UnityEnvironmentException(
f"The communication API version is not compatible between Unity and python. "
f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_com_ver}.\n "
f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
f"to download the latest version of ML-Agents."
)

@staticmethod
def check_communication_compatibility(
unity_com_ver: str, python_api_version: str, unity_package_version: str
) -> bool:
unity_communicator_version = StrictVersion(unity_com_ver)
api_version = StrictVersion(python_api_version)
if unity_communicator_version.version[0] == 0:
if (
unity_communicator_version.version[0] != api_version.version[0]
or unity_communicator_version.version[1] != api_version.version[1]
):
# Minor beta versions differ.
return False
elif unity_communicator_version.version[0] != api_version.version[0]:
# Major versions mismatch.
return False
elif unity_communicator_version.version[1] != api_version.version[1]:
# Non-beta minor versions mismatch. Log a warning but allow execution to continue.
logger.warning(
f"WARNING: The communication API versions between Unity and python differ at the minor version level. "
f"Python API: {python_api_version}, Unity API: {unity_communicator_version}.\n"
f"This means that some features may not work unless you upgrade the package with the lower version."
f"Please find the versions that work best together from our release page.\n"
"https://github.com/Unity-Technologies/ml-agents/releases"
)
else:
logger.info(
f"Connected to Unity environment with package version {unity_package_version} "
f"and communication version {unity_com_ver}"
)
return True

def __init__(
self,
file_name: Optional[str] = None,
Expand Down Expand Up @@ -153,20 +194,14 @@ def __init__(
self._close(0)
raise

unity_communicator_version = aca_params.communication_version
if unity_communicator_version != UnityEnvironment.API_VERSION:
if not UnityEnvironment.check_communication_compatibility(
aca_params.communication_version,
UnityEnvironment.API_VERSION,
aca_params.package_version,
):
self._close(0)
raise UnityEnvironmentException(
f"The communication API version is not compatible between Unity and python. "
f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_communicator_version}.\n "
f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
f"to download the latest version of ML-Agents."
)
else:
logger.info(
f"Connected to Unity environment with package version {aca_params.package_version} "
f"and communication version {aca_params.communication_version}"
)
UnityEnvironment._raise_version_exception(aca_params.communication_version)

self._env_state: Dict[str, Tuple[DecisionSteps, TerminalSteps]] = {}
self._env_specs: Dict[str, BehaviorSpec] = {}
self._env_actions: Dict[str, np.ndarray] = {}
Expand Down
31 changes: 31 additions & 0 deletions ml-agents-envs/mlagents_envs/tests/test_envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,37 @@ def test_close(mock_communicator, mock_launcher):
assert comm.has_been_closed


def test_check_communication_compatibility():
unity_ver = "1.0.0"
python_ver = "1.0.0"
unity_package_version = "0.15.0"
assert UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)
unity_ver = "1.1.0"
assert UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)
unity_ver = "2.0.0"
assert not UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)

unity_ver = "0.16.0"
python_ver = "0.16.0"
assert UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)
unity_ver = "0.17.0"
assert not UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)
unity_ver = "1.16.0"
assert not UnityEnvironment.check_communication_compatibility(
unity_ver, python_ver, unity_package_version
)


def test_returncode_to_signal_name():
assert UnityEnvironment.returncode_to_signal_name(-2) == "SIGINT"
assert UnityEnvironment.returncode_to_signal_name(42) is None
Expand Down

0 comments on commit fe6a63f

Please sign in to comment.