Move threading into the preview generation.

This commit is contained in:
Paul Chote
2013-04-06 01:21:36 +13:00
parent 792405d789
commit 8f7940f969
3 changed files with 104 additions and 47 deletions

View File

@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics; using OpenRA.Graphics;
@@ -26,8 +27,6 @@ namespace OpenRA.Widgets
public bool IgnoreMouseInput = false; public bool IgnoreMouseInput = false;
public bool ShowSpawnPoints = true; public bool ShowSpawnPoints = true;
static readonly Cache<Map,Bitmap> PreviewCache = new Cache<Map, Bitmap>(stub => Minimap.RenderMapPreview( new Map( stub.Path )));
public MapPreviewWidget() : base() { } public MapPreviewWidget() : base() { }
protected MapPreviewWidget(MapPreviewWidget other) protected MapPreviewWidget(MapPreviewWidget other)
@@ -68,19 +67,30 @@ namespace OpenRA.Widgets
public override void Draw() public override void Draw()
{ {
var map = Map(); var map = Map();
if( map == null ) return; if (map == null)
return;
// Preview unavailable
if (!Loaded)
{
GeneratePreview();
return;
}
if (lastMap != map) if (lastMap != map)
{ {
lastMap = map; lastMap = map;
// Update image data // Update image data
var preview = PreviewCache[map]; Bitmap preview;
if( mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height ) lock (syncRoot)
mapChooserSheet = new Sheet(new Size( preview.Width, preview.Height ) ); preview = Previews[map.Uid];
mapChooserSheet.Texture.SetData( preview ); if (mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height)
mapChooserSprite = new Sprite( mapChooserSheet, new Rectangle( 0, 0, map.Bounds.Width, map.Bounds.Height ), TextureChannel.Alpha ); mapChooserSheet = new Sheet(new Size(preview.Width, preview.Height));
mapChooserSheet.Texture.SetData(preview);
mapChooserSprite = new Sprite(mapChooserSheet, new Rectangle(0, 0, map.Bounds.Width, map.Bounds.Height), TextureChannel.Alpha);
} }
// Update map rect // Update map rect
@@ -90,9 +100,9 @@ namespace OpenRA.Widgets
var dh = (int)(PreviewScale * (size - map.Bounds.Height)) / 2; var dh = (int)(PreviewScale * (size - map.Bounds.Height)) / 2;
MapRect = new Rectangle(RenderBounds.X + dw, RenderBounds.Y + dh, (int)(map.Bounds.Width * PreviewScale), (int)(map.Bounds.Height * PreviewScale)); MapRect = new Rectangle(RenderBounds.X + dw, RenderBounds.Y + dh, (int)(map.Bounds.Width * PreviewScale), (int)(map.Bounds.Height * PreviewScale));
Game.Renderer.RgbaSpriteRenderer.DrawSprite( mapChooserSprite, Game.Renderer.RgbaSpriteRenderer.DrawSprite(mapChooserSprite,
new float2(MapRect.Location), new float2(MapRect.Location),
new float2( MapRect.Size ) ); new float2(MapRect.Size));
if (ShowSpawnPoints) if (ShowSpawnPoints)
{ {
@@ -119,15 +129,70 @@ namespace OpenRA.Widgets
} }
} }
/// <summary> // Async map preview generation bits
/// Forces loading the preview into the map cache. enum PreviewStatus { Invalid, Uncached, Generating, Cached }
/// </summary> static Thread previewLoaderThread;
public Bitmap LoadMapPreview() static object syncRoot = new object();
{ static Queue<string> cacheUids = new Queue<string>();
var map = Map(); static readonly Dictionary<string, Bitmap> Previews = new Dictionary<string, Bitmap>();
if( map == null ) return null;
return PreviewCache[map]; void LoadAsyncInternal()
{
for (;;)
{
string uid;
lock (syncRoot)
{
if (cacheUids.Count == 0)
break;
uid = cacheUids.Peek();
}
var bitmap = Minimap.RenderMapPreview(Game.modData.AvailableMaps[uid]);
lock (syncRoot)
{
// TODO: We should add previews to a sheet here (with multiple previews per sheet)
Previews.Add(uid, bitmap);
cacheUids.Dequeue();
}
}
} }
void GeneratePreview()
{
var m = Map();
if (m == null)
return;
var status = Status(m);
if (status == PreviewStatus.Uncached)
lock (syncRoot)
cacheUids.Enqueue(m.Uid);
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
{
previewLoaderThread = new Thread(LoadAsyncInternal);
previewLoaderThread.Priority = ThreadPriority.Lowest;
previewLoaderThread.Start();
}
}
static PreviewStatus Status(Map m)
{
if (m == null)
return PreviewStatus.Invalid;
lock (syncRoot)
{
if (Previews.ContainsKey(m.Uid))
return PreviewStatus.Cached;
if (cacheUids.Contains(m.Uid))
return PreviewStatus.Generating;
}
return PreviewStatus.Uncached;
}
public bool Loaded { get { return Status(Map()) == PreviewStatus.Cached; } }
} }
} }

