From 91b3c8965709a3473900fb19aa2c5f203f347c14 Mon Sep 17 00:00:00 2001 From: BOTAlex Date: Wed, 14 Feb 2024 15:34:22 +0100 Subject: [PATCH] Imported ParallelSync --- Assets/Plugins/ParrelSync.meta | 8 + Assets/Plugins/ParrelSync/Editor.meta | 8 + .../ParrelSync/Editor/AssetModBlock.meta | 8 + .../Editor/AssetModBlock/EditorQuit.cs | 22 + .../Editor/AssetModBlock/EditorQuit.cs.meta | 11 + .../ParrelSyncAssetModificationProcessor.cs | 34 + ...rrelSyncAssetModificationProcessor.cs.meta | 11 + .../ParrelSync/Editor/ClonesManager.cs | 692 ++++++++++++++++++ .../ParrelSync/Editor/ClonesManager.cs.meta | 11 + .../ParrelSync/Editor/ClonesManagerWindow.cs | 198 +++++ .../Editor/ClonesManagerWindow.cs.meta | 11 + .../ParrelSync/Editor/ExternalLinks.cs | 13 + .../ParrelSync/Editor/ExternalLinks.cs.meta | 11 + .../ParrelSync/Editor/FileUtilities.cs | 31 + .../ParrelSync/Editor/FileUtilities.cs.meta | 11 + Assets/Plugins/ParrelSync/Editor/NonCore.meta | 8 + .../Editor/NonCore/AskFeedbackDialog.cs | 78 ++ .../Editor/NonCore/AskFeedbackDialog.cs.meta | 11 + .../Editor/NonCore/OtherMenuItem.cs | 26 + .../Editor/NonCore/OtherMenuItem.cs.meta | 11 + .../Editor/ParrelSyncProjectSettings.cs | 140 ++++ .../Editor/ParrelSyncProjectSettings.cs.meta | 11 + .../Plugins/ParrelSync/Editor/Preferences.cs | 215 ++++++ .../ParrelSync/Editor/Preferences.cs.meta | 11 + Assets/Plugins/ParrelSync/Editor/Project.cs | 112 +++ .../Plugins/ParrelSync/Editor/Project.cs.meta | 11 + .../ParrelSync/Editor/UpdateChecker.cs | 60 ++ .../ParrelSync/Editor/UpdateChecker.cs.meta | 11 + .../Editor/ValidateCopiedFoldersIntegrity.cs | 73 ++ .../ValidateCopiedFoldersIntegrity.cs.meta | 11 + Assets/Plugins/ParrelSync/package.json | 10 + Assets/Plugins/ParrelSync/package.json.meta | 7 + .../Plugins/ParrelSync/projectCloner.asmdef | 15 + .../ParrelSync/projectCloner.asmdef.meta | 7 + .../Scripts/Multiplayer/NetworkedGameSetup.cs | 2 +- 35 files changed, 1909 insertions(+), 1 deletion(-) create mode 100644 Assets/Plugins/ParrelSync.meta create mode 100644 Assets/Plugins/ParrelSync/Editor.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/AssetModBlock.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/ClonesManager.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/ClonesManager.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/FileUtilities.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/FileUtilities.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/NonCore.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/Preferences.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/Preferences.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/Project.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/Project.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs.meta create mode 100644 Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs create mode 100644 Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta create mode 100644 Assets/Plugins/ParrelSync/package.json create mode 100644 Assets/Plugins/ParrelSync/package.json.meta create mode 100644 Assets/Plugins/ParrelSync/projectCloner.asmdef create mode 100644 Assets/Plugins/ParrelSync/projectCloner.asmdef.meta diff --git a/Assets/Plugins/ParrelSync.meta b/Assets/Plugins/ParrelSync.meta new file mode 100644 index 0000000..82fb126 --- /dev/null +++ b/Assets/Plugins/ParrelSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 711bbd1e36ca42a4bad871eb6a3de1bc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor.meta b/Assets/Plugins/ParrelSync/Editor.meta new file mode 100644 index 0000000..56fd131 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a31ea7d0315594440839cdb0db6bc411 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/AssetModBlock.meta b/Assets/Plugins/ParrelSync/Editor/AssetModBlock.meta new file mode 100644 index 0000000..3bb4f70 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/AssetModBlock.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8b14e706b1e7cb044b23837e8a70cad9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs new file mode 100644 index 0000000..dc181d1 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs @@ -0,0 +1,22 @@ +using UnityEditor; +namespace ParrelSync +{ + [InitializeOnLoad] + public class EditorQuit + { + /// + /// Is editor being closed + /// + static public bool IsQuiting { get; private set; } + static void Quit() + { + IsQuiting = true; + } + + static EditorQuit() + { + IsQuiting = false; + EditorApplication.quitting += Quit; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta new file mode 100644 index 0000000..2296dac --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf2888ff90706904abc2d851c3e59e00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs new file mode 100644 index 0000000..6587482 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs @@ -0,0 +1,34 @@ +using UnityEditor; +using UnityEngine; +namespace ParrelSync +{ + /// + /// For preventing assets being modified from the clone instance. + /// + public class ParrelSyncAssetModificationProcessor : UnityEditor.AssetModificationProcessor + { + public static string[] OnWillSaveAssets(string[] paths) + { + if (ClonesManager.IsClone() && Preferences.AssetModPref.Value) + { + if (paths != null && paths.Length > 0 && !EditorQuit.IsQuiting) + { + EditorUtility.DisplayDialog( + ClonesManager.ProjectName + ": Asset modifications saving detected and blocked", + "Asset modifications saving are blocked in the clone instance. \n\n" + + "This is a clone of the original project. \n" + + "Making changes to asset files via the clone editor is not recommended. \n" + + "Please use the original editor window if you want to make changes to the project files.", + "ok" + ); + foreach (var path in paths) + { + Debug.Log("Attempting to save " + path + " are blocked."); + } + } + return new string[0] { }; + } + return paths; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta new file mode 100644 index 0000000..7158175 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 755e570bd21b39440a923056e60f1450 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs b/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs new file mode 100644 index 0000000..df47ca0 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs @@ -0,0 +1,692 @@ +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using UnityEditor; +using System.Linq; +using System.IO; +using Debug = UnityEngine.Debug; + +namespace ParrelSync +{ + /// + /// Contains all required methods for creating a linked clone of the Unity project. + /// + public class ClonesManager + { + /// + /// Name used for an identifying file created in the clone project directory. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneFileName = ".clone"; + + /// + /// Suffix added to the end of the project clone name when it is created. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneNameSuffix = "_clone"; + + public const string ProjectName = "ParrelSync"; + + /// + /// The maximum number of clones + /// + public const int MaxCloneProjectCount = 10; + + /// + /// Name of the file for storing clone's argument. + /// + public const string ArgumentFileName = ".parrelsyncarg"; + + /// + /// Default argument of the new clone + /// + public const string DefaultArgument = "client"; + + #region Managing clones + + /// + /// Creates clone from the project currently open in Unity Editor. + /// + /// + public static Project CreateCloneFromCurrent() + { + if (IsClone()) + { + Debug.LogError("This project is already a clone. Cannot clone it."); + return null; + } + + string currentProjectPath = ClonesManager.GetCurrentProjectPath(); + return ClonesManager.CreateCloneFromPath(currentProjectPath); + } + + /// + /// Creates clone of the project located at the given path. + /// + /// + /// + public static Project CreateCloneFromPath(string sourceProjectPath) + { + Project sourceProject = new Project(sourceProjectPath); + + string cloneProjectPath = null; + + //Find available clone suffix id + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string possibleCloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (!Directory.Exists(possibleCloneProjectPath)) + { + cloneProjectPath = possibleCloneProjectPath; + break; + } + } + + if (string.IsNullOrEmpty(cloneProjectPath)) + { + Debug.LogError("The number of cloned projects has reach its limit. Limit: " + MaxCloneProjectCount); + return null; + } + + Project cloneProject = new Project(cloneProjectPath); + + Debug.Log("Start cloning project, original project: " + sourceProject + ", clone project: " + cloneProject); + + ClonesManager.CreateProjectFolder(cloneProject); + + //Copy Folders + Debug.Log("Library copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, cloneProject.libraryPath, + "Cloning Project Library '" + sourceProject.name + "'. "); + Debug.Log("Packages copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.packagesPath, cloneProject.packagesPath, + "Cloning Project Packages '" + sourceProject.name + "'. "); + + + //Link Folders + ClonesManager.LinkFolders(sourceProject.assetPath, cloneProject.assetPath); + ClonesManager.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath); + ClonesManager.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath); + ClonesManager.LinkFolders(sourceProject.localPackages, cloneProject.localPackages); + + //Optional Link Folders + var optionalLinkPaths = Preferences.OptionalSymbolicLinkFolders.GetStoredValue(); + var projectSettings = ParrelSyncProjectSettings.GetSerializedSettings(); + var projectSettingsProperty = projectSettings.FindProperty("m_OptionalSymbolicLinkFolders"); + if (projectSettingsProperty is { isArray: true, arrayElementType: "string" }) + { + for (var i = 0; i < projectSettingsProperty.arraySize; ++i) + { + optionalLinkPaths.Add(projectSettingsProperty.GetArrayElementAtIndex(i).stringValue); + } + } + foreach (var path in optionalLinkPaths) + { + var sourceOptionalPath = sourceProjectPath + path; + var cloneOptionalPath = cloneProjectPath + path; + LinkFolders(sourceOptionalPath, cloneOptionalPath); + } + + ClonesManager.RegisterClone(cloneProject); + + return cloneProject; + } + + /// + /// Registers a clone by placing an identifying ".clone" file in its root directory. + /// + /// + private static void RegisterClone(Project cloneProject) + { + /// Add clone identifier file. + string identifierFile = Path.Combine(cloneProject.projectPath, ClonesManager.CloneFileName); + File.Create(identifierFile).Dispose(); + + //Add argument file with default argument + string argumentFilePath = Path.Combine(cloneProject.projectPath, ClonesManager.ArgumentFileName); + File.WriteAllText(argumentFilePath, DefaultArgument, System.Text.Encoding.UTF8); + + /// Add collabignore.txt to stop the clone from messing with Unity Collaborate if it's enabled. Just in case. + string collabignoreFile = Path.Combine(cloneProject.projectPath, "collabignore.txt"); + File.WriteAllText(collabignoreFile, "*"); /// Make it ignore ALL files in the clone. + } + + /// + /// Opens a project located at the given path (if one exists). + /// + /// + public static void OpenProject(string projectPath) + { + if (!Directory.Exists(projectPath)) + { + Debug.LogError("Cannot open the project - provided folder (" + projectPath + ") does not exist."); + return; + } + + if (projectPath == ClonesManager.GetCurrentProjectPath()) + { + Debug.LogError("Cannot open the project - it is already open."); + return; + } + + //Validate (and update if needed) the "Packages" folder before opening clone project to ensure the clone project will have the + //same "compiling environment" as the original project + ValidateCopiedFoldersIntegrity.ValidateFolder(projectPath, GetOriginalProjectPath(), "Packages"); + + string fileName = GetApplicationPath(); + string args = "-projectPath \"" + projectPath + "\""; + Debug.Log("Opening project \"" + fileName + " " + args + "\""); + ClonesManager.StartHiddenConsoleProcess(fileName, args); + } + + private static string GetApplicationPath() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + return EditorApplication.applicationPath; + case RuntimePlatform.OSXEditor: + return EditorApplication.applicationPath + "/Contents/MacOS/Unity"; + case RuntimePlatform.LinuxEditor: + return EditorApplication.applicationPath; + default: + throw new System.NotImplementedException("Platform has not supported yet ;("); + } + } + + /// + /// Is this project being opened by an Unity editor? + /// + /// + /// + public static bool IsCloneProjectRunning(string projectPath) + { + + //Determine whether it is opened in another instance by checking the UnityLockFile + string UnityLockFilePath = new string[] { projectPath, "Temp", "UnityLockfile" } + .Aggregate(Path.Combine); + + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + //Windows editor will lock "UnityLockfile" file when project is being opened. + //Sometime, for instance: windows editor crash, the "UnityLockfile" will not be deleted even the project + //isn't being opened, so a check to the "UnityLockfile" lock status may be necessary. + if (Preferences.AlsoCheckUnityLockFileStaPref.Value) + return File.Exists(UnityLockFilePath) && FileUtilities.IsFileLocked(UnityLockFilePath); + else + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.OSXEditor): + //Mac editor won't lock "UnityLockfile" file when project is being opened + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.LinuxEditor): + return File.Exists(UnityLockFilePath); + default: + throw new System.NotImplementedException("IsCloneProjectRunning: Unsupport Platfrom: " + Application.platform); + } + } + + /// + /// Deletes the clone of the currently open project, if such exists. + /// + public static void DeleteClone(string cloneProjectPath) + { + /// Clone won't be able to delete itself. + if (ClonesManager.IsClone()) return; + + ///Extra precautions. + if (cloneProjectPath == string.Empty) return; + if (cloneProjectPath == ClonesManager.GetOriginalProjectPath()) return; + + //Check what OS is + string identifierFile; + string args; + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + args = "/c " + @"rmdir /s/q " + string.Format("\"{0}\"", cloneProjectPath); + StartHiddenConsoleProcess("cmd.exe", args); + + break; + case (RuntimePlatform.OSXEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + case (RuntimePlatform.LinuxEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + default: + Debug.LogWarning("Not in a known editor. Where are you!?"); + break; + } + } + + #endregion + + #region Creating project folders + + /// + /// Creates an empty folder using data in the given Project object + /// + /// + public static void CreateProjectFolder(Project project) + { + string path = project.projectPath; + Debug.Log("Creating new empty folder at: " + path); + Directory.CreateDirectory(path); + } + + /// + /// Copies the full contents of the unity library. We want to do this to avoid the lengthy re-serialization of the whole project when it opens up the clone. + /// + /// + /// + [System.Obsolete] + public static void CopyLibraryFolder(Project sourceProject, Project destinationProject) + { + if (Directory.Exists(destinationProject.libraryPath)) + { + Debug.LogWarning("Library copy: destination path already exists! "); + return; + } + + Debug.Log("Library copy: " + destinationProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, + "Cloning project '" + sourceProject.name + "'. "); + } + + #endregion + + #region Creating symlinks + + /// + /// Creates a symlink between destinationPath and sourcePath (Mac version). + /// + /// + /// + private static void CreateLinkMac(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Mac hard link " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Linux version). + /// + /// + /// + private static void CreateLinkLinux(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Linux Symlink " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Windows version). + /// + /// + /// + private static void CreateLinkWin(string sourcePath, string destinationPath) + { + string cmd = "/C mklink /J " + string.Format("\"{0}\" \"{1}\"", destinationPath, sourcePath); + Debug.Log("Windows junction: " + cmd); + ClonesManager.StartHiddenConsoleProcess("cmd.exe", cmd); + } + + //TODO(?) avoid terminal calls and use proper api stuff. See below for windows! + ////https://docs.microsoft.com/en-us/windows/desktop/api/ioapiset/nf-ioapiset-deviceiocontrol + //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + //private static extern bool DeviceIoControl(System.IntPtr hDevice, uint dwIoControlCode, + // System.IntPtr InBuffer, int nInBufferSize, + // System.IntPtr OutBuffer, int nOutBufferSize, + // out int pBytesReturned, System.IntPtr lpOverlapped); + + /// + /// Create a link / junction from the original project to it's clone. + /// + /// + /// + public static void LinkFolders(string sourcePath, string destinationPath) + { + if ((Directory.Exists(destinationPath) == false) && (Directory.Exists(sourcePath) == true)) + { + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + CreateLinkWin(sourcePath, destinationPath); + break; + case (RuntimePlatform.OSXEditor): + CreateLinkMac(sourcePath, destinationPath); + break; + case (RuntimePlatform.LinuxEditor): + CreateLinkLinux(sourcePath, destinationPath); + break; + default: + Debug.LogWarning("Not in a known editor. Application.platform: " + Application.platform); + break; + } + } + else + { + Debug.LogWarning("Skipping Asset link, it already exists: " + destinationPath); + } + } + + #endregion + + #region Utility methods + + private static bool? isCloneFileExistCache = null; + + /// + /// Returns true if the project currently open in Unity Editor is a clone. + /// + /// + public static bool IsClone() + { + if (isCloneFileExistCache == null) + { + /// The project is a clone if its root directory contains an empty file named ".clone". + string cloneFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.CloneFileName); + isCloneFileExistCache = File.Exists(cloneFilePath); + } + + return (bool)isCloneFileExistCache; + } + + /// + /// Get the path to the current unityEditor project folder's info + /// + /// + public static string GetCurrentProjectPath() + { + return Application.dataPath.Replace("/Assets", ""); + } + + /// + /// Return a project object that describes all the paths we need to clone it. + /// + /// + public static Project GetCurrentProject() + { + string pathString = ClonesManager.GetCurrentProjectPath(); + return new Project(pathString); + } + + /// + /// Get the argument of this clone project. + /// If this is the original project, will return an empty string. + /// + /// + public static string GetArgument() + { + string argument = ""; + if (IsClone()) + { + string argumentFilePath = Path.Combine(GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + if (File.Exists(argumentFilePath)) + { + argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + } + } + + return argument; + } + + /// + /// Returns the path to the original project. + /// If currently open project is the original, returns its own path. + /// If the original project folder cannot be found, retuns an empty string. + /// + /// + public static string GetOriginalProjectPath() + { + if (IsClone()) + { + /// If this is a clone... + /// Original project path can be deduced by removing the suffix from the clone's path. + string cloneProjectPath = ClonesManager.GetCurrentProject().projectPath; + + int index = cloneProjectPath.LastIndexOf(ClonesManager.CloneNameSuffix); + if (index > 0) + { + string originalProjectPath = cloneProjectPath.Substring(0, index); + if (Directory.Exists(originalProjectPath)) return originalProjectPath; + } + + return string.Empty; + } + else + { + /// If this is the original, we return its own path. + return ClonesManager.GetCurrentProjectPath(); + } + } + + /// + /// Returns all clone projects path. + /// + /// + public static List GetCloneProjectsPath() + { + List projectsPath = new List(); + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string cloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (Directory.Exists(cloneProjectPath)) + projectsPath.Add(cloneProjectPath); + } + + return projectsPath; + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Optional string added to the beginning of the progress bar window header. + public static void CopyDirectoryWithProgressBar(string sourcePath, string destinationPath, + string progressBarPrefix = "") + { + var source = new DirectoryInfo(sourcePath); + var destination = new DirectoryInfo(destinationPath); + + long totalBytes = 0; + long copiedBytes = 0; + + ClonesManager.CopyDirectoryWithProgressBarRecursive(source, destination, ref totalBytes, ref copiedBytes, + progressBarPrefix); + EditorUtility.ClearProgressBar(); + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// Same as the previous method, but uses recursion to copy all nested folders as well. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Total bytes to be copied. Calculated automatically, initialize at 0. + /// To track already copied bytes. Calculated automatically, initialize at 0. + /// Optional string added to the beginning of the progress bar window header. + private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, + ref long totalBytes, ref long copiedBytes, string progressBarPrefix = "") + { + /// Directory cannot be copied into itself. + if (source.FullName.ToLower() == destination.FullName.ToLower()) + { + Debug.LogError("Cannot copy directory into itself."); + return; + } + + /// Calculate total bytes, if required. + if (totalBytes == 0) + { + totalBytes = ClonesManager.GetDirectorySize(source, true, progressBarPrefix); + } + + /// Create destination directory, if required. + if (!Directory.Exists(destination.FullName)) + { + Directory.CreateDirectory(destination.FullName); + } + + /// Copy all files from the source. + foreach (FileInfo file in source.GetFiles()) + { + // Ensure file exists before continuing. + if (!file.Exists) + { + continue; + } + + try + { + file.CopyTo(Path.Combine(destination.ToString(), file.Name), true); + } + catch (IOException) + { + /// Some files may throw IOException if they are currently open in Unity editor. + /// Just ignore them in such case. + } + + /// Account the copied file size. + copiedBytes += file.Length; + + /// Display the progress bar. + float progress = (float)copiedBytes / (float)totalBytes; + bool cancelCopy = EditorUtility.DisplayCancelableProgressBar( + progressBarPrefix + "Copying '" + source.FullName + "' to '" + destination.FullName + "'...", + "(" + (progress * 100f).ToString("F2") + "%) Copying file '" + file.Name + "'...", + progress); + if (cancelCopy) return; + } + + /// Copy all nested directories from the source. + foreach (DirectoryInfo sourceNestedDir in source.GetDirectories()) + { + DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name); + ClonesManager.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, + ref totalBytes, ref copiedBytes, progressBarPrefix); + } + } + + /// + /// Calculates the size of the given directory. Displays a progress bar. + /// + /// Directory, which size has to be calculated. + /// If true, size will include all nested directories. + /// Optional string added to the beginning of the progress bar window header. + /// Size of the directory in bytes. + private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, + string progressBarPrefix = "") + { + EditorUtility.DisplayProgressBar(progressBarPrefix + "Calculating size of directories...", + "Scanning '" + directory.FullName + "'...", 0f); + + /// Calculate size of all files in directory. + long filesSize = directory.GetFiles().Sum((FileInfo file) => file.Exists ? file.Length : 0); + + /// Calculate size of all nested directories. + long directoriesSize = 0; + if (includeNested) + { + IEnumerable nestedDirectories = directory.GetDirectories(); + foreach (DirectoryInfo nestedDir in nestedDirectories) + { + directoriesSize += ClonesManager.GetDirectorySize(nestedDir, true, progressBarPrefix); + } + } + + return filesSize + directoriesSize; + } + + /// + /// Starts process in the system console, taking the given fileName and args. + /// + /// + /// + private static void StartHiddenConsoleProcess(string fileName, string args) + { + System.Diagnostics.Process.Start(fileName, args); + } + + /// + /// Thanks to https://github.com/karl-/unity-symlink-utility/blob/master/SymlinkUtility.cs + /// + /// + private static void ExecuteBashCommand(string command) + { + command = command.Replace("\"", "\"\""); + + var proc = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = "-c \"" + command + "\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + using (proc) + { + proc.Start(); + proc.WaitForExit(); + + if (!proc.StandardError.EndOfStream) + { + UnityEngine.Debug.LogError(proc.StandardError.ReadToEnd()); + } + } + } + + public static void OpenProjectInFileExplorer(string path) + { + System.Diagnostics.Process.Start(@path); + } + #endregion + } +} diff --git a/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs.meta b/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs.meta new file mode 100644 index 0000000..5800cf8 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ClonesManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6148e48ed6b61d748b187d06d3687b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs b/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs new file mode 100644 index 0000000..ad9619e --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs @@ -0,0 +1,198 @@ +using UnityEngine; +using UnityEditor; +using System.IO; + +namespace ParrelSync +{ + /// + ///Clones manager Unity editor window + /// + public class ClonesManagerWindow : EditorWindow + { + /// + /// Returns true if project clone exists. + /// + public bool isCloneCreated + { + get { return ClonesManager.GetCloneProjectsPath().Count >= 1; } + } + + [MenuItem("ParrelSync/Clones Manager", priority = 0)] + private static void InitWindow() + { + ClonesManagerWindow window = (ClonesManagerWindow)EditorWindow.GetWindow(typeof(ClonesManagerWindow)); + window.titleContent = new GUIContent("Clones Manager"); + window.Show(); + } + + /// + /// For storing the scroll position of clones list + /// + Vector2 clonesScrollPos; + + private void OnGUI() + { + /// If it is a clone project... + if (ClonesManager.IsClone()) + { + //Find out the original project name and show the help box + string originalProjectPath = ClonesManager.GetOriginalProjectPath(); + if (originalProjectPath == string.Empty) + { + /// If original project cannot be found, display warning message. + EditorGUILayout.HelpBox( + "This project is a clone, but the link to the original seems lost.\nYou have to manually open the original and create a new clone instead of this one.\n", + MessageType.Warning); + } + else + { + /// If original project is present, display some usage info. + EditorGUILayout.HelpBox( + "This project is a clone of the project '" + Path.GetFileName(originalProjectPath) + "'.\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.", + MessageType.Info); + } + + //Clone project custom argument. + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + //Need to be careful with file reading / writing since it will effect the deletion of + // the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + } + else// If it is an original project... + { + if (isCloneCreated) + { + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Clones of this Project"); + + //List all clones + clonesScrollPos = + EditorGUILayout.BeginScrollView(clonesScrollPos); + var cloneProjectsPath = ClonesManager.GetCloneProjectsPath(); + for (int i = 0; i < cloneProjectsPath.Count; i++) + { + + GUILayout.BeginVertical("GroupBox"); + string cloneProjectPath = cloneProjectsPath[i]; + + bool isOpenInAnotherInstance = ClonesManager.IsCloneProjectRunning(cloneProjectPath); + + if (isOpenInAnotherInstance == true) + EditorGUILayout.LabelField("Clone " + i + " (Running)", EditorStyles.boldLabel); + else + EditorGUILayout.LabelField("Clone " + i); + + + GUILayout.BeginHorizontal(); + EditorGUILayout.TextField("Clone project path", cloneProjectPath, EditorStyles.textField); + if (GUILayout.Button("View Folder", GUILayout.Width(80))) + { + ClonesManager.OpenProjectInFileExplorer(cloneProjectPath); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + //Need to be careful with file reading/writing since it will effect the deletion of + //the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + + EditorGUILayout.Space(); + EditorGUILayout.Space(); + EditorGUILayout.Space(); + + + EditorGUI.BeginDisabledGroup(isOpenInAnotherInstance); + + if (GUILayout.Button("Open in New Editor")) + { + ClonesManager.OpenProject(cloneProjectPath); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Delete")) + { + bool delete = EditorUtility.DisplayDialog( + "Delete the clone?", + "Are you sure you want to delete the clone project '" + ClonesManager.GetCurrentProject().name + "_clone'?", + "Delete", + "Cancel"); + if (delete) + { + ClonesManager.DeleteClone(cloneProjectPath); + } + } + + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + GUILayout.EndVertical(); + + } + EditorGUILayout.EndScrollView(); + + if (GUILayout.Button("Add new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + } + else + { + /// If no clone created yet, we must create it. + EditorGUILayout.HelpBox("No project clones found. Create a new one!", MessageType.Info); + if (GUILayout.Button("Create new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + } + } + } + } +} diff --git a/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs.meta b/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs.meta new file mode 100644 index 0000000..ac75a04 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ClonesManagerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a041d83486c20b84bbf5077ddfbbca37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs b/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs new file mode 100644 index 0000000..84809bc --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs @@ -0,0 +1,13 @@ +namespace ParrelSync +{ + public class ExternalLinks + { + public const string RemoteVersionURL = "https://raw.githubusercontent.com/VeriorPies/ParrelSync/master/VERSION.txt"; + public const string Releases = "https://github.com/VeriorPies/ParrelSync/releases"; + public const string CustomArgumentHelpLink = "https://github.com/VeriorPies/ParrelSync/wiki/Argument"; + + public const string GitHubHome = "https://github.com/VeriorPies/ParrelSync/"; + public const string GitHubIssue = "https://github.com/VeriorPies/ParrelSync/issues"; + public const string FAQ = "https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs"; + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs.meta b/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs.meta new file mode 100644 index 0000000..c238b5c --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ExternalLinks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65daf17fbe5101b41977305639f30c65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs b/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs new file mode 100644 index 0000000..999ee02 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs @@ -0,0 +1,31 @@ +using System.IO; +using UnityEngine; + +namespace ParrelSync +{ + public class FileUtilities + { + public static bool IsFileLocked(string path) + { + FileInfo file = new FileInfo(path); + try + { + using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None)) + { + stream.Close(); + } + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + return true; + } + + //file is not locked + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs.meta b/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs.meta new file mode 100644 index 0000000..2733944 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/FileUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11fdc6f78f8c965499a870ca06dca6bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/NonCore.meta b/Assets/Plugins/ParrelSync/Editor/NonCore.meta new file mode 100644 index 0000000..5b4e192 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/NonCore.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74a7aa389726f964ab34c52e208c2a43 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs b/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs new file mode 100644 index 0000000..2bb988a --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs @@ -0,0 +1,78 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + /// + /// A simple script to display feedback/star dialog after certain time of project being opened/re-compiled. + /// Will only pop-up once unless "Remind me next time" are chosen. + /// Removing this file from project wont effect any other functions. + /// + [InitializeOnLoad] + public class AskFeedbackDialog + { + const string InitializeOnLoadCountKey = "ParrelSync_InitOnLoadCount", StopShowingKey = "ParrelSync_StopShowFeedBack"; + static AskFeedbackDialog() + { + if (EditorPrefs.HasKey(StopShowingKey)) { return; } + + int InitializeOnLoadCount = EditorPrefs.GetInt(InitializeOnLoadCountKey, 0); + if (InitializeOnLoadCount > 20) + { + ShowDialog(); + } + else + { + EditorPrefs.SetInt(InitializeOnLoadCountKey, InitializeOnLoadCount + 1); + } + } + + //[MenuItem("ParrelSync/(Debug)Show AskFeedbackDialog ")] + private static void ShowDialog() + { + int option = EditorUtility.DisplayDialogComplex("Do you like " + ParrelSync.ClonesManager.ProjectName + "?", + "Do you like " + ParrelSync.ClonesManager.ProjectName + "?\n" + + "If so, please don't hesitate to star it on GitHub and contribute to the project!", + "Star on GitHub", + "Close", + "Remind me next time" + ); + + switch (option) + { + // First parameter. + case 0: + Debug.Log("AskFeedbackDialog: Star on GitHub selected"); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + Application.OpenURL(ExternalLinks.GitHubHome); + break; + // Second parameter. + case 1: + Debug.Log("AskFeedbackDialog: Close and never show again."); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + break; + // Third parameter. + case 2: + Debug.Log("AskFeedbackDialog: Remind me next time"); + EditorPrefs.SetInt(InitializeOnLoadCountKey, 0); + break; + default: + //Debug.Log("Close windows."); + break; + } + } + + ///// + ///// For debug purpose + ///// + //[MenuItem("ParrelSync/(Debug)Delete AskFeedbackDialog keys")] + //private static void DebugDeleteAllKeys() + //{ + // EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + // EditorPrefs.DeleteKey(StopShowingKey); + // Debug.Log("AskFeedbackDialog keys deleted"); + //} + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta b/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta new file mode 100644 index 0000000..20a2a0b --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 894412a5b602e6c4ba2cf2d01f4f92b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs b/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs new file mode 100644 index 0000000..0f42af9 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs @@ -0,0 +1,26 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + public class OtherMenuItem + { + [MenuItem("ParrelSync/GitHub/View this project on GitHub", priority = 10)] + private static void OpenGitHub() + { + Application.OpenURL(ExternalLinks.GitHubHome); + } + + [MenuItem("ParrelSync/GitHub/View FAQ", priority = 11)] + private static void OpenFAQ() + { + Application.OpenURL(ExternalLinks.FAQ); + } + + [MenuItem("ParrelSync/GitHub/View Issues", priority = 12)] + private static void OpenGitHubIssues() + { + Application.OpenURL(ExternalLinks.GitHubIssue); + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta b/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta new file mode 100644 index 0000000..563d7a2 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7191fa4bfa12ae749b27f73ed292eaf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs b/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs new file mode 100644 index 0000000..576d41c --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ParrelSync +{ + // With ScriptableObject derived classes, .cs and .asset filenames MUST be identical + public class ParrelSyncProjectSettings : ScriptableObject + { + private const string ParrelSyncScriptableObjectsDirectory = "Assets/Plugins/ParrelSync/ScriptableObjects"; + private const string ParrelSyncSettingsPath = ParrelSyncScriptableObjectsDirectory + "/" + + nameof(ParrelSyncProjectSettings) + ".asset"; + + [SerializeField] + [HideInInspector] + private List m_OptionalSymbolicLinkFolders; + public const string NameOfOptionalSymbolicLinkFolders = nameof(m_OptionalSymbolicLinkFolders); + + private static ParrelSyncProjectSettings GetOrCreateSettings() + { + ParrelSyncProjectSettings projectSettings; + if (File.Exists(ParrelSyncSettingsPath)) + { + projectSettings = AssetDatabase.LoadAssetAtPath(ParrelSyncSettingsPath); + + if (projectSettings == null) + Debug.LogError("File Exists, but failed to load: " + ParrelSyncSettingsPath); + + return projectSettings; + } + + projectSettings = CreateInstance(); + projectSettings.m_OptionalSymbolicLinkFolders = new List(); + if (!Directory.Exists(ParrelSyncScriptableObjectsDirectory)) + { + Directory.CreateDirectory(ParrelSyncScriptableObjectsDirectory); + } + AssetDatabase.CreateAsset(projectSettings, ParrelSyncSettingsPath); + AssetDatabase.SaveAssets(); + return projectSettings; + } + + public static SerializedObject GetSerializedSettings() + { + return new SerializedObject(GetOrCreateSettings()); + } + } + + public class ParrelSyncSettingsProvider : SettingsProvider + { + private const string MenuLocationInProjectSettings = "Project/ParrelSync"; + + private SerializedObject _parrelSyncProjectSettings; + + private class Styles + { + public static readonly GUIContent SymlinkSectionHeading = new GUIContent("Optional Folders to Symbolically Link"); + } + + private ParrelSyncSettingsProvider(string path, SettingsScope scope = SettingsScope.User) + : base(path, scope) + { + } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + // This function is called when the user clicks on the ParrelSyncSettings element in the Settings window. + _parrelSyncProjectSettings = ParrelSyncProjectSettings.GetSerializedSettings(); + } + + public override void OnGUI(string searchContext) + { + var property = _parrelSyncProjectSettings.FindProperty(ParrelSyncProjectSettings.NameOfOptionalSymbolicLinkFolders); + if (property is null || !property.isArray || property.arrayElementType != "string") + return; + + var optionalFolderPaths = new List(property.arraySize); + for (var i = 0; i < property.arraySize; ++i) + { + optionalFolderPaths.Add(property.GetArrayElementAtIndex(i).stringValue); + } + optionalFolderPaths.Add(""); + + GUILayout.BeginVertical("GroupBox"); + GUILayout.Label(Styles.SymlinkSectionHeading); + GUILayout.Space(5); + var projectPath = ClonesManager.GetCurrentProjectPath(); + var optionalFolderPathsIsDirty = false; + for (var i = 0; i < optionalFolderPaths.Count; ++i) + { + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(optionalFolderPaths[i], EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + if (GUILayout.Button("Select", GUILayout.Width(60))) + { + var result = EditorUtility.OpenFolderPanel("Select Folder to Symbolically Link...", "", ""); + if (result.Contains(projectPath)) + { + optionalFolderPaths[i] = result.Replace(projectPath, ""); + optionalFolderPathsIsDirty = true; + } + else if (result != "") + { + Debug.LogWarning("Symbolic Link folder must be within the project directory"); + } + } + if (GUILayout.Button("Clear", GUILayout.Width(60))) + { + optionalFolderPaths[i] = ""; + optionalFolderPathsIsDirty = true; + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + + if (!optionalFolderPathsIsDirty) + return; + + optionalFolderPaths.RemoveAll(str => str == ""); + property.arraySize = optionalFolderPaths.Count; + for (var i = 0; i < property.arraySize; ++i) + { + property.GetArrayElementAtIndex(i).stringValue = optionalFolderPaths[i]; + } + _parrelSyncProjectSettings.ApplyModifiedProperties(); + AssetDatabase.SaveAssets(); + } + + // Register the SettingsProvider + [SettingsProvider] + public static SettingsProvider CreateParrelSyncSettingsProvider() + { + return new ParrelSyncSettingsProvider(MenuLocationInProjectSettings, SettingsScope.Project) + { + keywords = GetSearchKeywordsFromGUIContentProperties() + }; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta b/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta new file mode 100644 index 0000000..95506e4 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0011418c9d75434988a06b6df93b283 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/Preferences.cs b/Assets/Plugins/ParrelSync/Editor/Preferences.cs new file mode 100644 index 0000000..095a470 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/Preferences.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEditor; + +namespace ParrelSync +{ + /// + /// To add value caching for functions + /// + public class BoolPreference + { + public string key { get; private set; } + public bool defaultValue { get; private set; } + public BoolPreference(string key, bool defaultValue) + { + this.key = key; + this.defaultValue = defaultValue; + } + + private bool? valueCache = null; + + public bool Value + { + get + { + if (valueCache == null) + valueCache = EditorPrefs.GetBool(key, defaultValue); + + return (bool)valueCache; + } + set + { + if (valueCache == value) + return; + + EditorPrefs.SetBool(key, value); + valueCache = value; + Debug.Log("Editor preference updated. key: " + key + ", value: " + value); + } + } + + public void ClearValue() + { + EditorPrefs.DeleteKey(key); + valueCache = null; + } + } + + + /// + /// To add value caching for functions + /// + public class ListOfStringsPreference + { + private static string serializationToken = "|||"; + public string Key { get; private set; } + public ListOfStringsPreference(string key) + { + Key = key; + } + public List GetStoredValue() + { + return this.Deserialize(EditorPrefs.GetString(Key)); + } + public void SetStoredValue(List strings) + { + EditorPrefs.SetString(Key, this.Serialize(strings)); + } + public void ClearStoredValue() + { + EditorPrefs.DeleteKey(Key); + } + public string Serialize(List data) + { + string result = string.Empty; + foreach (var item in data) + { + if (item.Contains(serializationToken)) + { + Debug.LogError("Unable to serialize this value ["+item+"], it contains the serialization token ["+serializationToken+"]"); + continue; + } + + result += item + serializationToken; + } + return result; + } + public List Deserialize(string data) + { + return data.Split(serializationToken).ToList(); + } + } + public class Preferences : EditorWindow + { + [MenuItem("ParrelSync/Preferences", priority = 1)] + private static void InitWindow() + { + Preferences window = (Preferences)EditorWindow.GetWindow(typeof(Preferences)); + window.titleContent = new GUIContent(ClonesManager.ProjectName + " Preferences"); + window.minSize = new Vector2(550, 300); + window.Show(); + } + + /// + /// Disable asset saving in clone editors? + /// + public static BoolPreference AssetModPref = new BoolPreference("ParrelSync_DisableClonesAssetSaving", true); + + /// + /// In addition of checking the existence of UnityLockFile, + /// also check is the is the UnityLockFile being opened. + /// + public static BoolPreference AlsoCheckUnityLockFileStaPref = new BoolPreference("ParrelSync_CheckUnityLockFileOpenStatus", true); + + /// + /// A list of folders to create sybolic links for, + /// useful for data that lives outside of the assets folder + /// eg. Wwise project data + /// + public static ListOfStringsPreference OptionalSymbolicLinkFolders = new ListOfStringsPreference("ParrelSync_OptionalSymbolicLinkFolders"); + + private void OnGUI() + { + if (ClonesManager.IsClone()) + { + EditorGUILayout.HelpBox( + "This is a clone project. Please use the original project editor to change preferences.", + MessageType.Info); + return; + } + + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Preferences"); + GUILayout.BeginVertical("GroupBox"); + + AssetModPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "(recommended) Disable asset saving in clone editors- require re-open clone editors", + "Disable asset saving in clone editors so all assets can only be modified from the original project editor" + ), + AssetModPref.Value); + + if (Application.platform == RuntimePlatform.WindowsEditor) + { + AlsoCheckUnityLockFileStaPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "Also check UnityLockFile lock status while checking clone projects running status", + "Disable this can slightly increase Clones Manager window performance, but will lead to in-correct clone project running status" + + "(the Clones Manager window show the clone project is still running even it's not) if the clone editor crashed" + ), + AlsoCheckUnityLockFileStaPref.Value); + } + GUILayout.EndVertical(); + + GUILayout.BeginVertical("GroupBox"); + GUILayout.Label("Optional Folders to Symbolically Link"); + GUILayout.Space(5); + + // cache the current value + List optionalFolderPaths = OptionalSymbolicLinkFolders.GetStoredValue(); + bool optionalFolderPathsAreDirty = false; + + // append a new row if full + if (optionalFolderPaths.Last() != "") + { + optionalFolderPaths.Add(""); + } + + var projectPath = ClonesManager.GetCurrentProjectPath(); + for (int i = 0; i < optionalFolderPaths.Count; ++i) + { + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(optionalFolderPaths[i], EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + if (GUILayout.Button("Select Folder", GUILayout.Width(100))) + { + var result = EditorUtility.OpenFolderPanel("Select Folder to Symbolically Link...", "", ""); + if (result.Contains(projectPath)) + { + optionalFolderPaths[i] = result.Replace(projectPath,""); + optionalFolderPathsAreDirty = true; + } + else if( result != "") + { + Debug.LogWarning("Symbolic Link folder must be within the project directory"); + } + } + if (GUILayout.Button("Clear", GUILayout.Width(100))) + { + optionalFolderPaths[i] = ""; + optionalFolderPathsAreDirty = true; + } + GUILayout.EndHorizontal(); + } + + // only set the preference if the value is marked dirty + if (optionalFolderPathsAreDirty) + { + optionalFolderPaths.RemoveAll(str=> str == ""); + OptionalSymbolicLinkFolders.SetStoredValue(optionalFolderPaths); + } + + GUILayout.EndVertical(); + + if (GUILayout.Button("Reset to default")) + { + AssetModPref.ClearValue(); + AlsoCheckUnityLockFileStaPref.ClearValue(); + OptionalSymbolicLinkFolders.ClearStoredValue(); + Debug.Log("Editor preferences cleared"); + } + GUILayout.EndVertical(); + } + } +} diff --git a/Assets/Plugins/ParrelSync/Editor/Preferences.cs.meta b/Assets/Plugins/ParrelSync/Editor/Preferences.cs.meta new file mode 100644 index 0000000..0166f9a --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/Preferences.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24641be1c0410a745b529e61b508679f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/Project.cs b/Assets/Plugins/ParrelSync/Editor/Project.cs new file mode 100644 index 0000000..7e7c387 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/Project.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ParrelSync +{ + public class Project : System.ICloneable + { + public string name; + public string projectPath; + string rootPath; + public string assetPath; + public string projectSettingsPath; + public string libraryPath; + public string packagesPath; + public string autoBuildPath; + public string localPackages; + + char[] separator = new char[1] { '/' }; + + + /// + /// Default constructor + /// + public Project() + { + + } + + + /// + /// Initialize the project object by parsing its full path returned by Unity into a bunch of individual folder names and paths. + /// + /// + public Project(string path) + { + ParsePath(path); + } + + + /// + /// Create a new object with the same settings + /// + /// + public object Clone() + { + Project newProject = new Project(); + newProject.rootPath = rootPath; + newProject.projectPath = projectPath; + newProject.assetPath = assetPath; + newProject.projectSettingsPath = projectSettingsPath; + newProject.libraryPath = libraryPath; + newProject.name = name; + newProject.separator = separator; + newProject.packagesPath = packagesPath; + newProject.autoBuildPath = autoBuildPath; + newProject.localPackages = localPackages; + + + return newProject; + } + + + /// + /// Update the project object by renaming and reparsing it. Pass in the new name of a project, and it'll update the other member variables to match. + /// + /// + public void updateNewName(string newName) + { + name = newName; + ParsePath(rootPath + "/" + name + "/Assets"); + } + + + /// + /// Debug override so we can quickly print out the project info. + /// + /// + public override string ToString() + { + string printString = name + "\n" + + rootPath + "\n" + + projectPath + "\n" + + assetPath + "\n" + + projectSettingsPath + "\n" + + packagesPath + "\n" + + autoBuildPath + "\n" + + localPackages + "\n" + + libraryPath; + return (printString); + } + + private void ParsePath(string path) + { + //Unity's Application functions return the Assets path in the Editor. + projectPath = path; + + //pop off the last part of the path for the project name, keep the rest for the root path + List pathArray = projectPath.Split(separator).ToList(); + name = pathArray.Last(); + + pathArray.RemoveAt(pathArray.Count() - 1); + rootPath = string.Join(separator[0].ToString(), pathArray.ToArray()); + + assetPath = projectPath + "/Assets"; + projectSettingsPath = projectPath + "/ProjectSettings"; + libraryPath = projectPath + "/Library"; + packagesPath = projectPath + "/Packages"; + autoBuildPath = projectPath + "/AutoBuild"; + localPackages = projectPath + "/LocalPackages"; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/Project.cs.meta b/Assets/Plugins/ParrelSync/Editor/Project.cs.meta new file mode 100644 index 0000000..84d9855 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/Project.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec8d3a1577179ef44815739178cf75b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs b/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs new file mode 100644 index 0000000..a93895d --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs @@ -0,0 +1,60 @@ +using System; +using UnityEditor; +using UnityEngine; +namespace ParrelSync.Update +{ + /// + /// A simple update checker + /// + public class UpdateChecker + { + //const string LocalVersionFilePath = "Assets/ParrelSync/VERSION.txt"; + public const string LocalVersion = "1.5.2"; + [MenuItem("ParrelSync/Check for update", priority = 20)] + static void CheckForUpdate() + { + using (System.Net.WebClient client = new System.Net.WebClient()) + { + try + { + //This won't work with UPM packages + //string localVersionText = AssetDatabase.LoadAssetAtPath(LocalVersionFilePath).text; + + string localVersionText = LocalVersion; + Debug.Log("Local version text : " + LocalVersion); + + string latesteVersionText = client.DownloadString(ExternalLinks.RemoteVersionURL); + Debug.Log("latest version text got: " + latesteVersionText); + string messageBody = "Current Version: " + localVersionText +"\n" + +"Latest Version: " + latesteVersionText + "\n"; + var latestVersion = new Version(latesteVersionText); + var localVersion = new Version(localVersionText); + + if (latestVersion > localVersion) + { + Debug.Log("There's a newer version"); + messageBody += "There's a newer version available"; + if(EditorUtility.DisplayDialog("Check for update.", messageBody, "Get latest release", "Close")) + { + Application.OpenURL(ExternalLinks.Releases); + } + } + else + { + Debug.Log("Current version is up-to-date."); + messageBody += "Current version is up-to-date."; + EditorUtility.DisplayDialog("Check for update.", messageBody,"OK"); + } + + } + catch (Exception exp) + { + Debug.LogError("Error with checking update. Exception: " + exp); + EditorUtility.DisplayDialog("Update Error","Error with checking update. \nSee console for more details.", + "OK" + ); + } + } + } + } +} diff --git a/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs.meta b/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs.meta new file mode 100644 index 0000000..8dcd733 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/UpdateChecker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3453b3f1a20ea148b5028f8556a7be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs b/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs new file mode 100644 index 0000000..1ee73bc --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs @@ -0,0 +1,73 @@ +namespace ParrelSync +{ + using UnityEditor; + using UnityEngine; + using System; + using System.Text; + using System.Security.Cryptography; + using System.IO; + using System.Linq; + + [InitializeOnLoad] + public class ValidateCopiedFoldersIntegrity + { + const string SessionStateKey = "ValidateCopiedFoldersIntegrity_Init"; + /// + /// Called once on editor startup. + /// Validate copied folders integrity in clone project + /// + static ValidateCopiedFoldersIntegrity() + { + if (!SessionState.GetBool(SessionStateKey, false)) + { + SessionState.SetBool(SessionStateKey, true); + if (!ClonesManager.IsClone()) { return; } + + ValidateFolder(ClonesManager.GetCurrentProjectPath(), ClonesManager.GetOriginalProjectPath(), "Packages"); + } + } + + public static void ValidateFolder(string targetRoot, string originalRoot, string folderName) + { + var targetFolderPath = Path.Combine(targetRoot, folderName); + var targetFolderHash = CreateMd5ForFolder(targetFolderPath); + + var originalFolderPath = Path.Combine(originalRoot, folderName); + var originalFolderHash = CreateMd5ForFolder(originalFolderPath); + + if (targetFolderHash != originalFolderHash) + { + Debug.Log("ParrelSync: Detected changes in '" + folderName + "' directory. Updating cloned project..."); + FileUtil.ReplaceDirectory(originalFolderPath, targetFolderPath); + } + } + + static string CreateMd5ForFolder(string path) + { + // assuming you want to include nested folders + var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) + .OrderBy(p => p).ToList(); + + MD5 md5 = MD5.Create(); + + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + + // hash path + string relativePath = file.Substring(path.Length + 1); + byte[] pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower()); + md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); + + // hash contents + byte[] contentBytes = File.ReadAllBytes(file); + if (i == files.Count - 1) + md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length); + else + md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); + } + + return BitConverter.ToString(md5.Hash).Replace("-", "").ToLower(); + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta b/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta new file mode 100644 index 0000000..daab522 --- /dev/null +++ b/Assets/Plugins/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8fb344b9abf5274abd744833474b087 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/package.json b/Assets/Plugins/ParrelSync/package.json new file mode 100644 index 0000000..08bb747 --- /dev/null +++ b/Assets/Plugins/ParrelSync/package.json @@ -0,0 +1,10 @@ +{ + "name": "com.veriorpies.parrelsync", + "displayName": "ParrelSync", + "version": "1.5.2", + "unity": "2018.4", + "description": "ParrelSync is a Unity editor extension that allows users to test multiplayer gameplay without building the project by having another Unity editor window opened and mirror the changes from the original project.", + "license": "MIT", + "keywords": [ "Networking", "Utils", "Editor", "Extensions" ], + "dependencies": {} +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/package.json.meta b/Assets/Plugins/ParrelSync/package.json.meta new file mode 100644 index 0000000..4ced740 --- /dev/null +++ b/Assets/Plugins/ParrelSync/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a2a889c264e34b47a7349cbcb2cbedd7 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ParrelSync/projectCloner.asmdef b/Assets/Plugins/ParrelSync/projectCloner.asmdef new file mode 100644 index 0000000..1e56b06 --- /dev/null +++ b/Assets/Plugins/ParrelSync/projectCloner.asmdef @@ -0,0 +1,15 @@ +{ + "name": "ParrelSync", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Plugins/ParrelSync/projectCloner.asmdef.meta b/Assets/Plugins/ParrelSync/projectCloner.asmdef.meta new file mode 100644 index 0000000..3aa8857 --- /dev/null +++ b/Assets/Plugins/ParrelSync/projectCloner.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 894a6cc6ed5cd2645bb542978cbed6a9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/NetworkedGameSetup.cs b/Assets/Scripts/Multiplayer/NetworkedGameSetup.cs index 0bad16a..b538805 100644 --- a/Assets/Scripts/Multiplayer/NetworkedGameSetup.cs +++ b/Assets/Scripts/Multiplayer/NetworkedGameSetup.cs @@ -63,7 +63,7 @@ public class NetworkedGameSetup : NetworkBehaviour { RopeSimulator ropeSim = GetComponentInChildren(); - //// Assuming 2 players + // Assuming 2 players ropeSim.BuildRope(players[0].GetComponent(), players[1].GetComponent()); }