一篇资源更新流程记录

 

最近在看进入游戏做资源更新流程相关的东西,结合项目的实际流程,做个学习和记录,这里是基于GF的。可以作为参考。

在GF中,将游戏划分为了多个阶段,被称为流程(Procedure),这是基于状态机封装实现的。下文的叙述皆基于此概念,在不同的框架中,可能有不同的叫法(比如状态机),但大体的思路都是差不多的。

ProcedureLaunch

进入游戏流程,进入游戏后一开始的流程。

  • 在这里主要做一些基础的工作,比如设置资源变体;
  • 设置转屏;
  • 初始化游戏的构建信息,供游戏逻辑读取;
  • 语言配置

ProcedureSplash

显示等待画面,等待视频,防沉迷提示。加载常用的GM指令

ProcedureCheckVersion

检查版本流程

  • 检查联网,检查磁盘空间等,权限等。

    //联网/权限
    if(GameEntry.SystemInfo.Device.InternetReachability == NetworkReachability.NotReachable)
    {
        ...
    }
      
    if(GameEntry.UpdateResource.MinDesk > m_freeSpaceSize)
    {
        ...
    }
      
    ...
    //向目录服请求游戏的版本信息 
    GameEntry.WebRequest.AddWebRequest(gameVersionURL, wwwForm, this);
    
  • 在这之后,获取设备信息不,根据平台版本,去目录服获取版本信息

  • 版本信息存储为一个json格式,包含了以下几个字段:

    • InternalGameVersion,InternalResourceVersion,VersionListLength,VersionListHashCode,VersionListZipLenght,VersionListZipHashCode,GameUpdateUrl,StandByGameUpdateUrl
  • 下载完成后解析,根据解析结果,判定是否需要强更(换包更新),以及获取游戏热更的资源地址(资源服)

  • 如果需要强更,就直接打开相应的URL或者走相应的应用商店

  • 检查目录服版本信息的资源版本号是否与本地读写区的资源版本号是否一致 一致则不需要更新

  • 如果需要更新,则调用GameEntry.Resosurce.UpdateVersionList进行资源热更

    //从资源服下载GameFrameworkVersion.dat,这是一个在打包时生成的包含了进包资源信息的列表文件
    //下载完成后会将他和本地的资源列表文件进行比较,找出哪些资源需要进行更新,哪些不需要,哪些要删除
    //本地的资源列表文件是GameFrameworkList.dat
    GameEntry.Resource.UpdateVersionList(m_VersionInfo.VersionListLength, m_VersionInfo.VersionListHashCode, m_VersionInfo.VersionListZipLength, m_VersionInfo.VersionListZipHashCode, m_UpdateVersionListCallbacks);
    
  • 下载资源版本文件完成后,进入ProcedureCheckResources,检查客户端资源

ProcedureCheckResources

资源检查流程,该流程的主要作用是对比本地和下载到的版本资源信息,这一不是为了找出要处理的资源(比如有的资源需要更新就下载,有的删掉了,有的变更位置了等)

使用GameEntry.Resource.CheckResources(CallBack)来检查资源,一路调用走到ResourceChecker.CheckResource(string currentVariant, bool ignoreOtherVariant)方法。

		   /// <summary>
            /// 首先尝试恢复读写区版本资源列表(GameFrameworkList.dat)。
            /// </summary>
            private bool TryRecoverReadWriteVersionList()
            {
                string file = Utility.Path.GetRegularPath(Path.Combine(m_ResourceManager.m_ReadWritePath, LocalVersionListFileName));
                string backupFile = Utility.Text.Format("{0}.{1}", file, BackupExtension);
                try
                {
                    if (!File.Exists(backupFile))
                        return false;

                    if (File.Exists(file))
                        File.Delete(file);

                    File.Move(backupFile, file);
                }
                catch
                {
                    return false;
                }
                return true;
            }

然后分别读取三个位置的版本资源信息文件进行比对,三次读取分别是:

  • 读取从资源服务器上下载的资源信息文件(这次更下来的)
  • 读取只读区,即apk里带的资源信息文件(包里带的)
  • 读取读写区,即上一次从资源服务器上更新的资源会放在读写区,并生成资源信息文件(上次更下来的)
