Move many mods to Deprecated folder, fix spelling

This commit is contained in:
NotAKidoS 2025-04-03 02:57:35 -05:00
parent 5e822cec8d
commit 0042590aa6
539 changed files with 7475 additions and 3120 deletions

View file

@ -0,0 +1,43 @@
namespace NAK.BetterContentLoading;
public class ConcurrentPriorityQueue<TElement, TPriority> where TPriority : IComparable<TPriority>
{
private readonly object _lock = new();
private readonly SortedDictionary<TPriority, Queue<TElement>> _queues = new();
public void Enqueue(TElement item, TPriority priority)
{
lock (_lock)
{
if (!_queues.TryGetValue(priority, out var queue))
{
queue = new Queue<TElement>();
_queues[priority] = queue;
}
queue.Enqueue(item);
}
}
public bool TryDequeue(out TElement item, out TPriority priority)
{
lock (_lock)
{
if (_queues.Count == 0)
{
item = default;
priority = default;
return false;
}
var firstQueue = _queues.First();
priority = firstQueue.Key;
var queue = firstQueue.Value;
item = queue.Dequeue();
if (queue.Count == 0)
_queues.Remove(priority);
return true;
}
}
}

View file

@ -0,0 +1,24 @@
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
private void StartBandwidthMonitor()
{
Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000);
Interlocked.Exchange(ref _bytesReadLastSecond, 0);
Interlocked.Exchange(ref _completedDownloads, 0);
}
});
}
private int ComputeUsableBandwidthPerDownload()
{
var activeCount = _activeDownloads.Count;
if (activeCount == 0) return MaxDownloadBandwidth;
return MaxDownloadBandwidth / activeCount;
}
}

View file

@ -0,0 +1,126 @@
using System.Collections.Concurrent;
using ABI_RC.Core;
using ABI_RC.Core.IO;
using ABI_RC.Core.IO.AssetManagement;
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
#region Singleton
private static DownloadManager2 _instance;
public static DownloadManager2 Instance => _instance ??= new DownloadManager2();
#endregion
#region Settings
public bool IsDebugEnabled { get; set; } = true;
public bool PrioritizeFriends { get; set; } = true;
public bool PrioritizeDistance { get; set; } = true;
public float PriorityDownloadDistance { get; set; } = 25f;
public int MaxConcurrentDownloads { get; set; } = 5;
public int MaxDownloadBandwidth { get; set; } // 100MB default
private const int THROTTLE_THRESHOLD = 25 * 1024 * 1024; // 25MB threshold for throttling
#endregion
#region State
private readonly ConcurrentDictionary<string, DownloadTask2> _activeDownloads;
private readonly ConcurrentPriorityQueue<DownloadTask2, float> _queuedDownloads;
private readonly ConcurrentDictionary<string, DownloadTask2> _completedDownloads;
private readonly object _downloadLock = new();
private long _bytesReadLastSecond;
#endregion
private DownloadManager2()
{
_activeDownloads = new ConcurrentDictionary<string, DownloadTask2>();
_queuedDownloads = new ConcurrentPriorityQueue<DownloadTask2, float>();
MaxDownloadBandwidth = 100 * 1024 * 1024;
StartBandwidthMonitor();
}
public async Task<bool> QueueAvatarDownload(DownloadInfo info, string playerId)
{
if (await ValidateAndCheckCache(info))
return true;
DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Avatar);
task2.AddTarget(playerId);
QueueDownload(task2);
return false;
}
public async Task<bool> QueuePropDownload(DownloadInfo info, string instanceId, string spawnerId)
{
if (await ValidateAndCheckCache(info))
return true;
DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.Prop);
task2.AddTarget(instanceId, spawnerId);
QueueDownload(task2);
return false;
}
public async Task<bool> QueueWorldDownload(DownloadInfo info, bool loadOnComplete)
{
if (await ValidateAndCheckCache(info))
return true;
DownloadTask2 task2 = GetOrCreateDownloadTask(info, DownloadTaskType.World, loadOnComplete);
QueueDownload(task2);
return false;
}
private async Task<bool> ValidateAndCheckCache(DownloadInfo info)
{
// Check if already cached and up to date
if (await CacheManager.Instance.IsCachedFileUpToDate(
info.AssetId,
info.FileId,
info.FileHash))
{
return true;
}
// Validate disk space
var filePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId);
if (!CVRTools.HasEnoughDiskSpace(filePath, info.FileSize))
{
BetterContentLoadingMod.Logger.Error($"Not enough disk space to download {info.AssetId}");
return false;
}
// Ensure cache directory exists
CacheManager.Instance.EnsureCacheDirectoryExists(info.AssetId);
return false;
}
private DownloadTask2 GetOrCreateDownloadTask(DownloadInfo info, DownloadTaskType type, bool loadOnComplete = false)
{
// Check if task already exists in active downloads
if (_activeDownloads.TryGetValue(info.DownloadId, out var activeTask))
return activeTask;
// Check if task exists in queued downloads
var queuedTask = _queuedDownloads.TryFind(t => t.Info.DownloadId == info.DownloadId);
if (queuedTask != null)
return queuedTask;
// Create new task
var cachePath = CacheManager.Instance.GetCachePath(info.AssetId, info.FileId);
return new DownloadTask2(info, cachePath, type, loadOnComplete);
}
public bool TryFindTask(string downloadId, out DownloadTask2 task2)
{
return _activeDownloads.TryGetValue(downloadId, out task2) ||
_completedDownloads.TryGetValue(downloadId, out task2) ||
TryFindQueuedTask(downloadId, out task2);
}
private bool TryFindQueuedTask(string downloadId, out DownloadTask2 task2)
{
task2 = _queuedDownloads.UnorderedItems
.FirstOrDefault(x => x.Element.Info.DownloadId == downloadId).Element;
return task2 != null;
}
}

