From 9dbbc239677d6b288e5f7dc6f0fee05cfb7f9aba Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 31 May 2014 17:55:56 +0100 Subject: [PATCH] Make map preview generation fast. - Change Map.LoadMapTiles and Map.LoadResourceTiles to read the whole stream into memory before processing individual bytes. This removes the cost of significant overhead from repeated calls to ReadUInt8/16. - Remove significant UI jank caused by the map chooser by not including the placeholder widget. The maps render fast enough that it is no longer worthwhile and it was causing a lot of flushes which were the source of the jank. - Trigger async generation for all maps when the chooser is loaded. This means in practice all previews will be ready by the time the user begins to scroll the selection. Since generation is fast, there is no issue with scrolling straight to the bottom and having to wait for the backlog to clear. --- OpenRA.Game/Map/Map.cs | 16 +++++----- OpenRA.Game/Map/MapCache.cs | 4 +-- OpenRA.Game/Map/MapPreview.cs | 31 +++++++++---------- OpenRA.Game/Widgets/MapPreviewWidget.cs | 2 +- .../Widgets/Logic/MapChooserLogic.cs | 10 +++--- mods/cnc/chrome/mapchooser.yaml | 7 ----- mods/ra/chrome/map-chooser.yaml | 7 ----- 7 files changed, 30 insertions(+), 47 deletions(-) diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 946ad1c90d..0389a49199 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -355,14 +355,16 @@ namespace OpenRA throw new InvalidDataException("Invalid tile data"); // Load tile data + var data = dataStream.ReadBytes(MapSize.X * MapSize.Y * 3); + var d = 0; for (int i = 0; i < MapSize.X; i++) for (int j = 0; j < MapSize.Y; j++) { - var tile = dataStream.ReadUInt16(); - var index = dataStream.ReadUInt8(); + var tile = BitConverter.ToUInt16(data, d); + d += 2; + var index = data[d++]; if (index == byte.MaxValue) index = (byte)(i % 4 + (j % 4) * 4); - tiles[i, j] = new TileReference(tile, index); } } @@ -389,14 +391,12 @@ namespace OpenRA // Skip past tile data dataStream.Seek(3 * MapSize.X * MapSize.Y, SeekOrigin.Current); + var data = dataStream.ReadBytes(MapSize.X * MapSize.Y * 2); + var d = 0; // Load resource data for (var i = 0; i < MapSize.X; i++) for (var j = 0; j < MapSize.Y; j++) - { - var type = dataStream.ReadUInt8(); - var index = dataStream.ReadUInt8(); - resources[i, j] = new TileReference(type, index); - } + resources[i, j] = new TileReference(data[d++], data[d++]); } return resources; diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index 9eba996f5b..dc986a2a5b 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -144,7 +144,7 @@ namespace OpenRA List todo; lock (syncRoot) { - todo = generateMinimap.Where(p => p.Minimap == null).ToList(); + todo = generateMinimap.Where(p => p.GetMinimap() == null).ToList(); generateMinimap.Clear(); } if (todo.Count == 0) @@ -168,7 +168,7 @@ namespace OpenRA // the next render cycle. // (d) Any partially written bytes from the next minimap is in an // unallocated area, and will be committed in the next cycle. - p.Minimap = sheetBuilder.Add(bitmap); + p.SetMinimap(sheetBuilder.Add(bitmap)); // Yuck... But this helps the UI Jank when opening the map selector significantly. Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5); diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 7e33da92d8..e0c686303a 100755 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -69,27 +69,24 @@ namespace OpenRA Sprite minimap; bool generatingMinimap; - public Sprite Minimap + public Sprite GetMinimap() { - get + if (minimap != null) + return minimap; + + if (!generatingMinimap && Status == MapStatus.Available) { - if (minimap != null) - return minimap; - - if (!generatingMinimap && Status == MapStatus.Available) - { - generatingMinimap = true; - cache.CacheMinimap(this); - } - - return null; + generatingMinimap = true; + cache.CacheMinimap(this); } - set - { - minimap = value; - generatingMinimap = false; - } + return null; + } + + public void SetMinimap(Sprite minimap) + { + this.minimap = minimap; + generatingMinimap = false; } public MapPreview(string uid, MapCache cache) diff --git a/OpenRA.Game/Widgets/MapPreviewWidget.cs b/OpenRA.Game/Widgets/MapPreviewWidget.cs index f047b77c16..b16d892af6 100644 --- a/OpenRA.Game/Widgets/MapPreviewWidget.cs +++ b/OpenRA.Game/Widgets/MapPreviewWidget.cs @@ -124,7 +124,7 @@ namespace OpenRA.Widgets // Stash a copy of the minimap to ensure consistency // (it may be modified by another thread) - minimap = preview.Minimap; + minimap = preview.GetMinimap(); if (minimap == null) return; diff --git a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs index ebf0b594a7..1e360e5bbf 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs @@ -96,7 +96,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic foreach (var loop in maps) { var preview = loop; + + // Access the minimap to trigger async generation of the minimap. + preview.GetMinimap(); + var item = ScrollItemWidget.Setup(preview.Uid, itemTemplate, () => selectedUid == preview.Uid, () => selectedUid = preview.Uid, () => { Ui.CloseWindow(); onSelect(preview.Uid); }); + item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanel.RenderBounds); var titleLabel = item.Get("TITLE"); titleLabel.GetText = () => preview.Title; @@ -105,11 +110,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic previewWidget.IgnoreMouseOver = true; previewWidget.IgnoreMouseInput = true; previewWidget.Preview = () => preview; - previewWidget.IsVisible = () => previewWidget.RenderBounds.IntersectsWith(scrollpanel.RenderBounds); - - var previewLoadingWidget = item.GetOrNull("PREVIEW_PLACEHOLDER"); - if (previewLoadingWidget != null) - previewLoadingWidget.IsVisible = () => !previewWidget.Loaded; var detailsWidget = item.GetOrNull("DETAILS"); if (detailsWidget != null) diff --git a/mods/cnc/chrome/mapchooser.yaml b/mods/cnc/chrome/mapchooser.yaml index 15deb5d0e7..02cf381bfc 100644 --- a/mods/cnc/chrome/mapchooser.yaml +++ b/mods/cnc/chrome/mapchooser.yaml @@ -43,13 +43,6 @@ Container@MAPCHOOSER_PANEL: Y: 0 Visible: false Children: - Background@PREVIEW_PLACEHOLDER: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 4 - Width: 173 - Height: 173 - Background: panel-black - ClickThrough: false MapPreview@PREVIEW: X: (PARENT_RIGHT - WIDTH)/2 Y: 4 diff --git a/mods/ra/chrome/map-chooser.yaml b/mods/ra/chrome/map-chooser.yaml index 500ed1f432..210dc251c2 100644 --- a/mods/ra/chrome/map-chooser.yaml +++ b/mods/ra/chrome/map-chooser.yaml @@ -26,13 +26,6 @@ Background@MAPCHOOSER_PANEL: Y: 0 Visible: false Children: - Background@PREVIEW_PLACEHOLDER: - X: (PARENT_RIGHT - WIDTH)/2 - Y: 4 - Width: 184 - Height: 184 - Background: dialog3 - ClickThrough: true MapPreview@PREVIEW: X: (PARENT_RIGHT - WIDTH)/2 Y: 4