unsafe void ReceiveAssetTargetHash(MessageEventArgs args) { using (var resolvedAssets = args.ReceiveArray <ResolvedAssetID>()) { foreach (var asset in resolvedAssets) { if (!asset.TargetHash.IsValid) { // If hash is invalid, then it means we should be waiting on it, but the hash will come later when it finishes importing on the editor LiveLinkMsg.LogReceived($"ReceiveAssetTargetHash => {asset.GUID} | {asset.TargetHash}, Invalid Hash (Still waiting)"); m_WaitingForAssets[asset.GUID] = new Hash128(); } else { //TODO: Should we compare against already loaded assets here? if (File.Exists(GetCachePath(asset.TargetHash))) { LiveLinkMsg.LogReceived($"ReceiveAssetTargetHash => {asset.GUID} | {asset.TargetHash}, File.Exists => 'True'"); m_WaitingForAssets[asset.GUID] = asset.TargetHash; } else { LiveLinkMsg.LogReceived($"ReceiveAssetTargetHash => {asset.GUID} | {asset.TargetHash}, File.Exists => 'False'"); m_WaitingForAssets[asset.GUID] = new Hash128(); LiveLinkMsg.LogSend($"AssetBundleBuild request '{asset.GUID}'"); PlayerConnection.instance.Send(LiveLinkMsg.RequestAssetByGUID, asset.GUID); } } } } }
unsafe void ReceiveSubSceneTargetHash(MessageEventArgs args) { using (var subSceneAssets = args.ReceiveArray <ResolvedSubSceneID>()) { foreach (var subSceneAsset in subSceneAssets) { if (m_WaitingForSubScenes.ContainsKey(subSceneAsset.SubSceneGUID)) { return; } // If subscene exists locally already, just load it if (IsSubSceneAvailable(subSceneAsset)) { LiveLinkMsg.LogInfo($"ReceiveResponseSubSceneTargetHash => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, File.Exists => 'True'"); AddWaitForSubScene(subSceneAsset); } else { LiveLinkMsg.LogInfo($"ReceiveResponseSubSceneTargetHash => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, File.Exists => 'False'"); m_Connection.Send(LiveLinkMsg.PlayerRequestSubSceneForGUID, subSceneAsset); } } } }
void ResetGame() { while (m_ResourcePacketQueue.Count != 0) { m_ResourcePacketQueue.Dequeue().Dispose(); } EntityManager.DestroyEntity(EntityManager.UniversalQuery); //@TODO: Once we have build settings & loading of game object scenes we can remove this hack. var scenes = Object.FindObjectsOfType <SubScene>(); foreach (var scene in scenes) { scene.enabled = false; scene.enabled = true; } LiveLinkPlayerAssetRefreshSystem.Reset(); LiveLinkMsg.LogSend("ConnectLiveLink"); PlayerConnection.instance.Send(LiveLinkMsg.ConnectLiveLink, World.GetExistingSystem <SceneSystem>().BuildSettingsGUID); m_LiveLinkSceneChange.Reset(); SendSetLoadedScenes(); }
unsafe void ReceiveSubSceneTargetHash(MessageEventArgs args) { using (var subSceneAssets = args.ReceiveArray <ResolvedSubSceneID>()) { foreach (var subSceneAsset in subSceneAssets) { if (m_WaitingForSubScenes.ContainsKey(subSceneAsset.SubSceneGUID)) { return; } // If subscene exists locally already, just load it var assetDependencies = new HashSet <RuntimeGlobalObjectId>(); if (IsSubSceneAvailable(subSceneAsset, assetDependencies)) { LiveLinkMsg.LogInfo($"ReceiveResponseSubSceneTargetHash => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, File.Exists => 'True'"); //TODO: This is a hack to make sure assets are managed by asset manifest when loading from cache for first run AddWaitForSubScene(subSceneAsset, assetDependencies); } else { LiveLinkMsg.LogInfo($"ReceiveResponseSubSceneTargetHash => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, File.Exists => 'False'"); PlayerConnection.instance.Send(LiveLinkMsg.RequestSubSceneByGUID, subSceneAsset); } } } }
protected override void OnUpdate() { // BuildConfigurationGUID isn't known in OnCreate since it could be configured from OnCreate of other systems, // So we delay connecting live link until first OnUpdate if (!m_DidRequestConnection) { if (!m_SessionHandshake) { if (m_SessionHandshakeTimeoutTimstamp < Time.ElapsedTime) { Debug.LogError("LiveLink handshake timed out. This may be because your player connection is connected to an AssetWorker process or incorrect Editor."); World.GetExistingSystem <LiveLinkRuntimeSystemGroup>().Enabled = false; } return; } m_DidRequestConnection = true; LiveLinkMsg.LogSend("ConnectLiveLink"); m_Connection.Send(LiveLinkMsg.PlayerRequestConnectLiveLink, World.GetExistingSystem <SceneSystem>().BuildConfigurationGUID); } SendSetLoadedScenes(); while (m_ResourcePacketQueue.Count != 0 && IsResourceReady(m_ResourcePacketQueue.Peek().GlobalObjectIds)) { LiveLinkMsg.LogInfo($"Applying changeset ({m_ResourcePacketQueue.Count-1} left in queue)"); using (var resourcePacket = m_ResourcePacketQueue.Dequeue()) { ApplyChangeSet(resourcePacket); } } }
void SendSetLoadedScenes() { if (m_LiveLinkSceneChange.GetSceneMessage(out var msg)) { LiveLinkMsg.LogSend($"SetLoadedScenes: Loaded {msg.LoadedScenes.ToDebugString()}, Removed {msg.RemovedScenes.ToDebugString()}"); m_Connection.Send(LiveLinkMsg.PlayerSetLoadedScenes, msg.ToMsg()); msg.Dispose(); } }
//@TODO: Support some sort of transaction like API so we can reload all changed things in batch. unsafe void ReceiveAssetBundle(MessageEventArgs args) { LiveLinkMsg.LogReceived($"AssetBundle: '{args.data.Length}' bytes"); fixed(byte *ptr = args.data) { var reader = new UnsafeAppendBuffer.Reader(ptr, args.data.Length); var asset = reader.ReadNext <ResolvedAssetID>(); var assetBundleCachePath = GetCachePath(asset.TargetHash); var tempCachePath = GetTempCachePath(); // cache: look up asset by target hash to see if the version we want is already on the target device //if we already have the asset bundle revision we want, then just put that in the resolver as the active revision of the asset // cache: if not in cache, write actual file to Application.persistentDatapath var stream = File.OpenWrite(tempCachePath); stream.Write(args.data, reader.Offset, args.data.Length - reader.Offset); stream.Close(); stream.Dispose(); LiveLinkMsg.LogInfo($"ReceiveAssetBundle => {asset.GUID} | {asset.TargetHash}, '{tempCachePath}' => '{assetBundleCachePath}'"); if (File.Exists(assetBundleCachePath)) { Debug.LogError($"Received {asset.GUID} | {asset.TargetHash} but it already exists on disk"); LiveLinkMsg.LogInfo($"Received {asset.GUID} | {asset.TargetHash} but it already exists on disk"); File.Delete(tempCachePath); } else { try { File.Move(tempCachePath, assetBundleCachePath); } catch (Exception e) { File.Delete(tempCachePath); if (!File.Exists(assetBundleCachePath)) { Debug.LogError($"Failed to move temporary file. Exception: {e.Message}"); LiveLinkMsg.LogInfo($"Failed to move temporary file. Exception: {e.Message}"); } } } if (!_WaitingForAssets.ContainsKey(asset.GUID)) { Debug.LogError($"Received {asset.GUID} | {asset.TargetHash} without requesting it"); LiveLinkMsg.LogInfo($"Received {asset.GUID} | {asset.TargetHash} without requesting it"); } _WaitingForAssets[asset.GUID] = asset.TargetHash; } }
void ReceiveEntityChangeSet(MessageEventArgs args) { var resourcePacket = new EntityChangeSetSerialization.ResourcePacket(args.data); LiveLinkMsg.LogReceived($"EntityChangeSet patch: '{args.data.Length}' bytes, " + $"object GUIDs: {resourcePacket.GlobalObjectIds.ToDebugString(id => id.AssetGUID.ToString())}"); m_ResourcePacketQueue.Enqueue(resourcePacket); }
//TODO: There is too much code duplication here, refactor this to receive general artifacts from the editor unsafe void ReceiveSubSceneRefGUIDs(MessageEventArgs args) { fixed(byte *ptr = args.data) { var reader = new UnsafeAppendBuffer.Reader(ptr, args.data.Length); var subSceneAsset = reader.ReadNext <ResolvedSubSceneID>(); var sectionIndex = reader.ReadNext <int>(); reader.ReadNext(out NativeArray <RuntimeGlobalObjectId> objRefGUIDs, Allocator.Persistent); var refObjGUIDsPath = EntityScenesPaths.GetLiveLinkCachePath(subSceneAsset.TargetHash, EntityScenesPaths.PathType.EntitiesUnityObjectReferences, sectionIndex); // Not printing error because this can happen when running the same player multiple times on the same machine if (File.Exists(refObjGUIDsPath)) { LiveLinkMsg.LogInfo($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} but it already exists on disk"); } else { LiveLinkMsg.LogInfo($"ReceieveSubSceneRefGUIDs => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash},"); var tempCachePath = GetTempCachePath(); using (var writer = new StreamBinaryWriter(tempCachePath)) { writer.Write(objRefGUIDs.Length); writer.WriteArray(objRefGUIDs); } try { File.Move(tempCachePath, refObjGUIDsPath); } catch (Exception e) { File.Delete(tempCachePath); if (!File.Exists(refObjGUIDsPath)) { Debug.LogError($"Failed to move temporary file. Exception: {e.Message}"); LiveLinkMsg.LogInfo($"Failed to move temporary file. Exception: {e.Message}"); } } } if (!_WaitingForSubScenes.ContainsKey(subSceneAsset.SubSceneGUID)) { Debug.LogError($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} without requesting it"); LiveLinkMsg.LogInfo($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} without requesting it"); return; } var waitingSubScene = _WaitingForSubScenes[subSceneAsset.SubSceneGUID]; waitingSubScene.SubSections.Add(objRefGUIDs); _WaitingForSubScenes[subSceneAsset.SubSceneGUID] = waitingSubScene; } }
unsafe void ReceiveResponseSubSceneTargetHash(MessageEventArgs args) { using (var subSceneAssets = args.ReceiveArray <ResolvedSubSceneID>()) { foreach (var subSceneAsset in subSceneAssets) { if (_WaitingForSubScenes.ContainsKey(subSceneAsset.SubSceneGUID)) { return; } var headerPath = EntityScenesPaths.GetLiveLinkCachePath(subSceneAsset.TargetHash, EntityScenesPaths.PathType.EntitiesHeader, -1); if (File.Exists(headerPath)) { LiveLinkMsg.LogInfo($"ReceiveResponseSubSceneTargetHash => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, File.Exists => 'True', {Path.GetFullPath(headerPath)}"); //TODO: This is a hack to make sure assets are managed by asset manifest when loading from cache for first run if (!BlobAssetReference <SceneMetaData> .TryRead(headerPath, SceneMetaDataSerializeUtility.CurrentFileFormatVersion, out var sceneMetaDataRef)) { Debug.LogError("Loading Entity Scene failed because the entity header file was an old version: " + subSceneAsset.SubSceneGUID); return; } ref var sceneMetaData = ref sceneMetaDataRef.Value; var waitingSubScene = new WaitingSubScene { TargetHash = subSceneAsset.TargetHash, SubSections = new List <NativeArray <RuntimeGlobalObjectId> >(), SubSectionCount = sceneMetaData.Sections.Length }; for (int i = 0; i < sceneMetaData.Sections.Length; i++) { if (sceneMetaData.Sections[i].ObjectReferenceCount != 0) { var refObjGUIDsPath = EntityScenesPaths.GetLiveLinkCachePath(subSceneAsset.TargetHash, EntityScenesPaths.PathType.EntitiesUnityObjectReferences, i); using (var reader = new StreamBinaryReader(refObjGUIDsPath)) { var numObjRefGUIDs = reader.ReadInt(); NativeArray <RuntimeGlobalObjectId> objRefGUIDs = new NativeArray <RuntimeGlobalObjectId>(numObjRefGUIDs, Allocator.Persistent); reader.ReadArray(objRefGUIDs, numObjRefGUIDs); waitingSubScene.SubSections.Add(objRefGUIDs); } } else { waitingSubScene.SubSections.Add(new NativeArray <RuntimeGlobalObjectId>(0, Allocator.Persistent)); } } _WaitingForSubScenes[subSceneAsset.SubSceneGUID] = waitingSubScene; }
void ReceiveLoadScenes(MessageEventArgs args) { using (var scenes = args.ReceiveArray <Hash128>()) { LiveLinkMsg.LogReceived($"LoadScenes {scenes.ToDebugString()}"); foreach (var scene in scenes) { m_Patcher.TriggerLoad(scene); } } }
unsafe void ReceiveSubSceneHeader(MessageEventArgs args) { fixed(byte *ptr = args.data) { var reader = new UnsafeAppendBuffer.Reader(ptr, args.data.Length); var subSceneAsset = reader.ReadNext <ResolvedSubSceneID>(); var subSectionCount = reader.ReadNext <int>(); var headerPath = EntityScenesPaths.GetLiveLinkCachePath(subSceneAsset.TargetHash, EntityScenesPaths.PathType.EntitiesHeader, -1); // Not printing error because this can happen when running the same player multiple times on the same machine if (File.Exists(headerPath)) { LiveLinkMsg.LogInfo($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} but it already exists on disk"); } else { var tempCachePath = GetTempCachePath(); LiveLinkMsg.LogInfo($"ReceiveSubSceneHeader => {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash}, '{tempCachePath}' => '{headerPath}'"); var stream = File.OpenWrite(tempCachePath); stream.Write(args.data, reader.Offset, args.data.Length - reader.Offset); stream.Close(); stream.Dispose(); try { File.Move(tempCachePath, headerPath); } catch (Exception e) { File.Delete(tempCachePath); if (!File.Exists(headerPath)) { Debug.LogError($"Failed to move temporary file. Exception: {e.Message}"); LiveLinkMsg.LogInfo($"Failed to move temporary file. Exception: {e.Message}"); } } } if (!_WaitingForSubScenes.ContainsKey(subSceneAsset.SubSceneGUID)) { Debug.LogError($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} without requesting it"); LiveLinkMsg.LogInfo($"Received {subSceneAsset.SubSceneGUID} | {subSceneAsset.TargetHash} without requesting it"); return; } var waitingSubScene = _WaitingForSubScenes[subSceneAsset.SubSceneGUID]; waitingSubScene.TargetHash = subSceneAsset.TargetHash; waitingSubScene.SubSectionCount = subSectionCount; _WaitingForSubScenes[subSceneAsset.SubSceneGUID] = waitingSubScene; } }
unsafe void ReceiveUnloadScenes(MessageEventArgs args) { using (var scenes = args.ReceiveArray <Hash128>()) { LiveLinkMsg.LogReceived($"UnloadScenes {scenes.ToDebugString()}"); foreach (var scene in scenes) { m_Patcher.UnloadScene(scene); m_LiveLinkedScenes.Remove(scene); } } }
unsafe void ReceiveSubScene(MessageEventArgs args) { var subSceneId = args.Receive <ResolvedSubSceneID>(); LiveLinkMsg.LogInfo($"ReceiveSubScene => SubScene received {subSceneId}"); if (!IsSubSceneAvailable(subSceneId)) { Debug.LogError("SubScene is missing artifacts!"); return; } AddWaitForSubScene(subSceneId); }
void ReceiveInitialScenes(MessageEventArgs args) { using (var scenes = args.ReceiveArray <Hash128>()) { if (scenes.Length > 0) { LiveLinkMsg.LogReceived($"ReceiveInitialScenes {scenes.ToString()}"); var sceneSystem = World.GetOrCreateSystem <SceneSystem>(); for (int i = 0; i < scenes.Length; i++) { sceneSystem.LoadSceneAsync(scenes[i], new SceneSystem.LoadParameters() { Flags = SceneLoadFlags.LoadAdditive | SceneLoadFlags.LoadAsGOScene }); } } } }
unsafe void ReceiveBuildArtifact(MessageEventArgs args) { fixed(byte *ptr = args.data) { LiveLinkMsg.LogInfo($"ReceiveBuildArtifact => Buffer Size: {args.data.Length}"); var reader = new UnsafeAppendBuffer.Reader(ptr, args.data.Length); reader.ReadNext(out string artifactFileName); string artifactPath = EntityScenesPaths.ComposeLiveLinkCachePath(artifactFileName); if (!File.Exists(artifactPath)) { LiveLinkMsg.LogInfo($"ReceiveBuildArtifact => {artifactPath}"); var tempCachePath = GetTempCachePath(); try { var stream = File.OpenWrite(tempCachePath); stream.Write(args.data, reader.Offset, args.data.Length - reader.Offset); stream.Close(); stream.Dispose(); File.Move(tempCachePath, artifactPath); LiveLinkMsg.LogInfo($"ReceiveBuildArtifact => Successfully written to disc."); } catch (Exception e) { if (File.Exists(tempCachePath)) { File.Delete(tempCachePath); } if (!File.Exists(artifactPath)) { Debug.LogError($"Failed to move temporary file. Exception: {e.Message}"); } } } } }
unsafe void ReceiveSubScene(MessageEventArgs args) { fixed(byte *ptr = args.data) { var reader = new UnsafeAppendBuffer.Reader(ptr, args.data.Length); reader.ReadNext(out ResolvedSubSceneID subSceneId); reader.ReadNext(out NativeArray <RuntimeGlobalObjectId> runtimeGlobalObjectIds, Allocator.Persistent); LiveLinkMsg.LogInfo($"ReceiveSubScene => SubScene received {subSceneId} | Asset Dependencies {runtimeGlobalObjectIds.Length}"); if (!IsSubSceneAvailable(subSceneId)) { Debug.LogError("SubScene is missing artifacts!"); return; } AddWaitForSubScene(subSceneId, runtimeGlobalObjectIds); } }
void ResetGame() { var sceneSystem = World.GetExistingSystem <SceneSystem>(); sceneSystem.UnloadAllScenes(); while (m_ResourcePacketQueue.Count != 0) { m_ResourcePacketQueue.Dequeue().Dispose(); } EntityManager.DestroyEntity(EntityManager.UniversalQuery); LiveLinkPlayerAssetRefreshSystem.Reset(); LiveLinkMsg.LogSend("ConnectLiveLink"); m_Connection.Send(LiveLinkMsg.PlayerRequestConnectLiveLink, sceneSystem.BuildConfigurationGUID); m_LiveLinkSceneChange.Reset(); SendSetLoadedScenes(); }
protected override void OnUpdate() { // Request any new guids that we haven't seen yet from the editor using (var requestedGuids = _ResourceRequests.ToComponentDataArray <ResourceGUID>(Allocator.TempJob)) { if (requestedGuids.Length > 0) { EntityManager.AddComponent(_ResourceRequests, typeof(ResourceRequested)); LiveLinkMsg.LogSend($"AssetBundleTargetHash request {requestedGuids.Reinterpret<Hash128>().ToDebugString()}"); PlayerConnection.instance.SendArray(LiveLinkMsg.RequestAssetBundleTargetHash, requestedGuids); } } // * Ensure all assets we are waiting for have arrived. // * LoadAll asset bundles in one go when everything is ready if (_WaitingForAssets.Count != 0) { bool hasAllAssets = true; var assets = new NativeArray <ResolvedAssetID>(_WaitingForAssets.Count, Allocator.TempJob); int o = 0; foreach (var asset in _WaitingForAssets) { if (asset.Value == new Hash128()) { hasAllAssets = false; } assets[o++] = new ResolvedAssetID { GUID = asset.Key, TargetHash = asset.Value }; } if (hasAllAssets) { LoadAssetBundles(assets); _WaitingForAssets.Clear(); } assets.Dispose(); } }
protected override void OnUpdate() { var sceneSystem = World.GetExistingSystem <SceneSystem>(); Entities.With(m_AllScenes).ForEach((Entity e, ref SceneReference sceneRef) => { if (m_LiveLinkedScenes.Contains(sceneRef.SceneGUID)) { EntityManager.AddComponent <DisableSceneResolveAndLoad>(e); } else { EntityManager.RemoveComponent <DisableSceneResolveAndLoad>(e); } } ); // BuildSettingsGUID isn't known in OnCreate since it could be configured from OnCreate of other systems, // So we delay connecting live link until first OnUpdate if (!m_DidRequestConnection) { m_DidRequestConnection = true; LiveLinkMsg.LogSend("ConnectLiveLink"); PlayerConnection.instance.Send(LiveLinkMsg.ConnectLiveLink, World.GetExistingSystem <SceneSystem>().BuildSettingsGUID); } SendSetLoadedScenes(); while (m_ResourcePacketQueue.Count != 0 && IsResourceReady(m_ResourcePacketQueue.Peek())) { LiveLinkMsg.LogInfo($"Applying changeset ({m_ResourcePacketQueue.Count-1} left in queue)"); using (var resourcePacket = m_ResourcePacketQueue.Dequeue()) { ApplyChangeSet(resourcePacket); } } }
unsafe void ReceiveResponseAssetBundleTargetHash(MessageEventArgs args) { using (var resolvedAssets = args.ReceiveArray <ResolvedAssetID>()) { foreach (var asset in resolvedAssets) { //TODO: Should we compare against already loaded assets here? if (File.Exists(GetCachePath(asset.TargetHash))) { LiveLinkMsg.LogReceived($"AssetBundleTargetHash => {asset.GUID} | {asset.TargetHash}, File.Exists => 'True'"); _WaitingForAssets[asset.GUID] = asset.TargetHash; } else { LiveLinkMsg.LogReceived($"AssetBundleTargetHash => {asset.GUID} | {asset.TargetHash}, File.Exists => 'False'"); _WaitingForAssets[asset.GUID] = new Hash128(); LiveLinkMsg.LogSend($"AssetBundleBuild request '{asset.GUID}'"); PlayerConnection.instance.Send(LiveLinkMsg.RequestAssetBundleForGUID, asset.GUID); } } } }
protected override void OnUpdate() { var sceneSystem = World.GetExistingSystem <SceneSystem>(); // BuildConfigurationGUID isn't known in OnCreate since it could be configured from OnCreate of other systems, // So we delay connecting live link until first OnUpdate if (!m_DidRequestConnection) { m_DidRequestConnection = true; LiveLinkMsg.LogSend("ConnectLiveLink"); PlayerConnection.instance.Send(LiveLinkMsg.RequestConnectLiveLink, World.GetExistingSystem <SceneSystem>().BuildConfigurationGUID); } SendSetLoadedScenes(); while (m_ResourcePacketQueue.Count != 0 && IsResourceReady(m_ResourcePacketQueue.Peek().GlobalObjectIds)) { LiveLinkMsg.LogInfo($"Applying changeset ({m_ResourcePacketQueue.Count-1} left in queue)"); using (var resourcePacket = m_ResourcePacketQueue.Dequeue()) { ApplyChangeSet(resourcePacket); } } }
void ReceiveResetGame(MessageEventArgs args) { LiveLinkMsg.LogReceived("ResetGame"); ResetGame(); }
void LoadAssetBundles(NativeArray <ResolvedAssetID> assets) { LiveLinkMsg.LogInfo("--- Begin Load asset bundles"); var patchAssetBundles = new List <AssetBundle>(); var patchAssetBundlesPath = new List <string>(); var newAssetBundles = new List <Hash128>(); var assetBundleToValidate = new List <Hash128>(); foreach (var asset in assets) { var assetGUID = asset.GUID; var targetHash = asset.TargetHash; var assetBundleCachePath = GetCachePath(targetHash); //if we already loaded an asset bundle and we just need a refresh var oldAssetBundle = globalAssetObjectResolver.GetAssetBundle(assetGUID); if (oldAssetBundle != null) { if (oldAssetBundle.isStreamedSceneAssetBundle) { LiveLinkMsg.LogInfo($"Unloading scene bundle: {assetGUID}"); var sceneSystem = World.GetExistingSystem <SceneSystem>(); if (sceneSystem != null) { sceneSystem.ReloadScenesWithHash(assetGUID, targetHash); } globalAssetObjectResolver.UnloadAsset(assetGUID); continue; } else { LiveLinkMsg.LogInfo($"patching asset bundle: {assetGUID}"); patchAssetBundles.Add(oldAssetBundle); patchAssetBundlesPath.Add(assetBundleCachePath); globalAssetObjectResolver.UpdateTargetHash(assetGUID, targetHash); newAssetBundles.Add(assetGUID); } } else { LiveLinkMsg.LogInfo($"Loaded asset bundle: {assetGUID}"); var loadedAssetBundle = AssetBundle.LoadFromFile(assetBundleCachePath); globalAssetObjectResolver.AddAsset(assetGUID, targetHash, null, loadedAssetBundle); newAssetBundles.Add(assetGUID); } assetBundleToValidate.Add(assetGUID); //@TODO: Keep a hashtable of guid -> entity? Entities.ForEach((Entity entity, ref ResourceGUID guid) => { if (guid.Guid == assetGUID) { EntityManager.AddComponentData(entity, new ResourceLoaded()); } }); } AssetBundleUtility.PatchAssetBundles(patchAssetBundles.ToArray(), patchAssetBundlesPath.ToArray()); foreach (var assetGUID in newAssetBundles) { var assetBundle = globalAssetObjectResolver.GetAssetBundle(assetGUID); if (assetBundle == null) { Debug.LogError($"Could not load requested asset bundle.'"); return; } if (!assetBundle.isStreamedSceneAssetBundle) { var loadedManifest = assetBundle.LoadAsset <AssetObjectManifest>(assetGUID.ToString()); if (loadedManifest == null) { Debug.LogError($"Loaded {assetGUID} failed to load ObjectManifest"); return; } globalAssetObjectResolver.UpdateObjectManifest(assetGUID, loadedManifest); } } foreach (var assetGUID in assetBundleToValidate) { globalAssetObjectResolver.Validate(assetGUID); } LiveLinkMsg.LogInfo("--- End Load asset bundles"); }