View file

@ -0,0 +1,58 @@
using ABI_RC.Core.Networking.IO.Social;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Util;
using UnityEngine;
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
internal static bool TryGetPlayerEntity(string playerId, out CVRPlayerEntity playerEntity)
{
CVRPlayerEntity player = CVRPlayerManager.Instance.NetworkPlayers.Find(p => p.Uuid == playerId);
if (player == null)
{
// BetterContentLoadingMod.Logger.Error($"Player entity not found for ID: {playerId}");
playerEntity = null;
return false;
}
playerEntity = player;
return true;
}
internal static bool TryGetPropData(string instanceId, out CVRSyncHelper.PropData propData)
{
CVRSyncHelper.PropData prop = CVRSyncHelper.Props.Find(p => p.InstanceId == instanceId);
if (prop == null)
{
// BetterContentLoadingMod.Logger.Error($"Prop data not found for ID: {instanceId}");
propData = null;
return false;
}
propData = prop;
return true;
}
private static bool IsPlayerLocal(string playerId)
{
return playerId == MetaPort.Instance.ownerId;
}
private static bool IsPlayerFriend(string playerId)
{
return Friends.FriendsWith(playerId);
}
private bool IsPlayerWithinPriorityDistance(CVRPlayerEntity player)
{
if (player.PuppetMaster == null) return false;
return player.PuppetMaster.animatorManager.DistanceTo < PriorityDownloadDistance;
}
internal bool IsPropWithinPriorityDistance(CVRSyncHelper.PropData prop)
{
Vector3 propPosition = new(prop.PositionX, prop.PositionY, prop.PositionZ);
return Vector3.Distance(propPosition, PlayerSetup.Instance.GetPlayerPosition()) < PriorityDownloadDistance;
}
}

View file

@ -0,0 +1,47 @@
using ABI_RC.Core.IO;
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
private float CalculatePriority(DownloadTask2 task2)
{
return task2.Type switch
{
DownloadTaskType.Avatar => CalculateAvatarPriority(task2),
DownloadTaskType.Prop => CalculatePropPriority(task2),
DownloadTaskType.World => CalculateWorldPriority(task2),
_ => task2.Info.FileSize
};
}
private float CalculateAvatarPriority(DownloadTask2 task2)
{
float priority = task2.Info.FileSize;
if (IsPlayerLocal(task2.PlayerId))
return 0f;
if (!TryGetPlayerEntity(task2.PlayerId, out var player))
return priority;
if (PrioritizeFriends && IsPlayerFriend(task2.PlayerId))
priority *= 0.5f;
if (PrioritizeDistance && IsPlayerWithinPriorityDistance(player))
priority *= 0.75f;
// Factor in download progress
priority *= (1 + task2.Progress / 100f);
return priority;
}
private float CalculatePropPriority(DownloadTask2 task2)
{
float priority = task2.Info.FileSize;
if (IsPlayerLocal(task2.PlayerId))
return 0f;
}
}

