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

add a method for copying components #5654

Merged
merged 12 commits into from
Feb 20, 2025
91 changes: 91 additions & 0 deletions Robust.Shared/GameObjects/EntityManager.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,97 @@ public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId,
return TryGetComponent(uid.Value, netId, out component, meta);
}

/// <inheritdoc/>
public bool TryCopyComponent<T>(EntityUid source, EntityUid target, ref T? sourceComponent, [NotNullWhen(true)] out T? targetComp, MetaDataComponent? meta = null) where T : IComponent
{
if (!MetaQuery.Resolve(target, ref meta))
{
targetComp = default;
return false;
}

if (sourceComponent == null && !TryGetComponent(source, out sourceComponent))
{
targetComp = default;
return false;
}

targetComp = CopyComponentInternal(source, target, sourceComponent, meta);
return true;
}

/// <inheritdoc/>
public bool TryCopyComponents(
EntityUid source,
EntityUid target,
MetaDataComponent? meta = null,
params Type[] sourceComponents)
{
if (!MetaQuery.TryGetComponent(source, out meta))
return false;

var allCopied = true;

foreach (var type in sourceComponents)
{
if (!TryGetComponent(source, type, out var srcComp))
{
allCopied = false;
continue;
}

CopyComponent(source, target, srcComp, meta: meta);
}

return allCopied;
}

/// <inheritdoc/>
public IComponent CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null)
{
if (!MetaQuery.Resolve(target, ref meta))
{
throw new InvalidOperationException();
}

return CopyComponentInternal(source, target, sourceComponent, meta);
}

/// <inheritdoc/>
public T CopyComponent<T>(EntityUid source, EntityUid target, T sourceComponent,MetaDataComponent? meta = null) where T : IComponent
{
if (!MetaQuery.Resolve(target, ref meta))
{
throw new InvalidOperationException();
}

return CopyComponentInternal(source, target, sourceComponent, meta);
}

/// <inheritdoc/>
public void CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
{
if (!MetaQuery.Resolve(target, ref meta))
return;

foreach (var comp in sourceComponents)
{
CopyComponentInternal(source, target, comp, meta);
}
}

private T CopyComponentInternal<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent meta) where T : IComponent
{
var compReg = ComponentFactory.GetRegistration(sourceComponent.GetType());
var component = (T)ComponentFactory.GetComponent(compReg);

_serManager.CopyTo(sourceComponent, ref component, notNullableOverride: true);
component.Owner = target;

AddComponentInternal(target, component, compReg, true, false, meta);
return component;
}

public EntityQuery<TComp1> GetEntityQuery<TComp1>() where TComp1 : IComponent
{
var comps = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
Expand Down
46 changes: 46 additions & 0 deletions Robust.Shared/GameObjects/EntitySystem.Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,52 @@ protected bool TryGetEntityData(NetEntity nuid, [NotNullWhen(true)] out EntityUi

#endregion

#region Component Copy

/// <inheritdoc cref="IEntityManager.TryCopyComponent"/>
protected bool TryCopyComponent<T>(
EntityUid source,
EntityUid target,
ref T? sourceComponent,
[NotNullWhen(true)] out T? targetComp,
MetaDataComponent? meta = null) where T : IComponent
{
return EntityManager.TryCopyComponent(source, target, ref sourceComponent, out targetComp, meta);
}

/// <inheritdoc cref="IEntityManager.TryCopyComponents"/>
protected bool TryCopyComponents(
EntityUid source,
EntityUid target,
MetaDataComponent? meta = null,
params Type[] sourceComponents)
{
return EntityManager.TryCopyComponents(source, target, meta, sourceComponents);
}

/// <inheritdoc cref="IEntityManager.CopyComponent"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected IComponent CopyComp(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null)
{
return EntityManager.CopyComponent(source, target, sourceComponent, meta);
}

/// <inheritdoc cref="IEntityManager.CopyComponent{T}"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected T CopyComp<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent? meta = null) where T : IComponent
{
return EntityManager.CopyComponent(source, target, sourceComponent, meta);
}

/// <inheritdoc cref="IEntityManager.CopyComponents"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void CopyComps(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
{
EntityManager.CopyComponents(source, target, meta, sourceComponents);
}

#endregion

#region Component Has

/// <summary>
Expand Down
45 changes: 45 additions & 0 deletions Robust.Shared/GameObjects/IEntityManager.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,51 @@ public partial interface IEntityManager
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);

/// <summary>
/// Tries to run <see cref="CopyComponents"/> without throwing if the component doesn't exist.
/// </summary>
bool TryCopyComponent<T>(
EntityUid source,
EntityUid target,
ref T? sourceComponent,
[NotNullWhen(true)] out T? targetComp,
MetaDataComponent? meta = null) where T : IComponent;

/// <summary>
/// Tries to run <see cref="CopyComponents"/> without throwing if the components don't exist.
/// </summary>
bool TryCopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params Type[] sourceComponents);

