Skip to content

Commit

Permalink
Add a method for copying components (#5654)
Browse files Browse the repository at this point in the history
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
  • Loading branch information
MilonPL and metalgearsloth authored Feb 20, 2025
1 parent 7104a4f commit 8a04a4f
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 1 deletion.
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;
}
}
4 changes: 3 additions & 1 deletion Robust.UnitTesting/Shared/GameObjects/IEntityManagerTests.cs
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

0 comments on commit 8a04a4f

Please sign in to comment.