//ReadWritePath:persitentDataPath/temporaryCachePath
//readOnlyPath:streamingAssetsPath
//readWritePath:persitentDataPath/temporaryCachePath
//LocalVersionListFileName:GameFrameworkList.dat
//RemoteVersionListFileName:GameFrameworkVersion.dat
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);
            //读取远程资源信息文件GameFrameworkVersion.dat
		    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);
                    //读取到的bytes解析成一个UpdatableVersionList类型的对象,他存储了所有的资源信息
                    UpdatableVersionList versionList = m_ResourceManager.m_UpdatableVersionListSerializer.Deserialize(memoryStream);
                    if (!versionList.IsValid)
                    {
                        throw new GameFrameworkException("Deserialize updatable version list failure.");
                    }
                    //获取所有的assets,resource,对应的文件系统,资源组,资源版本号等
                    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<string, AssetInfo>(assets.Length, StringComparer.Ordinal);
                    m_ResourceManager.m_ResourceInfos = new Dictionary<ResourceName, ResourceInfo>(resources.Length, new ResourceNameComparer());
                    m_ResourceManager.m_ReadWriteResourceInfos = new SortedDictionary<ResourceName, ReadWriteResourceInfo>(new ResourceNameComparer());
                    ResourceGroup defaultResourceGroup = m_ResourceManager.GetOrAddResourceGroup(string.Empty);

                    //将resource和缓存到其所属的文件系统中
                    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);
                        }
                    }//end foreach
                    
                    //将resource和所属的asset进行关联,并且设置asset的版本信息
                    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);
                    }//end foreach
                    
                    //将resource和所属的resource group进行关联
                    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)
                {
                    //...
                }
                finally
                {
                    //...
                }
            }
            //读取本地资源信息文件
		   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
                {
                    //步骤和上面一样
                }
                catch (Exception exception)
                {
                    //...
                }
                finally
                {
                    //...
                }
            }
            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
                {
                    //步骤和上面一样
                }
                catch (Exception exception)
                {
                    //...
                }
                finally
                {
                    //...
                }
            }

通过UpdatableVersionListSerializer解析版本资源信息并生成UpdatableVersionList实例,它表示可更新模式版本资源列表。

		/// <summary>
        /// 反序列化可更新模式版本资源列表(版本 2)回调函数。
        /// </summary>
        /// <param name="stream">指定流。</param>
        /// <returns>反序列化的可更新模式版本资源列表(版本 2)。</returns>
        public static UpdatableVersionList UpdatableVersionListDeserializeCallback_V2(Stream stream)
        {
            using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8))
            {
                //读取CachedHashBytesLength数量的字节作为计算资源正确性的hash key
                byte[] encryptBytes = binaryReader.ReadBytes(CachedHashBytesLength);
                //读游戏版本,资源版本
                string applicableGameVersion = binaryReader.ReadEncryptedString(encryptBytes);
                int internalResourceVersion = binaryReader.Read7BitEncodedInt32();
                int assetCount = binaryReader.Read7BitEncodedInt32();
                UpdatableVersionList.Asset[] assets = assetCount > 0 ? new UpdatableVersionList.Asset[assetCount] : null;
                for (int i = 0; i < assetCount; i++)
                {
                    //获取具体的资源信息
                    string name = binaryReader.ReadEncryptedString(encryptBytes);
                    //依赖
                    int dependencyAssetCount = binaryReader.Read7BitEncodedInt32();
                    int[] dependencyAssetIndexes = dependencyAssetCount > 0 ? new int[dependencyAssetCount] : null;
                    for (int j = 0; j < dependencyAssetCount; j++)
                    {
                        dependencyAssetIndexes[j] = binaryReader.Read7BitEncodedInt32();
                    }

                    assets[i] = new UpdatableVersionList.Asset(name, dependencyAssetIndexes);
                }

                int resourceCount = binaryReader.Read7BitEncodedInt32();
                UpdatableVersionList.Resource[] resources = resourceCount > 0 ? new UpdatableVersionList.Resource[resourceCount] : null;
                //接下来处理resource...
                for (int i = 0; i < resourceCount; i++)
                {
                    //...
                    //...
                    //...
                    
                    string name = binaryReader.ReadEncryptedString(encryptBytes);
                    string variant = binaryReader.ReadEncryptedString(encryptBytes);
                    string extension = binaryReader.ReadEncryptedString(encryptBytes) ?? DefaultExtension;
                    
                    //...
                    //...
                    //...
                }

                //返回UpdatableVersionList实例
                return new UpdatableVersionList(applicableGameVersion, internalResourceVersion, assets, resources, fileSystems, resourceGroups);
            }
        }

