GenerationFailureWindow.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. using DunGen.Generation;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using UnityEngine.UIElements;
  8. namespace DunGen.Editor.Windows
  9. {
  10. public class GenerationFailureWindow : EditorWindow
  11. {
  12. #region Statics
  13. public static void ShowWindow(GenerationFailureReport report)
  14. {
  15. var window = GetWindow<GenerationFailureWindow>("Generation Failure Report");
  16. window.minSize = new Vector2(400, 300);
  17. window.GenerationFailureReport = report;
  18. window.Show();
  19. window.CreateUI();
  20. }
  21. [InitializeOnLoadMethod]
  22. private static void Initialise()
  23. {
  24. // Clear and re-register the event handler so we're notified of future generation failures
  25. DungeonGenerator.OnGenerationFailureReportProduced -= OnGenerationFailureReportProduced;
  26. DungeonGenerator.OnGenerationFailureReportProduced += OnGenerationFailureReportProduced;
  27. }
  28. private static void OnGenerationFailureReportProduced(DungeonGenerator generator, GenerationFailureReport report)
  29. {
  30. if(DunGenSettings.Instance.DisplayFailureReportWindow)
  31. ShowWindow(report);
  32. }
  33. #endregion
  34. public static Dictionary<Type, string> FailureTypeDescriptions = new Dictionary<Type, string>
  35. {
  36. { typeof(OutOfBoundsPlacementResult), "The tile was placed outside the bounding box of the dungeon. Increase the size of the 'Placement Bounds' in your dungeon generator settings, or turn off 'Restrict to Bounds?' if you don't need it." },
  37. { typeof(TileIsCollidingPlacementResult), "The tile placement collided with another tile. Tile collisions happen a lot naturally. You could potentially reduce collisions by: Reducing the size of your dungeon, adding more doorways to tiles, or adding more variety to your tile shapes to give DunGen more opportunity to succeed." },
  38. { typeof(NoMatchingDoorwayPlacementResult), "No matching doorway pairs were found when trying to attach one room to another. This can happen if you don't have enough doorways on your tiles, you have too restrictive conditions on which doorways are allowed to connect, or if the doorways are not aligned properly. Try adding more doorways to your tiles, or adjusting the doorway positions and restrictions." }
  39. };
  40. public GenerationFailureReport GenerationFailureReport { get; set; }
  41. private VisualElement leftPanel;
  42. private VisualElement rightPanel;
  43. private List<TilePlacementResult> currentTypeResults = new List<TilePlacementResult>();
  44. private string currentTypeName = string.Empty;
  45. private string currentTypeDescription = string.Empty;
  46. private void OnEnable()
  47. {
  48. CreateUI();
  49. }
  50. private void CreateUI()
  51. {
  52. var root = rootVisualElement;
  53. root.Clear();
  54. if (GenerationFailureReport == null)
  55. return;
  56. // Top panel
  57. var topPanel = new VisualElement();
  58. topPanel.style.flexDirection = FlexDirection.Column;
  59. topPanel.style.paddingTop = 8;
  60. topPanel.style.paddingBottom = 8;
  61. topPanel.style.paddingLeft = 12;
  62. topPanel.style.paddingRight = 12;
  63. var headerLabel = new Label("Generation Failure Report");
  64. headerLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
  65. headerLabel.style.fontSize = 16;
  66. topPanel.Add(headerLabel);
  67. var descriptionLabel = new Label($"The generator failed after {GenerationFailureReport.MaxRetryAttempts} attempts. See below for details on each failure type.");
  68. descriptionLabel.style.marginTop = 4;
  69. descriptionLabel.style.fontSize = 12;
  70. descriptionLabel.style.whiteSpace = WhiteSpace.Normal;
  71. topPanel.Add(descriptionLabel);
  72. root.Add(topPanel);
  73. // Main split area
  74. var mainSplit = new VisualElement();
  75. mainSplit.style.flexDirection = FlexDirection.Row;
  76. mainSplit.style.flexGrow = 1;
  77. mainSplit.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
  78. root.Add(mainSplit);
  79. // Left panel: Failure type list
  80. leftPanel = new VisualElement();
  81. leftPanel.style.width = 180;
  82. leftPanel.style.flexShrink = 0;
  83. leftPanel.style.marginTop = 8;
  84. leftPanel.style.marginBottom = 8;
  85. leftPanel.style.marginRight = 8;
  86. leftPanel.style.flexDirection = FlexDirection.Column;
  87. mainSplit.Add(leftPanel);
  88. // Right panel: Details
  89. rightPanel = new VisualElement();
  90. rightPanel.style.flexGrow = 1;
  91. rightPanel.style.marginTop = 8;
  92. rightPanel.style.marginBottom = 8;
  93. rightPanel.style.flexDirection = FlexDirection.Column;
  94. mainSplit.Add(rightPanel);
  95. // Populate failure type list
  96. PopulateFailureTypeList();
  97. }
  98. private void PopulateFailureTypeList()
  99. {
  100. leftPanel.Clear();
  101. if (GenerationFailureReport.TilePlacementResults == null || GenerationFailureReport.TilePlacementResults.Count == 0)
  102. return;
  103. // Group by failure type, order so the most common types are first
  104. var grouped = GenerationFailureReport.TilePlacementResults
  105. .GroupBy(r => r.GetType())
  106. .OrderByDescending(g => g.Count());
  107. foreach (var group in grouped)
  108. {
  109. var type = group.Key;
  110. string displayName = group.First().DisplayName;
  111. int count = group.Count();
  112. FailureTypeDescriptions.TryGetValue(type, out string typeDescription);
  113. var button = new Button(() =>
  114. {
  115. currentTypeName = displayName;
  116. currentTypeDescription = typeDescription;
  117. currentTypeResults = group.ToList();
  118. PopulateFailureTypeDetails();
  119. })
  120. {
  121. style =
  122. {
  123. flexDirection = FlexDirection.Row,
  124. justifyContent = Justify.SpaceBetween,
  125. alignItems = Align.Center,
  126. marginBottom = 2,
  127. paddingLeft = 8,
  128. paddingRight = 8,
  129. height = 28
  130. }
  131. };
  132. var nameLabel = new Label(displayName)
  133. {
  134. style =
  135. {
  136. unityTextAlign = TextAnchor.MiddleLeft,
  137. flexGrow = 1
  138. }
  139. };
  140. var countLabel = new Label(count.ToString())
  141. {
  142. style =
  143. {
  144. unityTextAlign = TextAnchor.MiddleRight,
  145. marginLeft = 8
  146. }
  147. };
  148. button.Add(nameLabel);
  149. button.Add(countLabel);
  150. leftPanel.Add(button);
  151. }
  152. // Auto-select the first (most common) failure type
  153. if (grouped.Any())
  154. {
  155. var firstFailureTypeEntry = grouped.First();
  156. currentTypeName = firstFailureTypeEntry.First().DisplayName;
  157. currentTypeResults = firstFailureTypeEntry.ToList();
  158. FailureTypeDescriptions.TryGetValue(firstFailureTypeEntry.Key, out currentTypeDescription);
  159. PopulateFailureTypeDetails();
  160. }
  161. }
  162. private void PopulateFailureTypeDetails()
  163. {
  164. rightPanel.Clear();
  165. // Header
  166. var header = new Label(currentTypeName)
  167. {
  168. style =
  169. {
  170. unityFontStyleAndWeight = FontStyle.Bold,
  171. fontSize = 14,
  172. marginBottom = 2,
  173. marginTop = 2
  174. }
  175. };
  176. rightPanel.Add(header);
  177. // Description
  178. var description = new Label(currentTypeDescription)
  179. {
  180. style =
  181. {
  182. fontSize = 11,
  183. marginBottom = 8,
  184. whiteSpace = WhiteSpace.Normal
  185. }
  186. };
  187. rightPanel.Add(description);
  188. // Scrollable list of failure instances
  189. var scrollView = new ScrollView(ScrollViewMode.Vertical)
  190. {
  191. style =
  192. {
  193. flexGrow = 1,
  194. paddingTop = 4,
  195. paddingBottom = 4
  196. }
  197. };
  198. // Group results by the asset that caused the failure (ordered by most common first)
  199. var problematicAssets = currentTypeResults
  200. .GroupBy(TryGetAssetFromResult)
  201. .Where(g => g.Key != null)
  202. .OrderByDescending(g => g.Count())
  203. .ToList();
  204. foreach (var group in problematicAssets)
  205. {
  206. var asset = group.Key;
  207. string label = asset.name;
  208. int count = group.Count();
  209. var instanceButton = new Button(() =>
  210. {
  211. Selection.activeObject = asset;
  212. EditorGUIUtility.PingObject(asset);
  213. })
  214. {
  215. style =
  216. {
  217. flexDirection = FlexDirection.Row,
  218. justifyContent = Justify.SpaceBetween,
  219. alignItems = Align.Center,
  220. marginBottom = 2,
  221. paddingLeft = 8,
  222. paddingRight = 8,
  223. height = 24
  224. }
  225. };
  226. var nameLabel = new Label(label)
  227. {
  228. style =
  229. {
  230. unityTextAlign = TextAnchor.MiddleLeft,
  231. flexGrow = 1
  232. }
  233. };
  234. var countLabel = new Label(count.ToString())
  235. {
  236. style =
  237. {
  238. unityTextAlign = TextAnchor.MiddleRight,
  239. marginLeft = 8
  240. }
  241. };
  242. instanceButton.Add(nameLabel);
  243. instanceButton.Add(countLabel);
  244. scrollView.Add(instanceButton);
  245. }
  246. rightPanel.Add(scrollView);
  247. }
  248. private UnityEngine.Object TryGetAssetFromResult(TilePlacementResult result)
  249. {
  250. if (result is TileTemplatePlacementResult templatePlacementResult)
  251. return templatePlacementResult.TileTemplatePrefab;
  252. else if (result is NoMatchingDoorwayPlacementResult noMatchingDoorwayPlacementResult)
  253. return noMatchingDoorwayPlacementResult.FromTilePrefab;
  254. else
  255. return null;
  256. }
  257. }
  258. }