mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
IRobustCloneable and generator support (#5692)
* Add IRobustCloneable and check for it in compnet generator. * Redo compnetgenerator support; add test * Disconnect client at end of test * Actually test for client entities * Cleanup * Cleanup 2
This commit is contained in:
@@ -35,6 +35,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
|
||||
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
|
||||
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
|
||||
private const string GlobalIRobustCloneableName = "global::Robust.Shared.Serialization.IRobustCloneable";
|
||||
|
||||
private static readonly SymbolDisplayFormat FullNullableFormat =
|
||||
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
|
||||
@@ -375,7 +376,39 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
stateFields.Append($@"
|
||||
public {networkedType} {name} = default!;");
|
||||
|
||||
if (IsCloneType(type))
|
||||
if (ImplementsInterface(type, GlobalIRobustCloneableName))
|
||||
{
|
||||
getField = $"component.{name}";
|
||||
cast = $"({castString})";
|
||||
|
||||
var nullCast = nullable ? castString.Substring(0, castString.Length - 1) : castString;
|
||||
|
||||
if (nullable)
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = state.{name} == null ? null! : state.{name}.Clone();");
|
||||
deltaHandleFields.Append($@"
|
||||
var {name}Value = {cast} {fieldHandleValue};
|
||||
if ({name}Value == null)
|
||||
component.{name} = null!;
|
||||
else
|
||||
component.{name} = {nullCast}({name}Value.Clone());");
|
||||
shallowClone.Append($@"
|
||||
{name} = this.{name},");
|
||||
deltaApply.Add($"fullState.{name} = {name} == null ? null! : {name}.Clone();");
|
||||
}
|
||||
else
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = state.{name}.Clone();");
|
||||
deltaHandleFields.Append($@"
|
||||
component.{name} = {cast}({fieldHandleValue}.Clone());");
|
||||
shallowClone.Append($@"
|
||||
{name} = this.{name},");
|
||||
deltaApply.Add($"fullState.{name} = {name}.Clone();");
|
||||
}
|
||||
}
|
||||
else if (IsCloneType(type))
|
||||
{
|
||||
getField = $"component.{name}";
|
||||
cast = $"({castString})";
|
||||
@@ -758,5 +791,19 @@ public partial class {componentName}{deltaInterface}
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ImplementsInterface(ITypeSymbol type, string interfaceName)
|
||||
{
|
||||
foreach (var interfaceType in type.AllInterfaces)
|
||||
{
|
||||
if (interfaceType.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName)
|
||||
|| interfaceType.ConstructedFrom.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Shared/Serialization/IRobustCloneable.cs
Normal file
19
Robust.Shared/Serialization/IRobustCloneable.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Robust.Shared.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Implementers of this interface will have their <see cref="Clone"/> method
|
||||
/// called when generating component states. This can be useful for reference types
|
||||
/// used as datafields to make copies of values instead of references.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// Type returned by the <see cref="Clone"/> method.
|
||||
/// This should probably be the same Type as the implementer.
|
||||
/// </typeparam>
|
||||
public interface IRobustCloneable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a new instance of <typeparamref name="T"/> with the same values as this instance.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of <typeparamref name="T"/> with the same values as this instance.</returns>
|
||||
T Clone();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public sealed partial class RobustCloneableTestClass : IRobustCloneable<RobustCloneableTestClass>
|
||||
{
|
||||
[DataField]
|
||||
public int IntValue;
|
||||
|
||||
public RobustCloneableTestClass Clone()
|
||||
{
|
||||
return new RobustCloneableTestClass
|
||||
{
|
||||
IntValue = IntValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
[DataDefinition]
|
||||
public partial struct RobustCloneableTestStruct : IRobustCloneable<RobustCloneableTestStruct>
|
||||
{
|
||||
[DataField]
|
||||
public int IntValue;
|
||||
|
||||
public RobustCloneableTestStruct Clone()
|
||||
{
|
||||
return new RobustCloneableTestStruct
|
||||
{
|
||||
IntValue = IntValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class RobustCloneableTestComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public RobustCloneableTestClass TestClass = new();
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public RobustCloneableTestStruct TestStruct = new();
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public RobustCloneableTestStruct? NullableTestStruct;
|
||||
}
|
||||
|
||||
public sealed class RobustCloneableTest() : RobustIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestClone()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(server.WaitIdleAsync(), client.WaitIdleAsync());
|
||||
|
||||
var sEntMan = server.EntMan;
|
||||
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
|
||||
var cEntMan = client.EntMan;
|
||||
var cNetMan = client.ResolveDependency<IClientNetManager>();
|
||||
|
||||
MapId mapId = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
server.System<SharedMapSystem>().CreateMap(out mapId);
|
||||
var coords = new MapCoordinates(0, 0, mapId);
|
||||
var uid = sEntMan.SpawnEntity(null, coords);
|
||||
var comp = sEntMan.EnsureComponent<RobustCloneableTestComponent>(uid);
|
||||
comp.TestClass.IntValue = 50;
|
||||
comp.TestStruct.IntValue = 60;
|
||||
comp.NullableTestStruct = new() { IntValue = 70 };
|
||||
});
|
||||
|
||||
// Connect client.
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
await client.WaitPost(() => cNetMan.ClientConnect(null!, 0, null!));
|
||||
|
||||
async Task RunTicks()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
}
|
||||
await RunTicks();
|
||||
|
||||
EntityUid player = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var coords = new MapCoordinates(0, 0, mapId);
|
||||
player = sEntMan.SpawnEntity(null, coords);
|
||||
var session = sPlayerMan.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(session, player);
|
||||
sPlayerMan.JoinGame(session);
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(cNetMan.IsConnected, Is.True);
|
||||
var ents = cEntMan.AllEntities<RobustCloneableTestComponent>().ToList();
|
||||
Assert.That(ents, Has.Count.EqualTo(1));
|
||||
var testEnt = ents[0];
|
||||
|
||||
Assert.That(testEnt.Comp.TestClass.IntValue, Is.EqualTo(50));
|
||||
Assert.That(testEnt.Comp.TestStruct.IntValue, Is.EqualTo(60));
|
||||
Assert.That(testEnt.Comp.NullableTestStruct, Is.Not.Null);
|
||||
Assert.That(testEnt.Comp.NullableTestStruct!.Value.IntValue, Is.EqualTo(70));
|
||||
});
|
||||
|
||||
// Disconnect client
|
||||
await client.WaitPost(() => cNetMan.ClientDisconnect(""));
|
||||
await server.WaitRunTicks(5);
|
||||
await client.WaitRunTicks(5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user