/// <summary>
/// Copy a single component from source to target entity.
/// </summary>
/// <param name="source">The source entity to copy from.</param>
/// <param name="target">The target entity to copy to.</param>
/// <param name="sourceComponent">The source component instance to copy.</param>
/// <param name="component">The copied component if successful.</param>
/// <param name="meta">Optional metadata of the target entity.</param>
IComponent CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null);

/// <summary>
/// Copy a single component from source to target entity.
/// </summary>
/// <typeparam name="T">The type of component to copy.</typeparam>
/// <param name="source">The source entity to copy from.</param>
/// <param name="target">The target entity to copy to.</param>
/// <param name="sourceComponent">The source component instance to copy.</param>
/// <param name="component">The copied component if successful.</param>
/// <param name="meta">Optional metadata of the target entity.</param>
T CopyComponent<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent? meta = null) where T : IComponent;

/// <summary>
/// Copy multiple components from source to target entity using existing component instances.
/// </summary>
/// <param name="source">The source entity to copy from.</param>
/// <param name="target">The target entity to copy to.</param>
/// <param name="meta">Optional metadata of the target entity.</param>
/// <param name="sourceComponents">Array of component instances to copy.</param>
void CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents);

/// <summary>
/// Returns a cached struct enumerator with the specified component.
/// </summary>
Expand Down
128 changes: 128 additions & 0 deletions Robust.UnitTesting/Shared/GameObjects/EntityManagerCopyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.UnitTesting.Server;

namespace Robust.UnitTesting.Shared.GameObjects;

[TestFixture]
public sealed partial class EntityManagerCopyTests
{
[Test]
public void CopyComponentGeneric()
{
var instant = RobustServerSimulation.NewSimulation();
instant.RegisterComponents(fac =>
{
fac.RegisterClass<AComponent>();
});

var sim = instant.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapSystem = entManager.System<SharedMapSystem>();

mapSystem.CreateMap(out var mapId);

var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
var comp = entManager.AddComponent<AComponent>(original);

Assert.That(comp.Value, Is.EqualTo(false));
comp.Value = true;

var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(!entManager.HasComponent<AComponent>(target));

var targetComp = entManager.CopyComponent(original, target, comp);

Assert.That(targetComp!.Owner == target);
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
Assert.That(!ReferenceEquals(comp, targetComp));
}

[Test]
public void CopyComponentNonGeneric()
{
var instant = RobustServerSimulation.NewSimulation();
instant.RegisterComponents(fac =>
{
fac.RegisterClass<AComponent>();
});

var sim = instant.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapSystem = entManager.System<SharedMapSystem>();

mapSystem.CreateMap(out var mapId);

var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
var comp = entManager.AddComponent<AComponent>(original);

Assert.That(comp.Value, Is.EqualTo(false));
comp.Value = true;

var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(!entManager.HasComponent<AComponent>(target));

var targetComp = entManager.CopyComponent(original, target, (IComponent) comp);

Assert.That(targetComp!.Owner == target);
Assert.That(((AComponent) targetComp).Value, Is.EqualTo(comp.Value));
Assert.That(!ReferenceEquals(comp, targetComp));
}

[Test]
public void CopyComponentMultiple()
{
var instant = RobustServerSimulation.NewSimulation();
instant.RegisterComponents(fac =>
{
fac.RegisterClass<AComponent>();
fac.RegisterClass<BComponent>();
});

var sim = instant.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapSystem = entManager.System<SharedMapSystem>();

mapSystem.CreateMap(out var mapId);

var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
var comp = entManager.AddComponent<AComponent>(original);
var comp2 = entManager.AddComponent<BComponent>(original);

Assert.That(comp.Value, Is.EqualTo(false));
comp.Value = true;

var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(!entManager.HasComponent<AComponent>(target));

entManager.CopyComponents(original, target, null, comp, comp2);
var targetComp = entManager.GetComponent<AComponent>(target);
var targetComp2 = entManager.GetComponent<BComponent>(target);

Assert.That(targetComp!.Owner == target);
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));

Assert.That(targetComp2!.Owner == target);
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));

Assert.That(!ReferenceEquals(comp, targetComp));
Assert.That(!ReferenceEquals(comp2, targetComp2));
}

[DataDefinition]
private sealed partial class AComponent : Component
{
[DataField]
public bool Value = false;
}

[DataDefinition]
private sealed partial class BComponent : Component
{
[DataField]
public bool Value = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.UnitTesting.Server;

namespace Robust.UnitTesting.Shared.GameObjects
{
[TestFixture, Parallelizable]
sealed class EntityManagerTests
sealed partial class EntityManagerTests
{
private static ISimulation SimulationFactory()
{
Expand Down
Loading