Introduce MapCache and MapPreview for improved UI map previews.
This commit is contained in:
@@ -21,7 +21,7 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
public class MapPreviewWidget : Widget
|
||||
{
|
||||
public Func<Map> Map = () => null;
|
||||
public Func<MapPreview> Preview = () => null;
|
||||
public Func<Dictionary<CPos, Session.Client>> SpawnClients = () => new Dictionary<CPos, Session.Client>();
|
||||
public Action<MouseInput> OnMouseDown = _ => {};
|
||||
public bool IgnoreMouseInput = false;
|
||||
@@ -32,6 +32,9 @@ namespace OpenRA.Widgets
|
||||
Lazy<TooltipContainerWidget> tooltipContainer;
|
||||
public int TooltipSpawnIndex = -1;
|
||||
|
||||
Rectangle MapRect;
|
||||
float PreviewScale = 0;
|
||||
|
||||
public MapPreviewWidget()
|
||||
{
|
||||
tooltipContainer = Lazy.New(() => Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
|
||||
@@ -40,8 +43,7 @@ namespace OpenRA.Widgets
|
||||
protected MapPreviewWidget(MapPreviewWidget other)
|
||||
: base(other)
|
||||
{
|
||||
lastMap = other.lastMap;
|
||||
Map = other.Map;
|
||||
Preview = other.Preview;
|
||||
SpawnClients = other.SpawnClients;
|
||||
ShowSpawnPoints = other.ShowSpawnPoints;
|
||||
TooltipTemplate = other.TooltipTemplate;
|
||||
@@ -65,74 +67,51 @@ namespace OpenRA.Widgets
|
||||
|
||||
public override void MouseEntered()
|
||||
{
|
||||
if (TooltipContainer == null) return;
|
||||
tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() {{ "preview", this }});
|
||||
if (TooltipContainer != null)
|
||||
tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() {{ "preview", this }});
|
||||
}
|
||||
|
||||
public override void MouseExited()
|
||||
{
|
||||
if (TooltipContainer == null) return;
|
||||
tooltipContainer.Value.RemoveTooltip();
|
||||
if (TooltipContainer != null)
|
||||
tooltipContainer.Value.RemoveTooltip();
|
||||
}
|
||||
|
||||
public int2 ConvertToPreview(CPos point)
|
||||
{
|
||||
var map = Map();
|
||||
return new int2(MapRect.X + (int)(PreviewScale*(point.X - map.Bounds.Left)) , MapRect.Y + (int)(PreviewScale*(point.Y - map.Bounds.Top)));
|
||||
var preview = Preview();
|
||||
return new int2(MapRect.X + (int)(PreviewScale*(point.X - preview.Bounds.Left)) , MapRect.Y + (int)(PreviewScale*(point.Y - preview.Bounds.Top)));
|
||||
}
|
||||
|
||||
Sheet mapChooserSheet;
|
||||
Sprite mapChooserSprite;
|
||||
Map lastMap;
|
||||
Rectangle MapRect;
|
||||
float PreviewScale = 0;
|
||||
|
||||
Sprite minimap;
|
||||
public override void Draw()
|
||||
{
|
||||
var map = Map();
|
||||
if (map == null)
|
||||
var preview = Preview();
|
||||
if (preview == null)
|
||||
return;
|
||||
|
||||
// Preview unavailable
|
||||
if (!Loaded)
|
||||
{
|
||||
GeneratePreview();
|
||||
// Stash a copy of the minimap to ensure consistency
|
||||
// (it may be modified by another thread)
|
||||
minimap = preview.Minimap;
|
||||
if (minimap == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastMap != map)
|
||||
{
|
||||
lastMap = map;
|
||||
|
||||
// Update image data
|
||||
Bitmap preview;
|
||||
lock (syncRoot)
|
||||
preview = Previews[map.Uid];
|
||||
|
||||
if (mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height)
|
||||
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
|
||||
PreviewScale = Math.Min(RenderBounds.Width * 1.0f / map.Bounds.Width, RenderBounds.Height * 1.0f / map.Bounds.Height);
|
||||
var size = Math.Max(map.Bounds.Width, map.Bounds.Height);
|
||||
var dw = (int)(PreviewScale * (size - map.Bounds.Width)) / 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));
|
||||
PreviewScale = Math.Min(RenderBounds.Width / minimap.size.X, RenderBounds.Height / minimap.size.Y);
|
||||
var w = (int)(PreviewScale * minimap.size.X);
|
||||
var h = (int)(PreviewScale * minimap.size.Y);
|
||||
var x = RenderBounds.X + (RenderBounds.Width - w) / 2;
|
||||
var y = RenderBounds.Y + (RenderBounds.Height - h) / 2;
|
||||
MapRect = new Rectangle(x, y, w, h);
|
||||
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(mapChooserSprite,
|
||||
new float2(MapRect.Location),
|
||||
new float2(MapRect.Size));
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(minimap, new float2(MapRect.Location), new float2(MapRect.Size));
|
||||
|
||||
TooltipSpawnIndex = -1;
|
||||
if (ShowSpawnPoints)
|
||||
{
|
||||
var colors = SpawnClients().ToDictionary(c => c.Key, c => c.Value.Color.RGB);
|
||||
|
||||
var spawnPoints = map.GetSpawnPoints().ToList();
|
||||
var spawnPoints = preview.SpawnPoints;
|
||||
foreach (var p in spawnPoints)
|
||||
{
|
||||
var owned = colors.ContainsKey(p);
|
||||
@@ -151,79 +130,6 @@ namespace OpenRA.Widgets
|
||||
}
|
||||
}
|
||||
|
||||
// Async map preview generation bits
|
||||
enum PreviewStatus { Invalid, Uncached, Generating, Cached }
|
||||
static Thread previewLoaderThread;
|
||||
static object syncRoot = new object();
|
||||
static Queue<string> cacheUids = new Queue<string>();
|
||||
static readonly Dictionary<string, Bitmap> Previews = new Dictionary<string, Bitmap>();
|
||||
|
||||
void LoadAsyncInternal()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
string uid;
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (cacheUids.Count == 0)
|
||||
break;
|
||||
uid = cacheUids.Peek();
|
||||
}
|
||||
|
||||
var bitmap = Minimap.RenderMapPreview(Game.modData.AvailableMaps[uid], false);
|
||||
lock (syncRoot)
|
||||
{
|
||||
// TODO: We should add previews to a sheet here (with multiple previews per sheet)
|
||||
Previews.Add(uid, bitmap);
|
||||
cacheUids.Dequeue();
|
||||
}
|
||||
|
||||
// Yuck... But this helps the UI Jank when opening the map selector significantly.
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
bool compatibleTileset;
|
||||
|
||||
void GeneratePreview()
|
||||
{
|
||||
var m = Map();
|
||||
if (m == null)
|
||||
return;
|
||||
|
||||
compatibleTileset = Rules.TileSets.Values.Any(t => t.Id == m.Tileset);
|
||||
|
||||
var status = Status(m);
|
||||
if (status == PreviewStatus.Uncached)
|
||||
lock (syncRoot)
|
||||
cacheUids.Enqueue(m.Uid);
|
||||
|
||||
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
|
||||
{
|
||||
previewLoaderThread = new Thread(LoadAsyncInternal);
|
||||
previewLoaderThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!compatibleTileset)
|
||||
return PreviewStatus.Invalid;
|
||||
}
|
||||
return PreviewStatus.Uncached;
|
||||
}
|
||||
|
||||
public bool Loaded { get { return Status(Map()) == PreviewStatus.Cached; } }
|
||||
public bool Loaded { get { return minimap != null; } }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user