View file

@ -0,0 +1,144 @@
using System.Diagnostics;
using System.Net.Http.Headers;
using ABI_RC.Core.IO;
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
private async Task ProcessDownload(DownloadTask2 task2)
{
using var client = new HttpClient();
// Set up resume headers if we have a resume token
if (!string.IsNullOrEmpty(task2.ResumeToken))
{
client.DefaultRequestHeaders.Range = new RangeHeaderValue(task2.BytesRead, null);
}
using var response = await client.GetAsync(task2.Info.AssetUrl, HttpCompletionOption.ResponseHeadersRead);
using var dataStream = await response.Content.ReadAsStreamAsync();
// Open file in append mode if resuming, otherwise create new
using var fileStream = new FileStream(
task2.TargetPath,
string.IsNullOrEmpty(task2.ResumeToken) ? FileMode.Create : FileMode.Append,
FileAccess.Write);
bool isEligibleForThrottle = task2.Info.FileSize > THROTTLE_THRESHOLD;
var stopwatch = new Stopwatch();
int bytesRead;
do
{
if (task2.Status == DownloadTaskStatus.Paused)
{
task2.ResumeToken = GenerateResumeToken(task2);
await fileStream.FlushAsync();
return;
}
if (task2.Status != DownloadTaskStatus.Downloading)
{
HandleCancellation(task2, dataStream, fileStream);
return;
}
stopwatch.Restart();
var lengthToRead = isEligibleForThrottle ?
ComputeUsableBandwidthPerDownload() :
16384;
var buffer = new byte[lengthToRead];
bytesRead = await dataStream.ReadAsync(buffer, 0, lengthToRead);
if (isEligibleForThrottle)
Interlocked.Add(ref _bytesReadLastSecond, bytesRead);
await fileStream.WriteAsync(buffer, 0, bytesRead);
UpdateProgress(task2, bytesRead);
} while (bytesRead > 0);
CompleteDownload(task2);
}
private string GenerateResumeToken(DownloadTask2 task2)
{
// Generate a unique token that includes file position and hash
return $"{task2.BytesRead}:{DateTime.UtcNow.Ticks}";
}
private async Task FinalizeDownload(DownloadTask2 task2)
{
var tempPath = task2.CachePath + ".tmp";
try
{
// Decrypt the file if needed
if (task2.Info.EncryptionAlgorithm != 0)
{
await DecryptFile(tempPath, task2.CachePath, task2.Info);
File.Delete(tempPath);
}
else
{
File.Move(tempPath, task2.CachePath);
}
CompleteDownload(task2);
}
catch (Exception ex)
{
// _logger.Error($"Failed to finalize download for {task.Info.AssetId}: {ex.Message}");
task2.Status = DownloadTaskStatus.Failed;
File.Delete(tempPath);
_activeDownloads.TryRemove(task2.Info.DownloadId, out _);
}
}
private async Task DecryptFile(string sourcePath, string targetPath, DownloadInfo info)
{
// Implementation of file decryption based on EncryptionAlgorithm
// This would use the FileKey from the DownloadInfo
throw new NotImplementedException("File decryption not implemented");
}
private void HandleCancellation(DownloadTask2 task2, Stream dataStream, Stream fileStream)
{
if (task2.Status != DownloadTaskStatus.Failed)
task2.Status = DownloadTaskStatus.Cancelled;
dataStream.Close();
fileStream.Close();
task2.Progress = 0;
task2.BytesRead = 0;
_activeDownloads.TryRemove(task2.Info.DownloadId, out _);
}
private void UpdateProgress(DownloadTask2 task2, int bytesRead)
{
task2.BytesRead += bytesRead;
task2.Progress = Math.Clamp(
(int)(((float)task2.BytesRead / task2.Info.FileSize) * 100f),
0, 100);
}
private void CompleteDownload(DownloadTask2 task2)
{
task2.Status = DownloadTaskStatus.Complete;
task2.Progress = 100;
_activeDownloads.TryRemove(task2.Info.DownloadId, out _);
_completedDownloads.TryAdd(task2.Info.DownloadId, task2);
lock (_downloadLock)
{
if (_queuedDownloads.TryDequeue(out var nextTask, out _))
{
StartDownload(nextTask);
}
}
}
}

