/// <summary> /// Saves the current project or module to the assets directory /// /// NOTE: If the project has never been saved before a `Save As` is called instead /// </summary> public static bool Save() { var persistentObject = EditorContext.PersistentObject; // No `PersistenceId` means this object has never been saved, promt the user with the `Save As` instead if (string.IsNullOrEmpty(persistentObject.PersistenceId)) { return(SaveAs()); } // Use a DontSaveScope since saving may trigger the `OnWillSaveAssets` callback using (new UTinyModificationProcessor.DontSaveScope()) { var path = UTinyPersistence.PersistObject(persistentObject); if (string.IsNullOrEmpty(path)) { return(false); } } UTinyEditorPrefs.SaveWorkspace(EditorContext.Workspace, persistentObject.PersistenceId); OnSaveProject?.Invoke(EditorContext.Project); UTinyModificationProcessor.ClearChanged(); return(true); }
private void AddRegistryObjectDependency(UTinyModule module) { if (null == module) { return; } foreach (var m in module.EnumerateDependencies()) { var path = UTinyPersistence.GetLocation(m); // Core or sample module/project don't need to be packaged, they will be included from the manifest. if (ShouldPackage(path)) { AddFileElements(path, false); } foreach (var asset in AssetIterator.EnumerateAssets(m)) { if (ShouldPackage(asset.AssetPath)) { AddFileElements(asset.AssetPath, false); } } } }
public static bool SaveAs() { var persistentObject = EditorContext.PersistentObject; var extension = UTinyPersistence.GetFileExtension(persistentObject); var path = EditorUtility.SaveFilePanelInProject($"Save {ContextType}", persistentObject.Name, extension.Substring(1), string.Empty); if (string.IsNullOrEmpty(path)) { return(false); } // Use a DontSaveScope since saving may trigger the `OnWillSaveAssets` callback using (new UTinyModificationProcessor.DontSaveScope()) { // Fix-up the name persistentObject.Name = Path.GetFileNameWithoutExtension(path); // Flush the caretaker so this operation is not undoable EditorContext.Caretaker.Update(); path = UTinyPersistence.PersistObjectAs(persistentObject, path); if (string.IsNullOrEmpty(path)) { return(false); } } UTinyEditorPrefs.SaveWorkspace(EditorContext.Workspace, persistentObject.PersistenceId); OnSaveProject?.Invoke(EditorContext.Project); UTinyModificationProcessor.ClearChanged(); return(true); }
/// <summary> /// Loads the utmodule at the given file path /// </summary> /// <param name="moduleFile">Relative path to the .utmodule file</param> public static void LoadModule(string moduleFile) { Assert.IsFalse(EditorApplication.isPlayingOrWillChangePlaymode); var context = new UTinyContext(); var registry = context.Registry; UTinyPersistence.LoadModule(moduleFile, registry); var module = registry.FindAllBySource(UTinyRegistry.DefaultSourceIdentifier).OfType <UTinyModule>().First(); Assert.IsNotNull(module); module.Name = Path.GetFileNameWithoutExtension(moduleFile); SetupModule(registry, module); var project = registry.CreateProject(UTinyId.Generate(KWorkspaceProjectName), KWorkspaceProjectName); project.Module = (UTinyModule.Reference)module; var editorContext = new UTinyEditorContext((UTinyProject.Reference)project, EditorContextType.Module, context, UTinyEditorPrefs.LoadWorkspace(project.PersistenceId)); LoadContext(editorContext, isChanged: false); }
/// <summary> /// Creates and loads a new .utmodule /// @NOTE The module only exists in memory until Save() is called /// </summary> public static void NewModule() { var context = new UTinyContext(); var registry = context.Registry; UTinyPersistence.LoadAllModules(registry); // Create a `workspace` project to host the module for editing purposes var project = registry.CreateProject(UTinyId.Generate(KWorkspaceProjectName), KWorkspaceProjectName); // Create objects for the new module var module = registry.CreateModule(UTinyId.New(), "NewModule"); // Setup initial state for the module module.Namespace = "module"; SetupModule(registry, module); project.Module = (UTinyModule.Reference)module; var workspace = new UTinyEditorWorkspace(); UTinyEditorPrefs.SaveWorkspace(workspace); var editorContext = new UTinyEditorContext((UTinyProject.Reference)project, EditorContextType.Module, context, workspace); LoadContext(editorContext, isChanged: true); }
/// <summary> /// Creates and loads a new .utproject /// @NOTE The project only exists in memory until Save() is called /// </summary> public static void NewProject() { Assert.IsFalse(EditorApplication.isPlayingOrWillChangePlaymode); var context = new UTinyContext(); var registry = context.Registry; UTinyPersistence.LoadAllModules(registry); // Create new objects for the project var project = registry.CreateProject(UTinyId.New(), "NewProject"); var module = registry.CreateModule(UTinyId.New(), "Main"); // Setup the start scene var entityGroup = registry.CreateEntityGroup(UTinyId.New(), "NewEntityGroup"); var entityGroupRef = (UTinyEntityGroup.Reference)entityGroup; var cameraEntity = registry.CreateEntity(UTinyId.New(), "Camera"); var transform = cameraEntity.AddComponent(registry.GetTransformType()); transform.Refresh(); var camera = cameraEntity.AddComponent(registry.GetCamera2DType()); camera.Refresh(); camera["clearFlags"] = new UTinyEnum.Reference(registry.GetCameraClearFlagsType().Dereference(registry), 1); camera.AssignPropertyFrom("backgroundColor", Color.black); camera["depth"] = -1.0f; entityGroup.AddEntityReference((UTinyEntity.Reference)cameraEntity); // Setup initial state for the project module.Options |= UTinyModuleOptions.ProjectModule; module.Namespace = "game"; module.StartupEntityGroup = (UTinyEntityGroup.Reference)entityGroup; module.AddEntityGroupReference(entityGroupRef); project.Module = (UTinyModule.Reference)module; project.Settings.EmbedAssets = true; project.Settings.CanvasWidth = 1920; project.Settings.CanvasHeight = 1080; SetupProject(registry, project); module.AddExplicitModuleDependency((UTinyModule.Reference)registry.FindByName <UTinyModule>("UTiny.Core2D")); // Always include a dependency on core, math, core2d by default // And HTML for now, since it is the only renderer we have right now. module.AddExplicitModuleDependency((UTinyModule.Reference)registry.FindByName <UTinyModule>("UTiny.HTML")); var workspace = new UTinyEditorWorkspace { OpenedEntityGroups = { entityGroupRef }, ActiveEntityGroup = entityGroupRef }; UTinyEditorPrefs.SaveWorkspace(workspace); var editorContext = new UTinyEditorContext((UTinyProject.Reference)project, EditorContextType.Project, context, workspace); LoadContext(editorContext, isChanged: true); }
public override void OnImportAsset(AssetImportContext ctx) { var asset = ScriptableObject.CreateInstance <UTModule>(); ctx.AddObjectToAsset("asset", asset); ctx.SetMainObject(asset); UTinyPersistence.MarkAssetChanged(ctx.assetPath); }
private void AddRequiredFileAndFolders() { var project = UTinyEditorApplication.Project; if (null == project) { return; } AddFileElements(UTinyPersistence.GetLocation(project), false); }
private static DirectoryInfo GetOrCreateContextDirectory(UTinyEditorContext context) { string directory; // [MP] @TODO: Get the actual folder where the project/solution is stored. if (context.ContextType == EditorContextType.Project) { directory = UTinyPersistence.GetLocation(context.Project); } else { directory = UTinyPersistence.GetLocation(context.Module); } return(Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(directory ?? "Assets/"), k_UTinyTempFolderName))); }
private void AddRegistryObjectDependency(UTinyProject project) { if (null == project) { return; } var path = UTinyPersistence.GetLocation(project); // Core or sample module/project don't need to be packaged, they will be included from the manifest. if (ShouldPackage(path)) { AddFileElements(path, false); } foreach (var module in project.Module.Dereference(project.Registry).EnumerateDependencies()) { AddRegistryObjectDependency(module); } }
/// <summary> /// Loads the utproject at the given file path /// </summary> /// <param name="projectFile">Relative path to the .utproject file</param> public static void LoadProject(string projectFile) { Assert.IsFalse(EditorApplication.isPlayingOrWillChangePlaymode); var context = new UTinyContext(); var registry = context.Registry; UTinyPersistence.LoadProject(projectFile, registry); var project = registry.FindAllByType <UTinyProject>().First(); Assert.IsNotNull(project); project.Name = Path.GetFileNameWithoutExtension(projectFile); SetupProject(registry, project); var editorContext = new UTinyEditorContext((UTinyProject.Reference)project, EditorContextType.Project, context, UTinyEditorPrefs.LoadWorkspace(project.PersistenceId)); LoadContext(editorContext, isChanged: false); }
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { // scripted importers take care of importedAssets // we don't care about moved assets, only deleted ones foreach (var a in deletedAssets) { if (a.EndsWith(UTinyPersistence.ProjectFileExtension) || a.EndsWith(UTinyPersistence.ModuleFileExtension)) { UTinyPersistence.MarkAssetChanged(a); } } foreach (var a in movedAssets) { if (a.EndsWith(UTinyPersistence.ProjectFileExtension) || a.EndsWith(UTinyPersistence.ModuleFileExtension)) { UTinyPersistence.MarkAssetMoved(a); } } }
/// <summary> /// Packages assets to `assets.js` or `Assets/*.*` /// </summary> private static void PackageAssets(UTinyBuildOptions options, UTinyBuildResults results) { var buildFolder = options.Destination; var binFolder = results.BinaryFolder; // Export assets to the build directory var buildAssetsFolder = new DirectoryInfo(Path.Combine(buildFolder.FullName, "Assets")); buildAssetsFolder.Create(); var export = UTinyAssetExporter.Export(options.Project, buildAssetsFolder); // copy assets to bin AND/OR encode assets to 'assets.js' var binAssetsFolder = new DirectoryInfo(Path.Combine(binFolder.FullName, "Assets")); binAssetsFolder.Create(); var assetsFile = new FileInfo(Path.Combine(binFolder.FullName, KAssetsFileName)); var writer = new UTinyCodeWriter(); PrependGeneratedHeader(writer, options.Project.Name); var reportAssets = results.BuildReport.AddChild(UTinyBuildReport.AssetsNode); var reportJavaScript = reportAssets.AddChild("JavaScript"); using (var jsdoc = new UTinyJsdoc.Writer(writer)) { jsdoc.Type("object"); jsdoc.Desc("Map containing URLs for all assets. If assets are included as base64 blobs, these will be data URLs."); jsdoc.Line("@example var assetUrl = UT_ASSETS[\"MyCustomAsset\"]"); } long totalBase64Size = 0; using (writer.Scope("var UT_ASSETS =")) { var i = 0; foreach (var info in export) { var reportAsset = reportAssets.AddChild(info.AssetInfo.AssetPath, 0, info.AssetInfo.Object); var settings = UTinyUtility.GetAssetExportSettings(options.Project, info.AssetInfo.Object); if (settings.Embedded) { foreach (var file in info.Exported) { var buffer = File.ReadAllBytes(file.FullName); var base64 = Convert.ToBase64String(buffer); var fileExtension = Path.GetExtension(file.FullName).ToLower(); string mimeType; switch (fileExtension) { case ".png": mimeType = "image/png"; break; case ".jpg": case ".jpeg": mimeType = "image/jpeg"; break; case ".webp": mimeType = "image/webp"; break; case ".mp3": mimeType = "audio/mpeg"; break; case ".wav": mimeType = "audio/wav"; break; case ".json": mimeType = "application/json"; break; case ".ttf": mimeType = "font/truetype"; break; default: Debug.LogWarningFormat("Asset {0} has unknown extension, included as text/plain in assets", file); mimeType = "text/plain"; break; } var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"data:{mimeType};base64,{base64}\""); i++; reportAsset.AddChild(UTinyBuildPipeline.GetRelativePath(file), Encoding.ASCII.GetBytes(base64), info.AssetInfo.Object); totalBase64Size += base64.Length; file.Delete(); } } else { foreach (var file in info.Exported) { var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"Assets/{file.Name}\""); i++; reportAsset.AddChild(file, info.AssetInfo.Object); } } } } writer.Line(); writer.WriteRaw("var UT_ASSETS_SETUP = "); { var registry = new UTinyRegistry(); UTinyPersistence.LoadAllModules(registry); var entityGroup = UTinyAssetEntityGroupGenerator.Generate(registry, options.Project); EntityGroupSetupVisitor.WriteEntityGroupSetupFunction(writer, options.Project, entityGroup, false, false); } // Write `assets.js` File.WriteAllText(assetsFile.FullName, writer.ToString()); reportJavaScript.Item.Size = assetsFile.Length - totalBase64Size; // Remaining assets are binplaced foreach (var info in export) { foreach (var file in info.Exported) { if (!file.Exists) { // this asset has been packaged already continue; } file.MoveTo(Path.Combine(binAssetsFolder.FullName, file.Name)); } } // Clean up the build directory buildAssetsFolder.Delete(true); // if we have no standalone assets, cleanup if (binAssetsFolder.GetFiles().Length <= 0) { binAssetsFolder.Delete(); } }
/// <summary> /// @TODO This method has too many conditionals and checks... it should be managed at a higher level /// </summary> /// <param name="registry"></param> /// <param name="persistenceId"></param> /// <returns></returns> /// <exception cref="ArgumentOutOfRangeException"></exception> public static bool Accept(IRegistry registry, out string persistenceId) { Assert.IsTrue(Exists()); registry.Clear(); UTinyPersistence.LoadAllModules(registry); registry.UnregisterAllBySource(UTinyRegistry.DefaultSourceIdentifier); persistenceId = null; using (var command = new MemoryStream()) using (var stream = File.OpenRead(GetTempLocation().FullName)) using (var reader = new BinaryReader(stream)) using (registry.SourceIdentifierScope(UTinyRegistry.DefaultSourceIdentifier)) { var version = reader.ReadInt32(); Assert.IsTrue(version > 0); var type = (SaveType)reader.ReadByte(); switch (type) { case SaveType.PersistentUnchanged: persistenceId = reader.ReadString(); return(false); case SaveType.PersistentChanged: persistenceId = reader.ReadString(); var hash = reader.ReadString(); if (!string.IsNullOrEmpty(hash) && !string.Equals(hash, ComputeHash(persistenceId))) { // Ask the user if they want to keep their changes or reload from disc if (EditorUtility.DisplayDialog($"{UTinyConstants.ApplicationName} assets changed", $"{UTinyConstants.ApplicationName} assets have changed on disk, would you like to reload the current project?", "Yes", "No")) { return(false); } } break; case SaveType.Temporary: break; default: throw new ArgumentOutOfRangeException(); } // This is to handle module editing. // We want to unregister it from its current source and re-register it with the persistenceId as the scope if (!string.IsNullOrEmpty(persistenceId)) { registry.UnregisterAllBySource(persistenceId); } FrontEnd.Accept(stream, command); command.Position = 0; Serialization.CommandStream.FrontEnd.Accept(command, registry); } foreach (var project in registry.FindAllBySource(UTinyRegistry.DefaultSourceIdentifier).OfType <UTinyProject>()) { project.PersistenceId = persistenceId; } return(true); }
private static void Update() { if (EditorApplication.isPlayingOrWillChangePlaymode) { return; } if (null == EditorContext) { // Flush asset changes from the persistence system // We don't care about any changes unless we have a project loaded UTinyPersistence.ClearChanges(); return; } // Poll for workspace changes if (null != EditorContext.Workspace && s_WorkspaceVersion != EditorContext.Workspace.Version) { UTinyEditorPrefs.SaveWorkspace(EditorContext.Workspace, EditorContext.PersistentObject.PersistenceId); s_WorkspaceVersion = EditorContext.Workspace.Version; } // Poll for file/asset changes var changes = UTinyPersistence.DetectChanges(Registry); if (changes.changesDetected) { var persistenceId = EditorContext.PersistentObject.PersistenceId; foreach (var change in changes.changedSources) { // The currently opened project or module has been changed on disc if (change.Equals(persistenceId)) { // Ask the user if they want to keep their changes or reload from disc if (EditorUtility.DisplayDialog($"{UTinyConstants.ApplicationName} assets changed", $"{UTinyConstants.ApplicationName} assets have changed on disk, would you like to reload the current project?", "Yes", "No")) { LoadPersistenceId(persistenceId); } else { UTinyModificationProcessor.MarkChanged(); } } else { // This is some other file. We assume they are in a readonly state and we silently reload the object UTinyPersistence.ReloadObject(EditorContext.Registry, change); } } foreach (var deletion in changes.deletedSources) { // The currently opened project or module has been deleted on disc if (deletion.Equals(persistenceId)) { // Ask the user if they want to keep their changes or close the project if (EditorUtility.DisplayDialog($"{UTinyConstants.ApplicationName} assets changed", "The current project has been deleted, would you like to close the current project?", "Yes", "No")) { // Force close the project Close(); } else { UTinyModificationProcessor.MarkChanged(); EditorContext.PersistentObject.PersistenceId = string.Empty; } } else { // This is some other file. We assume they are in a readonly state and we silently reload the object EditorContext.Registry.UnregisterAllBySource(deletion); } } foreach (var moved in changes.movedSources) { if (!moved.Equals(persistenceId)) { continue; } var path = AssetDatabase.GUIDToAssetPath(moved); var asset = AssetDatabase.LoadAssetAtPath(path, typeof(UnityEngine.Object)); if (null != asset) { EditorContext.PersistentObject.Name = asset.name; } } OnChangesDetected?.Invoke(); } // Poll for module or project changes if (EditorContext.ContextType == EditorContextType.Project && (s_ProjectVersion != EditorContext.Project.Version || s_ModuleVersion != EditorContext.Module.Version)) { EditorContext.Project.RefreshConfiguration(); s_ProjectVersion = EditorContext.Project.Version; s_ModuleVersion = EditorContext.Module.Version; } if (s_Save) { s_Save = false; // NOTE: It is possible that this call will fail Save(); } }