解析资源路径:

	/// <summary>
    /// 从二进制流读取加密字符串。
    /// </summary>
    /// <param name="binaryReader">要读取的二进制流。</param>
    /// <param name="encryptBytes">密钥数组。</param>
    /// <returns>读取的字符串。</returns>
    public static string ReadEncryptedString(this BinaryReader binaryReader, byte[] encryptBytes)
    {
        //先读一个字节作为路径长度,
        byte length = binaryReader.ReadByte();
        if (length <= 0)
        {
            return null;
        }

        if (length > byte.MaxValue)
        {
            throw new GameFrameworkException("String is too long.");
        }

        //根据长度读相应的字节,然后解析成资源路径
        for (byte i = 0; i < length; i++)
        {
            s_CachedBytes[i] = binaryReader.ReadByte();
        }

        Utility.Encryption.GetSelfXorBytes(s_CachedBytes, 0, length, encryptBytes);
        string value = Utility.Converter.GetString(s_CachedBytes, 0, length);
        Array.Clear(s_CachedBytes, 0, length);
        return value;
    }

反序列化解析器则是在资源组件初始化注册好的

  • 检查【要检查的资源集合】,CheckInfo实例根据当前资源变体,刷新状态检查。
  • 资源在只读区,说明是包体自带的(StreamingAssets),保存该资源信息,方便后续使用;
  • 在读写区说明更新过,对于变更了位置的文件系统的文件,处理他们的变更,然后保存资源方便后续使用;,对于在读写区但是需要更新的文件,对比服务器资源版本文件和本地文件的哈希值
  • 不在只读区也不在读写区说明资源需要更新,加入下载列表;
  • 当所有文件都检查完成后,调用RefreshCheckInfoStatus对所有资源进行检查,然后调用回调OnCheckResourcesComplete
  • 在RefreshCheckInfoStatus中,会将需要更新的资源加入到ResourceUpdater的待更新资源列表中,以便在后续的更新流程中拿到要更新的资源
        /// <summary>
        /// 资源检查完成回调
        /// </summary>
        /// <param name="movedCount">改变位置的文件数量</param>
        /// <param name="removedCount">移除的文件数量</param>
        /// <param name="updateCount">要更新的文件数量</param>
        /// <param name="updateTotalLength">要更新的文件总大小</param>
        /// <param name="updateTotalCompressedLength">要更新的文件压缩后的总大小</param>
		private void OnCheckResourcesComplete(int movedCount, int removedCount, int updateCount, long updateTotalLength, long updateTotalCompressedLength)
        {
            //看一下需要更新的资源数量,0表示不用更新,直接进游戏,否则表示需要更新,此时根据要更新的资源信息再检查一下磁盘空间。将资源更新标记设置为true
            //没有需要更新的文件
            if (updateCount <= 0)
            {
                GameEntry.UpdateResource.NeedUpdateResource = false;
                m_CheckResourcesComplete = true;
                return;
            }
            else
            {
                GameEntry.UpdateResource.NeedUpdateResource = true;
            }
            //保存更新信息,以便后续更新对比使用
		   this.m_UpdateCount = updateCount;
            this.m_UpdateTotalLength = updateTotalLength;
            this.m_UpdateTotalCompressedLength = updateTotalCompressedLength;
            GameEntry.UpdateResource.SetNeedDownLoadTotalCompressedLength(updateTotalCompressedLength);
            
            //...
            //下载弹窗什么的
        }

ProcedureUpdatePremier

更新Premier资源组的资源,一般包含首选项,比如全局的配置文件,表格,字典等数据。

检查GameEntry.UpdateResource.NeedUpdateResource是否需要进行更新,这在前一个流程中已经被设置

		   //获取对应的资源组,检查状态,检查是否需要更新
		   m_ResourceGroup = GameEntry.Resource.GetResourceGroup("Premier");
            //m_ResourceGroup.Ready意思是属于这个分组的资源都更新完成了 或者 没有需要更新的
            if (!GameEntry.UpdateResource.NeedUpdateResource || m_ResourceGroup.Ready)
            {
                m_UpdateComplete = true;
                return;
            }
		   //设置要更新的压缩资源大小,然后调用,GameEntry.Resource.UpdateResources更新对应资源组的资源
            GameEntry.UpdateResource.SetTotalCompressedLength(m_ResourceGroup.TotalCompressedLength - m_ResourceGroup.ReadyLength);
            GameEntry.Resource.UpdateResources("Premier", OnUpdateResourcesComplete);
            GameEntry.UpdateResource.OnCheckNetWork();

