diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 2e496b6873..df10e9d523 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -384,9 +384,9 @@ namespace OpenRA // Note: These delayed actions should only be used by widgets or disposing objects // - things that depend on a particular world should be queuing them on the worldactor. - static ActionQueue delayedActions = new ActionQueue(); - public static void RunAfterTick(Action a) { delayedActions.Add(a); } - public static void RunAfterDelay(int delay, Action a) { delayedActions.Add(a, delay); } + static volatile ActionQueue delayedActions = new ActionQueue(); + public static void RunAfterTick(Action a) { delayedActions.Add(a, Game.RunTime); } + public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, Game.RunTime + delayMilliseconds); } static void TakeScreenshotInner() { @@ -487,7 +487,7 @@ namespace OpenRA static void LogicTick() { - delayedActions.PerformActions(); + delayedActions.PerformActions(Game.RunTime); if (OrderManager.Connection.ConnectionState != lastConnectionState) { diff --git a/OpenRA.Game/Primitives/ActionQueue.cs b/OpenRA.Game/Primitives/ActionQueue.cs index 3d623a4321..7afa85d8aa 100644 --- a/OpenRA.Game/Primitives/ActionQueue.cs +++ b/OpenRA.Game/Primitives/ActionQueue.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; namespace OpenRA.Primitives { @@ -17,37 +18,56 @@ namespace OpenRA.Primitives /// public class ActionQueue { - object syncRoot = new object(); - PriorityQueue actions = new PriorityQueue(); + readonly List actions = new List(); - public void Add(Action a) { Add(a, 0); } - public void Add(Action a, int delay) + public void Add(Action a, int desiredTime) { - lock (syncRoot) - actions.Add(new DelayedAction(a, Game.RunTime + delay)); + if (a == null) + throw new ArgumentNullException("a"); + + lock (actions) + { + var action = new DelayedAction(a, desiredTime); + var index = Index(action); + actions.Insert(index, action); + } } - public void PerformActions() + public void PerformActions(int currentTime) { - Action a = () => { }; - lock (syncRoot) + DelayedAction[] pendingActions; + lock (actions) { - var t = Game.RunTime; - while (!actions.Empty && actions.Peek().Time <= t) - { - var da = actions.Pop(); - a = da.Action + a; - } + var dummyAction = new DelayedAction(null, currentTime); + var index = Index(dummyAction); + if (index <= 0) + return; + + pendingActions = new DelayedAction[index]; + actions.CopyTo(0, pendingActions, 0, index); + actions.RemoveRange(0, index); } - a(); + foreach (var delayedAction in pendingActions) + delayedAction.Action(); + } + + int Index(DelayedAction action) + { + // Returns the index of the next action with a strictly greater time. + var index = actions.BinarySearch(action); + if (index < 0) + return ~index; + while (index < actions.Count && action.CompareTo(actions[index]) >= 0) + index++; + return index; } } struct DelayedAction : IComparable { - public int Time; - public Action Action; + public readonly int Time; + public readonly Action Action; public DelayedAction(Action action, int time) { @@ -59,5 +79,10 @@ namespace OpenRA.Primitives { return Time.CompareTo(other.Time); } + + public override string ToString() + { + return "Time: " + Time + " Action: " + Action; + } } } diff --git a/OpenRA.Test/OpenRA.Game/ActionQueueTest.cs b/OpenRA.Test/OpenRA.Game/ActionQueueTest.cs new file mode 100644 index 0000000000..bb23d5a414 --- /dev/null +++ b/OpenRA.Test/OpenRA.Game/ActionQueueTest.cs @@ -0,0 +1,42 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using OpenRA.Primitives; + +namespace OpenRA.Test.OpenRA.Game +{ + [TestFixture] + class ActionQueueTest + { + [TestCase(TestName = "ActionQueue performs actions in order of time, then insertion order.")] + public void ActionsArePerformedOrderedByTimeThenByInsertionOrder() + { + var list = new List(); + var queue = new ActionQueue(); + queue.Add(() => list.Add(1), 0); + queue.Add(() => list.Add(7), 2); + queue.Add(() => list.Add(8), 2); + queue.Add(() => list.Add(4), 1); + queue.Add(() => list.Add(2), 0); + queue.Add(() => list.Add(3), 0); + queue.Add(() => list.Add(9), 2); + queue.Add(() => list.Add(5), 1); + queue.Add(() => list.Add(6), 1); + queue.PerformActions(1); + queue.PerformActions(2); + queue.PerformActions(3); + if (!list.SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + Assert.Fail("Actions were not performed in the correct order. Actual order was: " + string.Join(", ", list)); + } + } +} diff --git a/OpenRA.Test/OpenRA.Test.csproj b/OpenRA.Test/OpenRA.Test.csproj index 18fd3c8590..c74f9b429d 100644 --- a/OpenRA.Test/OpenRA.Test.csproj +++ b/OpenRA.Test/OpenRA.Test.csproj @@ -45,6 +45,7 @@ +