using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnitTestSharp; using Main = Foundations.Main< Foundations.UnitTests.FauxSimulationState, Foundations.UnitTests.MainTests.FauxRenderState>; namespace Foundations.UnitTests { public class MainTests : TestFixture { public class FauxRenderState { public int Integer; } public override void TestTeardown() { _main.Dispose(); } [IgnoreTest] private Main CreateMain(Main.SimulationCallback simulationCallback, Main.RenderCallback renderCallback, Main.DrawCallback drawCallback) { _threadManager = new CooperativeThreadedLoopManager(); _exceptionHandler = new RecordingExceptionHandler(); return new Main(simulationCallback, renderCallback, drawCallback, _threadManager, _exceptionHandler); } public void CanPassSimulationStateToCtor() { var state = new FauxSimulationState { Integer = 7, }; int integer = 0; _threadManager = new CooperativeThreadedLoopManager(); _exceptionHandler = new RecordingExceptionHandler(); Main.SimulationCallback simulation = _state => { integer = _state.Integer; }; _main = new Main(simulation, _state => new FauxRenderState(), _state => { }, _threadManager, _exceptionHandler, state); _threadManager.IterateSimulation(); CheckEqual(7, integer); } public void RunsSimulation() { bool run = false; Main.SimulationCallback simulation = state => { run = true; }; _main = CreateMain(simulation, state => new FauxRenderState(), state => {}); _threadManager.IterateSimulation(); Check(run); } public void RunsRender() { bool run = false; Main.RenderCallback render = state => { run = true; return new FauxRenderState(); }; _main = CreateMain(state => { }, render, state => { }); _threadManager.IterateRender(); Check(run); } public void RunsDraw() { bool run = false; Main.DrawCallback draw = state => run = true; _main = CreateMain(state => { }, state => new FauxRenderState(), draw); _threadManager.IterateRender(); // To populate a scene for Draw to use. _threadManager.IterateDraw(); Check(run); } public void PulseChangeEventsTriggersSimulationLoop() { bool ran = false; _main = CreateMain(state => { }, state => new FauxRenderState(), state => { }); _main.SimulationLoop.Paused.OnChanged += newValue => ran = true; _main.PulseChangeEvents(); Check(ran); } public void PulseChangeEventsTriggersRenderLoop() { bool ran = false; _main = CreateMain(state => { }, state => new FauxRenderState(), state => { }); _main.RenderLoop.Paused.OnChanged += newValue => ran = true; _main.PulseChangeEvents(); Check(ran); } public void PulseChangeEventsTriggersDrawLoop() { bool ran = false; _main = CreateMain(state => { }, state => new FauxRenderState(), state => { }); _main.DrawLoop.Paused.OnChanged += newValue => ran = true; _main.PulseChangeEvents(); Check(ran); } public void PulseChangeEventsTriggersFrameLocked() { bool ran = false; _main = CreateMain(state => { }, state => new FauxRenderState(), state => { }); _main.FrameLocked.OnChanged += newValue => ran = true; _main.PulseChangeEvents(); Check(ran); } public void ModifiersAreAppliedBeforeSimulationCallbackRuns() { int? runValue = null; _main = CreateMain(state => { runValue = state.Integer; }, state => new FauxRenderState(), state => { }); _main.EnqueueModifier(state => { state.Integer = 7; }); _threadManager.IterateSimulation(); CheckEqual(7, runValue); } public void ModifiersAreAppliedToRenderCallbackBeforeSimulationRuns() { int? runValue = null; Main.RenderCallback render = state => { runValue = state.Integer; return new FauxRenderState(); }; _main = CreateMain(state => { }, render, state => { }); _main.EnqueueModifier(state => { state.Integer = 7; }); _threadManager.IterateRender(); CheckEqual(7, runValue); } public void MultipleModifiersAreApplied() { int? runValue = null; _main = CreateMain(state => { runValue = state.Integer; }, state => new FauxRenderState(), state => { }); _main.EnqueueModifier(state => { state.Integer = 7; }); _main.EnqueueModifier(state => { state.Integer = 4; }); _threadManager.IterateSimulation(); CheckEqual(4, runValue); } public void NewModifiersAreNotAppliedUntilBeforeNextSimulationStep() { int? runValue = null; _main = CreateMain(state => { runValue = state.Integer; }, state => new FauxRenderState(), state => { }); _main.EnqueueModifier(state => _main.EnqueueModifier(state2 => { state2.Integer = 7; })); _threadManager.IterateSimulation(); CheckEqual(0, runValue); } public void NewModifiersAreAppliedAfterNextSimulationStep() { int? runValue = null; _main = CreateMain(state => { runValue = state.Integer; }, state => new FauxRenderState(), state => { }); _main.EnqueueModifier(state => _main.EnqueueModifier(state2 => { state2.Integer = 7; })); _threadManager.IterateSimulation(); _threadManager.IterateSimulation(); CheckEqual(7, runValue); } public void FrameLockedAllowsNextSimulationIteration() { int? runValue = null; _main = CreateMain(state => { runValue = state.Integer; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); CheckEqual(0, runValue); } public void FrameLockedDoesNotAllowMultipleSimulationIterationsBeforeDrawn() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); _threadManager.IterateSimulation(); CheckEqual(1, callCount); } public void FrameLockedDoesNotUnblockAfterRenderAndBeforeDrawn() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); _threadManager.IterateRender(); _threadManager.IterateSimulation(); CheckEqual(1, callCount); } public void FrameLockedDoesNotUnblockAfterDrawnButBeforeRender() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); _threadManager.IterateDraw(); _threadManager.IterateSimulation(); CheckEqual(1, callCount); } public void FrameLockedUnblocksAfterRenderAndDraw() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); _threadManager.IterateRender(); _threadManager.IterateDraw(); _threadManager.IterateSimulation(); CheckEqual(2, callCount); } public void FrameLockedDoesNotBlockDrawing() { int callCount = 0; _main = CreateMain(state => { }, state => new FauxRenderState(), state => { ++callCount; }); _main.FrameLocked.Set(true); _threadManager.IterateRender(); _threadManager.IterateDraw(); _threadManager.IterateDraw(); CheckEqual(2, callCount); } public void RenderNotCalledIfSimulationStateNotUpdated() { int callCount = 0; _main = CreateMain(state => { }, state => { ++callCount; return new FauxRenderState(); }, state => { }); _main.FrameLocked.Set(true); _threadManager.IterateRender(); _threadManager.IterateRender(); CheckEqual(1, callCount); } public void RenderCalledIfSimulationStateUpdated() { int callCount = 0; _main = CreateMain(state => { }, state => { ++callCount; return new FauxRenderState(); }, state => { }); _main.FrameLocked.Set(true); _threadManager.IterateRender(); _threadManager.IterateSimulation(); _threadManager.IterateRender(); CheckEqual(2, callCount); } public void CanAbortFrameLocked() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _main.FrameLocked.Set(false); _threadManager.IterateSimulation(); _threadManager.IterateSimulation(); CheckEqual(2, callCount); } public void CanReleaseFrameLocked() { int callCount = 0; _main = CreateMain(state => { ++callCount; }, state => new FauxRenderState(), state => { }); _main.FrameLocked.Set(true); _threadManager.IterateSimulation(); _main.FrameLocked.Set(false); _threadManager.IterateSimulation(); CheckEqual(2, callCount); } public void RenderCannotChangeSimulationData() { int? simData = null; _main = CreateMain( state => simData = state.Integer, state => { state.Integer = 7; return new FauxRenderState(); }, state => { }); _threadManager.IterateRender(); _threadManager.IterateSimulation(); CheckEqual(0, simData); } public void DrawCanChangeRenderData() { // If we don't want draw calls to be able to modify render data we would need to make a copy of the render // data, which both adds overhead and adds additional constraints to the RenderStateType. int? drawData = null; _main = CreateMain( state => { }, state => new FauxRenderState { Integer = 7 }, state => { drawData = state.Integer; state.Integer = 4; }); _threadManager.IterateRender(); _threadManager.IterateDraw(); _threadManager.IterateDraw(); CheckEqual(4, drawData); } public void DataFlowsFromSimulationToDraw() { int? drawData = null; _main = CreateMain( state => state.Integer = 7, state => new FauxRenderState { Integer = state.Integer }, state => { drawData = state.Integer; }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); _threadManager.IterateDraw(); CheckEqual(7, drawData); } public void DisposeDoesNotCallDisposeOnThreadManagerItDidNotCreate() { _main = CreateMain( state => { }, state => new FauxRenderState(), state => { }); _main.Dispose(); _threadManager.IterateSimulation(); } public void EnqueueModifierTriggersRender() { int callCount = 0; _main = CreateMain(state => { }, state => { ++callCount; return new FauxRenderState(); }, state => { }); _main.FrameLocked.Set(true); _threadManager.IterateRender(); _main.EnqueueModifier(state => { }); _threadManager.IterateRender(); CheckEqual(2, callCount); } public void QuerySimulationReturnsData() { _main = CreateMain(state => state.Integer = 7, state => { return new FauxRenderState(); }, state => { }); Task queryTask = _main.QuerySimulation(state => state.Integer); _threadManager.IterateSimulation(); _threadManager.IterateRender(); CheckEqual(7, queryTask.Result); } public void QuerySimulationDoesNotCompleteTaskBeforeRender() { _main = CreateMain(state => state.Integer = 7, state => { return new FauxRenderState(); }, state => { }); Task queryTask = _main.QuerySimulation(state => state.Integer); _threadManager.IterateSimulation(); CheckFalse(queryTask.IsCompleted); } public void QuerySimulationCompletesTaskBeforeFirstSimulation() { _main = CreateMain(state => state.Integer = 7, state => { return new FauxRenderState(); }, state => { }); Task queryTask = _main.QuerySimulation(state => state.Integer); _threadManager.IterateRender(); CheckEqual(0, queryTask.Result); } public void QuerySimulationCompletesTaskIfRenderIsNotStale() { _main = CreateMain(state => state.Integer = 7, state => { return new FauxRenderState(); }, state => { }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); Task queryTask = _main.QuerySimulation(state => state.Integer); _threadManager.IterateRender(); CheckEqual(7, queryTask.Result); } public void QuerySimulationPreventsAsyncQueries() { _main = CreateMain(state => { }, state => { return new FauxRenderState(); }, state => { }); TaskCompletionSource source = new TaskCompletionSource(); CheckThrow(typeof(InvalidOperationException)); _main.QuerySimulation(async state => { await source.Task; return 7; }); } public void QuerySimulationCanPassBackAnonymousTypes() { _main = CreateMain(state => state.Integer = 7, state => { return new FauxRenderState(); }, state => { }); var task = _main.QuerySimulation(state => new { IntegerOut = state.Integer }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); CheckEqual(7, task.Result.IntegerOut); } public void CrashInSimulationIsRecordedOnce() { _main = CreateMain( state => { throw new InvalidOperationException("foo"); }, state => { return new FauxRenderState(); }, state => { }); _threadManager.IterateSimulation(); CheckEqual(1, _exceptionHandler.SimulationExceptions.Count); CheckEqual("foo", _exceptionHandler.SimulationExceptions[0].Message); } public void CrashInSimulationCallbackRecordsStates() { _main = CreateMain( state => { state.Integer = 7; throw new InvalidOperationException("foo"); }, state => { return new FauxRenderState(); }, state => { }); _threadManager.IterateSimulation(); CheckEqual("7", _exceptionHandler.SimulationExceptions[0].Data["WorkingState"]); CheckEqual("0", _exceptionHandler.SimulationExceptions[0].Data["PreviousState"]); } public void CrashInSimulationCallbackAndCrashInSerializeWorkingStateRecordsSerializationException() { _main = CreateMain( state => { state.CrashOnSerialize = true; throw new InvalidOperationException("bar"); }, state => { return new FauxRenderState(); }, state => { }); _threadManager.IterateSimulation(); Exception exception = (Exception)_exceptionHandler.SimulationExceptions[0] .Data["WorkingStateSerializationException"]; CheckEqual("Foo", exception.Message); } public void CrashInSimulationCallbackAndCrashInSerializePreviousStateRecordsSerializationException() { bool error = false; _main = CreateMain( state => { state.CrashOnSerialize = true; if (error) throw new InvalidOperationException("bar"); }, state => { return new FauxRenderState(); }, state => { }); _threadManager.IterateSimulation(); error = true; _threadManager.IterateSimulation(); Exception exception = (Exception)_exceptionHandler.SimulationExceptions[0] .Data["PreviousStateSerializationException"]; CheckEqual("Foo", exception.Message); } public void CrashInRenderCallbackRecordsOnce() { _main = CreateMain( state => state.Integer = 7, state => { throw new InvalidOperationException("foo"); }, state => { }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); CheckEqual(1, _exceptionHandler.RenderExceptions.Count); CheckEqual("foo", _exceptionHandler.RenderExceptions[0].Message); } public void CrashInRenderCallbackRecordsState() { _main = CreateMain( state => state.Integer = 7, state => { throw new InvalidOperationException("bar"); }, state => { }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); CheckEqual("7", _exceptionHandler.RenderExceptions[0].Data["State"]); } public void CrashInRenderCallbackAndCrashInSerializeRecordsSerializationException() { _main = CreateMain( state => { state.Integer = 7; state.CrashOnSerialize = true; }, state => { throw new InvalidOperationException("bar"); }, state => { }); _threadManager.IterateSimulation(); _threadManager.IterateRender(); Exception exception = (Exception)_exceptionHandler.RenderExceptions[0].Data["StateSerializationException"]; CheckEqual("Foo", exception.Message); } private Main _main; private CooperativeThreadedLoopManager _threadManager; private RecordingExceptionHandler _exceptionHandler; } }