ResourceUpdater.UpdateResources:

            /// <summary>
            /// 更新指定资源组的资源。
            /// </summary>
            /// <param name="resourceGroup">要更新的资源组。</param>
            public void UpdateResources(ResourceGroup resourceGroup)
            {
                //一些判空检查...
                //...
                //...
                //...
                //如果不传入资源组,就全部更新
                if (string.IsNullOrEmpty(resourceGroup.Name))
                {
                    foreach (KeyValuePair<ResourceName, UpdateInfo> updateInfo in m_UpdateCandidateInfo)
                    {
                        m_UpdateWaitingInfo.Add(updateInfo.Value);
                    }

                    m_UpdateCandidateInfo.Clear();
                }
                else
                {
                    //添加到【要更新的资源】集合内
                    ResourceName[] resourceNames = resourceGroup.InternalGetResourceNames();
                    foreach (ResourceName resourceName in resourceNames)
                    {
                        UpdateInfo updateInfo = null;
                        if (!m_UpdateCandidateInfo.TryGetValue(resourceName, out updateInfo))
                        {
                            continue;
                        }

                        m_UpdateWaitingInfo.Add(updateInfo);
                        m_UpdateCandidateInfo.Remove(resourceName);
                    }
                }
                m_UpdatingResourceGroup = resourceGroup;
                m_FailureFlag = false;
            }

接下来其实是刷帧去调用ResourceUpdater.Update,来检查下载资源的各种状态

		   /// <summary>
            /// 资源更新器轮询。
            /// </summary>
            public void Update(float elapseSeconds, float realElapseSeconds)
            {
                if (m_ApplyingResourcePackStream != null)
                {
                    while (m_ApplyWaitingInfo.Count > 0)
                    {
                        ApplyInfo applyInfo = m_ApplyWaitingInfo[0];
                        m_ApplyWaitingInfo.RemoveAt(0);
                        if (ApplyResource(applyInfo))
                        {
                            return;
                        }
                    }

                    Array.Clear(m_CachedBytes, 0, CachedBytesLength);
                    string resourcePackPath = m_ApplyingResourcePackPath;
                    m_ApplyingResourcePackPath = null;
                    m_ApplyingResourcePackStream.Dispose();
                    m_ApplyingResourcePackStream = null;
                    if (ResourceApplyComplete != null)
                    {
                        ResourceApplyComplete(resourcePackPath, !m_FailureFlag, m_UpdateCandidateInfo.Count <= 0);
                    }
                }
                
                //拿出要更新的资源,,用download agent进行更新
                if (m_UpdateWaitingInfo.Count > 0)
                {
                    if (m_DownloadManager.FreeAgentCount > 0)
                    {
                        UpdateInfo updateInfo = m_UpdateWaitingInfo[0];
                        m_UpdateWaitingInfo.RemoveAt(0);
                        string resourceFullNameWithCrc32 = updateInfo.ResourceName.Variant != null ? Utility.Text.Format("{0}.{1}.{2:x8}.{3}", updateInfo.ResourceName.Name, updateInfo.ResourceName.Variant, updateInfo.HashCode, DefaultExtension) : Utility.Text.Format("{0}.{1:x8}.{2}", updateInfo.ResourceName.Name, updateInfo.HashCode, DefaultExtension);
                        m_DownloadManager.AddDownload(updateInfo.ResourcePath, Utility.Path.GetRemotePath(Path.Combine(m_ResourceManager.m_UpdatePrefixUri, resourceFullNameWithCrc32)), updateInfo);
                        m_UpdatingCount++;
                    }

                    return;
                }
                
                //对应组的资源更完了,走回调
                if (m_UpdatingResourceGroup != null && m_UpdatingCount <= 0)
                {
                    ResourceGroup updatingResourceGroup = m_UpdatingResourceGroup;
                    m_UpdatingResourceGroup = null;
                    if (ResourceUpdateComplete != null)
                    {
                        ResourceUpdateComplete(updatingResourceGroup, !m_FailureFlag, m_UpdateCandidateInfo.Count <= 0);
                    }

                    return;
                }
            }

