fish-station/Content.Client/_Sunrise/NetTexturesManager.cs

239 lines
8.4 KiB
C#

using Content.Shared._Sunrise.NetTextures;
using Robust.Client.Upload;
using Robust.Shared.ContentPack;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client._Sunrise;
/// <summary>
/// Manager that handles dynamic loading of network textures from server.
/// Textures are loaded into MemoryContentRoot on the client.
/// </summary>
public sealed class NetTexturesManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly NetworkResourceManager _networkResourceManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
private const string UploadedPrefix = "/Uploaded";
private readonly HashSet<string> _requestedResources = new();
private readonly Dictionary<string, ResPath> _pendingResources = new(); // resourcePath -> ResPath
/// <summary>
/// Event fired when a network texture becomes available.
/// </summary>
public event Action<string>? ResourceLoaded; // resourcePath
public void Initialize()
{
_sawmill = _logManager.GetSawmill("network.textures");
// NetworkResourceUploadMessage is already registered by SharedNetworkResourceManager
// We'll check for loaded resources in Update() method very frequently
}
public void Update(float frameTime)
{
// If there are no pending resources, skip checking
if (_pendingResources.Count == 0)
return;
// Check for loaded resources every frame when there are pending resources
// This ensures we catch resources as soon as they're loaded
var completedResources = new List<string>();
foreach (var (resourcePath, resPath) in _pendingResources)
{
// Check if resource is available
// MemoryContentRoot stores paths relative to /Uploaded prefix
var relativePath = resPath.ToRelativePath();
bool exists = false;
ResPath checkPath;
// For RSI directories, check for meta.json file
// Check if path ends with .rsi (more reliable than Extension property)
var pathStr = relativePath.ToString();
if (pathStr.EndsWith(".rsi") || pathStr.EndsWith(".rsi/"))
{
checkPath = relativePath / "meta.json";
exists = _networkResourceManager.FileExists(checkPath);
}
else
{
// Single file
checkPath = relativePath;
exists = _networkResourceManager.FileExists(checkPath);
}
if (exists)
{
_requestedResources.Add(resourcePath);
completedResources.Add(resourcePath);
ResourceLoaded?.Invoke(resourcePath);
}
}
foreach (var resourcePath in completedResources)
{
_pendingResources.Remove(resourcePath);
}
}
/// <summary>
/// Checks if a network texture is available, and requests it if not.
/// </summary>
/// <param name="resourcePath">Path to the resource (as specified in prototype, e.g., "/NetTextures/Lobby/Animations/bar.rsi")</param>
/// <returns>True if the resource is available, false if it's being requested</returns>
public bool EnsureResource(string resourcePath)
{
// Normalize the path
ResPath resPath;
if (resourcePath.StartsWith("/"))
{
resPath = new ResPath(resourcePath);
}
else
{
var rootPath = new ResPath("/");
resPath = rootPath / resourcePath;
}
// Check if the resource is actually available
var relativePath = resPath.ToRelativePath();
var uploadedPath = (new ResPath(UploadedPrefix) / relativePath).ToRootedPath();
bool isAvailable = false;
// For RSI directories, check for meta.json
// Check if path ends with .rsi (more reliable than Extension property)
var pathStr = relativePath.ToString();
if (pathStr.EndsWith(".rsi") || pathStr.EndsWith(".rsi/"))
{
var metaPath = relativePath / "meta.json";
var metaUploadedPath = (new ResPath(UploadedPrefix) / metaPath).ToRootedPath();
isAvailable = _networkResourceManager.FileExists(metaPath) || _resourceManager.ContentFileExists(metaUploadedPath);
}
else
{
// Single file
isAvailable = _networkResourceManager.FileExists(relativePath) || _resourceManager.ContentFileExists(uploadedPath);
}
if (isAvailable)
{
// Resource is available
if (!_requestedResources.Contains(resourcePath))
_requestedResources.Add(resourcePath);
// Remove from pending if it was there
_pendingResources.Remove(resourcePath);
return true;
}
// Resource is not available yet
// If it's already in pending, we're already tracking it
if (_pendingResources.ContainsKey(resourcePath))
{
return false;
}
// If it was requested before but not in pending, add it to pending to track it
if (_requestedResources.Contains(resourcePath))
{
_pendingResources[resourcePath] = resPath;
return false;
}
// Request the resource for the first time
RequestResource(resourcePath);
_pendingResources[resourcePath] = resPath;
// Immediately check if the resource is already available (might be cached or loaded very fast)
// This helps catch resources that load synchronously
CheckResourceImmediately(resourcePath, resPath);
return false;
}
private void RequestResource(string resourcePath)
{
if (_requestedResources.Contains(resourcePath))
return;
// Check if client is connected to server before trying to send message
if (!_netManager.IsConnected)
{
_sawmill.Debug($"Cannot request resource {resourcePath}: client not connected to server");
return;
}
_requestedResources.Add(resourcePath);
var msg = new RequestNetworkResourceMessage
{
ResourcePath = resourcePath
};
_netManager.ClientSendMessage(msg);
}
/// <summary>
/// Immediately checks if a resource is available and fires the event if it is.
/// This helps catch resources that load very quickly.
/// </summary>
private void CheckResourceImmediately(string resourcePath, ResPath resPath)
{
var relativePath = resPath.ToRelativePath();
bool exists = false;
// For RSI directories, check for meta.json file
// Check if path ends with .rsi (more reliable than Extension property)
var pathStr = relativePath.ToString();
if (pathStr.EndsWith(".rsi") || pathStr.EndsWith(".rsi/"))
{
var metaRelativePath = relativePath / "meta.json";
exists = _networkResourceManager.FileExists(metaRelativePath);
}
else
{
// Single file
exists = _networkResourceManager.FileExists(relativePath);
}
if (exists)
{
if (!_requestedResources.Contains(resourcePath))
_requestedResources.Add(resourcePath);
_pendingResources.Remove(resourcePath);
ResourceLoaded?.Invoke(resourcePath);
}
}
/// <summary>
/// Gets the uploaded path for a network texture.
/// </summary>
/// <param name="resourcePath">Original resource path from prototype</param>
/// <returns>Rooted path to the resource in MemoryContentRoot</returns>
public ResPath GetUploadedPath(string resourcePath)
{
ResPath resPath;
if (resourcePath.StartsWith("/"))
{
resPath = new ResPath(resourcePath);
}
else
{
resPath = new ResPath("/") / resourcePath;
}
var relativePath = resPath.ToRelativePath();
var path = new ResPath(UploadedPrefix) / relativePath;
return path.ToRootedPath(); // Ensure it's always rooted
}
}