mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Better entity exception tolerance (#996)
* Use separate define constants for exception tolerance stuff. * Improve handling of exceptions during entity creation. The entity in question now gets marked as deleted immediately. * Glorious. * Testing components, tests, wrapper exception type.
This commit is contained in:
committed by
GitHub
parent
fc212f983e
commit
0b1f088f8c
@@ -20,4 +20,7 @@
|
||||
<PropertyGroup Condition="'$(FullRelease)' == 'True'">
|
||||
<DefineConstants>$(DefineConstants);FULL_RELEASE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DefineConstants>$(DefineConstants);EXCEPTION_TOLERANCE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -68,6 +68,14 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionExposeDataComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Interfaces.GameObjects;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -21,6 +19,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog;
|
||||
#pragma warning restore 649
|
||||
|
||||
private int _nextClientEntityUid = EntityUid.ClientUid + 1;
|
||||
@@ -178,7 +177,7 @@ namespace Robust.Client.GameObjects
|
||||
return new EntityUid(_nextClientEntityUid++);
|
||||
}
|
||||
|
||||
private static void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState curState,
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState curState,
|
||||
EntityState nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<uint, (ComponentState curState, ComponentState nextState)>();
|
||||
@@ -244,8 +243,13 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("entity", $"Failed to apply comp state: entity={component.Owner}, comp={component.Name}\n {e}");
|
||||
DebugTools.Assert(e.Message);
|
||||
var wrapper = new ComponentStateApplyException(
|
||||
$"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_runtimeLog.LogException(wrapper, "Component state apply");
|
||||
#else
|
||||
throw wrapper;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
Robust.Client/GameObjects/ComponentStateApplyException.cs
Normal file
27
Robust.Client/GameObjects/ComponentStateApplyException.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Serializable]
|
||||
public class ComponentStateApplyException : Exception
|
||||
{
|
||||
public ComponentStateApplyException()
|
||||
{
|
||||
}
|
||||
|
||||
public ComponentStateApplyException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ComponentStateApplyException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ComponentStateApplyException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,13 @@ namespace Robust.Server.GameObjects
|
||||
Register<IgnorePauseComponent>();
|
||||
|
||||
RegisterIgnore("AnimationPlayer");
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionExposeDataComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace Robust.Shared.Asynchronous
|
||||
{
|
||||
while (_pending.TryTake(out var task))
|
||||
{
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
task.d(task.state);
|
||||
}
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception e)
|
||||
{
|
||||
_runtimeLog.LogException(e, "Async Queued Callback");
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects.Components
|
||||
{
|
||||
#if DEBUG
|
||||
// If you wanna use these, add it to some random prototype.
|
||||
// I recommend the #1 mug:
|
||||
// 1. it doesn't spawn on the map (currently).
|
||||
// 2. it's at the top of the entity list (currently).
|
||||
// 3. it crashed the game before, it's iconic!
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception in <see cref="OnAdd" />.
|
||||
/// </summary>
|
||||
public sealed class DebugExceptionOnAddComponent : Component
|
||||
{
|
||||
public override string Name => "DebugExceptionOnAdd";
|
||||
|
||||
public override void OnAdd() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception in <see cref="ExposeData" />.
|
||||
/// </summary>
|
||||
public sealed class DebugExceptionExposeDataComponent : Component
|
||||
{
|
||||
public override string Name => "DebugExceptionExposeData";
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception in <see cref="Initialize" />.
|
||||
/// </summary>
|
||||
public sealed class DebugExceptionInitializeComponent : Component
|
||||
{
|
||||
public override string Name => "DebugExceptionInitialize";
|
||||
|
||||
public override void Initialize() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception in <see cref="Startup" />.
|
||||
/// </summary>
|
||||
public sealed class DebugExceptionStartupComponent : Component
|
||||
{
|
||||
public override string Name => "DebugExceptionStartup";
|
||||
|
||||
protected override void Startup() => throw new NotSupportedException();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
33
Robust.Shared/GameObjects/EntityCreationException.cs
Normal file
33
Robust.Shared/GameObjects/EntityCreationException.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown if an entity fails to be created due to an exception inside a component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See the <see cref="Exception.InnerException"/> for the actual exception.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class EntityCreationException : Exception
|
||||
{
|
||||
public EntityCreationException()
|
||||
{
|
||||
}
|
||||
|
||||
public EntityCreationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public EntityCreationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected EntityCreationException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
Entities[entity.Uid] = entity;
|
||||
AllEntities.Add(entity);
|
||||
UpdateEntityTree(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -269,8 +268,18 @@ namespace Robust.Shared.GameObjects
|
||||
return AllocEntity(uid);
|
||||
|
||||
var entity = AllocEntity(prototypeName, uid);
|
||||
EntityPrototype.LoadEntity(entity.Prototype, entity, ComponentFactory, null);
|
||||
return entity;
|
||||
try
|
||||
{
|
||||
EntityPrototype.LoadEntity(entity.Prototype, entity, ComponentFactory, null);
|
||||
return entity;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Exception during entity loading.
|
||||
// Need to delete the entity to avoid corrupt state causing crashes later.
|
||||
DeleteEntity(entity);
|
||||
throw new EntityCreationException("Exception inside CreateEntity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private protected void LoadEntity(Entity entity, IEntityLoadContext context)
|
||||
@@ -278,10 +287,18 @@ namespace Robust.Shared.GameObjects
|
||||
EntityPrototype.LoadEntity(entity.Prototype, entity, ComponentFactory, context);
|
||||
}
|
||||
|
||||
private protected static void InitializeAndStartEntity(Entity entity)
|
||||
private protected void InitializeAndStartEntity(Entity entity)
|
||||
{
|
||||
InitializeEntity(entity);
|
||||
StartEntity(entity);
|
||||
try
|
||||
{
|
||||
InitializeEntity(entity);
|
||||
StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DeleteEntity(entity);
|
||||
throw new EntityCreationException("Exception inside InitializeAndStartEntity", e);
|
||||
}
|
||||
}
|
||||
|
||||
private protected static void InitializeEntity(Entity entity)
|
||||
|
||||
@@ -51,13 +51,13 @@ namespace Robust.Shared.Timers
|
||||
|
||||
if (_timeCounter <= 0)
|
||||
{
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
OnFired();
|
||||
}
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception e)
|
||||
{
|
||||
runtimeLog.LogException(e, "Timer Callback");
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Shared.Timing
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly IRuntimeLog _runtimeLog;
|
||||
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
private int _tickExceptions;
|
||||
#endif
|
||||
|
||||
@@ -143,14 +143,14 @@ namespace Robust.Shared.Timing
|
||||
|
||||
_timing.StartFrame();
|
||||
realFrameEvent = new FrameEventArgs((float) _timing.RealFrameTime.TotalSeconds);
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
// process Net/KB/Mouse input
|
||||
Input?.Invoke(this, realFrameEvent);
|
||||
}
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception exp)
|
||||
{
|
||||
_runtimeLog.LogException(exp, "GameLoop Input");
|
||||
@@ -172,13 +172,13 @@ namespace Robust.Shared.Timing
|
||||
|
||||
// update the simulation
|
||||
simFrameEvent = new FrameEventArgs((float) _timing.FrameTime.TotalSeconds);
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
var threw = false;
|
||||
try
|
||||
{
|
||||
#endif
|
||||
Tick?.Invoke(this, simFrameEvent);
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
@@ -215,13 +215,13 @@ namespace Robust.Shared.Timing
|
||||
// update out of the simulation
|
||||
|
||||
simFrameEvent = new FrameEventArgs((float) _timing.FrameTime.TotalSeconds);
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
Update?.Invoke(this, simFrameEvent);
|
||||
}
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception exp)
|
||||
{
|
||||
_runtimeLog.LogException(exp, "GameLoop Update");
|
||||
@@ -229,13 +229,13 @@ namespace Robust.Shared.Timing
|
||||
#endif
|
||||
|
||||
// render the simulation
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
#endif
|
||||
{
|
||||
Render?.Invoke(this, realFrameEvent);
|
||||
}
|
||||
#if RELEASE
|
||||
#if EXCEPTION_TOLERANCE
|
||||
catch (Exception exp)
|
||||
{
|
||||
_runtimeLog.LogException(exp, "GameLoop Render");
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.UnitTesting.Server.GameObjects
|
||||
{
|
||||
[TestFixture]
|
||||
public class ThrowingEntityDeletion_Test : RobustUnitTest
|
||||
{
|
||||
private IServerEntityManager EntityManager;
|
||||
private IComponentFactory _componentFactory;
|
||||
private IMapManager MapManager;
|
||||
|
||||
const string PROTOTYPES = @"
|
||||
- type: entity
|
||||
id: throwInAdd
|
||||
components:
|
||||
- type: ThrowsInAdd
|
||||
- type: entity
|
||||
id: throwInExposeData
|
||||
components:
|
||||
- type: ThrowsInExposeData
|
||||
- type: entity
|
||||
id: throwsInInitialize
|
||||
components:
|
||||
- type: ThrowsInInitialize
|
||||
- type: entity
|
||||
id: throwsInStartup
|
||||
components:
|
||||
- type: ThrowsInStartup
|
||||
";
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
_componentFactory.Register<ThrowsInAddComponent>();
|
||||
_componentFactory.Register<ThrowsInExposeDataComponent>();
|
||||
_componentFactory.Register<ThrowsInInitializeComponent>();
|
||||
_componentFactory.Register<ThrowsInStartupComponent>();
|
||||
|
||||
EntityManager = IoCManager.Resolve<IServerEntityManager>();
|
||||
MapManager = IoCManager.Resolve<IMapManager>();
|
||||
MapManager.Initialize();
|
||||
MapManager.Startup();
|
||||
|
||||
MapManager.CreateNewMapEntity(MapId.Nullspace);
|
||||
|
||||
var manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.LoadFromStream(new StringReader(PROTOTYPES));
|
||||
manager.Resync();
|
||||
|
||||
//NOTE: The grids have not moved, so we can assert worldpos == localpos for the test
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test([Values("throwInAdd", "throwInExposeData", "throwsInInitialize", "throwsInStartup")]
|
||||
string prototypeName)
|
||||
{
|
||||
Assert.That(() => EntityManager.SpawnEntity(prototypeName, MapCoordinates.Nullspace),
|
||||
Throws.TypeOf<EntityCreationException>());
|
||||
|
||||
Assert.That(EntityManager.GetEntities().Where(p => p.Prototype?.ID == prototypeName), Is.Empty);
|
||||
}
|
||||
|
||||
private sealed class ThrowsInAddComponent : Component
|
||||
{
|
||||
public override string Name => "ThrowsInAdd";
|
||||
|
||||
public override void OnAdd() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private sealed class ThrowsInExposeDataComponent : Component
|
||||
{
|
||||
public override string Name => "ThrowsInExposeData";
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private sealed class ThrowsInInitializeComponent : Component
|
||||
{
|
||||
public override string Name => "ThrowsInInitialize";
|
||||
|
||||
public override void Initialize() => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private sealed class ThrowsInStartupComponent : Component
|
||||
{
|
||||
public override string Name => "ThrowsInStartup";
|
||||
|
||||
protected override void Startup() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user