下载完成调用OnDownloadSuccess,写入相应资源,更新文件系统,完成后调用ResourceUpdateComplete完成更新

            //下载完成
            private void OnDownloadSuccess(object sender, DownloadSuccessEventArgs e)
            {
                UpdateInfo updateInfo = e.UserData as UpdateInfo;
                if (updateInfo == null)
                {
                    return;
                }

                using (FileStream fileStream = new FileStream(e.DownloadPath, FileMode.Open, FileAccess.ReadWrite))
                {
                    //和文件的压缩大小不一致,表示文件被压缩了
                    bool compressed = updateInfo.Length != updateInfo.CompressedLength || updateInfo.HashCode != updateInfo.CompressedHashCode;
                    int length = (int)fileStream.Length;
                    if (length != updateInfo.CompressedLength)
                    {
                        //...
                        return;
                    }
                    
                    //文件是否压缩
                    if (compressed)
                    {
                        fileStream.Position = 0L;
                        int hashCode = Utility.Verifier.GetCrc32(fileStream);
                        if (hashCode != updateInfo.CompressedHashCode)
                        {
                            //...
                            return;
                        }

                        if (m_ResourceManager.m_DecompressCachedStream == null)
                        {
                            m_ResourceManager.m_DecompressCachedStream = new MemoryStream();
                        }

                        try
                        {
                            fileStream.Position = 0L;
                            m_ResourceManager.m_DecompressCachedStream.Position = 0L;
                            m_ResourceManager.m_DecompressCachedStream.SetLength(0L);
                            if (!Utility.Compression.Decompress(fileStream, m_ResourceManager.m_DecompressCachedStream))
                            {
                                //...
                                return;
                            }

                            if ((int)m_ResourceManager.m_DecompressCachedStream.Length != updateInfo.Length)
                            {
                                //...
                                return;
                            }

                            fileStream.Position = 0L;
                            fileStream.SetLength(0L);
                            //写入bytes,写入对应的文件系统
                            fileStream.Write(m_ResourceManager.m_DecompressCachedStream.GetBuffer(), 0, (int)m_ResourceManager.m_DecompressCachedStream.Length);
                        }
                        catch (Exception exception)
                        {
                            //...
                            return;
                        }
                        finally
                        {
                            m_ResourceManager.m_DecompressCachedStream.Position = 0L;
                            m_ResourceManager.m_DecompressCachedStream.SetLength(0L);
                        }
                    }
                    else
                    {
                        int hashCode = 0;
                        fileStream.Position = 0L;
                        if (updateInfo.LoadType == LoadType.LoadFromMemoryAndQuickDecrypt || updateInfo.LoadType == LoadType.LoadFromMemoryAndDecrypt
                            || updateInfo.LoadType == LoadType.LoadFromBinaryAndQuickDecrypt || updateInfo.LoadType == LoadType.LoadFromBinaryAndDecrypt)
                        {
                            Utility.Converter.GetBytes(updateInfo.HashCode, m_CachedHashBytes);
                            if (updateInfo.LoadType == LoadType.LoadFromMemoryAndQuickDecrypt || updateInfo.LoadType == LoadType.LoadFromBinaryAndQuickDecrypt)
                            {
                                hashCode = Utility.Verifier.GetCrc32(fileStream, m_CachedHashBytes, Utility.Encryption.QuickEncryptLength);
                            }
                            else if (updateInfo.LoadType == LoadType.LoadFromMemoryAndDecrypt || updateInfo.LoadType == LoadType.LoadFromBinaryAndDecrypt)
                            {
                                hashCode = Utility.Verifier.GetCrc32(fileStream, m_CachedHashBytes, length);
                            }

                            Array.Clear(m_CachedHashBytes, 0, CachedHashBytesLength);
                        }
                        else
                        {
                            hashCode = Utility.Verifier.GetCrc32(fileStream);
                        }

                        if (hashCode != updateInfo.HashCode)
                        {
                            fileStream.Close();
                            string errorMessage = Utility.Text.Format("Resource hash code error, need '{0}', downloaded '{1}'.", updateInfo.HashCode.ToString(), hashCode.ToString());
                            DownloadFailureEventArgs downloadFailureEventArgs = DownloadFailureEventArgs.Create(e.SerialId, e.DownloadPath, e.DownloadUri, errorMessage, e.UserData);
                            OnDownloadFailure(this, downloadFailureEventArgs);
                            ReferencePool.Release(downloadFailureEventArgs);
                            return;
                        }
                    }
                }//end using

                if (updateInfo.UseFileSystem)
                {
                    IFileSystem fileSystem = m_ResourceManager.GetFileSystem(updateInfo.FileSystemName, false);
                    bool retVal = fileSystem.WriteFile(updateInfo.ResourceName.FullName, updateInfo.ResourcePath);
                    if (File.Exists(updateInfo.ResourcePath))
                    {
                        File.Delete(updateInfo.ResourcePath);
                    }

                    if (!retVal)
                    {
                        string errorMessage = Utility.Text.Format("Write resource to file system '{0}' error.", fileSystem.FullPath);
                        DownloadFailureEventArgs downloadFailureEventArgs = DownloadFailureEventArgs.Create(e.SerialId, e.DownloadPath, e.DownloadUri, errorMessage, e.UserData);
                        OnDownloadFailure(this, downloadFailureEventArgs);
                        return;
                    }
                }//end if
                
                //更完了一个文件
                m_UpdatingCount--;
                //这个资源下载完毕之后 标记他在m_ResourceManager.m_ResourceInfos里的状态 意思是标记这个资源已经下载过了
                m_ResourceManager.m_ResourceInfos[updateInfo.ResourceName].MarkReady();
                //更完的,把文件放到读写区
                m_ResourceManager.m_ReadWriteResourceInfos.Add(updateInfo.ResourceName, new ReadWriteResourceInfo(updateInfo.FileSystemName, updateInfo.LoadType, updateInfo.Length, updateInfo.HashCode));
                m_CurrentGenerateReadWriteVersionListLength += updateInfo.CompressedLength;
                if (m_UpdatingCount <= 0 || m_CurrentGenerateReadWriteVersionListLength >= m_GenerateReadWriteVersionListLength)
                {
                    m_CurrentGenerateReadWriteVersionListLength = 0;
                    GenerateReadWriteVersionList();
                }

                if (ResourceUpdateSuccess != null)
                {
                    ResourceUpdateSuccess(updateInfo.ResourceName, e.DownloadPath, e.DownloadUri, updateInfo.Length, updateInfo.CompressedLength);
                }
            }

