Skip to content
This repository has been archived by the owner on Jan 18, 2022. It is now read-only.

UTY-2508: enable command tests #1437

Merged
merged 23 commits into from
Jul 29, 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- Fixed an `IndexOutOfRangeException` that could be thrown when editing your 'Build Configuration' asset. [#1441](https://github.com/spatialos/gdk-for-unity/pull/1441)
- The 'Build Configuration' Inspector window will no longer report your Android SDK installation as missing if you have a completely fresh Unity installation with the bundled Android SDK. [#1441](https://github.com/spatialos/gdk-for-unity/pull/1441)

### Added

- Added capability to test commands through the `MockConnectionHandler`. [#1437](https://github.com/spatialos/gdk-for-unity/pull/1437)

### Internal

- Added C# bindings for C Event Tracing API. [#1440](https://github.com/spatialos/gdk-for-unity/pull/1440)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Improbable.Gdk.Core;
using Improbable.Gdk.Core.Commands;
using Improbable.Gdk.TestUtils;
using Improbable.Worker.CInterop;
using NUnit.Framework;
using Playground;

namespace Improbable.Gdk.EditmodeTests.Core
{
public class CommandReceivedResponseTests : MockBase
{
private const long EntityId = 101;
private const long LaunchedId = 102;

[Test]
public void ReceivedResponse_isNull_when_Response_is_not_received()
{
World.Step(world =>
{
world.Connection.CreateEntity(EntityId, GetTemplate());
})
.Step(world =>
{
return world.GetSystem<CommandSystem>().SendCommand(GetRequest());
})
.Step((world, id) =>
{
Assert.IsFalse(world.GetSystem<CommandSystem>()
.GetResponse<Launcher.LaunchEntity.ReceivedResponse>(id).HasValue);
});
}

[Test]
public void ReceivedResponse_isNotNull_when_Response_is_received()
{
World.Step(world =>
{
world.Connection.CreateEntity(EntityId, GetTemplate());
})
.Step(world =>
{
return world.GetSystem<CommandSystem>().SendCommand(GetRequest());
})
.Step((world, id) =>
{
world.Connection
.GenerateResponse<Launcher.LaunchEntity.Request, Launcher.LaunchEntity.ReceivedResponse>(id,
ResponseGenerator);
return id;
})
.Step((world, id) =>
{
var response = world.GetSystem<CommandSystem>()
.GetResponse<Launcher.LaunchEntity.ReceivedResponse>(id);
Assert.IsTrue(response.HasValue);
Assert.AreEqual(response.Value.EntityId, GetRequest().TargetEntityId);
Assert.AreEqual(response.Value.RequestPayload, GetRequest().Payload);
});
}

private static Launcher.LaunchEntity.ReceivedResponse ResponseGenerator(CommandRequestId id, Launcher.LaunchEntity.Request request)
{
return new Launcher.LaunchEntity.ReceivedResponse(
default,
request.TargetEntityId,
null,
StatusCode.Success,
default,
request.Payload,
null,
id
);
}

private static EntityTemplate GetTemplate()
{
var template = new EntityTemplate();
template.AddComponent(new Position.Snapshot(), "worker");
template.AddComponent(new Launcher.Snapshot(), "worker");
return template;
}

private static Launcher.LaunchEntity.Request GetRequest()
{
return new Launcher.LaunchEntity.Request(new EntityId(EntityId), new LaunchCommandRequest(new EntityId(LaunchedId),
new Vector3f(1, 0, 1),
new Vector3f(0, 1, 0), 5, new EntityId(EntityId)));
}
}
}

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,76 @@
using Improbable.Gdk.Core.Commands;
using Improbable.Gdk.TestUtils;
using Improbable.Gdk.Subscriptions;
using Improbable.Gdk.Test;
using Improbable.Worker.CInterop;
using NUnit.Framework;
using UnityEngine;

namespace Improbable.Gdk.Core.EditmodeTests.Subscriptions
{
[TestFixture]
public class CommandTests : MockBase
{
private const long EntityId = 101;

[Test]
public void SubscriptionSystem_invokes_callback_on_receiving_response()
{
var pass = false;
World.Step(world =>
{
world.Connection.CreateEntity(EntityId, GetTemplate());
})
.Step(world =>
{
return world.CreateGameObject<CommandStub>(EntityId).Item2.Sender;
})
.Step((world, sender) =>
{
sender.SendTestCommand(GetRequest(), response => pass = true);
})
.Step(world =>
{
world.Connection.GenerateResponses<TestCommands.Test.Request, TestCommands.Test.ReceivedResponse>(
ResponseGenerator);
})
.Step(world =>
{
Assert.IsTrue(pass);
});
}

private static TestCommands.Test.ReceivedResponse ResponseGenerator(CommandRequestId id, TestCommands.Test.Request request)
{
return new TestCommands.Test.ReceivedResponse(
default,
request.TargetEntityId,
null,
StatusCode.Success,
default,
request.Payload,
null,
id
);
}

private static EntityTemplate GetTemplate()
{
var template = new EntityTemplate();
template.AddComponent(new Position.Snapshot(), "worker");
template.AddComponent(new TestCommands.Snapshot(), "worker");
return template;
}

private static TestCommands.Test.Request GetRequest()
{
return new TestCommands.Test.Request(new EntityId(EntityId), new Empty());
}
#pragma warning disable 649
private class CommandStub : MonoBehaviour
{
[Require] public TestCommandsCommandSender Sender;
}
#pragma warning restore 649
}
}

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
Expand Up @@ -76,6 +76,7 @@ private static TypeBlock GenerateCommandMetaclass(string qualifiedNamespace, str
commandMetaclass =>
{
commandMetaclass.Line($@"
public uint ComponentId => {rootNamespace}.ComponentId;
public uint CommandIndex => {command.CommandIndex};
public string Name => ""{command.PascalCaseName}"";

Expand All @@ -85,6 +86,11 @@ private static TypeBlock GenerateCommandMetaclass(string qualifiedNamespace, str
public Type MetaDataStorage {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}CommandMetaDataStorage);
public Type SendStorage {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}CommandsToSendStorage);
public Type DiffStorage {{ get; }} = typeof({rootNamespace}.Diff{command.PascalCaseName}CommandStorage);

public Type Response {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.Response);
public Type ReceivedResponse {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.ReceivedResponse);
public Type Request {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.Request);
public Type ReceivedRequest {{ get; }} = typeof({rootNamespace}.{command.PascalCaseName}.ReceivedRequest);
");
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace Improbable.Gdk.Core.Commands
{
public interface ICommandMetaclass
{
uint ComponentId { get; }
uint CommandIndex { get; }
string Name { get; }

Expand All @@ -13,5 +14,10 @@ public interface ICommandMetaclass
Type MetaDataStorage { get; }
Type SendStorage { get; }
Type DiffStorage { get; }

Type Response { get; }
Type ReceivedResponse { get; }
Type Request { get; }
Type ReceivedRequest { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Unity.Entities;

namespace Improbable.Gdk.Core.Commands
{
public interface IOutgoingCommandHandler
{
CommandRequestId SendCommand<T>(T request, Entity sendingEntity = default) where T : ICommandRequest;

void SendResponse<T>(T response) where T : ICommandResponse;
}
}

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
Expand Up @@ -13,6 +13,10 @@ public static class ComponentDatabase

private static Dictionary<Type, uint> SnapshotsToIds { get; }

private static Dictionary<Type, ICommandMetaclass> RequestsToCommandMetaclass { get; }

private static Dictionary<Type, ICommandMetaclass> ReceivedRequestsToCommandMetaclass { get; }

static ComponentDatabase()
{
Metaclasses = ReflectionUtility.GetNonAbstractTypes(typeof(IComponentMetaclass))
Expand All @@ -21,6 +25,12 @@ static ComponentDatabase()

ComponentsToIds = Metaclasses.ToDictionary(pair => pair.Value.Data, pair => pair.Key);
SnapshotsToIds = Metaclasses.ToDictionary(pair => pair.Value.Snapshot, pair => pair.Key);
RequestsToCommandMetaclass = Metaclasses
.SelectMany(pair => pair.Value.Commands.Select(cmd => (cmd.Request, cmd)))
.ToDictionary(pair => pair.Request, pair => pair.cmd);
ReceivedRequestsToCommandMetaclass = Metaclasses
.SelectMany(pair => pair.Value.Commands.Select(cmd => (cmd.ReceivedRequest, cmd)))
.ToDictionary(pair => pair.ReceivedRequest, pair => pair.cmd);
}

public static IComponentMetaclass GetMetaclass(uint componentId)
Expand Down Expand Up @@ -62,5 +72,25 @@ public static uint GetSnapshotComponentId<T>() where T : ISpatialComponentSnapsh

return id;
}

public static ICommandMetaclass GetCommandMetaclassFromRequest<T>() where T : ICommandRequest
{
if (!RequestsToCommandMetaclass.TryGetValue(typeof(T), out var metaclass))
{
throw new ArgumentException($"Can not find ID for unregistered SpatialOS command request {nameof(T)}.");
}

return metaclass;
}

public static ICommandMetaclass GetCommandMetaclassFromReceivedRequest<T>() where T : IReceivedCommandRequest
{
if (!ReceivedRequestsToCommandMetaclass.TryGetValue(typeof(T), out var metaclass))
{
throw new ArgumentException($"Can not find ID for unregistered SpatialOS received command request {nameof(T)}.");
}

return metaclass;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@ namespace Improbable.Gdk.Core
[DisableAutoCreation]
public class CommandSystem : ComponentSystem
{
internal IOutgoingCommandHandler OutgoingHandler { get; set; }
private WorkerSystem worker;

private long nextRequestId = 1;

public CommandRequestId SendCommand<T>(T request, Entity sendingEntity = default) where T : ICommandRequest
{
var requestId = new CommandRequestId(nextRequestId++);
worker.MessagesToSend.AddCommandRequest(request, sendingEntity, requestId);
return requestId;
return OutgoingHandler.SendCommand(request, sendingEntity);
}

public void SendResponse<T>(T response) where T : ICommandResponse
BryanJY-Wong marked this conversation as resolved.
Show resolved Hide resolved
{
worker.MessagesToSend.AddCommandResponse(response);
OutgoingHandler.SendResponse(response);
}

public MessagesSpan<T> GetRequests<T>() where T : struct, IReceivedCommandRequest
Expand Down Expand Up @@ -49,13 +46,37 @@ public MessagesSpan<T> GetResponses<T>() where T : struct, IReceivedCommandRespo
protected override void OnCreate()
{
base.OnCreate();

worker = World.GetExistingSystem<WorkerSystem>();
OutgoingHandler = new OutgoingCommandHandler(worker);
Enabled = false;
}

protected override void OnUpdate()
{
}

private class OutgoingCommandHandler : IOutgoingCommandHandler
{
private long nextRequestId = 1;

private WorkerSystem Worker { get; }

public OutgoingCommandHandler(WorkerSystem worker)
{
Worker = worker;
}

public CommandRequestId SendCommand<T>(T request, Entity sendingEntity = default) where T : ICommandRequest
{
var requestId = new CommandRequestId(nextRequestId++);
Worker.MessagesToSend.AddCommandRequest(request, sendingEntity, requestId);
return requestId;
}

public void SendResponse<T>(T response) where T : ICommandResponse
{
Worker.MessagesToSend.AddCommandResponse(response);
}
}
}
}
Loading