//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 loyalsoft. All rights reserved.
// Homepage: http://www.game7000.com/
// Feedback: http://www.game7000.com/
//------------------------------------------------------------
using GameFramework.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
namespace GameFramework.Resource
{
internal sealed partial class ResourceManager : GameFrameworkModule, IResourceManager
{
///
/// 资源检查器。
///
private sealed partial class ResourceChecker
{
private readonly ResourceManager m_ResourceManager;
private readonly Dictionary m_CheckInfos;
private string m_CurrentVariant;
private bool m_IgnoreOtherVariant;
private bool m_UpdatableVersionListReady;
private bool m_ReadOnlyVersionListReady;
private bool m_ReadWriteVersionListReady;
public GameFrameworkAction ResourceNeedUpdate;
public GameFrameworkAction ResourceCheckComplete;
///
/// 初始化资源检查器的新实例。
///
/// 资源管理器。
public ResourceChecker(ResourceManager resourceManager)
{
m_ResourceManager = resourceManager;
m_CheckInfos = new Dictionary();
m_CurrentVariant = null;
m_IgnoreOtherVariant = false;
m_UpdatableVersionListReady = false;
m_ReadOnlyVersionListReady = false;
m_ReadWriteVersionListReady = false;
ResourceNeedUpdate = null;
ResourceCheckComplete = null;
}
///
/// 关闭并清理资源检查器。
///
public void Shutdown()
{
m_CheckInfos.Clear();
}
///
/// 检查资源。
///
/// 当前使用的变体。
/// 是否忽略处理其它变体的资源,若不忽略,将会移除其它变体的资源。
public void CheckResources(string currentVariant, bool ignoreOtherVariant)
{
if (m_ResourceManager.m_ResourceHelper == null)
{
throw new GameFrameworkException("Resource helper is invalid.");
}
if (string.IsNullOrEmpty(m_ResourceManager.m_ReadOnlyPath))
{
throw new GameFrameworkException("Read-only path is invalid.");
}
if (string.IsNullOrEmpty(m_ResourceManager.m_ReadWritePath))
{
throw new GameFrameworkException("Read-write path is invalid.");
}
m_CurrentVariant = currentVariant;
m_IgnoreOtherVariant = ignoreOtherVariant;
m_ResourceManager.m_ResourceHelper.LoadBytes(Utility.Path.GetRemotePath(Path.Combine(m_ResourceManager.m_ReadWritePath, RemoteVersionListFileName)), new LoadBytesCallbacks(OnLoadUpdatableVersionListSuccess, OnLoadUpdatableVersionListFailure), null);
m_ResourceManager.m_ResourceHelper.LoadBytes(Utility.Path.GetRemotePath(Path.Combine(m_ResourceManager.m_ReadOnlyPath, LocalVersionListFileName)), new LoadBytesCallbacks(OnLoadReadOnlyVersionListSuccess, OnLoadReadOnlyVersionListFailure), null);
m_ResourceManager.m_ResourceHelper.LoadBytes(Utility.Path.GetRemotePath(Path.Combine(m_ResourceManager.m_ReadWritePath, LocalVersionListFileName)), new LoadBytesCallbacks(OnLoadReadWriteVersionListSuccess, OnLoadReadWriteVersionListFailure), null);
}
private void SetCachedFileSystemName(ResourceName resourceName, string fileSystemName)
{
GetOrAddCheckInfo(resourceName).SetCachedFileSystemName(fileSystemName);
}
private void SetVersionInfo(ResourceName resourceName, LoadType loadType, int length, int hashCode, int compressedLength, int compressedHashCode)
{
GetOrAddCheckInfo(resourceName).SetVersionInfo(loadType, length, hashCode, compressedLength, compressedHashCode);
}
private void SetReadOnlyInfo(ResourceName resourceName, LoadType loadType, int length, int hashCode)
{
GetOrAddCheckInfo(resourceName).SetReadOnlyInfo(loadType, length, hashCode);
}
private void SetReadWriteInfo(ResourceName resourceName, LoadType loadType, int length, int hashCode)
{
GetOrAddCheckInfo(resourceName).SetReadWriteInfo(loadType, length, hashCode);
}
private CheckInfo GetOrAddCheckInfo(ResourceName resourceName)
{
CheckInfo checkInfo = null;
if (m_CheckInfos.TryGetValue(resourceName, out checkInfo))
{
return checkInfo;
}
checkInfo = new CheckInfo(resourceName);
m_CheckInfos.Add(checkInfo.ResourceName, checkInfo);
return checkInfo;
}
private void RefreshCheckInfoStatus()
{
if (!m_UpdatableVersionListReady || !m_ReadOnlyVersionListReady || !m_ReadWriteVersionListReady)
{
return;
}
int movedCount = 0;
int removedCount = 0;
int updateCount = 0;
long updateTotalLength = 0L;
long updateTotalCompressedLength = 0L;
foreach (KeyValuePair checkInfo in m_CheckInfos)
{
CheckInfo ci = checkInfo.Value;
ci.RefreshStatus(m_CurrentVariant, m_IgnoreOtherVariant);
if (ci.Status == CheckInfo.CheckStatus.StorageInReadOnly)
{
m_ResourceManager.m_ResourceInfos.Add(ci.ResourceName, new ResourceInfo(ci.ResourceName, ci.FileSystemName, ci.LoadType, ci.Length, ci.HashCode, ci.CompressedLength, true, true));
}
else if (ci.Status == CheckInfo.CheckStatus.StorageInReadWrite)
{
if (ci.NeedMoveToDisk || ci.NeedMoveToFileSystem)
{
movedCount++;
string resourceFullName = ci.ResourceName.FullName;
string resourcePath = Utility.Path.GetRegularPath(Path.Combine(m_ResourceManager.m_ReadWritePath, resourceFullName));
if (ci.NeedMoveToDisk)
{
IFileSystem fileSystem = m_ResourceManager.GetFileSystem(ci.ReadWriteFileSystemName, false);
if (!fileSystem.SaveAsFile(resourceFullName, resourcePath))
{
throw new GameFrameworkException(Utility.Text.Format("Save as file '{0}' to '{1}' from file system '{2}' error.", resourceFullName, resourcePath, fileSystem.FullPath));
}
fileSystem.DeleteFile(resourceFullName);
}
if (ci.NeedMoveToFileSystem)
{
IFileSystem fileSystem = m_ResourceManager.GetFileSystem(ci.FileSystemName, false);
if (!fileSystem.WriteFile(resourceFullName, resourcePath))
{
throw new GameFrameworkException(Utility.Text.Format("Write resource '{0}' to file system '{1}' error.", resourceFullName, fileSystem.FullPath));
}
if (File.Exists(resourcePath))
{
File.Delete(resourcePath);
}
}
}
m_ResourceManager.m_ResourceInfos.Add(ci.ResourceName, new ResourceInfo(ci.ResourceName, ci.FileSystemName, ci.LoadType, ci.Length, ci.HashCode, ci.CompressedLength, false, true));
m_ResourceManager.m_ReadWriteResourceInfos.Add(ci.ResourceName, new ReadWriteResourceInfo(ci.FileSystemName, ci.LoadType, ci.Length, ci.HashCode));
}
else if (ci.Status == CheckInfo.CheckStatus.Update)
{
m_ResourceManager.m_ResourceInfos.Add(ci.ResourceName, new ResourceInfo(ci.ResourceName, ci.FileSystemName, ci.LoadType, ci.Length, ci.HashCode, ci.CompressedLength, false, false));
updateCount++;
updateTotalLength += ci.Length;
updateTotalCompressedLength += ci.CompressedLength;
if (ResourceNeedUpdate != null)
{
ResourceNeedUpdate(ci.ResourceName, ci.FileSystemName, ci.LoadType, ci.Length, ci.HashCode, ci.CompressedLength, ci.CompressedHashCode);
}
}
else if (ci.Status == CheckInfo.CheckStatus.Unavailable || ci.Status == CheckInfo.CheckStatus.Disuse)
{
// Do nothing.
}
else
{
throw new GameFrameworkException(Utility.Text.Format("Check resources '{0}' error with unknown status.", ci.ResourceName.FullName));
}
if (ci.NeedRemove)
{
removedCount++;
if (ci.ReadWriteUseFileSystem)
{
IFileSystem fileSystem = m_ResourceManager.GetFileSystem(ci.ReadWriteFileSystemName, false);
fileSystem.DeleteFile(ci.ResourceName.FullName);
}
else
{
string resourcePath = Utility.Path.GetRegularPath(Path.Combine(m_ResourceManager.m_ReadWritePath, ci.ResourceName.FullName));
if (File.Exists(resourcePath))
{
File.Delete(resourcePath);
}
}
}
}
if (movedCount > 0 || removedCount > 0)
{
RemoveEmptyFileSystems();
Utility.Path.RemoveEmptyDirectory(m_ResourceManager.m_ReadWritePath);
}
if (ResourceCheckComplete != null)
{
ResourceCheckComplete(movedCount, removedCount, updateCount, updateTotalLength, updateTotalCompressedLength);
}
}
private void RemoveEmptyFileSystems()
{
List removedFileSystemNames = null;
foreach (KeyValuePair fileSystem in m_ResourceManager.m_ReadWriteFileSystems)
{
if (fileSystem.Value.FileCount <= 0)
{
if (removedFileSystemNames == null)
{
removedFileSystemNames = new List();
}
m_ResourceManager.m_FileSystemManager.DestroyFileSystem(fileSystem.Value, true);
removedFileSystemNames.Add(fileSystem.Key);
}
}
if (removedFileSystemNames != null)
{
foreach (string removedFileSystemName in removedFileSystemNames)
{
m_ResourceManager.m_ReadWriteFileSystems.Remove(removedFileSystemName);
}
}
}
private void OnLoadUpdatableVersionListSuccess(string fileUri, byte[] bytes, float duration, object userData)
{
if (m_UpdatableVersionListReady)
{
throw new GameFrameworkException("Updatable version list has been parsed.");
}
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream(bytes, false);
UpdatableVersionList versionList = m_ResourceManager.m_UpdatableVersionListSerializer.Deserialize(memoryStream);
if (!versionList.IsValid)
{
throw new GameFrameworkException("Deserialize updatable version list failure.");
}
UpdatableVersionList.Asset[] assets = versionList.GetAssets();
UpdatableVersionList.Resource[] resources = versionList.GetResources();
UpdatableVersionList.FileSystem[] fileSystems = versionList.GetFileSystems();
UpdatableVersionList.ResourceGroup[] resourceGroups = versionList.GetResourceGroups();
m_ResourceManager.m_ApplicableGameVersion = versionList.ApplicableGameVersion;
m_ResourceManager.m_InternalResourceVersion = versionList.InternalResourceVersion;
m_ResourceManager.m_AssetInfos = new Dictionary(assets.Length, StringComparer.Ordinal);
m_ResourceManager.m_ResourceInfos = new Dictionary(resources.Length, new ResourceNameComparer());
m_ResourceManager.m_ReadWriteResourceInfos = new SortedDictionary(new ResourceNameComparer());
ResourceGroup defaultResourceGroup = m_ResourceManager.GetOrAddResourceGroup(string.Empty);
foreach (UpdatableVersionList.FileSystem fileSystem in fileSystems)
{
int[] resourceIndexes = fileSystem.GetResourceIndexes();
foreach (int resourceIndex in resourceIndexes)
{
UpdatableVersionList.Resource resource = resources[resourceIndex];
if (resource.Variant != null && resource.Variant != m_CurrentVariant)
{
continue;
}
SetCachedFileSystemName(new ResourceName(resource.Name, resource.Variant, resource.Extension), fileSystem.Name);
}
}
foreach (UpdatableVersionList.Resource resource in resources)
{
if (resource.Variant != null && resource.Variant != m_CurrentVariant)
{
continue;
}
ResourceName resourceName = new ResourceName(resource.Name, resource.Variant, resource.Extension);
int[] assetIndexes = resource.GetAssetIndexes();
foreach (int assetIndex in assetIndexes)
{
UpdatableVersionList.Asset asset = assets[assetIndex];
int[] dependencyAssetIndexes = asset.GetDependencyAssetIndexes();
int index = 0;
string[] dependencyAssetNames = new string[dependencyAssetIndexes.Length];
foreach (int dependencyAssetIndex in dependencyAssetIndexes)
{
dependencyAssetNames[index++] = assets[dependencyAssetIndex].Name;
}
m_ResourceManager.m_AssetInfos.Add(asset.Name, new AssetInfo(asset.Name, resourceName, dependencyAssetNames));
}
SetVersionInfo(resourceName, (LoadType)resource.LoadType, resource.Length, resource.HashCode, resource.CompressedLength, resource.CompressedHashCode);
defaultResourceGroup.AddResource(resourceName, resource.Length, resource.CompressedLength);
}
foreach (UpdatableVersionList.ResourceGroup resourceGroup in resourceGroups)
{
ResourceGroup group = m_ResourceManager.GetOrAddResourceGroup(resourceGroup.Name);
int[] resourceIndexes = resourceGroup.GetResourceIndexes();
foreach (int resourceIndex in resourceIndexes)
{
UpdatableVersionList.Resource resource = resources[resourceIndex];
if (resource.Variant != null && resource.Variant != m_CurrentVariant)
{
continue;
}
group.AddResource(new ResourceName(resource.Name, resource.Variant, resource.Extension), resource.Length, resource.CompressedLength);
}
}
m_UpdatableVersionListReady = true;
RefreshCheckInfoStatus();
}
catch (Exception exception)
{
if (exception is GameFrameworkException)
{
throw;
}
throw new GameFrameworkException(Utility.Text.Format("Parse updatable version list exception '{0}'.", exception), exception);
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
memoryStream = null;
}
}
}
private void OnLoadUpdatableVersionListFailure(string fileUri, string errorMessage, object userData)
{
throw new GameFrameworkException(Utility.Text.Format("Updatable version list '{0}' is invalid, error message is '{1}'.", fileUri, string.IsNullOrEmpty(errorMessage) ? "" : errorMessage));
}
private void OnLoadReadOnlyVersionListSuccess(string fileUri, byte[] bytes, float duration, object userData)
{
if (m_ReadOnlyVersionListReady)
{
throw new GameFrameworkException("Read-only version list has been parsed.");
}
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream(bytes, false);
LocalVersionList versionList = m_ResourceManager.m_ReadOnlyVersionListSerializer.Deserialize(memoryStream);
if (!versionList.IsValid)
{
throw new GameFrameworkException("Deserialize read-only version list failure.");
}
LocalVersionList.Resource[] resources = versionList.GetResources();
LocalVersionList.FileSystem[] fileSystems = versionList.GetFileSystems();
foreach (LocalVersionList.FileSystem fileSystem in fileSystems)
{
int[] resourceIndexes = fileSystem.GetResourceIndexes();
foreach (int resourceIndex in resourceIndexes)
{
LocalVersionList.Resource resource = resources[resourceIndex];
SetCachedFileSystemName(new ResourceName(resource.Name, resource.Variant, resource.Extension), fileSystem.Name);
}
}
foreach (LocalVersionList.Resource resource in resources)
{
SetReadOnlyInfo(new ResourceName(resource.Name, resource.Variant, resource.Extension), (LoadType)resource.LoadType, resource.Length, resource.HashCode);
}
m_ReadOnlyVersionListReady = true;
RefreshCheckInfoStatus();
}
catch (Exception exception)
{
if (exception is GameFrameworkException)
{
throw;
}
throw new GameFrameworkException(Utility.Text.Format("Parse read-only version list exception '{0}'.", exception), exception);
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
memoryStream = null;
}
}
}
private void OnLoadReadOnlyVersionListFailure(string fileUri, string errorMessage, object userData)
{
if (m_ReadOnlyVersionListReady)
{
throw new GameFrameworkException("Read-only version list has been parsed.");
}
m_ReadOnlyVersionListReady = true;
RefreshCheckInfoStatus();
}
private void OnLoadReadWriteVersionListSuccess(string fileUri, byte[] bytes, float duration, object userData)
{
if (m_ReadWriteVersionListReady)
{
throw new GameFrameworkException("Read-write version list has been parsed.");
}
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream(bytes, false);
LocalVersionList versionList = m_ResourceManager.m_ReadWriteVersionListSerializer.Deserialize(memoryStream);
if (!versionList.IsValid)
{
throw new GameFrameworkException("Deserialize read-write version list failure.");
}
LocalVersionList.Resource[] resources = versionList.GetResources();
LocalVersionList.FileSystem[] fileSystems = versionList.GetFileSystems();
foreach (LocalVersionList.FileSystem fileSystem in fileSystems)
{
int[] resourceIndexes = fileSystem.GetResourceIndexes();
foreach (int resourceIndex in resourceIndexes)
{
LocalVersionList.Resource resource = resources[resourceIndex];
SetCachedFileSystemName(new ResourceName(resource.Name, resource.Variant, resource.Extension), fileSystem.Name);
}
}
foreach (LocalVersionList.Resource resource in resources)
{
SetReadWriteInfo(new ResourceName(resource.Name, resource.Variant, resource.Extension), (LoadType)resource.LoadType, resource.Length, resource.HashCode);
}
m_ReadWriteVersionListReady = true;
RefreshCheckInfoStatus();
}
catch (Exception exception)
{
if (exception is GameFrameworkException)
{
throw;
}
throw new GameFrameworkException(Utility.Text.Format("Parse read-write version list exception '{0}'.", exception), exception);
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
memoryStream = null;
}
}
}
private void OnLoadReadWriteVersionListFailure(string fileUri, string errorMessage, object userData)
{
if (m_ReadWriteVersionListReady)
{
throw new GameFrameworkException("Read-write version list has been parsed.");
}
m_ReadWriteVersionListReady = true;
RefreshCheckInfoStatus();
}
}
}
}