public CacheEntry(Guid id, Guid containerId, AssetSource source, Object asset) { Id = id; ContainerId = containerId; Source = source; Asset = asset; }
public CacheEntry(Guid id, Guid containerId, AssetSource source, Object asset, ColliderGeometry collider = null) { Id = id; ContainerId = containerId; Source = source; Asset = asset; ColliderGeometry = collider; }
public AssetMetadata(Guid id, Guid containerId, Object asset, ColliderGeometry collider = null, AssetSource source = null) { Id = id; ContainerId = containerId; Source = source; Asset = asset; ColliderGeometry = collider; }
/// <inheritdoc cref="GetAssetIdsInSource"/> public IEnumerable<Guid> GetAssetIdsInSource(AssetSource source = null) { List<Guid> guids; if (source != null) { assetsBySource.TryGetValue(source, out guids); } else { guids = manualAssets; } return guids; }
/// <inheritdoc cref="CacheAsset"/> public void CacheAsset(Object asset, Guid id, AssetSource source = null) { List<Guid> assetList; if(source != null) { assetList = assetsBySource.GetOrCreate(source, () => new List<Guid>(ASSET_DEFAULT_COUNT)); } else { assetList = manualAssets; } assetList.Add(id); assets[id] = asset; ids[asset] = id; }
/// <inheritdoc cref="CacheAsset"/> public void CacheAsset(Object asset, Guid id, Guid containerId, AssetSource source = null, ColliderGeometry colliderGeo = null) { if (!cache.Any(c => c.Id == id)) { cache.Add(new CacheEntry(id, containerId, source, asset, colliderGeo)); } if (cacheCallbacks.TryGetValue(id, out List <CacheCallback> callbacks)) { cacheCallbacks.Remove(id); foreach (var cb in callbacks) { try { cb?.Invoke(asset); } catch (Exception e) { Debug.LogException(e); } } } }
/// <summary> /// Track a new asset reference. Will be called during asset creation, after the asset content is downloaded /// or retrieved from cache. /// </summary> /// <param name="id"></param> /// <param name="containerId"></param> /// <param name="asset"></param> /// <param name="colliderGeo"></param> /// <param name="source"></param> public void Set(Guid id, Guid containerId, Object asset, ColliderGeometry colliderGeo = null, AssetSource source = null) { if (!Assets.ContainsKey(id)) { Assets[id] = new AssetMetadata(id, containerId, asset, colliderGeo, source); } if (Callbacks.TryGetValue(id, out List <AssetCallback> callbacks)) { Callbacks.Remove(id); foreach (var cb in callbacks) { try { cb?.Invoke(Assets[id]); } catch (Exception e) { Debug.LogException(e); } } } }
internal async void OnCreateAsset(CreateAsset payload, Action onCompleteCallback) { var def = payload.Definition; var response = new AssetsLoaded(); var unityAsset = _app.AssetManager.GetById(def.Id)?.Asset; ColliderGeometry colliderGeo = null; AssetSource source = null; ActiveContainers.Add(payload.ContainerId); // create materials if (unityAsset == null && def.Material != null) { unityAsset = UnityEngine.Object.Instantiate(MREAPI.AppsAPI.DefaultMaterial); } // create textures else if (unityAsset == null && def.Texture != null) { var texUri = new Uri(_app.ServerAssetUri, def.Texture.Value.Uri); source = new AssetSource(AssetContainerType.None, texUri.AbsoluteUri); var result = await AssetFetcher <UnityEngine.Texture> .LoadTask(_owner, texUri); // this is a newly loaded texture, so we decide initial settings for the shared asset if (result.ReturnCode == 200) { result.Asset.wrapModeU = def.Texture.Value.WrapModeU ?? TextureWrapMode.Repeat; result.Asset.wrapModeV = def.Texture.Value.WrapModeV ?? TextureWrapMode.Repeat; } unityAsset = result.Asset; source.Version = result.ETag; if (result.FailureMessage != null) { response.FailureMessage = result.FailureMessage; } } // create meshes else if (unityAsset == null && def.Mesh != null) { if (def.Mesh.Value.PrimitiveDefinition != null) { var factory = MREAPI.AppsAPI.PrimitiveFactory; try { unityAsset = factory.CreatePrimitive(def.Mesh.Value.PrimitiveDefinition.Value); colliderGeo = ConvertPrimToCollider(def.Mesh.Value.PrimitiveDefinition.Value, def.Id); } catch (Exception e) { response.FailureMessage = e.Message; MREAPI.Logger.LogError(response.FailureMessage); } } else { response.FailureMessage = $"Cannot create mesh {def.Id} without a primitive definition"; } } // create sounds else if (unityAsset == null && def.Sound != null) { var soundUri = new Uri(_app.ServerAssetUri, def.Sound.Value.Uri); source = new AssetSource(AssetContainerType.None, soundUri.AbsoluteUri); var result = await AssetFetcher <UnityEngine.AudioClip> .LoadTask(_owner, soundUri); unityAsset = result.Asset; source.Version = result.ETag; if (result.FailureMessage != null) { response.FailureMessage = result.FailureMessage; } } // create video streams else if (unityAsset == null && def.VideoStream != null) { if (MREAPI.AppsAPI.VideoPlayerFactory != null) { string videoString; // These youtube "URIs" are not valid URIs because they are case-sensitive. Don't parse, and // deprecate this URL scheme as soon as feasible. if (def.VideoStream.Value.Uri.StartsWith("youtube://")) { videoString = def.VideoStream.Value.Uri; } else { var videoUri = new Uri(_app.ServerAssetUri, def.VideoStream.Value.Uri); videoString = videoUri.AbsoluteUri; } PluginInterfaces.FetchResult result2 = MREAPI.AppsAPI.VideoPlayerFactory.PreloadVideoAsset(videoString); unityAsset = result2.Asset; if (result2.FailureMessage != null) { response.FailureMessage = result2.FailureMessage; } } else { response.FailureMessage = "VideoPlayerFactory not implemented"; } } // create animation data else if (unityAsset == null && def.AnimationData != null) { var animDataCache = ScriptableObject.CreateInstance <AnimationDataCached>(); animDataCache.Tracks = def.AnimationData.Value.Tracks; unityAsset = animDataCache; } _app.AssetManager.Set(def.Id, payload.ContainerId, unityAsset, colliderGeo, source); // verify creation and apply initial patch if (unityAsset != null) { unityAsset.name = def.Name; OnAssetUpdate(new AssetUpdate() { Asset = def }, null); try { response.Assets = new Asset[] { GenerateAssetPatch(unityAsset, def.Id) }; } catch (Exception e) { response.FailureMessage = e.Message; _app.Logger.LogError(response.FailureMessage); } } else { if (response.FailureMessage == null) { response.FailureMessage = $"Not implemented: CreateAsset of new asset type"; } _app.Logger.LogError(response.FailureMessage); } _app.Protocol.Send(new Message() { ReplyToId = payload.MessageId, Payload = response }); onCompleteCallback?.Invoke(); }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { WebRequestLoader loader = null; Stream stream = null; source.ParsedUri = new Uri(_app.ServerAssetUri, source.ParsedUri); var rootUri = URIHelper.GetDirectoryName(source.ParsedUri.AbsoluteUri); var cachedVersion = MREAPI.AppsAPI.AssetCache.SupportsSync ? MREAPI.AppsAPI.AssetCache.GetVersionSync(source.ParsedUri) : await MREAPI.AppsAPI.AssetCache.GetVersion(source.ParsedUri); // Wait asynchronously until the load throttler lets us through. using (var scope = await AssetLoadThrottling.AcquireLoadScope()) { // set up loader loader = new WebRequestLoader(rootUri); if (!string.IsNullOrEmpty(cachedVersion)) { loader.BeforeRequestCallback += (msg) => { if (msg.RequestUri == source.ParsedUri) { msg.Headers.Add("If-None-Match", cachedVersion); } }; } // download root gltf file, check for cache hit try { stream = await loader.LoadStreamAsync(URIHelper.GetFileFromUri(source.ParsedUri)); source.Version = loader.LastResponse.Headers.ETag?.Tag ?? ""; } catch (HttpRequestException) { if (loader.LastResponse.StatusCode == System.Net.HttpStatusCode.NotModified) { source.Version = cachedVersion; } else { throw; } } } IList <Asset> assetDefs = new List <Asset>(30); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); IList <UnityEngine.Object> assets; // fetch assets from glTF stream or cache if (source.Version != cachedVersion) { assets = await LoadGltfFromStream(loader, stream, colliderType); MREAPI.AppsAPI.AssetCache.StoreAssets(source.ParsedUri, assets, source.Version); } else { var assetsEnum = MREAPI.AppsAPI.AssetCache.SupportsSync ? MREAPI.AppsAPI.AssetCache.LeaseAssetsSync(source.ParsedUri) : await MREAPI.AppsAPI.AssetCache.LeaseAssets(source.ParsedUri); assets = assetsEnum.ToList(); } // catalog assets int textureIndex = 0, meshIndex = 0, materialIndex = 0, prefabIndex = 0; foreach (var asset in assets) { var assetDef = GenerateAssetPatch(asset, guidGenerator.Next()); assetDef.Name = asset.name; string internalId = null; if (asset is UnityEngine.Texture) { internalId = $"texture:{textureIndex++}"; } else if (asset is UnityEngine.Mesh) { internalId = $"mesh:{meshIndex++}"; } else if (asset is UnityEngine.Material) { internalId = $"material:{materialIndex++}"; } else if (asset is GameObject) { internalId = $"scene:{prefabIndex++}"; } assetDef.Source = new AssetSource(source.ContainerType, source.ParsedUri.AbsoluteUri, internalId, source.Version); ColliderGeometry colliderGeo = null; if (asset is UnityEngine.Mesh mesh) { colliderGeo = colliderType == ColliderType.Mesh ? (ColliderGeometry) new MeshColliderGeometry() { MeshId = assetDef.Id } : (ColliderGeometry) new BoxColliderGeometry() { Size = (mesh.bounds.size * 0.8f).CreateMWVector3(), Center = mesh.bounds.center.CreateMWVector3() }; } _app.AssetManager.Set(assetDef.Id, containerId, asset, colliderGeo, assetDef.Source); assetDefs.Add(assetDef); } return(assetDefs); }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { IList <Asset> assets = new List <Asset>(); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); // download file UtilMethods.GetUrlParts(source.ParsedUri.AbsoluteUri, out string rootUrl, out string filename); var loader = new WebRequestLoader(rootUrl); await loader.LoadStream(filename); // pre-parse glTF document so we can get a scene count // TODO: run this in thread GLTF.GLTFParser.ParseJson(loader.LoadedStream, out GLTF.Schema.GLTFRoot gltfRoot); GLTFSceneImporter importer = MREAPI.AppsAPI.GLTFImporterFactory.CreateImporter(gltfRoot, loader, _asyncHelper, loader.LoadedStream); importer.SceneParent = MREAPI.AppsAPI.AssetCache.CacheRootGO().transform; importer.Collider = colliderType.ToGLTFColliderType(); // load prefabs if (gltfRoot.Scenes != null) { for (var i = 0; i < gltfRoot.Scenes.Count; i++) { await importer.LoadSceneAsync(i); GameObject rootObject = importer.LastLoadedScene; rootObject.name = gltfRoot.Scenes[i].Name ?? $"scene:{i}"; MWGOTreeWalker.VisitTree(rootObject, (go) => { go.layer = UnityConstants.ActorLayerIndex; }); var def = GenerateAssetPatch(rootObject, guidGenerator.Next()); def.Name = rootObject.name; def.Source = new AssetSource(source.ContainerType, source.Uri, $"scene:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(rootObject, def.Id, containerId, source); assets.Add(def); } } // load textures if (gltfRoot.Textures != null) { for (var i = 0; i < gltfRoot.Textures.Count; i++) { await importer.LoadTextureAsync(gltfRoot.Textures[i], i, true); var texture = importer.GetTexture(i); texture.name = gltfRoot.Textures[i].Name ?? $"texture:{i}"; var asset = GenerateAssetPatch(texture, guidGenerator.Next()); asset.Name = texture.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"texture:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(texture, asset.Id, containerId, source); assets.Add(asset); } } // load materials if (gltfRoot.Materials != null) { for (var i = 0; i < gltfRoot.Materials.Count; i++) { var matdef = gltfRoot.Materials[i]; var material = await importer.LoadMaterialAsync(i); material.name = matdef.Name ?? $"material:{i}"; var asset = GenerateAssetPatch(material, guidGenerator.Next()); asset.Name = material.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"material:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(material, asset.Id, containerId, source); assets.Add(asset); } } importer.Dispose(); return(assets); }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { IList <Asset> assets = new List <Asset>(); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); // download file var rootUrl = URIHelper.GetDirectoryName(source.ParsedUri.AbsoluteUri); var loader = new WebRequestLoader(rootUrl); var stream = await loader.LoadStreamAsync(URIHelper.GetFileFromUri(source.ParsedUri)); // pre-parse glTF document so we can get a scene count // TODO: run this in thread GLTF.GLTFParser.ParseJson(stream, out GLTF.Schema.GLTFRoot gltfRoot); stream.Position = 0; GLTFSceneImporter importer = MREAPI.AppsAPI.GLTFImporterFactory.CreateImporter(gltfRoot, loader, _asyncHelper, stream); importer.SceneParent = MREAPI.AppsAPI.AssetCache.CacheRootGO().transform; importer.Collider = colliderType.ToGLTFColliderType(); // load textures if (gltfRoot.Textures != null) { for (var i = 0; i < gltfRoot.Textures.Count; i++) { await importer.LoadTextureAsync(gltfRoot.Textures[i], i, true); var texture = importer.GetTexture(i); texture.name = gltfRoot.Textures[i].Name ?? $"texture:{i}"; var asset = GenerateAssetPatch(texture, guidGenerator.Next()); asset.Name = texture.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"texture:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(texture, asset.Id, containerId, source); assets.Add(asset); } } // load meshes if (gltfRoot.Meshes != null) { var cancellationSource = new System.Threading.CancellationTokenSource(); for (var i = 0; i < gltfRoot.Meshes.Count; i++) { var mesh = await importer.LoadMeshAsync(i, cancellationSource.Token); mesh.name = gltfRoot.Meshes[i].Name ?? $"mesh:{i}"; var asset = GenerateAssetPatch(mesh, guidGenerator.Next()); asset.Name = mesh.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"mesh:{i}"); var colliderGeo = colliderType == ColliderType.Mesh ? (ColliderGeometry) new MeshColliderGeometry() { MeshId = asset.Id } : (ColliderGeometry) new BoxColliderGeometry() { Size = (mesh.bounds.size * 0.8f).CreateMWVector3() }; MREAPI.AppsAPI.AssetCache.CacheAsset(mesh, asset.Id, containerId, source, colliderGeo); assets.Add(asset); } } // load materials if (gltfRoot.Materials != null) { for (var i = 0; i < gltfRoot.Materials.Count; i++) { var matdef = gltfRoot.Materials[i]; var material = await importer.LoadMaterialAsync(i); material.name = matdef.Name ?? $"material:{i}"; var asset = GenerateAssetPatch(material, guidGenerator.Next()); asset.Name = material.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"material:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(material, asset.Id, containerId, source); assets.Add(asset); } } // load prefabs if (gltfRoot.Scenes != null) { for (var i = 0; i < gltfRoot.Scenes.Count; i++) { await importer.LoadSceneAsync(i).ConfigureAwait(true); GameObject rootObject = importer.LastLoadedScene; rootObject.name = gltfRoot.Scenes[i].Name ?? $"scene:{i}"; var animation = rootObject.GetComponent <UnityEngine.Animation>(); if (animation != null) { animation.playAutomatically = false; // initialize mapping so we know which gameobjects are targeted by which animation clips var mapping = rootObject.AddComponent <PrefabAnimationTargets>(); mapping.Initialize(gltfRoot, i); } MWGOTreeWalker.VisitTree(rootObject, (go) => { go.layer = MREAPI.AppsAPI.LayerApplicator.DefaultLayer; }); var def = GenerateAssetPatch(rootObject, guidGenerator.Next()); def.Name = rootObject.name; def.Source = new AssetSource(source.ContainerType, source.Uri, $"scene:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(rootObject, def.Id, containerId, source); assets.Add(def); } } importer.Dispose(); return(assets); }