public override void OnImportAsset(AssetImportContext ctx) { try { var sceneWithBuildConfiguration = SceneWithBuildConfigurationGUIDs.ReadFromFile(ctx.assetPath); // Ensure we have as many dependencies as possible registered early in case an exception is thrown EditorEntityScenes.AddEntityBinaryFileDependencies(ctx, sceneWithBuildConfiguration.BuildConfiguration); EditorEntityScenes.DependOnSceneGameObjects(sceneWithBuildConfiguration.SceneGUID, ctx); var config = BuildConfiguration.LoadAsset(sceneWithBuildConfiguration.BuildConfiguration); var scenePath = AssetDatabaseCompatibility.GuidToPath(sceneWithBuildConfiguration.SceneGUID); var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); try { EditorSceneManager.SetActiveScene(scene); var settings = new GameObjectConversionSettings(); settings.SceneGUID = sceneWithBuildConfiguration.SceneGUID; if (!sceneWithBuildConfiguration.IsBuildingForEditor) { settings.ConversionFlags |= GameObjectConversionUtility.ConversionFlags.IsBuildingForPlayer; } settings.BuildConfiguration = config; settings.AssetImportContext = ctx; settings.FilterFlags = WorldSystemFilterFlags.HybridGameObjectConversion; WriteEntitySceneSettings writeEntitySettings = new WriteEntitySceneSettings(); if (config != null && config.TryGetComponent <DotsRuntimeBuildProfile>(out var profile)) { if (config.TryGetComponent <DotsRuntimeRootAssembly>(out var rootAssembly)) { writeEntitySettings.Codec = Codec.LZ4; writeEntitySettings.IsDotsRuntime = true; writeEntitySettings.BuildAssemblyCache = new BuildAssemblyCache() { BaseAssemblies = rootAssembly.RootAssembly.asset, PlatformName = profile.Target.UnityPlatformName }; settings.FilterFlags = WorldSystemFilterFlags.DotsRuntimeGameObjectConversion; //Updating the root asmdef references or its references should re-trigger conversion ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(rootAssembly.RootAssembly.asset)); foreach (var assemblyPath in writeEntitySettings.BuildAssemblyCache.AssembliesPath) { ctx.DependsOnArtifact(assemblyPath); } } } var sectionRefObjs = new List <ReferencedUnityObjects>(); var sectionData = EditorEntityScenes.ConvertAndWriteEntityScene(scene, settings, sectionRefObjs, writeEntitySettings); WriteAssetDependencyGUIDs(sectionRefObjs, sectionData, ctx); foreach (var objRefs in sectionRefObjs) { DestroyImmediate(objRefs); } } finally { EditorSceneManager.CloseScene(scene, true); } } // Currently it's not acceptable to let the asset database catch the exception since it will create a default asset without any dependencies // This means a reimport will not be triggered if the scene is subsequently modified catch (Exception e) { Debug.Log($"Exception thrown during SubScene import: {e}"); } }
// This function is responsible for providing all the subscenes to the build. // // The way these files get generated is that we have a SceneWithBuildConfiguration file, (which is a bit of a hack to work around the inability for scriptable importers to take arguments, so // instead we create a different file that points to the scene we want to import, and points to the buildconfiguration we want to import it for). The SubsceneImporter will import this file, // and it will make 3 (relevant) kind of files: // - headerfile // - entitybinaryformat file (the actual entities payloads) // - a SerializedFile that has an array of UnityEngine.Object PPtrs that are used by this entity file. // // The first two we deal with very simply: they just need to be copied into the build, and we're done. // the third one, we will feed as input to the Scriptable build pipeline (which is actually about creating assetbundles), and create an assetbundle that // has all those objects in it that the 3rd file referred to. We do this with a batch api, first we loop through all subscenes, and register with this batch // api which assetbundles we'd like to see produced, and then at the end, we say "okay make them please". this assetbundle creation api has a caching system // that is separate from the assetpipeline caching system, so if all goes well, the call to produce these assetbundles will return very fast and did nothing. // // The reason for the strange looking api, where a two callbacks get passed in is to make integration of the new incremental buildpipeline easier, as this code // needs to be compatible both with the current buildpipeline in the dots-repo, as well as with the incremental buildpipeline. When that is merged, we can simplify this. public static void PrepareAdditionalFiles(GUID[] sceneGuids, ArtifactKey[] entitySceneArtifacts, BuildTarget target, Action <string, string> RegisterFileCopy, string outputStreamingAssetsDirectory, string buildWorkingDirectory) { if (target == BuildTarget.NoTarget) { throw new InvalidOperationException($"Invalid build target '{target.ToString()}'."); } if (target != EditorUserBuildSettings.activeBuildTarget) { throw new InvalidOperationException($"ActiveBuildTarget must be switched before the {nameof(SubSceneBuildCode)} runs."); } Assert.AreEqual(sceneGuids.Length, entitySceneArtifacts.Length); var content = new BundleBuildContent(new AssetBundleBuild[0]); var bundleNames = new HashSet <string>(); var subScenePaths = new Dictionary <Hash128, string>(); var dependencyInputData = new Dictionary <SceneSection, SectionDependencyInfo>(); var refExt = EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesUnityObjectReferences); var headerExt = EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesHeader); var binaryExt = EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesBinary); string conversionLogExtension = EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesConversionLog); var group = BuildPipeline.GetBuildTargetGroup(target); var parameters = new BundleBuildParameters(target, @group, buildWorkingDirectory) { BundleCompression = BuildCompression.LZ4Runtime }; var artifactHashes = new UnityEngine.Hash128[entitySceneArtifacts.Length]; AssetDatabaseCompatibility.ProduceArtifactsRefreshIfNecessary(entitySceneArtifacts, artifactHashes); for (int i = 0; i != entitySceneArtifacts.Length; i++) { var sceneGuid = sceneGuids[i]; var sceneBuildConfigGuid = entitySceneArtifacts[i].guid; var artifactHash = artifactHashes[i]; bool foundEntityHeader = false; if (!artifactHash.isValid) { throw new Exception($"Building EntityScene artifact failed: '{AssetDatabaseCompatibility.GuidToPath(sceneGuid)}' ({sceneGuid}). There were exceptions during the entity scene imports."); } AssetDatabaseCompatibility.GetArtifactPaths(artifactHash, out var artifactPaths); foreach (var artifactPath in artifactPaths) { //UnityEngine.Debug.Log($"guid: {sceneGuid} artifact: '{artifactPath}'"); //@TODO: This looks like a workaround. Whats going on here? var ext = Path.GetExtension(artifactPath).Replace(".", ""); if (ext == conversionLogExtension) { var res = ConversionLogUtils.PrintConversionLogToUnityConsole(artifactPath); if (res.HasException) { throw new Exception("Building entity scenes failed. There were exceptions during the entity scene imports."); } } else if (ext == headerExt) { foundEntityHeader = true; if (!string.IsNullOrEmpty(artifactPaths.FirstOrDefault(a => a.EndsWith(refExt)))) { subScenePaths[sceneGuid] = artifactPath; } else { //if there are no reference bundles, then deduplication can be skipped var destinationFile = EntityScenesPaths.RelativePathFolderFor(sceneGuid, EntityScenesPaths.PathType.EntitiesHeader, -1); DoCopy(RegisterFileCopy, outputStreamingAssetsDirectory, artifactPath, destinationFile); } } else if (ext == binaryExt) { var destinationFile = EntityScenesPaths.RelativePathFolderFor(sceneGuid, EntityScenesPaths.PathType.EntitiesBinary, EntityScenesPaths.GetSectionIndexFromPath(artifactPath)); DoCopy(RegisterFileCopy, outputStreamingAssetsDirectory, artifactPath, destinationFile); } else if (ext == refExt) { content.CustomAssets.Add(new CustomContent { Asset = sceneBuildConfigGuid, Processor = (guid, processor) => { var sectionIndex = EntityScenesPaths.GetSectionIndexFromPath(artifactPath); processor.GetObjectIdentifiersAndTypesForSerializedFile(artifactPath, out ObjectIdentifier[] objectIds, out Type[] types);
public override BuildResult Run(BuildContext context) { var manifest = context.BuildManifest; var rootAssembly = context.GetComponentOrDefault <DotsRuntimeRootAssembly>(); var buildScenes = context.GetComponentOrDefault <SceneList>(); var targetName = rootAssembly.MakeBeeTargetName(context.BuildConfigurationName); var scenePaths = buildScenes.GetScenePathsForBuild(); var buildConfigurationGuid = context.BuildConfigurationAssetGUID; var dataDirectory = WorldExport.GetOrCreateDataDirectoryFrom(rootAssembly.StagingDirectory.Combine(targetName)); var logsDirectory = WorldExport.GetOrCreateLogDirectoryFrom(targetName); var sceneGuids = scenePaths.SelectMany(scenePath => { var guids = EditorEntityScenes.GetSubScenes(AssetDatabaseCompatibility.PathToGUID(scenePath)).ToList(); guids.Add(AssetDatabaseCompatibility.PathToGUID(scenePath)); return(guids); }).Distinct().ToList(); //Save all unsaved scenes of the project first foreach (var guid in sceneGuids) { string scenePath = AssetDatabase.GUIDToAssetPath(guid.ToString()); var scene = SceneManager.GetSceneByPath(scenePath); EditorSceneManager.SaveScene(scene); } var requiresRefresh = false; var sceneBuildConfigGuids = new NativeArray <GUID>(sceneGuids.Count, Allocator.TempJob); for (int i = 0; i != sceneBuildConfigGuids.Length; i++) { sceneBuildConfigGuids[i] = SceneWithBuildConfigurationGUIDs.EnsureExistsFor(sceneGuids[i], new Hash128(buildConfigurationGuid), false, out var thisRequiresRefresh); requiresRefresh |= thisRequiresRefresh; } if (requiresRefresh) { AssetDatabase.Refresh(); } var artifactHashes = new NativeArray <UnityEngine.Hash128>(sceneGuids.Count, Allocator.TempJob); AssetDatabaseCompatibility.ProduceArtifactsRefreshIfNecessary(sceneBuildConfigGuids, typeof(SubSceneImporter), artifactHashes); bool succeeded = true; for (int i = 0; i != sceneBuildConfigGuids.Length; i++) { var sceneGuid = sceneGuids[i]; var artifactHash = artifactHashes[i]; AssetDatabaseCompatibility.GetArtifactPaths(artifactHash, out var artifactPaths); List <FileInfo> exportedFiles = new List <FileInfo>(); bool foundEntityHeader = false; foreach (var artifactPath in artifactPaths) { var ext = Path.GetExtension(artifactPath).ToLower().Replace(".", ""); if (ext == EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesHeader)) { foundEntityHeader = true; var destinationFile = dataDirectory.FullName + Path.DirectorySeparatorChar + EntityScenesPaths.RelativePathFolderFor(sceneGuid, EntityScenesPaths.PathType.EntitiesHeader, -1); new NPath(artifactPath).MakeAbsolute().Copy(new NPath(destinationFile).MakeAbsolute().EnsureParentDirectoryExists()); exportedFiles.Add(new FileInfo(destinationFile)); } else if (ext == EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesBinary)) { var destinationFile = dataDirectory.FullName + Path.DirectorySeparatorChar + EntityScenesPaths.RelativePathFolderFor(sceneGuid, EntityScenesPaths.PathType.EntitiesBinary, EntityScenesPaths.GetSectionIndexFromPath(artifactPath)); new NPath(artifactPath).MakeAbsolute().Copy(new NPath(destinationFile).MakeAbsolute().EnsureParentDirectoryExists()); exportedFiles.Add(new FileInfo(destinationFile)); } else if (ext == EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesConversionLog)) { var destinationFile = logsDirectory.FullName + Path.DirectorySeparatorChar + $"{sceneGuid}.{EntityScenesPaths.GetExtension(EntityScenesPaths.PathType.EntitiesConversionLog)}"; new NPath(artifactPath).MakeAbsolute().Copy(new NPath(destinationFile).MakeAbsolute().EnsureParentDirectoryExists()); var result = PrintConversionLogToUnityConsole(artifactPath); if (result.HasError || result.HasException) { UnityEngine.Debug.LogError("Failed to export scene: " + Path.GetFileName(AssetDatabase.GUIDToAssetPath(sceneGuid.ToString()))); succeeded = false; } } else if (new Hash128(ext).IsValid) //Asset files are exported as {artifactHash}.{assetguid} { var destinationFile = dataDirectory.FullName + Path.DirectorySeparatorChar + ext; new NPath(artifactPath).MakeAbsolute().Copy(new NPath(destinationFile).MakeAbsolute().EnsureParentDirectoryExists()); exportedFiles.Add(new FileInfo(destinationFile)); } } if (!foundEntityHeader) { Debug.LogError($"Failed to build EntityScene for '{AssetDatabaseCompatibility.GuidToPath(sceneGuid)}'."); succeeded = false; } //UpdateManifest manifest.Add(new Guid(sceneGuid.ToString()), AssetDatabase.GUIDToAssetPath(sceneGuid.ToString()), exportedFiles); } var catalogPath = Path.Combine(dataDirectory.ToString(), SceneSystem.k_SceneInfoFileName); WriteCatalogFile(catalogPath, buildScenes); manifest.AddAdditionalFilesToDeploy(new FileInfo(catalogPath.ToString())); sceneBuildConfigGuids.Dispose(); artifactHashes.Dispose(); if (succeeded) { return(context.Success()); } return(context.Failure($"Failed to export scenes")); }