-
-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add map preview cache #630
base: develop
Are you sure you want to change the base?
Changes from all commits
5cae363
eee4261
cc24bcb
ec2aec8
100d5bb
dad5fdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ public GameInformationPanel(WindowManager windowManager, MapLoader mapLoader) | |
DrawMode = ControlDrawMode.UNIQUE_RENDER_TARGET; | ||
} | ||
|
||
private MapLoader mapLoader; | ||
private readonly MapLoader mapLoader; | ||
|
||
private XNALabel lblGameInformation; | ||
private XNALabel lblGameMode; | ||
|
@@ -43,7 +43,7 @@ public GameInformationPanel(WindowManager windowManager, MapLoader mapLoader) | |
|
||
private GenericHostedGame game = null; | ||
|
||
private bool disposeTextures = false; | ||
private bool disposeTextures = true; | ||
private Texture2D mapTexture = null; | ||
private Texture2D noMapPreviewTexture = null; | ||
|
||
|
@@ -194,17 +194,28 @@ public void SetInfo(GenericHostedGame game) | |
|
||
if (mapLoader != null) | ||
{ | ||
mapTexture = mapLoader.GameModeMaps.Find(m => m.Map.UntranslatedName.Equals(game.Map, StringComparison.InvariantCultureIgnoreCase) && m.Map.IsPreviewTextureCached())?.Map?.LoadPreviewTexture(); | ||
Map map = mapLoader.GameModeMaps.Find(m => m.Map.UntranslatedName.Equals(game.Map, StringComparison.InvariantCultureIgnoreCase))?.Map; | ||
|
||
if (map != null) | ||
{ | ||
if (map.IsPreviewTextureAvailableAsFile()) | ||
{ | ||
mapTexture = map.LoadPreviewTexture(); | ||
disposeTextures = true; | ||
} | ||
else | ||
{ | ||
mapTexture = AssetLoader.TextureFromImage(mapLoader.MapTextureCacheManager.GetMapTextureIfAvailable(map)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean the first show of the preview will always show null texture? When will it be updated? IMO there should be a callback that will update the info upon loading the map. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Currently it will be updated the second time it is visited. Will trying implementing a callback after I am available again (~weeks perhaps) |
||
disposeTextures = true; | ||
Metadorius marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
if (mapTexture == null && noMapPreviewTexture != null) | ||
{ | ||
Debug.Assert(!noMapPreviewTexture.IsDisposed, "noMapPreviewTexture should not be disposed."); | ||
mapTexture = noMapPreviewTexture; | ||
disposeTextures = false; | ||
} | ||
else | ||
{ | ||
disposeTextures = true; | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,75 @@ | ||||||
using System; | ||||||
using System.Collections.Concurrent; | ||||||
using System.Linq; | ||||||
using System.Threading; | ||||||
using System.Threading.Tasks; | ||||||
|
||||||
using DTAClient.Domain.Multiplayer; | ||||||
|
||||||
using SixLabors.ImageSharp; | ||||||
|
||||||
namespace DTAClient.DXGUI.Multiplayer | ||||||
{ | ||||||
public class MapTextureCacheManager : IDisposable | ||||||
{ | ||||||
public const int MaxCacheSize = 100; | ||||||
public const int SleepIntervalMS = 100; | ||||||
|
||||||
private readonly ConcurrentDictionary<Map, Image> mapTextures = []; | ||||||
|
||||||
private readonly ConcurrentDictionary<Map, byte> missedMaps = []; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So what do you suggest? I think we can't introduce RX here right now just because of introducing the unit type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I don't see a big problem with introducing Rx, but I guess you could make an empty type or just copy the unit type from |
||||||
|
||||||
private readonly CancellationTokenSource cancellationTokenSource = new(); | ||||||
|
||||||
public MapTextureCacheManager() => | ||||||
Task.Run(() => MapTextureLoadingService(cancellationTokenSource.Token)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no error handling and no task tracking. If your task errors out - there won't be any map previews anymore provided and the exception will not be caught. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix it by catching and recording exceptions in the while loop |
||||||
|
||||||
public void Dispose() => | ||||||
cancellationTokenSource?.Cancel(); | ||||||
|
||||||
public Image GetMapTextureIfAvailable(Map map) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will write There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO nullables should be enabled across all projects. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Rans4cker once did that. And the majority objected because it introduces too many warning messages on existing codes. So to smoothly migrate (no one expects another .NET 6 migration PR) I suggest we enable that on new files as well as files being major revised There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the main complaint was too strict StyleCop warnings, but alright. Those warnings are actually useful in this case IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's helpful. The point is there are too many warnings that would draw our attention from new warnings introduced |
||||||
{ | ||||||
if (mapTextures.TryGetValue(map, out Image image)) | ||||||
return image; | ||||||
|
||||||
if (missedMaps.Count < MaxCacheSize) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this check redundant? There seems to be no point in regulating the input size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limiting RAM usage? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You missed the point. The RAM usage regulation is going to happen in the main dictionary which actually holds the map previews. AFAIK the map list is already in memory, so there wouldn't be a point to also regulate this as the most it will save is a few bytes. Also I don't think hitting 100 maps limit in 100ms is possible at all, and the "incoming" dictionary is drained regularly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then do you suggest also removing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, only from the pending/missing maps. |
||||||
missedMaps.TryAdd(map, 0); | ||||||
|
||||||
return null; | ||||||
} | ||||||
|
||||||
private async Task MapTextureLoadingService(CancellationToken cancellationToken) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't think the functions should be called like so, the functions is "do something", not "a thing" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix it |
||||||
{ | ||||||
while (true) | ||||||
{ | ||||||
cancellationToken.ThrowIfCancellationRequested(); | ||||||
|
||||||
// Clear cache if it's too big | ||||||
if (mapTextures.Count > MaxCacheSize) | ||||||
mapTextures.Clear(); | ||||||
|
||||||
if (!missedMaps.IsEmpty) | ||||||
{ | ||||||
var missedMapCopy = missedMaps.ToArray(); | ||||||
foreach ((Map missedMap, _) in missedMapCopy) | ||||||
{ | ||||||
if (mapTextures.Count > MaxCacheSize) | ||||||
break; | ||||||
|
||||||
missedMaps.TryRemove(missedMap, out _); | ||||||
|
||||||
if (mapTextures.ContainsKey(missedMap)) | ||||||
continue; | ||||||
|
||||||
Image image = await Task.Run(missedMap.ExtractMapPreview); | ||||||
mapTextures.TryAdd(missedMap, image); | ||||||
} | ||||||
|
||||||
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. huh? No changes were made There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, I tried to remove the extra newline. Seems GitHub ate that. |
||||||
} | ||||||
|
||||||
await Task.Delay(SleepIntervalMS); | ||||||
} | ||||||
} | ||||||
|
||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just caught my attention, I don't think there should be such a flag, just the no map preview texture should be set as a singleton, and then when disposing the texture should be compared to the no map preview one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
disposeTextures
pattern is directly copied fromMapPreviewBox
class. In that class,PreloadMapPreviews
is such a texture that should not be disposed.I think we can leave this design as a future work since the client already has such a pattern for a long time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that's a trivial change to do so though, isn't that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be but it might require additional tests. Will try migrating it after I am available again