Merge pull request #5505 from pavlos256/map-preview-generation
Map preview generation
This commit is contained in:
@@ -21,6 +21,7 @@ namespace OpenRA.Graphics
|
|||||||
ITexture texture;
|
ITexture texture;
|
||||||
bool dirty;
|
bool dirty;
|
||||||
byte[] data;
|
byte[] data;
|
||||||
|
readonly object dirtyLock = new object();
|
||||||
|
|
||||||
public readonly Size Size;
|
public readonly Size Size;
|
||||||
public byte[] Data { get { return data ?? texture.GetData(); } }
|
public byte[] Data { get { return data ?? texture.GetData(); } }
|
||||||
@@ -68,6 +69,8 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public ITexture Texture
|
public ITexture Texture
|
||||||
{
|
{
|
||||||
|
// This is only called from the main thread but 'dirty'
|
||||||
|
// is set from other threads too via CommitData().
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
@@ -76,10 +79,13 @@ namespace OpenRA.Graphics
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty)
|
lock (dirtyLock)
|
||||||
{
|
{
|
||||||
texture.SetData(data, Size.Width, Size.Height);
|
if (dirty)
|
||||||
dirty = false;
|
{
|
||||||
|
texture.SetData(data, Size.Width, Size.Height);
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
@@ -140,7 +146,10 @@ namespace OpenRA.Graphics
|
|||||||
if (data == null)
|
if (data == null)
|
||||||
throw new InvalidOperationException("Texture-wrappers are read-only");
|
throw new InvalidOperationException("Texture-wrappers are read-only");
|
||||||
|
|
||||||
dirty = true;
|
lock (dirtyLock)
|
||||||
|
{
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,42 +131,50 @@ namespace OpenRA
|
|||||||
|
|
||||||
void LoadAsyncInternal()
|
void LoadAsyncInternal()
|
||||||
{
|
{
|
||||||
for (;;)
|
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
||||||
|
|
||||||
|
// Milliseconds to wait on one loop when nothing to do
|
||||||
|
var emptyDelay = 50;
|
||||||
|
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
||||||
|
var maxKeepAlive = 5000 / emptyDelay;
|
||||||
|
var keepAlive = maxKeepAlive;
|
||||||
|
|
||||||
|
while (keepAlive-- > 0)
|
||||||
{
|
{
|
||||||
MapPreview p;
|
List<MapPreview> todo;
|
||||||
lock (syncRoot)
|
lock (syncRoot)
|
||||||
{
|
{
|
||||||
if (generateMinimap.Count == 0)
|
todo = generateMinimap.Where(p => p.Minimap == null).ToList();
|
||||||
break;
|
generateMinimap.Clear();
|
||||||
|
|
||||||
p = generateMinimap.Peek();
|
|
||||||
|
|
||||||
// Preview already exists
|
|
||||||
if (p.Minimap != null)
|
|
||||||
{
|
|
||||||
generateMinimap.Dequeue();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (todo.Count == 0)
|
||||||
|
{
|
||||||
|
Thread.Sleep(emptyDelay);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
keepAlive = maxKeepAlive;
|
||||||
|
|
||||||
// Render the minimap into the shared sheet
|
// Render the minimap into the shared sheet
|
||||||
// Note: this is not generally thread-safe, but it works here because:
|
foreach (var p in todo)
|
||||||
// (a) This worker is the only thread writing to this sheet
|
{
|
||||||
// (b) The main thread is the only thread reading this sheet
|
// The rendering is thread safe because it only reads from the passed instances and writes to a new bitmap
|
||||||
// (c) The sheet is marked dirty after the write is completed,
|
var bitmap = p.CustomPreview ?? Minimap.RenderMapPreview(modData.DefaultRules.TileSets[p.Map.Tileset], p.Map, modData.DefaultRules, true);
|
||||||
// which causes the main thread to copy this to the texture during
|
// Note: this is not generally thread-safe, but it works here because:
|
||||||
// the next render cycle.
|
// (a) This worker is the only thread writing to this sheet
|
||||||
// (d) Any partially written bytes from the next minimap is in an
|
// (b) The main thread is the only thread reading this sheet
|
||||||
// unallocated area, and will be committed in the next cycle.
|
// (c) The sheet is marked dirty after the write is completed,
|
||||||
var bitmap = p.CustomPreview ?? Minimap.RenderMapPreview(modData.DefaultRules.TileSets[p.Map.Tileset], p.Map, modData.DefaultRules, true);
|
// which causes the main thread to copy this to the texture during
|
||||||
p.Minimap = sheetBuilder.Add(bitmap);
|
// 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);
|
||||||
|
|
||||||
lock (syncRoot)
|
// Yuck... But this helps the UI Jank when opening the map selector significantly.
|
||||||
generateMinimap.Dequeue();
|
Thread.Sleep(Environment.ProcessorCount == 1 ? 25 : 5);
|
||||||
|
};
|
||||||
// Yuck... But this helps the UI Jank when opening the map selector significantly.
|
|
||||||
Thread.Sleep(50);
|
|
||||||
}
|
}
|
||||||
|
Log.Write("debug", "MapCache.LoadAsyncInternal ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CacheMinimap(MapPreview preview)
|
public void CacheMinimap(MapPreview preview)
|
||||||
@@ -177,6 +185,7 @@ namespace OpenRA
|
|||||||
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
|
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
|
||||||
{
|
{
|
||||||
previewLoaderThread = new Thread(LoadAsyncInternal);
|
previewLoaderThread = new Thread(LoadAsyncInternal);
|
||||||
|
previewLoaderThread.IsBackground = true;
|
||||||
previewLoaderThread.Start();
|
previewLoaderThread.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user