[ME] Local / deduped textures #370
No reviewers
Labels
No labels
bug
dependencies
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
IllusionMods/KK_Plugins!370
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "Local-Textures"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
That's a lot of changes. Functionally it seems correct, just badly in need of some refactoring. Avoid changing privates into internals whenever possible since it promotes spaghetti code that's hard to maintain later.
Why not use the SceneLoad event instead? You can then invoke delayed from the event handler, if it's necessary.
Not a fan of exposing internals specific to scenes, ideally there would be a cleaner way to do it.
Since base.ExportTexture is always overriden why not make it abstract and move both logic branches to ExportTextureOriginal? Name it more appropriately then though.
Rather than call into studio directly from CharaController maybe the entire texture saving process from both charas and scenes should be extracted to a third class that deals just with that?
@ -252,0 +276,4 @@ConfigLocalTexturePath.Value = "";return;}if (ConfigLocalTexturePath.Value.Split(Path.GetInvalidPathChars()).Length == 1)If it's set in the modding api then should it still be a setting in ME? That seems redundant and likely to cause issues. If it's an override then it should be called as such, and probably marked as a debug feature instead.
Auditing and all of the related members should really be put into a separate class. Same with everything related to texture deduping, it should be split as cleanly as possible from the rest of ME code to keep it easy to maintain.
This is naughty. If you need the hash then expose just the hash in a new method or prop, eg GetTextureHash.
Pull Request Overview
This PR updates package dependencies and implements a new texture storage system for MaterialEditor. The changes introduce three texture save modes (Bundled, Local, and Deduped) to optimize file sizes and texture management, along with a local texture audit tool to identify unused and missing texture files.
Key changes:
Reviewed Changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 40 comments.
Show a summary per file
Comments suppressed due to low confidence (1)
src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.cs:72
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -39,1 +45,4 @@/// </summary>public static string LocalTexturePath = LocalTexturePathDefault;/// <summary>/// Saved material editsCall to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
The chained equality comparison
isLoading == wasLoading == falsedoes not work as intended in C#. This evaluates as(isLoading == wasLoading) == false, which checks if the boolean result of the first comparison equals false. Replace withif (!isLoading && !wasLoading && counter == 0)orif (isLoading == false && wasLoading == false && counter == 0).[nitpick] Missing space between 'while' and parenthesis. Should be
while (true)for consistency with C# style conventions.Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
@ -31,17 +34,21 @@ namespace KK_Plugins.MaterialEditor/// </summary>public class MaterialEditorCharaController : CharaCustomFunctionControllerField 'charaControllers' can be 'readonly'.
Variable sceneController.DedupedTextureData may be null at this access as suggested by this null check.
Field 'auditFoundHashToFiles' can be 'readonly'.
Field 'auditLock' can be 'readonly'.
@ -252,0 +276,4 @@ConfigLocalTexturePath.Value = "";return;}if (ConfigLocalTexturePath.Value.Split(Path.GetInvalidPathChars()).Length == 1)[nitpick] Trailing comma after the last property in the ConfigurationManagerAttributes object initializer is inconsistent with other entries. Remove the comma for consistency.
Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
The hardcoded cutoff date (October 21, 2025) will become outdated quickly. Consider making this configurable, using a relative date (e.g., DateTime.Now.AddMonths(-1)), or explaining why this specific date was chosen in a comment.
[nitpick] Missing space between 'while' and parenthesis. Should be
while (true)for consistency with C# style conventions.This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Write to static field from instance method, property, or constructor.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Call to 'System.IO.Path.Combine'.
Poor error handling: empty catch block.
Inefficient use of 'ContainsKey' and indexer.
Inefficient use of 'ContainsKey' and indexer.
Variable MaterialEditorPlugin.auditUnusedTextures may be null at this access as suggested by this null check.
Variable MaterialEditorPlugin.auditUnusedTextures may be null at this access as suggested by this null check.
These 'if' statements can be combined.
Generic catch clause.
Generic catch clause.
@ -55,7 +55,8 @@ namespace KK_PluginsCasting a long hash to int truncates the upper 32 bits, which may lead to hash collisions. Consider using HashCode.Combine or implementing a proper hash code that incorporates all bits of the long value, such as
return (int)(hash ^ (hash >> 32)).At least make the tower smaller by inverting them.
At least make the tower smaller by inverting them.
Fair point
@ -252,0 +276,4 @@ConfigLocalTexturePath.Value = "";return;}if (ConfigLocalTexturePath.Value.Split(Path.GetInvalidPathChars()).Length == 1)It's only for the autosaves, not the normal saves.
...I guess the autosave overrides can be added to KKAPI instead, then.
Because it was 4AM probably and I didn't wanna test which order Scene / Characontrollers fired their loads... :D
But it seems charas always go first, which is perfect, so I can just clear the variable when scenecontroller is done
Did so
I'd have to duplicate even more code in that case, this lets the Maker / Studio side figure out the specifics and then the actual export code is in the base class
The Maker / Studio UIs need access to it for exporting original files. I could add a Property, but that's about the same.
@ -252,0 +276,4 @@ConfigLocalTexturePath.Value = "";return;}if (ConfigLocalTexturePath.Value.Split(Path.GetInvalidPathChars()).Length == 1)Then it should explicitly state that it's for Autosave
Pull Request Overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 16 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Call to 'System.IO.Path.Combine'.
The magic string "XXX" is used as a sentinel value for unknown image types. Consider defining this as a named constant (e.g.,
private const string UnknownImageType = \"XXX\";) to improve code clarity and maintainability.Write to static field from instance method, property, or constructor.
The magic string "XXX" is used as a sentinel value for unknown image types. Consider defining this as a named constant (e.g.,
private const string UnknownImageType = \"XXX\";) to improve code clarity and maintainability.@ -0,0 +1,186 @@using System.Collections.Generic;The condition
!= nullis checking if the return value ofTryGetValueis not null, butTryGetValuereturns a bool, not a nullable type. This will always be true. The condition should be== trueor simply omit the comparison:if (MEStudio.GetSceneController().GetExtendedData()?.data.TryGetValue(..., out var dataBytes) == true && dataBytes != null).This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
Variable dict may be null at this access because of this assignment.
Variable dict may be null at this access because of this assignment.
Variable dict may be null at this access because of this assignment.
Generic catch clause.
@ -0,0 +29,4 @@dedupedTexSavePrefix,dedupedTexSavePostfix,localTexUnusedFolder) { Instance = this; }Write to static field from instance method, property, or constructor.
@ -0,0 +145,4 @@foreach (var kvp in hashDict){string fileName = LocalTexPrefix + kvp.Value + "." + ImageTypeIdentifier.Identify(dict[kvp.Key].Data);string filePath = Path.Combine(LocalTexturePath, fileName);Call to 'System.IO.Path.Combine'.
@ -249,6 +266,32 @@ namespace KK_Plugins.MaterialEditorLoadNormalMapConverter();Calling
.ToLower()on both strings creates two new string allocations. Consider usingStringComparison.OrdinalIgnoreCasewithStartsWithfor better performance:ConfigLocalTexturePath.Value.StartsWith(Paths.GameRootPath, StringComparison.OrdinalIgnoreCase).Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
Call to 'System.IO.Path.Combine'.
@ -22,14 +22,14 @@ namespace KK_Pluginsinternal class TextureKeyThe hardcoded values
1 << 11(2048) and1 << 9(512) for the hash calculation parameters lack explanation. Consider adding a comment explaining why these specific values are chosen for the start and end sizes when calculating the texture hash.