diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 76908d863b..53fa57d851 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -210,6 +210,9 @@ namespace OpenRA Status = MapStatus.Unavailable; } + // Note: multiple threads may try to access the value at the same time + // We rely on the thread-safety guarantees given by Lazy to prevent race conitions. + // If you're thinking about replacing this, then you must be careful to keep this safe. rules = Exts.Lazy(() => { try diff --git a/OpenRA.Game/Widgets/WidgetUtils.cs b/OpenRA.Game/Widgets/WidgetUtils.cs index 27ffd52c56..acd01e4e77 100644 --- a/OpenRA.Game/Widgets/WidgetUtils.cs +++ b/OpenRA.Game/Widgets/WidgetUtils.cs @@ -249,8 +249,6 @@ namespace OpenRA.Widgets return trimmed; } - public static Action Once(Action a) { return () => { if (a != null) { a(); a = null; } }; } - public static string ChooseInitialMap(string initialUid) { if (string.IsNullOrEmpty(initialUid) || Game.ModData.MapCache[initialUid].Status != MapStatus.Available) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index 35b825c48a..e6bfdfbf78 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -65,6 +65,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly LabelWidget chatLabel; bool teamChat; + bool addBotOnMapLoad; + int lobbyChatUnreadMessages; int globalChatLastReadMessages; int globalChatUnreadMessages; @@ -201,9 +203,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic (orderManager.LobbyInfo.Slots.Values.All(s => !s.AllowBots) && orderManager.LobbyInfo.Slots.Count(s => !s.Value.LockTeam && orderManager.LobbyInfo.ClientInSlot(s.Key) != null) == 0); - var botNames = modRules.Actors["player"].TraitInfos().Select(t => t.Name); slotsButton.OnMouseDown = _ => { + var botNames = Map.Rules.Actors["player"].TraitInfos().Select(t => t.Name); var options = new Dictionary>(); var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); @@ -711,16 +713,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic // Add a bot on the first lobbyinfo update if (skirmishMode) - { - Game.LobbyInfoChanged += WidgetUtils.Once(() => - { - var slot = orderManager.LobbyInfo.FirstEmptyBotSlot(); - var bot = modRules.Actors["player"].TraitInfos().Select(t => t.Name).FirstOrDefault(); - var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); - if (slot != null && bot != null) - orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot, botController.Index, bot))); - }); - } + addBotOnMapLoad = true; } public override void Tick() @@ -796,6 +789,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var currentMap = Map; new Task(() => { + // Force map rules to be loaded on this background thread var unused = currentMap.Rules; Game.RunAfterTick(() => { @@ -806,6 +800,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic // Tell the server that we have the map if (!currentMap.InvalidCustomRules) orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))); + + if (addBotOnMapLoad) + { + var slot = orderManager.LobbyInfo.FirstEmptyBotSlot(); + var bot = currentMap.Rules.Actors["player"].TraitInfos().Select(t => t.Name).FirstOrDefault(); + var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); + if (slot != null && bot != null) + orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot, botController.Index, bot))); + + addBotOnMapLoad = false; + } }); }).Start(); } @@ -837,7 +842,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic template = emptySlotTemplate.Clone(); if (Game.IsHost) - LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, modRules); + LobbyUtils.SetupEditableSlotWidget(this, template, slot, client, orderManager); else LobbyUtils.SetupSlotWidget(template, slot, client); @@ -856,7 +861,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic LobbyUtils.SetupClientWidget(template, client, orderManager, client.Bot == null); if (client.Bot != null) - LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager, modRules); + LobbyUtils.SetupEditableSlotWidget(this, template, slot, client, orderManager); else LobbyUtils.SetupEditableNameWidget(template, slot, client, orderManager); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs index fa5a661060..4e8fcb9833 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } } - public static void ShowSlotDropDown(Ruleset rules, DropDownButtonWidget dropdown, Session.Slot slot, + public static void ShowSlotDropDown(LobbyLogic logic, DropDownButtonWidget dropdown, Session.Slot slot, Session.Client client, OrderManager orderManager) { var options = new Dictionary>() { { "Slot", new List() @@ -50,7 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var bots = new List(); if (slot.AllowBots) { - foreach (var b in rules.Actors["player"].TraitInfos().Select(t => t.Name)) + foreach (var b in logic.Map.Rules.Actors["player"].TraitInfos().Select(t => t.Name)) { var bot = b; var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); @@ -303,13 +303,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic name.GetText = () => label; } - public static void SetupEditableSlotWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, Ruleset rules) + public static void SetupEditableSlotWidget(LobbyLogic logic, Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager) { var slot = parent.Get("SLOT_OPTIONS"); slot.IsVisible = () => true; slot.IsDisabled = () => orderManager.LocalClient.IsReady; slot.GetText = () => c != null ? c.Name : s.Closed ? "Closed" : "Open"; - slot.OnMouseDown = _ => ShowSlotDropDown(rules, slot, s, c, orderManager); + slot.OnMouseDown = _ => ShowSlotDropDown(logic, slot, s, c, orderManager); // Ensure Name selector (if present) is hidden var name = parent.GetOrNull("NAME");