View file

@ -0,0 +1,80 @@
using ABI_RC.Core.IO;
namespace NAK.BetterContentLoading;
public partial class DownloadManager2
{
public void QueueDownload(DownloadTask2 newTask2)
{
if (_completedDownloads.TryGetValue(newTask2.Info.DownloadId, out var completedTask))
{
completedTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault());
return;
}
if (TryFindTask(newTask2.Info.DownloadId, out var existingTask))
{
existingTask.AddPlayer(newTask2.PlayerIds.FirstOrDefault());
RecalculatePriority(existingTask);
return;
}
float priority = CalculatePriority(newTask2);
newTask2.CurrentPriority = priority;
lock (_downloadLock)
{
if (_activeDownloads.Count < MaxConcurrentDownloads)
{
StartDownload(newTask2);
return;
}
var lowestPriorityTask = _activeDownloads.Values
.OrderByDescending(t => t.CurrentPriority * (1 + t.Progress / 100f))
.LastOrDefault();
if (lowestPriorityTask != null && priority < lowestPriorityTask.CurrentPriority)
{
PauseDownload(lowestPriorityTask);
StartDownload(newTask2);
}
else
{
_queuedDownloads.Enqueue(newTask2, priority);
}
}
}
private void StartDownload(DownloadTask2 task2)
{
task2.Status = DownloadTaskStatus.Downloading;
_activeDownloads.TryAdd(task2.Info.DownloadId, task2);
Task.Run(() => ProcessDownload(task2));
}
private void PauseDownload(DownloadTask2 task2)
{
task2.Status = DownloadTaskStatus.Queued;
_activeDownloads.TryRemove(task2.Info.DownloadId, out _);
_queuedDownloads.Enqueue(task2, task2.CurrentPriority);
}
private void RecalculatePriority(DownloadTask2 task2)
{
var newPriority = CalculatePriority(task2);
if (Math.Abs(newPriority - task2.CurrentPriority) < float.Epsilon)
return;
task2.CurrentPriority = newPriority;
if (task2.Status == DownloadTaskStatus.Queued || task2.Status == DownloadTaskStatus.Paused)
{
// Re-enqueue with new priority
_queuedDownloads.UpdatePriority(task2, newPriority);
}
}
}

View file

@ -0,0 +1,62 @@
namespace NAK.BetterContentLoading;
public class DownloadTask2
{
public DownloadInfo Info { get; }
public DownloadTaskStatus Status { get; set; }
public DownloadTaskType Type { get; }
public float BasePriority { get; set; }
public float CurrentPriority => BasePriority * (1 + Progress / 100f);
public long BytesRead { get; set; }
public int Progress { get; set; }
public string CachePath { get; }
public Dictionary<string, string> Targets { get; } // Key: targetId (playerId/instanceId), Value: spawnerId
public bool LoadOnComplete { get; } // For worlds only
public DownloadTask2(
DownloadInfo info,
string cachePath,
DownloadTaskType type,
bool loadOnComplete = false)
{
Info = info;
CachePath = cachePath;
Type = type;
LoadOnComplete = loadOnComplete;
Targets = new Dictionary<string, string>();
Status = DownloadTaskStatus.Queued;
}
public void AddTarget(string targetId, string spawnerId = null)
{
if (Type == DownloadTaskType.World && Targets.Count > 0)
throw new InvalidOperationException("World downloads cannot have multiple targets");
Targets[targetId] = spawnerId;
}
public void RemoveTarget(string targetId)
{
Targets.Remove(targetId);
}
}
public enum DownloadTaskType
{
Avatar,
Prop,
World
}
public enum DownloadTaskStatus
{
Queued,
Downloading,
Paused,
Complete,
Failed,
Cancelled
}