Merge pull request 'update-decimation-filters' (#140) from update-decimation-filters into 2.0.3
Reviewed-on: #140
This commit was merged in pull request #140.
This commit is contained in:
@@ -1343,22 +1343,11 @@ internal static class MdlDecimator
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsBodyMaterial(mdl.Materials[mesh.MaterialIndex]);
|
||||
return ModelDecimationFilters.IsBodyMaterial(mdl.Materials[mesh.MaterialIndex]);
|
||||
}
|
||||
|
||||
private static bool IsBodyMaterial(string materialPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(materialPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalized = materialPath.Replace('\\', '/').ToLowerInvariant();
|
||||
var nameStart = normalized.LastIndexOf('/');
|
||||
var fileName = nameStart >= 0 ? normalized[(nameStart + 1)..] : normalized;
|
||||
return fileName.Contains("_bibo", StringComparison.Ordinal)
|
||||
|| fileName.EndsWith("_a.mtrl", StringComparison.Ordinal);
|
||||
}
|
||||
=> ModelDecimationFilters.IsBodyMaterial(materialPath);
|
||||
|
||||
private sealed class BodyCollisionData
|
||||
{
|
||||
|
||||
132
LightlessSync/Services/ModelDecimation/ModelDecimationFilters.cs
Normal file
132
LightlessSync/Services/ModelDecimation/ModelDecimationFilters.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace LightlessSync.Services.ModelDecimation;
|
||||
|
||||
internal static class ModelDecimationFilters
|
||||
{
|
||||
// MODELS ONLY HERE, NOT MATERIALS
|
||||
internal static readonly string[] HairPaths =
|
||||
[
|
||||
"/hair/",
|
||||
"hir.mdl",
|
||||
];
|
||||
|
||||
internal static readonly string[] ClothingPaths =
|
||||
[
|
||||
"chara/equipment/",
|
||||
"/equipment/",
|
||||
|
||||
"met.mdl",
|
||||
"top.mdl",
|
||||
"glv.mdl",
|
||||
"dwn.mdl",
|
||||
"sho.mdl",
|
||||
];
|
||||
|
||||
internal static readonly string[] AccessoryPaths =
|
||||
[
|
||||
"/accessory/",
|
||||
"chara/accessory/",
|
||||
|
||||
"ear.mdl",
|
||||
"nek.mdl",
|
||||
"wrs.mdl",
|
||||
"ril.mdl",
|
||||
"rir.mdl",
|
||||
];
|
||||
|
||||
internal static readonly string[] BodyPaths =
|
||||
[
|
||||
"/body/",
|
||||
"chara/equipment/e0000/model/",
|
||||
"chara/equipment/e9903/model/",
|
||||
"chara/equipment/e9903/model/",
|
||||
"chara/equipment/e0279/model/",
|
||||
];
|
||||
|
||||
internal static readonly string[] FaceHeadPaths =
|
||||
[
|
||||
"/face/",
|
||||
"/obj/face/",
|
||||
"/head/",
|
||||
"fac.mdl",
|
||||
];
|
||||
|
||||
internal static readonly string[] TailOrEarPaths =
|
||||
[
|
||||
"/tail/",
|
||||
"/obj/tail/",
|
||||
"/zear/",
|
||||
"/obj/zear/",
|
||||
|
||||
"til.mdl",
|
||||
"zer.mdl",
|
||||
];
|
||||
|
||||
// BODY MATERIALS ONLY, NOT MESHES
|
||||
internal static readonly string[] BodyMaterials =
|
||||
[
|
||||
"b0001_bibo.mtrl",
|
||||
"b0101_bibo.mtrl",
|
||||
|
||||
"b0001_a.mtrl",
|
||||
"b0001_b.mtrl",
|
||||
|
||||
"b0101_a.mtrl",
|
||||
"b0101_b.mtrl",
|
||||
];
|
||||
|
||||
internal static string NormalizePath(string path)
|
||||
=> path.Replace('\\', '/').ToLowerInvariant();
|
||||
|
||||
internal static bool IsHairPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, HairPaths);
|
||||
|
||||
internal static bool IsClothingPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, ClothingPaths);
|
||||
|
||||
internal static bool IsAccessoryPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, AccessoryPaths);
|
||||
|
||||
|
||||
internal static bool IsBodyPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, BodyPaths);
|
||||
|
||||
internal static bool IsFaceHeadPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, FaceHeadPaths);
|
||||
|
||||
internal static bool IsTailOrEarPath(string normalizedPath)
|
||||
=> ContainsAny(normalizedPath, TailOrEarPaths);
|
||||
|
||||
internal static bool ContainsAny(string normalizedPath, IReadOnlyList<string> markers)
|
||||
{
|
||||
for (var i = 0; i < markers.Count; i++)
|
||||
{
|
||||
if (normalizedPath.Contains(markers[i], StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsBodyMaterial(string materialPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(materialPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalized = NormalizePath(materialPath);
|
||||
var nameStart = normalized.LastIndexOf('/');
|
||||
var fileName = nameStart >= 0 ? normalized[(nameStart + 1)..] : normalized;
|
||||
foreach (var marker in BodyMaterials)
|
||||
{
|
||||
if (fileName.Contains(marker, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -348,46 +348,40 @@ public sealed class ModelDecimationService
|
||||
return true;
|
||||
}
|
||||
|
||||
var normalized = NormalizeGamePath(gamePath);
|
||||
if (normalized.Contains("/hair/", StringComparison.Ordinal))
|
||||
var normalized = ModelDecimationFilters.NormalizePath(gamePath);
|
||||
if (ModelDecimationFilters.IsHairPath(normalized))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalized.Contains("/chara/equipment/", StringComparison.Ordinal))
|
||||
if (ModelDecimationFilters.IsClothingPath(normalized))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowClothing;
|
||||
}
|
||||
|
||||
if (normalized.Contains("/chara/accessory/", StringComparison.Ordinal))
|
||||
if (ModelDecimationFilters.IsAccessoryPath(normalized))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowAccessories;
|
||||
}
|
||||
|
||||
if (normalized.Contains("/chara/human/", StringComparison.Ordinal))
|
||||
if (ModelDecimationFilters.IsBodyPath(normalized))
|
||||
{
|
||||
if (normalized.Contains("/body/", StringComparison.Ordinal))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowBody;
|
||||
}
|
||||
return _performanceConfigService.Current.ModelDecimationAllowBody;
|
||||
}
|
||||
|
||||
if (normalized.Contains("/face/", StringComparison.Ordinal) || normalized.Contains("/head/", StringComparison.Ordinal))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowFaceHead;
|
||||
}
|
||||
if (ModelDecimationFilters.IsFaceHeadPath(normalized))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowFaceHead;
|
||||
}
|
||||
|
||||
if (normalized.Contains("/tail/", StringComparison.Ordinal))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowTail;
|
||||
}
|
||||
if (ModelDecimationFilters.IsTailOrEarPath(normalized))
|
||||
{
|
||||
return _performanceConfigService.Current.ModelDecimationAllowTail;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string NormalizeGamePath(string path)
|
||||
=> path.Replace('\\', '/').ToLowerInvariant();
|
||||
|
||||
private bool TryGetDecimationSettings(out ModelDecimationSettings settings)
|
||||
{
|
||||
settings = new ModelDecimationSettings(
|
||||
|
||||
@@ -10,15 +10,18 @@ namespace LightlessSync.Services;
|
||||
public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly IpcManager _ipc;
|
||||
private readonly LightlessConfigService _config;
|
||||
private readonly TempCollectionConfigService _config;
|
||||
private readonly CancellationTokenSource _cleanupCts = new();
|
||||
private int _ran;
|
||||
private const int CleanupBatchSize = 50;
|
||||
private static readonly TimeSpan CleanupBatchDelay = TimeSpan.FromMilliseconds(50);
|
||||
private static readonly TimeSpan OrphanCleanupDelay = TimeSpan.FromDays(1);
|
||||
|
||||
public PenumbraTempCollectionJanitor(
|
||||
ILogger<PenumbraTempCollectionJanitor> logger,
|
||||
LightlessMediator mediator,
|
||||
IpcManager ipc,
|
||||
LightlessConfigService config) : base(logger, mediator)
|
||||
TempCollectionConfigService config) : base(logger, mediator)
|
||||
{
|
||||
_ipc = ipc;
|
||||
_config = config;
|
||||
@@ -31,10 +34,6 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
if (id == Guid.Empty) return;
|
||||
var changed = false;
|
||||
var config = _config.Current;
|
||||
if (config.OrphanableTempCollections.Add(id))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var existing = config.OrphanableTempCollectionEntries.FirstOrDefault(entry => entry.Id == id);
|
||||
@@ -63,8 +62,7 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
{
|
||||
if (id == Guid.Empty) return;
|
||||
var config = _config.Current;
|
||||
var changed = config.OrphanableTempCollections.Remove(id);
|
||||
changed |= RemoveEntry(config.OrphanableTempCollectionEntries, id) > 0;
|
||||
var changed = RemoveEntry(config.OrphanableTempCollectionEntries, id) > 0;
|
||||
if (changed)
|
||||
{
|
||||
_config.Save();
|
||||
@@ -79,14 +77,31 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
if (!_ipc.Penumbra.APIAvailable)
|
||||
return;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await CleanupOrphansOnBootAsync(_cleanupCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error cleaning orphaned temp collections");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task CleanupOrphansOnBootAsync(CancellationToken token)
|
||||
{
|
||||
var config = _config.Current;
|
||||
var ids = config.OrphanableTempCollections;
|
||||
var entries = config.OrphanableTempCollectionEntries;
|
||||
if (ids.Count == 0 && entries.Count == 0)
|
||||
if (entries.Count == 0)
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var changed = EnsureEntries(ids, entries, now);
|
||||
var changed = EnsureEntryTimes(entries, now);
|
||||
var cutoff = now - OrphanCleanupDelay;
|
||||
var expired = entries
|
||||
.Where(entry => entry.Id != Guid.Empty && entry.RegisteredAtUtc != DateTime.MinValue && entry.RegisteredAtUtc <= cutoff)
|
||||
@@ -105,25 +120,47 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
var appId = Guid.NewGuid();
|
||||
Logger.LogInformation("Cleaning up {count} orphaned Lightless temp collections older than {delay}", expired.Count, OrphanCleanupDelay);
|
||||
|
||||
List<Guid> removedIds = [];
|
||||
foreach (var id in expired)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id)
|
||||
.GetAwaiter().GetResult();
|
||||
await _ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogDebug(ex, "Failed removing orphaned temp collection {id}", id);
|
||||
}
|
||||
|
||||
removedIds.Add(id);
|
||||
if (removedIds.Count % CleanupBatchSize == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(CleanupBatchDelay, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in expired)
|
||||
if (removedIds.Count == 0)
|
||||
{
|
||||
ids.Remove(id);
|
||||
if (changed)
|
||||
{
|
||||
_config.Save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var id in expired)
|
||||
foreach (var id in removedIds)
|
||||
{
|
||||
RemoveEntry(entries, id);
|
||||
}
|
||||
@@ -131,6 +168,17 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_cleanupCts.Cancel();
|
||||
_cleanupCts.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private static int RemoveEntry(List<OrphanableTempCollectionEntry> entries, Guid id)
|
||||
{
|
||||
var removed = 0;
|
||||
@@ -148,29 +196,9 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
||||
return removed;
|
||||
}
|
||||
|
||||
private static bool EnsureEntries(HashSet<Guid> ids, List<OrphanableTempCollectionEntry> entries, DateTime now)
|
||||
private static bool EnsureEntryTimes(List<OrphanableTempCollectionEntry> entries, DateTime now)
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entries.Any(entry => entry.Id == id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.Add(new OrphanableTempCollectionEntry
|
||||
{
|
||||
Id = id,
|
||||
RegisteredAtUtc = now
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry.Id == Guid.Empty || entry.RegisteredAtUtc != DateTime.MinValue)
|
||||
|
||||
Reference in New Issue
Block a user