ProcedurePreloadPremier

预加载首选项流程,预加载首选项,全局配置,表,字典之类的。

ProcedureUpdateBasic

更新基础资源流程,该流程主要更新Basic资源组,表示游戏启动必要,并且非额外热更的资源。

//还是和刚才的流程一样,更新Basic资源组的资源
GameEntry.Resource.UpdateResources("Basic", OnUpdateResourcesComplete);//更新对应的资源组

ProcedurePreloadBasic

预加载基础资源组的资源,比如主动加载一些shader,音频资源,配置文件,或者启动脚本虚拟机。

加载一些必要组件后,进入ProceudreUpdateExtension,更新扩展资源组

ProcedureUpdateExtension

更新扩展资源,指除了刚才提到的以外的所有要更新的资源。如果需要更新的话,就切到等待流程,让玩家等待资源下载完成。

protected override void OnEnter(ProcedureOwner procedureOwner)
        {
            base.OnEnter(procedureOwner);
    	   //...
    	   //...
    	   //...
            m_ResourceGroup = GameEntry.Resource.GetResourceGroup();
            if (m_ResourceGroup != null)
            {
                //默认资源组已经就绪,这里的资源组其实就是从服务器下载的
                if (m_ResourceGroup.Ready)
                {
                    m_DownStart = true;
                    m_UpdateComplete = true;
                    return;
                }
                //还有多少资源要更
                GameEntry.UpdateResource.SetTotalCompressedLength(m_ResourceGroup.TotalCompressedLength - m_ResourceGroup.ReadyLength);
    	       //...
    	       //...
    	   	   //...
                //等待流程,等待下载
                ChangeState<ProcedurePreview>(procedureOwner);
            }
        }

ProcedurePreview

等待预览流程,等待分包资源下载完成,显示进度给玩家看。完成后通知别的模块下载完成。

//ProcedurePreview
protected override void OnEnter(ProcedureOwner procedureOwner)
        {
    		//...
    		//...
    		//...
    
    		//更新全部资源,也就是更新完permit和basic之后剩下的资源
             GameEntry.Resource.UpdateResources(OnUpdateResourcesComplete);
             GameEntry.UpdateResource.OnCheckNetWork();
        }

ProcedurePreloadExtension

预加载分包资源

ProcedureLogin

登陆

ProcedureCreate

创建角色

ProcedureSelect

选择角色

ProcedureChangeScene

切换场景,进入游戏