View File

@@ -27,7 +27,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic
ScrollPanelWidget scrollpanel; ScrollPanelWidget scrollpanel;
ScrollItemWidget itemTemplate; ScrollItemWidget itemTemplate;
string gameMode; string gameMode;
Thread mapLoaderThread;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<Map> onSelect) internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<Map> onSelect)
@@ -61,7 +60,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{ {
var item = ScrollItemWidget.Setup(template, var item = ScrollItemWidget.Setup(template,
() => gameMode == ii.First, () => gameMode == ii.First,
() => { gameMode = ii.First; EnumerateMapsAsync(); }); () => { gameMode = ii.First; EnumerateMaps(); });
item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii); item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii);
return item; return item;
}; };
@@ -84,16 +83,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Count == 0; randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Count == 0;
} }
EnumerateMapsAsync(); EnumerateMaps();
}
void EnumerateMapsAsync()
{
if (mapLoaderThread != null && mapLoaderThread.IsAlive)
mapLoaderThread.Abort(); // violent, but should be fine since we are not doing anything sensitive in this thread
mapLoaderThread = new Thread(EnumerateMaps);
mapLoaderThread.Start();
} }
void EnumerateMaps() void EnumerateMaps()
@@ -104,7 +94,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
.OrderBy(kv => kv.Value.PlayerCount) .OrderBy(kv => kv.Value.PlayerCount)
.ThenBy(kv => kv.Value.Title); .ThenBy(kv => kv.Value.Title);
var children = new List<ScrollItemWidget>(); scrollpanel.RemoveChildren();
foreach (var kv in maps) foreach (var kv in maps)
{ {
var m = kv.Value; var m = kv.Value;
@@ -117,17 +107,20 @@ namespace OpenRA.Mods.RA.Widgets.Logic
previewWidget.IgnoreMouseOver = true; previewWidget.IgnoreMouseOver = true;
previewWidget.IgnoreMouseInput = true; previewWidget.IgnoreMouseInput = true;
previewWidget.Map = () => m; previewWidget.Map = () => m;
previewWidget.LoadMapPreview();
var detailsWidget = item.Get<LabelWidget>("DETAILS"); var previewLoadingWidget = item.GetOrNull<BackgroundWidget>("PREVIEW_PLACEHOLDER");
if (previewLoadingWidget != null)
previewLoadingWidget.IsVisible = () => !previewWidget.Loaded;
var detailsWidget = item.GetOrNull<LabelWidget>("DETAILS");
if (detailsWidget != null) if (detailsWidget != null)
detailsWidget.GetText = () => "{0} ({1})".F(m.Type, m.PlayerCount); detailsWidget.GetText = () => "{0} ({1})".F(m.Type, m.PlayerCount);
var authorWidget = item.Get<LabelWidget>("AUTHOR"); var authorWidget = item.GetOrNull<LabelWidget>("AUTHOR");
if (authorWidget != null) if (authorWidget != null)
authorWidget.GetText = () => m.Author; authorWidget.GetText = () => m.Author;
var sizeWidget = item.Get<LabelWidget>("SIZE"); var sizeWidget = item.GetOrNull<LabelWidget>("SIZE");
if (sizeWidget != null) if (sizeWidget != null)
{ {
var size = m.Bounds.Width + "x" + m.Bounds.Height; var size = m.Bounds.Width + "x" + m.Bounds.Height;
@@ -139,20 +132,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic
sizeWidget.GetText = () => size; sizeWidget.GetText = () => size;
} }
children.Add(item); scrollpanel.AddChild(item);
} }
Game.RunAfterTick(() => visibleMaps = maps.ToDictionary(kv => kv.Key, kv => kv.Value);
{ if (visibleMaps.ContainsValue(map))
scrollpanel.RemoveChildren(); scrollpanel.ScrollToItem(visibleMaps.First(m => m.Value == map).Key);
foreach (var c in children)
scrollpanel.AddChild(c);
visibleMaps = maps.ToDictionary(kv => kv.Key, kv => kv.Value);
if (visibleMaps.ContainsValue(map))
scrollpanel.ScrollToItem(visibleMaps.First(m => m.Value == map).Key);
});
} }
} }
} }

View File

@@ -43,6 +43,13 @@ Container@MAPCHOOSER_PANEL:
Y:0 Y:0
Visible:false Visible:false
Children: Children:
Background@PREVIEW_PLACEHOLDER:
X:(PARENT_RIGHT - WIDTH)/2
Y:4
Width:158
Height:158
Background:panel-black
ClickThrough: false
MapPreview@PREVIEW: MapPreview@PREVIEW:
X:(PARENT_RIGHT - WIDTH)/2 X:(PARENT_RIGHT - WIDTH)/2
Y:4 Y:4