implementation of time dilation system for engines
parent
471117f4f4
commit
fc50bf9b81
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class IgnoresTimeDilation : Attribute { }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TimeDilationPriority : Attribute
|
||||
{
|
||||
public int timeDilationPriority;
|
||||
|
||||
public TimeDilationPriority(int timeDilationPriority)
|
||||
{
|
||||
this.timeDilationPriority = timeDilationPriority;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,10 +19,21 @@ namespace Encompass
|
|||
internal readonly HashSet<Type> receiveTypes = new HashSet<Type>();
|
||||
internal readonly Dictionary<Type, int> writePriorities = new Dictionary<Type, int>();
|
||||
|
||||
/// <summary>
|
||||
/// If false, the Engine will ignore time dilation.
|
||||
/// </summary>
|
||||
internal bool usesTimeDilation = true;
|
||||
public bool TimeDilationActive { get => usesTimeDilation && timeManager.TimeDilationActive; }
|
||||
/// <summary>
|
||||
/// Used when activating time dilation. Lower priority overrides higher priority.
|
||||
/// </summary>
|
||||
internal int? timeDilationPriority = null;
|
||||
|
||||
private EntityManager entityManager;
|
||||
private MessageManager messageManager;
|
||||
private ComponentManager componentManager;
|
||||
private ComponentMessageManager componentMessageManager;
|
||||
private TimeManager timeManager;
|
||||
|
||||
protected Engine()
|
||||
{
|
||||
|
@ -106,6 +117,11 @@ namespace Encompass
|
|||
this.componentMessageManager = componentMessageManager;
|
||||
}
|
||||
|
||||
internal void AssignTimeManager(TimeManager timeManager)
|
||||
{
|
||||
this.timeManager = timeManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs once per World update with the calculated delta-time.
|
||||
/// </summary>
|
||||
|
@ -673,5 +689,34 @@ namespace Encompass
|
|||
{
|
||||
componentManager.MarkForRemoval(componentID);
|
||||
}
|
||||
|
||||
private void CheckTimeDilationPriorityExists()
|
||||
{
|
||||
if (!timeDilationPriority.HasValue) { throw new TimeDilationPriorityUndefinedException("Engines that activate time dilation must use the TimeDilationPriority attribute."); }
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime)
|
||||
{
|
||||
CheckTimeDilationPriorityExists();
|
||||
timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime, timeDilationPriority.Value);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, System.Func<double, double, double, double, double> easeInFunction, double activeTime, double easeOutTime)
|
||||
{
|
||||
CheckTimeDilationPriorityExists();
|
||||
timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, timeDilationPriority.Value);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, System.Func<double, double, double, double, double> easeOutFunction)
|
||||
{
|
||||
CheckTimeDilationPriorityExists();
|
||||
timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime, easeOutFunction, timeDilationPriority.Value);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, System.Func<double, double, double, double, double> easeInFunction, double activeTime, double easeOutTime, System.Func<double, double, double, double, double> easeOutFunction)
|
||||
{
|
||||
CheckTimeDilationPriorityExists();
|
||||
timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction, timeDilationPriority.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Encompass.Engines
|
||||
namespace Encompass
|
||||
{
|
||||
internal class ComponentMessageEmitter<TComponent> : Engine where TComponent : struct, IComponent
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Encompass.Engines
|
||||
namespace Encompass
|
||||
{
|
||||
/// <summary>
|
||||
/// A Spawner is a special type of Engine that runs a Spawn method in response to each Message it receives.
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass.Exceptions
|
||||
{
|
||||
public class TimeDilationPriorityConflictException : Exception
|
||||
{
|
||||
public TimeDilationPriorityConflictException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass.Exceptions
|
||||
{
|
||||
public class TimeDilationPriorityUndefinedException : Exception
|
||||
{
|
||||
public TimeDilationPriorityUndefinedException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
namespace Encompass
|
||||
{
|
||||
internal struct TimeDilationData
|
||||
{
|
||||
public double elapsedTime;
|
||||
public double easeInTime;
|
||||
public System.Func<double, double, double, double, double> easeInFunction;
|
||||
public double activeTime;
|
||||
public double easeOutTime;
|
||||
public System.Func<double, double, double, double, double> easeOutFunction;
|
||||
public double factor;
|
||||
|
||||
public double Factor
|
||||
{
|
||||
get
|
||||
{
|
||||
double calculatedFactor = 1;
|
||||
|
||||
if (elapsedTime < easeInTime)
|
||||
{
|
||||
calculatedFactor = easeInFunction(elapsedTime, 1, factor - 1, easeInTime);
|
||||
}
|
||||
else if (elapsedTime < easeInTime + activeTime)
|
||||
{
|
||||
calculatedFactor = factor;
|
||||
}
|
||||
else if (elapsedTime < easeInTime + activeTime + easeOutTime)
|
||||
{
|
||||
var elapsedOutTime = elapsedTime - easeInTime - activeTime;
|
||||
calculatedFactor = easeOutFunction(elapsedOutTime, factor, 1 - factor, easeOutTime);
|
||||
}
|
||||
|
||||
return calculatedFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
namespace Encompass
|
||||
{
|
||||
internal class TimeManager
|
||||
{
|
||||
private TimeDilationData timeDilationData = new TimeDilationData { factor = 1 };
|
||||
private bool newTimeDilationData = false;
|
||||
private TimeDilationData nextFrameTimeDilationData = new TimeDilationData { factor = 1 };
|
||||
|
||||
private double Linear(double t, double b, double c, double d)
|
||||
{
|
||||
return c * t / d + b;
|
||||
}
|
||||
private int minPriority = int.MaxValue;
|
||||
|
||||
public double TimeDilationFactor
|
||||
{
|
||||
get
|
||||
{
|
||||
return timeDilationData.Factor;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TimeDilationActive
|
||||
{
|
||||
get => TimeDilationFactor != 1;
|
||||
}
|
||||
|
||||
public void Update(double dt)
|
||||
{
|
||||
if (newTimeDilationData)
|
||||
{
|
||||
timeDilationData = nextFrameTimeDilationData;
|
||||
}
|
||||
|
||||
timeDilationData.elapsedTime += dt;
|
||||
newTimeDilationData = false;
|
||||
minPriority = int.MaxValue;
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, int priority)
|
||||
{
|
||||
ActivateTimeDilation(factor, easeInTime, Linear, activeTime, easeOutTime, Linear, priority);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, System.Func<double, double, double, double, double> easeInFunction, double activeTime, double easeOutTime, int priority)
|
||||
{
|
||||
ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, Linear, priority);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, System.Func<double, double, double, double, double> easeOutFunction, int priority)
|
||||
{
|
||||
ActivateTimeDilation(factor, easeInTime, Linear, activeTime, easeOutTime, easeOutFunction, priority);
|
||||
}
|
||||
|
||||
public void ActivateTimeDilation(double factor, double easeInTime, System.Func<double, double, double, double, double> easeInFunction, double activeTime, double easeOutTime, System.Func<double, double, double, double, double> easeOutFunction, int priority)
|
||||
{
|
||||
if (priority <= minPriority)
|
||||
{
|
||||
newTimeDilationData = true;
|
||||
nextFrameTimeDilationData = new TimeDilationData
|
||||
{
|
||||
elapsedTime = 0,
|
||||
easeInTime = easeInTime,
|
||||
easeInFunction = easeInFunction,
|
||||
activeTime = activeTime,
|
||||
easeOutTime = easeOutTime,
|
||||
easeOutFunction = easeOutFunction,
|
||||
factor = factor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ namespace Encompass
|
|||
private readonly ComponentManager componentManager;
|
||||
private readonly MessageManager messageManager;
|
||||
private readonly ComponentMessageManager componentMessageManager;
|
||||
private readonly TimeManager timeManager;
|
||||
private readonly RenderManager renderManager;
|
||||
|
||||
internal World(
|
||||
|
@ -20,6 +21,7 @@ namespace Encompass
|
|||
ComponentManager componentManager,
|
||||
MessageManager messageManager,
|
||||
ComponentMessageManager componentMessageManager,
|
||||
TimeManager timeManager,
|
||||
RenderManager renderManager
|
||||
)
|
||||
{
|
||||
|
@ -28,6 +30,7 @@ namespace Encompass
|
|||
this.componentManager = componentManager;
|
||||
this.messageManager = messageManager;
|
||||
this.componentMessageManager = componentMessageManager;
|
||||
this.timeManager = timeManager;
|
||||
this.renderManager = renderManager;
|
||||
}
|
||||
|
||||
|
@ -38,11 +41,19 @@ namespace Encompass
|
|||
public void Update(double dt)
|
||||
{
|
||||
messageManager.ProcessDelayedMessages(dt);
|
||||
timeManager.Update(dt);
|
||||
|
||||
foreach (var engine in enginesInOrder)
|
||||
{
|
||||
if (engine.usesTimeDilation)
|
||||
{
|
||||
engine.Update(dt * timeManager.TimeDilationFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine.Update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
messageManager.ClearMessages();
|
||||
componentMessageManager.ClearMessages();
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using Encompass.Exceptions;
|
||||
using Encompass.Engines;
|
||||
using MoonTools.Core.Graph;
|
||||
using MoonTools.Core.Graph.Extensions;
|
||||
|
||||
|
@ -26,6 +25,7 @@ namespace Encompass
|
|||
private readonly EntityManager entityManager;
|
||||
private readonly MessageManager messageManager;
|
||||
private readonly ComponentMessageManager componentMessageManager;
|
||||
private readonly TimeManager timeManager;
|
||||
private readonly DrawLayerManager drawLayerManager;
|
||||
private readonly RenderManager renderManager;
|
||||
|
||||
|
@ -42,6 +42,7 @@ namespace Encompass
|
|||
messageManager = new MessageManager();
|
||||
componentMessageManager = new ComponentMessageManager();
|
||||
entityManager = new EntityManager(componentManager, componentMessageManager);
|
||||
timeManager = new TimeManager();
|
||||
renderManager = new RenderManager(componentManager, drawLayerManager, entityManager);
|
||||
}
|
||||
|
||||
|
@ -97,6 +98,7 @@ namespace Encompass
|
|||
engine.AssignComponentManager(componentManager);
|
||||
engine.AssignMessageManager(messageManager);
|
||||
engine.AssignComponentMessageManager(componentMessageManager);
|
||||
engine.AssignTimeManager(timeManager);
|
||||
|
||||
engines.Add(engine);
|
||||
engineGraph.AddNode(engine);
|
||||
|
@ -232,8 +234,27 @@ namespace Encompass
|
|||
var writePriorities = new Dictionary<Type, HashSet<int>>();
|
||||
var writeMessageToEngines = new Dictionary<Type, List<Engine>>();
|
||||
|
||||
var timeDilationPriorities = new Dictionary<int, HashSet<Engine>>();
|
||||
|
||||
foreach (var engine in engines)
|
||||
{
|
||||
var timeDilationPriorityAttribute = engine.GetType().GetCustomAttribute<TimeDilationPriority>();
|
||||
|
||||
if (timeDilationPriorityAttribute != null)
|
||||
{
|
||||
engine.timeDilationPriority = timeDilationPriorityAttribute.timeDilationPriority;
|
||||
if (!timeDilationPriorities.ContainsKey(timeDilationPriorityAttribute.timeDilationPriority))
|
||||
{
|
||||
timeDilationPriorities.Add(timeDilationPriorityAttribute.timeDilationPriority, new HashSet<Engine>());
|
||||
}
|
||||
timeDilationPriorities[timeDilationPriorityAttribute.timeDilationPriority].Add(engine);
|
||||
}
|
||||
|
||||
if (engine.GetType().GetCustomAttribute<IgnoresTimeDilation>() != null)
|
||||
{
|
||||
engine.usesTimeDilation = false;
|
||||
}
|
||||
|
||||
var defaultWritePriorityAttribute = engine.GetType().GetCustomAttribute<DefaultWritePriority>(false);
|
||||
|
||||
var writeTypes = engine.sendTypes.Where((type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ComponentWriteMessage<>));
|
||||
|
@ -323,6 +344,18 @@ namespace Encompass
|
|||
throw new EngineWriteConflictException(errorString);
|
||||
}
|
||||
|
||||
foreach (var timeDilationEngines in timeDilationPriorities)
|
||||
{
|
||||
var priority = timeDilationEngines.Key;
|
||||
var engines = timeDilationEngines.Value;
|
||||
if (engines.Count > 1)
|
||||
{
|
||||
var errorString = "Multiple Engines have the same Time Dilation Priority value: ";
|
||||
errorString += string.Join(", ", engines);
|
||||
throw new TimeDilationPriorityConflictException(errorString);
|
||||
}
|
||||
}
|
||||
|
||||
var engineOrder = new List<Engine>();
|
||||
foreach (var engine in engineGraph.TopologicalSort())
|
||||
{
|
||||
|
@ -335,6 +368,7 @@ namespace Encompass
|
|||
componentManager,
|
||||
messageManager,
|
||||
componentMessageManager,
|
||||
timeManager,
|
||||
renderManager
|
||||
);
|
||||
|
||||
|
|
|
@ -910,5 +910,152 @@ namespace Tests
|
|||
|
||||
resultComponents.Should().BeEmpty();
|
||||
}
|
||||
|
||||
static double dilatedDeltaTime;
|
||||
|
||||
[TimeDilationPriority(0)]
|
||||
class ActivateTimeDilationEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
if (!TimeDilationActive)
|
||||
{
|
||||
ActivateTimeDilation(0.2, 1, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dilatedDeltaTime = dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ActivateTimeDilation()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationEngine());
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
world.Update(0.01); // activate time dilation
|
||||
|
||||
world.Update(0.5);
|
||||
|
||||
dilatedDeltaTime.Should().BeApproximately(0.3, 0.01);
|
||||
|
||||
world.Update(0.5);
|
||||
|
||||
dilatedDeltaTime.Should().BeApproximately(0.1, 0.01);
|
||||
|
||||
world.Update(1);
|
||||
|
||||
world.Update(0.5);
|
||||
|
||||
dilatedDeltaTime.Should().BeApproximately(0.3, 0.01);
|
||||
}
|
||||
|
||||
class ActivateTimeDilationWithoutPriorityEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
ActivateTimeDilation(0.2, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ActivateTimeDilationWithoutPriorityThrows()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationWithoutPriorityEngine());
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
Assert.Throws<TimeDilationPriorityUndefinedException>(() => world.Update(0.01));
|
||||
}
|
||||
|
||||
[TimeDilationPriority(0)]
|
||||
class ActivateTimeDilationLowerPriorityEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
if (!TimeDilationActive)
|
||||
{
|
||||
ActivateTimeDilation(0.2, 1, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dilatedDeltaTime = dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TimeDilationPriority(1)]
|
||||
class ActivateTimeDilationHigherPriorityEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
if (!TimeDilationActive)
|
||||
{
|
||||
ActivateTimeDilation(0.5, 1, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dilatedDeltaTime = dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleActivateTimeDilation()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationLowerPriorityEngine());
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationHigherPriorityEngine());
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
world.Update(0.01); // activate time dilation
|
||||
|
||||
world.Update(0.5);
|
||||
|
||||
dilatedDeltaTime.Should().BeApproximately(0.3, 0.01);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleActivateTimeDilationWithDuplicatePriority()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationEngine());
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationLowerPriorityEngine());
|
||||
|
||||
Assert.Throws<TimeDilationPriorityConflictException>(() => worldBuilder.Build());
|
||||
}
|
||||
|
||||
static double undilatedDeltaTime;
|
||||
|
||||
[IgnoresTimeDilation]
|
||||
class IgnoresTimeDilationEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
undilatedDeltaTime = dt;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IgnoresTimeDilation()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new ActivateTimeDilationEngine());
|
||||
worldBuilder.AddEngine(new IgnoresTimeDilationEngine());
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
world.Update(0.01); // activate time dilation
|
||||
|
||||
world.Update(0.5);
|
||||
|
||||
undilatedDeltaTime.Should().Be(0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Encompass;
|
||||
using Encompass.Engines;
|
||||
using Encompass;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Tests
|
||||
|
|
Loading…
Reference in New Issue