Skip to content

Instantly share code, notes, and snippets.

@Thaina
Created November 12, 2023 14:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Thaina/eec5752b25f7bfd3737f7dd9ed2fa53c to your computer and use it in GitHub Desktop.
Save Thaina/eec5752b25f7bfd3737f7dd9ed2fa53c to your computer and use it in GitHub Desktop.
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.PackageManager;
// require package com.unity.editorcoroutines
using Unity.EditorCoroutines.Editor;
public class PackageMissingManagerWizard : ScriptableWizard
{
static int mainProgress = -1;
static PackageMissingManagerWizard instance;
static Progress.Status? MainProgressStatus => mainProgress != -1 && Progress.Exists(mainProgress) ? Progress.GetStatus(mainProgress) : null;
static bool MainProgressAvailable => MainProgressStatus is not null or Progress.Status.Canceled or Progress.Status.Failed;
[InitializeOnLoadMethod]
static void Initial()
{
if(mainProgress < 0)
mainProgress = Progress.Start("Start missing dependencies resolver",null,Progress.Options.Sticky);
EditorCoroutineUtility.StartCoroutineOwnerless(DoWork());
static IEnumerator DoWork()
{
var itor = WorkOnProgress();
while(itor.MoveNext())
{
if(!MainProgressAvailable)
yield break;
yield return itor.Current;
}
yield return new YieldInstruction();
if(MainProgressStatus is Progress.Status.Running or Progress.Status.Paused)
{
Debug.Log(MainProgressStatus);
Progress.Finish(mainProgress,Progress.Status.Failed);
}
if(instance)
{
instance.Close();
instance = null;
}
}
}
static IEnumerator WorkOnProgress()
{
var list = Client.List(false,true);
while(!list.IsCompleted)
{
Progress.SetDescription(mainProgress,"Listing project dependencies");
yield return new YieldInstruction();
}
yield return string.Join("\n",new object[] { list.Status,list.Error }.Where((obj) => obj != null));
var allMissings = list.Result.SelectMany((info) => {
return info.dependencies.Where((dep) => !list.Result.Any((item) => item.name == dep.name));
}).GroupBy((info) => info.name,(info) => info.version).Where((group) => group.Any()).Select((group) => {
return new MissingDependency() {
Name = group.Key,
Versions = group.ToArray()
};
}).ToArray();
if(allMissings.Length < 1)
{
Progress.Remove(mainProgress);
yield break;
}
if(!instance)
instance = ScriptableWizard.DisplayWizard<PackageMissingManagerWizard>("Sort missing dependency for resolving","Apply","Cancel");
instance.missingDependencies = allMissings;
instance.Focus();
var task = instance.source.Task;
while(!task.IsCompleted)
{
Progress.SetDescription(mainProgress,"Wait for accept");
yield return new YieldInstruction();
}
if(task.IsCanceled || !(task.Result is var missingDependencies && missingDependencies?.Length > 0))
{
Progress.Remove(mainProgress);
yield break;
}
var missingWithProgresses = missingDependencies.Select((missing) => {
return (missing,Progress.Start(missing.Name,null,Progress.Options.Sticky,mainProgress));
}).ToArray();
int length = missingWithProgresses.Length;
Progress.Report(mainProgress,0,length,"Resolving missing dependencies");
var resolveItor = DoWork(missingWithProgresses);
while(resolveItor.MoveNext())
{
Progress.Report(mainProgress,resolveItor.Current,length,"Resolving missing dependencies");
yield return new WaitForSecondsRealtime(0.1f);
}
yield return new YieldInstruction();
Progress.Finish(mainProgress,Progress.Status.Succeeded);
yield return new YieldInstruction();
Debug.Log(MainProgressStatus);
}
static IEnumerator<int> DoWork((MissingDependency missing, int progressID)[] missingWithProgresses)
{
int i = 0;
foreach(var (missing,progressID) in missingWithProgresses)
{
var lastStatus = StatusCode.InProgress;
foreach(var (version,index) in missing.Versions.Select((item,index) => (item,index)))
{
int retry = 1;
while(retry > 0 || lastStatus == StatusCode.Success)
{
retry--;
var add = Client.Add(version);
while(!add.IsCompleted)
{
Progress.Report(progressID,index,missing.Versions.Length,"try add dependency");
yield return i;
}
lastStatus = add.Status;
if(add.Error != null)
{
Debug.LogErrorFormat("{0} : {1}",add.Error.errorCode,add.Error.message);
if(add.Error.errorCode == ErrorCode.Unknown && add.Error.message.Contains("EBUSY"))
retry = 3;
}
}
if(lastStatus == StatusCode.Success)
break;
}
Progress.SetDescription(progressID,lastStatus.ToString());
Progress.Finish(progressID,lastStatus == StatusCode.Success ? Progress.Status.Succeeded : Progress.Status.Failed);
if(lastStatus == StatusCode.Success)
i++;
}
yield return i;
}
[Serializable]
public struct MissingDependency
{
public string Name;
public string[] Versions;
}
[SerializeField]
MissingDependency[] missingDependencies;
TaskCompletionSource<MissingDependency[]> source = new TaskCompletionSource<MissingDependency[]>();
private void OnDestroy() => source.TrySetCanceled();
private void OnWizardCreate() => source.TrySetResult(missingDependencies);
private void OnWizardOtherButton() => source.TrySetResult(null);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment