Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IUnitOption : IFuzzyOption
|
||||
{
|
||||
IUnit unit { get; }
|
||||
|
||||
IUnit InstantiateUnit();
|
||||
void PreconfigureUnit(IUnit unit);
|
||||
|
||||
HashSet<string> sourceScriptGuids { get; }
|
||||
int order { get; }
|
||||
UnitCategory category { get; }
|
||||
string favoriteKey { get; }
|
||||
bool favoritable { get; }
|
||||
Type unitType { get; }
|
||||
|
||||
#region Serialization
|
||||
|
||||
void Deserialize(UnitOptionRow row);
|
||||
UnitOptionRow Serialize();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Filtering
|
||||
|
||||
int controlInputCount { get; }
|
||||
int controlOutputCount { get; }
|
||||
HashSet<Type> valueInputTypes { get; }
|
||||
HashSet<Type> valueOutputTypes { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search
|
||||
|
||||
string haystack { get; }
|
||||
string formerHaystack { get; }
|
||||
string SearchResultLabel(string query);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class XUnitOption
|
||||
{
|
||||
public static bool UnitIs(this IUnitOption option, Type type)
|
||||
{
|
||||
return type.IsAssignableFrom(option.unitType);
|
||||
}
|
||||
|
||||
public static bool UnitIs<T>(this IUnitOption option)
|
||||
{
|
||||
return option.UnitIs(typeof(T));
|
||||
}
|
||||
|
||||
public static bool HasCompatibleValueInput(this IUnitOption option, Type outputType)
|
||||
{
|
||||
Ensure.That(nameof(outputType)).IsNotNull(outputType);
|
||||
|
||||
foreach (var valueInputType in option.valueInputTypes)
|
||||
{
|
||||
if (ConversionUtility.CanConvert(outputType, valueInputType, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasCompatibleValueOutput(this IUnitOption option, Type inputType)
|
||||
{
|
||||
Ensure.That(nameof(inputType)).IsNotNull(inputType);
|
||||
|
||||
foreach (var valueOutputType in option.valueOutputTypes)
|
||||
{
|
||||
if (ConversionUtility.CanConvert(valueOutputType, inputType, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fd6fd6310ecb2405fae7d988941661de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,874 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Unity.VisualScripting.Dependencies.Sqlite;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[InitializeAfterPlugins]
|
||||
[BackgroundWorker(nameof(AutoLoad))]
|
||||
public static class UnitBase
|
||||
{
|
||||
static UnitBase()
|
||||
{
|
||||
staticUnitsExtensions = new NonNullableList<Func<IEnumerable<IUnitOption>>>();
|
||||
dynamicUnitsExtensions = new NonNullableList<Func<IEnumerable<IUnitOption>>>();
|
||||
contextualUnitsExtensions = new NonNullableList<Func<GraphReference, IEnumerable<IUnitOption>>>();
|
||||
}
|
||||
|
||||
private static readonly object @lock = new object();
|
||||
|
||||
private static HashSet<IUnitOption> options;
|
||||
|
||||
private static void AutoLoad()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
// If the fuzzy finder was opened really fast,
|
||||
// a load operation might already have started.
|
||||
if (options == null)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
if (IsUnitOptionsBuilt())
|
||||
{
|
||||
// Update before loading if required, ensuring no "in-between" state
|
||||
// where the loaded options are not yet loaded.
|
||||
// The update code will not touch the options array if it is null.
|
||||
if (BoltFlow.Configuration.updateUnitsAutomatically)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProgressUtility.DisplayProgressBar("Checking for codebase changes...", null, 0);
|
||||
|
||||
if (requiresUpdate)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to update unit options.\nRetry with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n{ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProgressUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
using (ProfilingUtility.SampleBlock("Load Unit Database"))
|
||||
{
|
||||
using (NativeUtility.Module("sqlite3.dll"))
|
||||
{
|
||||
ProgressUtility.DisplayProgressBar("Loading unit database...", null, 0);
|
||||
|
||||
SQLiteConnection database = null;
|
||||
|
||||
try
|
||||
{
|
||||
database = new SQLiteConnection(BoltFlow.Paths.unitOptions, SQLiteOpenFlags.ReadOnly);
|
||||
|
||||
int total;
|
||||
|
||||
total = database.Table<UnitOptionRow>().Count();
|
||||
|
||||
var progress = 0f;
|
||||
|
||||
options = new HashSet<IUnitOption>();
|
||||
|
||||
var failedOptions = new Dictionary<UnitOptionRow, Exception>();
|
||||
|
||||
foreach (var row in database.Table<UnitOptionRow>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var option = row.ToOption();
|
||||
|
||||
options.Add(option);
|
||||
}
|
||||
catch (Exception rowEx)
|
||||
{
|
||||
failedOptions.Add(row, rowEx);
|
||||
}
|
||||
|
||||
ProgressUtility.DisplayProgressBar("Loading unit database...", BoltCore.Configuration.humanNaming ? row.labelHuman : row.labelProgrammer, progress++ / total);
|
||||
}
|
||||
|
||||
if (failedOptions.Count > 0)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($"{failedOptions.Count} unit options failed to load and were skipped.");
|
||||
sb.AppendLine($"Try rebuilding the unit options with '{UnitOptionUtility.GenerateUnitDatabasePath}' to purge outdated units.");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var failedOption in failedOptions)
|
||||
{
|
||||
sb.AppendLine(failedOption.Key.favoriteKey);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var failedOption in failedOptions)
|
||||
{
|
||||
sb.AppendLine(failedOption.Key.favoriteKey + ": ");
|
||||
sb.AppendLine(failedOption.Value.ToString());
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
Debug.LogWarning(sb.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
options = new HashSet<IUnitOption>();
|
||||
Debug.LogError($"Failed to load unit options.\nTry to rebuild them with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
database?.Close();
|
||||
//ConsoleProfiler.Dump();
|
||||
ProgressUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUnitOptionsBuilt()
|
||||
{
|
||||
return File.Exists(BoltFlow.Paths.unitOptions);
|
||||
}
|
||||
|
||||
public static void Rebuild()
|
||||
{
|
||||
if (IsUnitOptionsBuilt())
|
||||
{
|
||||
VersionControlUtility.Unlock(BoltFlow.Paths.unitOptions);
|
||||
File.Delete(BoltFlow.Paths.unitOptions);
|
||||
}
|
||||
|
||||
Build();
|
||||
}
|
||||
|
||||
public static void Build()
|
||||
{
|
||||
if (!IsUnitOptionsBuilt())
|
||||
{
|
||||
var progressTitle = "Building unit database...";
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
using (ProfilingUtility.SampleBlock("Update Unit Database"))
|
||||
{
|
||||
using (NativeUtility.Module("sqlite3.dll"))
|
||||
{
|
||||
SQLiteConnection database = null;
|
||||
|
||||
try
|
||||
{
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Creating database...", 0);
|
||||
|
||||
PathUtility.CreateParentDirectoryIfNeeded(BoltFlow.Paths.unitOptions);
|
||||
database = new SQLiteConnection(BoltFlow.Paths.unitOptions);
|
||||
database.CreateTable<UnitOptionRow>();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", 0);
|
||||
|
||||
UpdateCodebase();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", 0);
|
||||
|
||||
UpdateTypeMappings();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle,
|
||||
"Converting codebase to unit options...", 0);
|
||||
|
||||
options = new HashSet<IUnitOption>(GetStaticOptions());
|
||||
|
||||
var rows = new HashSet<UnitOptionRow>();
|
||||
|
||||
var progress = 0;
|
||||
var lastShownProgress = 0f;
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shownProgress = (float)progress / options.Count;
|
||||
|
||||
if (shownProgress > lastShownProgress + 0.01f)
|
||||
{
|
||||
ProgressUtility.DisplayProgressBar(progressTitle,
|
||||
"Converting codebase to unit options...", shownProgress);
|
||||
lastShownProgress = shownProgress;
|
||||
}
|
||||
|
||||
rows.Add(option.Serialize());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to save option '{option.GetType()}'.\n{ex}");
|
||||
}
|
||||
|
||||
progress++;
|
||||
}
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1);
|
||||
|
||||
try
|
||||
{
|
||||
database.CreateTable<UnitOptionRow>();
|
||||
database.InsertAll(rows);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to write options to database.\n{ex}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
database?.Close();
|
||||
ProgressUtility.ClearProgressBar();
|
||||
AssetDatabase.Refresh();
|
||||
//ConsoleProfiler.Dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (!IsUnitOptionsBuilt())
|
||||
{
|
||||
Build();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
using (ProfilingUtility.SampleBlock("Update Unit Database"))
|
||||
{
|
||||
using (NativeUtility.Module("sqlite3.dll"))
|
||||
{
|
||||
var progressTitle = "Updating unit database...";
|
||||
|
||||
SQLiteConnection database = null;
|
||||
|
||||
try
|
||||
{
|
||||
VersionControlUtility.Unlock(BoltFlow.Paths.unitOptions);
|
||||
|
||||
var steps = 7f;
|
||||
var step = 0f;
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Connecting to database...", step++ / steps);
|
||||
|
||||
database = new SQLiteConnection(BoltFlow.Paths.unitOptions);
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", step++ / steps);
|
||||
|
||||
UpdateTypeMappings();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Fetching modified scripts...", step++ / steps);
|
||||
|
||||
var modifiedScriptGuids = GetModifiedScriptGuids().Distinct().ToHashSet();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Fetching deleted scripts...", step++ / steps);
|
||||
|
||||
var deletedScriptGuids = GetDeletedScriptGuids().Distinct().ToHashSet();
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", step++ / steps);
|
||||
|
||||
var modifiedScriptTypes = modifiedScriptGuids.SelectMany(GetScriptTypes).ToArray();
|
||||
|
||||
UpdateCodebase(modifiedScriptTypes);
|
||||
|
||||
var outdatedScriptGuids = new HashSet<string>();
|
||||
outdatedScriptGuids.UnionWith(modifiedScriptGuids);
|
||||
outdatedScriptGuids.UnionWith(deletedScriptGuids);
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Removing outdated unit options...", step++ / steps);
|
||||
|
||||
options?.RemoveWhere(option => outdatedScriptGuids.Overlaps(option.sourceScriptGuids));
|
||||
|
||||
// We want to use the database level WHERE here for speed,
|
||||
// so we'll run multiple queries, one for each outdated script GUID.
|
||||
|
||||
foreach (var outdatedScriptGuid in outdatedScriptGuids)
|
||||
{
|
||||
foreach (var outdatedRowId in database.Table<UnitOptionRow>()
|
||||
.Where(row => row.sourceScriptGuids.Contains(outdatedScriptGuid))
|
||||
.Select(row => row.id))
|
||||
{
|
||||
database.Delete<UnitOptionRow>(outdatedRowId);
|
||||
}
|
||||
}
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Converting codebase to unit options...", step++ / steps);
|
||||
|
||||
var newOptions = new HashSet<IUnitOption>(modifiedScriptGuids.SelectMany(GetScriptTypes)
|
||||
.Distinct()
|
||||
.SelectMany(GetIncrementalOptions));
|
||||
|
||||
var rows = new HashSet<UnitOptionRow>();
|
||||
|
||||
float progress = 0;
|
||||
|
||||
foreach (var newOption in newOptions)
|
||||
{
|
||||
options?.Add(newOption);
|
||||
|
||||
try
|
||||
{
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, newOption.label, (step / steps) + ((1 / step) * (progress / newOptions.Count)));
|
||||
rows.Add(newOption.Serialize());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to serialize option '{newOption.GetType()}'.\n{ex}");
|
||||
}
|
||||
|
||||
progress++;
|
||||
}
|
||||
|
||||
ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1);
|
||||
|
||||
try
|
||||
{
|
||||
database.InsertAll(rows);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to write options to database.\n{ex}");
|
||||
}
|
||||
|
||||
// Make sure the database is touched to the current date,
|
||||
// even if we didn't do any change. This will avoid unnecessary
|
||||
// analysis in future update checks.
|
||||
File.SetLastWriteTimeUtc(BoltFlow.Paths.unitOptions, DateTime.UtcNow);
|
||||
}
|
||||
finally
|
||||
{
|
||||
database?.Close();
|
||||
ProgressUtility.ClearProgressBar();
|
||||
UnityAPI.Async(AssetDatabase.Refresh);
|
||||
//ConsoleProfiler.Dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IUnitOption> Subset(UnitOptionFilter filter, GraphReference reference)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
var dynamicOptions = UnityAPI.Await(() => GetDynamicOptions().ToHashSet());
|
||||
var contextualOptions = UnityAPI.Await(() => GetContextualOptions(reference).ToHashSet());
|
||||
|
||||
return LinqUtility.Concat<IUnitOption>(options, dynamicOptions, contextualOptions)
|
||||
.Where((filter ?? UnitOptionFilter.Any).ValidateOption)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
#region Units
|
||||
|
||||
private static CodebaseSubset codebase;
|
||||
|
||||
private static void UpdateCodebase(IEnumerable<Type> typeSet = null)
|
||||
{
|
||||
if (typeSet == null)
|
||||
{
|
||||
typeSet = Codebase.settingsTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeSet = typeSet.Where(t => Codebase.settingsTypes.Contains(t));
|
||||
}
|
||||
|
||||
Codebase.UpdateSettings();
|
||||
codebase = Codebase.Subset(typeSet, TypeFilter.Any.Configured(), MemberFilter.Any.Configured(), TypeFilter.Any.Configured(false));
|
||||
codebase.Cache();
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetStaticOptions()
|
||||
{
|
||||
// Standalones
|
||||
|
||||
foreach (var unit in Codebase.ludiqRuntimeTypes.Where(t => typeof(IUnit).IsAssignableFrom(t) &&
|
||||
t.IsConcrete() &&
|
||||
t.GetDefaultConstructor() != null &&
|
||||
!t.HasAttribute<SpecialUnitAttribute>() &&
|
||||
(EditorPlatformUtility.allowJit || !t.HasAttribute<AotIncompatibleAttribute>()) &&
|
||||
t.GetDefaultConstructor() != null)
|
||||
.Select(t => (IUnit)t.Instantiate()))
|
||||
{
|
||||
yield return unit.Option();
|
||||
}
|
||||
|
||||
// Self
|
||||
|
||||
yield return new This().Option();
|
||||
|
||||
// Types
|
||||
|
||||
foreach (var type in codebase.types)
|
||||
{
|
||||
foreach (var typeOption in GetTypeOptions(type))
|
||||
{
|
||||
yield return typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
// Members
|
||||
|
||||
foreach (var member in codebase.members)
|
||||
{
|
||||
foreach (var memberOption in GetMemberOptions(member))
|
||||
{
|
||||
yield return memberOption;
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
foreach (var eventType in Codebase.ludiqRuntimeTypes.Where(t => typeof(IEventUnit).IsAssignableFrom(t) && t.IsConcrete()))
|
||||
{
|
||||
yield return ((IEventUnit)eventType.Instantiate()).Option();
|
||||
}
|
||||
|
||||
// Blank Variables
|
||||
|
||||
yield return new GetVariableOption(VariableKind.Flow);
|
||||
yield return new GetVariableOption(VariableKind.Graph);
|
||||
yield return new GetVariableOption(VariableKind.Object);
|
||||
yield return new GetVariableOption(VariableKind.Scene);
|
||||
yield return new GetVariableOption(VariableKind.Application);
|
||||
yield return new GetVariableOption(VariableKind.Saved);
|
||||
|
||||
yield return new SetVariableOption(VariableKind.Flow);
|
||||
yield return new SetVariableOption(VariableKind.Graph);
|
||||
yield return new SetVariableOption(VariableKind.Object);
|
||||
yield return new SetVariableOption(VariableKind.Scene);
|
||||
yield return new SetVariableOption(VariableKind.Application);
|
||||
yield return new SetVariableOption(VariableKind.Saved);
|
||||
|
||||
yield return new IsVariableDefinedOption(VariableKind.Flow);
|
||||
yield return new IsVariableDefinedOption(VariableKind.Graph);
|
||||
yield return new IsVariableDefinedOption(VariableKind.Object);
|
||||
yield return new IsVariableDefinedOption(VariableKind.Scene);
|
||||
yield return new IsVariableDefinedOption(VariableKind.Application);
|
||||
yield return new IsVariableDefinedOption(VariableKind.Saved);
|
||||
|
||||
// Blank Super Unit
|
||||
|
||||
yield return SuperUnit.WithInputOutput().Option();
|
||||
|
||||
// Extensions
|
||||
|
||||
foreach (var staticUnitsExtension in staticUnitsExtensions)
|
||||
{
|
||||
foreach (var extensionStaticUnit in staticUnitsExtension())
|
||||
{
|
||||
yield return extensionStaticUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetIncrementalOptions(Type type)
|
||||
{
|
||||
if (!codebase.ValidateType(type))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var typeOption in GetTypeOptions(type))
|
||||
{
|
||||
yield return typeOption;
|
||||
}
|
||||
|
||||
foreach (var member in codebase.FilterMembers(type))
|
||||
{
|
||||
foreach (var memberOption in GetMemberOptions(member))
|
||||
{
|
||||
yield return memberOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetTypeOptions(Type type)
|
||||
{
|
||||
if (type == typeof(object))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Struct Initializer
|
||||
|
||||
if (type.IsStruct())
|
||||
{
|
||||
yield return new CreateStruct(type).Option();
|
||||
}
|
||||
|
||||
// Literals
|
||||
|
||||
if (type.HasInspector())
|
||||
{
|
||||
yield return new Literal(type).Option();
|
||||
|
||||
if (EditorPlatformUtility.allowJit)
|
||||
{
|
||||
var listType = typeof(List<>).MakeGenericType(type);
|
||||
|
||||
yield return new Literal(listType).Option();
|
||||
}
|
||||
}
|
||||
|
||||
// Exposes
|
||||
|
||||
if (!type.IsEnum)
|
||||
{
|
||||
yield return new Expose(type).Option();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetMemberOptions(Member member)
|
||||
{
|
||||
// Operators are handled with special math units
|
||||
// that are more elegant than the raw methods
|
||||
if (member.isOperator)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Conversions are handled automatically by connections
|
||||
if (member.isConversion)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (member.isAccessor)
|
||||
{
|
||||
if (member.isPubliclyGettable)
|
||||
{
|
||||
yield return new GetMember(member).Option();
|
||||
}
|
||||
|
||||
if (member.isPubliclySettable)
|
||||
{
|
||||
yield return new SetMember(member).Option();
|
||||
}
|
||||
}
|
||||
else if (member.isPubliclyInvocable)
|
||||
{
|
||||
yield return new InvokeMember(member).Option();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetDynamicOptions()
|
||||
{
|
||||
// Super Units
|
||||
|
||||
var flowMacros = AssetUtility.GetAllAssetsOfType<ScriptGraphAsset>().ToArray();
|
||||
|
||||
foreach (var superUnit in flowMacros.Select(flowMacro => new SuperUnit(flowMacro)))
|
||||
{
|
||||
yield return superUnit.Option();
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
foreach (var dynamicUnitsExtension in dynamicUnitsExtensions)
|
||||
{
|
||||
foreach (var extensionDynamicUnit in dynamicUnitsExtension())
|
||||
{
|
||||
yield return extensionDynamicUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IUnitOption> GetContextualOptions(GraphReference reference)
|
||||
{
|
||||
foreach (var variableKind in Enum.GetValues(typeof(VariableKind)).Cast<VariableKind>())
|
||||
{
|
||||
foreach (var graphVariableName in EditorVariablesUtility.GetVariableNameSuggestions(variableKind, reference))
|
||||
{
|
||||
yield return new GetVariableOption(variableKind, graphVariableName);
|
||||
yield return new SetVariableOption(variableKind, graphVariableName);
|
||||
yield return new IsVariableDefinedOption(variableKind, graphVariableName);
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
foreach (var contextualUnitsExtension in contextualUnitsExtensions)
|
||||
{
|
||||
foreach (var extensionContextualUnitOption in contextualUnitsExtension(reference))
|
||||
{
|
||||
yield return extensionContextualUnitOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripts
|
||||
|
||||
private static Dictionary<Type, HashSet<string>> typesToGuids;
|
||||
private static Dictionary<string, HashSet<Type>> guidsToTypes;
|
||||
|
||||
private static void UpdateTypeMappings()
|
||||
{
|
||||
typesToGuids = new Dictionary<Type, HashSet<string>>();
|
||||
guidsToTypes = new Dictionary<string, HashSet<Type>>();
|
||||
|
||||
UnityAPI.AwaitForever(() =>
|
||||
{
|
||||
foreach (var script in UnityEngine.Resources.FindObjectsOfTypeAll<MonoScript>())
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(script);
|
||||
var guid = AssetDatabase.AssetPathToGUID(path);
|
||||
var type = script.GetClass();
|
||||
|
||||
// Skip built-in Unity plugins, which are referenced by full path
|
||||
if (!path.StartsWith("Assets"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the GUID to the list, even if it doesn't have any type
|
||||
if (!guidsToTypes.ContainsKey(guid))
|
||||
{
|
||||
guidsToTypes.Add(guid, new HashSet<Type>());
|
||||
}
|
||||
|
||||
// Skip scripts without types
|
||||
if (type == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!typesToGuids.ContainsKey(type))
|
||||
{
|
||||
typesToGuids.Add(type, new HashSet<string>());
|
||||
}
|
||||
|
||||
typesToGuids[type].Add(guid);
|
||||
guidsToTypes[guid].Add(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetScriptGuids(Type type)
|
||||
{
|
||||
if (typesToGuids == null)
|
||||
{
|
||||
UpdateTypeMappings();
|
||||
}
|
||||
|
||||
using (var recursion = Recursion.New(1))
|
||||
{
|
||||
return GetScriptGuids(recursion, type).ToArray(); // No delayed execution for recursion disposal
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetScriptGuids(Recursion recursion, Type type)
|
||||
{
|
||||
if (!recursion?.TryEnter(type) ?? false)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (typesToGuids.ContainsKey(type))
|
||||
{
|
||||
foreach (var guid in typesToGuids[type])
|
||||
{
|
||||
yield return guid;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through generic arguments.
|
||||
// For example, a List<Enemy> type should return the script GUID for Enemy.
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
foreach (var genericArgument in type.GetGenericArguments())
|
||||
{
|
||||
foreach (var genericGuid in GetScriptGuids(recursion, genericArgument))
|
||||
{
|
||||
yield return genericGuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetScriptTypes(string guid)
|
||||
{
|
||||
if (guidsToTypes == null)
|
||||
{
|
||||
UpdateTypeMappings();
|
||||
}
|
||||
|
||||
if (guidsToTypes.ContainsKey(guid))
|
||||
{
|
||||
return guidsToTypes[guid];
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool requiresUpdate => GetModifiedScriptGuids().Any() || GetDeletedScriptGuids().Any();
|
||||
|
||||
private static IEnumerable<string> GetModifiedScriptGuids()
|
||||
{
|
||||
var guids = new HashSet<string>();
|
||||
|
||||
UnityAPI.AwaitForever(() =>
|
||||
{
|
||||
var databaseTimestamp = File.GetLastWriteTimeUtc(BoltFlow.Paths.unitOptions);
|
||||
|
||||
foreach (var script in UnityEngine.Resources.FindObjectsOfTypeAll<MonoScript>())
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(script);
|
||||
var guid = AssetDatabase.AssetPathToGUID(path);
|
||||
|
||||
// Skip built-in Unity plugins, which are referenced by full path
|
||||
if (!path.StartsWith("Assets"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scriptTimestamp = File.GetLastWriteTimeUtc(Path.Combine(Paths.project, path));
|
||||
|
||||
if (scriptTimestamp > databaseTimestamp)
|
||||
{
|
||||
guids.Add(guid);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return guids;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetDeletedScriptGuids()
|
||||
{
|
||||
if (!IsUnitOptionsBuilt())
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
using (NativeUtility.Module("sqlite3.dll"))
|
||||
{
|
||||
SQLiteConnection database = null;
|
||||
|
||||
try
|
||||
{
|
||||
HashSet<string> databaseGuids;
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
database = new SQLiteConnection(BoltFlow.Paths.unitOptions);
|
||||
|
||||
databaseGuids = database.Query<UnitOptionRow>($"SELECT DISTINCT {nameof(UnitOptionRow.sourceScriptGuids)} FROM {nameof(UnitOptionRow)}")
|
||||
.Select(row => row.sourceScriptGuids)
|
||||
.NotNull()
|
||||
.SelectMany(guids => guids.Split(','))
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
var assetGuids = UnityAPI.AwaitForever(() => UnityEngine.Resources
|
||||
.FindObjectsOfTypeAll<MonoScript>()
|
||||
.Where(script => script.GetClass() != null)
|
||||
.Select(script => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(script)))
|
||||
.ToHashSet());
|
||||
|
||||
databaseGuids.ExceptWith(assetGuids);
|
||||
|
||||
return databaseGuids;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extensions
|
||||
|
||||
public static NonNullableList<Func<IEnumerable<IUnitOption>>> staticUnitsExtensions { get; }
|
||||
|
||||
public static NonNullableList<Func<IEnumerable<IUnitOption>>> dynamicUnitsExtensions { get; }
|
||||
|
||||
public static NonNullableList<Func<GraphReference, IEnumerable<IUnitOption>>> contextualUnitsExtensions { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Duplicates
|
||||
|
||||
public static IEnumerable<T> WithoutInheritedDuplicates<T>(this IEnumerable<T> items, Func<T, IUnitOption> optionSelector, CancellationToken cancellation)
|
||||
{
|
||||
// Doing everything we can to avoid reflection here, as it then becomes the main search bottleneck
|
||||
|
||||
var _items = items.ToArray();
|
||||
|
||||
var pseudoDeclarers = new HashSet<Member>();
|
||||
|
||||
foreach (var item in _items.Cancellable(cancellation))
|
||||
{
|
||||
var option = optionSelector(item);
|
||||
|
||||
if (option is IMemberUnitOption memberOption)
|
||||
{
|
||||
if (memberOption.targetType == memberOption.pseudoDeclarer.targetType)
|
||||
{
|
||||
pseudoDeclarers.Add(memberOption.pseudoDeclarer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in _items.Cancellable(cancellation))
|
||||
{
|
||||
var option = optionSelector(item);
|
||||
|
||||
if (option is IMemberUnitOption memberOption)
|
||||
{
|
||||
if (pseudoDeclarers.Contains(memberOption.member) || !pseudoDeclarers.Contains(memberOption.pseudoDeclarer))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a03c32ec404b64125b88769f42e5b8a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[FuzzyOption(typeof(UnitCategory))]
|
||||
public class UnitCategoryOption : FuzzyOption<UnitCategory>
|
||||
{
|
||||
public UnitCategoryOption(UnitCategory category)
|
||||
{
|
||||
value = category;
|
||||
label = category.name.Split('/').Last().Prettify();
|
||||
UnityAPI.Async(() => icon = BoltFlow.Icons.UnitCategory(category));
|
||||
parentOnly = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1bc17db44a4104432a5abc99035fe920
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,674 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[FuzzyOption(typeof(IUnit))]
|
||||
public class UnitOption<TUnit> : IUnitOption where TUnit : IUnit
|
||||
{
|
||||
public UnitOption()
|
||||
{
|
||||
sourceScriptGuids = new HashSet<string>();
|
||||
}
|
||||
|
||||
public UnitOption(TUnit unit) : this()
|
||||
{
|
||||
this.unit = unit;
|
||||
|
||||
FillFromUnit();
|
||||
}
|
||||
|
||||
[DoNotSerialize]
|
||||
protected bool filled { get; private set; }
|
||||
|
||||
private TUnit _unit;
|
||||
|
||||
protected UnitOptionRow source { get; private set; }
|
||||
|
||||
public TUnit unit
|
||||
{
|
||||
get
|
||||
{
|
||||
// Load the unit on demand to avoid deserialization overhead
|
||||
// Deserializing the entire database takes many seconds,
|
||||
// which is the reason why UnitOptionRow and SQLite are used
|
||||
// in the first place.
|
||||
|
||||
if (_unit == null)
|
||||
{
|
||||
_unit = (TUnit)new SerializationData(source.unit).Deserialize();
|
||||
}
|
||||
|
||||
return _unit;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
_unit = value;
|
||||
}
|
||||
}
|
||||
|
||||
IUnit IUnitOption.unit => unit;
|
||||
|
||||
public Type unitType { get; private set; }
|
||||
|
||||
protected IUnitDescriptor descriptor => unit.Descriptor<IUnitDescriptor>();
|
||||
|
||||
// Avoid using the descriptions for each option, because we don't need all fields described until the option is hovered
|
||||
|
||||
protected UnitDescription description => unit.Description<UnitDescription>();
|
||||
|
||||
protected UnitPortDescription PortDescription(IUnitPort port)
|
||||
{
|
||||
return port.Description<UnitPortDescription>();
|
||||
}
|
||||
|
||||
public virtual IUnit InstantiateUnit()
|
||||
{
|
||||
var instance = unit.CloneViaSerialization();
|
||||
instance.Define();
|
||||
return instance;
|
||||
}
|
||||
|
||||
void IUnitOption.PreconfigureUnit(IUnit unit)
|
||||
{
|
||||
PreconfigureUnit((TUnit)unit);
|
||||
}
|
||||
|
||||
public virtual void PreconfigureUnit(TUnit unit)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void FillFromUnit()
|
||||
{
|
||||
unit.EnsureDefined();
|
||||
unitType = unit.GetType();
|
||||
|
||||
labelHuman = Label(true);
|
||||
haystackHuman = Haystack(true);
|
||||
|
||||
labelProgrammer = Label(false);
|
||||
haystackProgrammer = Haystack(false);
|
||||
|
||||
category = Category();
|
||||
order = Order();
|
||||
favoriteKey = FavoriteKey();
|
||||
UnityAPI.Async(() => icon = Icon());
|
||||
|
||||
showControlInputsInFooter = ShowControlInputsInFooter();
|
||||
showControlOutputsInFooter = ShowControlOutputsInFooter();
|
||||
showValueInputsInFooter = ShowValueInputsInFooter();
|
||||
showValueOutputsInFooter = ShowValueOutputsInFooter();
|
||||
|
||||
controlInputCount = unit.controlInputs.Count;
|
||||
controlOutputCount = unit.controlOutputs.Count;
|
||||
valueInputTypes = unit.valueInputs.Select(vi => vi.type).ToHashSet();
|
||||
valueOutputTypes = unit.valueOutputs.Select(vo => vo.type).ToHashSet();
|
||||
|
||||
filled = true;
|
||||
}
|
||||
|
||||
protected virtual void FillFromData()
|
||||
{
|
||||
unit.EnsureDefined();
|
||||
unitType = unit.GetType();
|
||||
UnityAPI.Async(() => icon = Icon());
|
||||
|
||||
showControlInputsInFooter = ShowControlInputsInFooter();
|
||||
showControlOutputsInFooter = ShowControlOutputsInFooter();
|
||||
showValueInputsInFooter = ShowValueInputsInFooter();
|
||||
showValueOutputsInFooter = ShowValueOutputsInFooter();
|
||||
|
||||
filled = true;
|
||||
}
|
||||
|
||||
public virtual void Deserialize(UnitOptionRow row)
|
||||
{
|
||||
source = row;
|
||||
|
||||
if (row.sourceScriptGuids != null)
|
||||
{
|
||||
sourceScriptGuids = row.sourceScriptGuids.Split(',').ToHashSet();
|
||||
}
|
||||
|
||||
unitType = Codebase.DeserializeType(row.unitType);
|
||||
|
||||
category = row.category == null ? null : new UnitCategory(row.category);
|
||||
labelHuman = row.labelHuman;
|
||||
labelProgrammer = row.labelProgrammer;
|
||||
order = row.order;
|
||||
haystackHuman = row.haystackHuman;
|
||||
haystackProgrammer = row.haystackProgrammer;
|
||||
favoriteKey = row.favoriteKey;
|
||||
|
||||
controlInputCount = row.controlInputCount;
|
||||
controlOutputCount = row.controlOutputCount;
|
||||
}
|
||||
|
||||
public virtual UnitOptionRow Serialize()
|
||||
{
|
||||
var row = new UnitOptionRow();
|
||||
|
||||
if (sourceScriptGuids.Count == 0)
|
||||
{
|
||||
// Important to set to null here, because the code relies on
|
||||
// null checks, not empty string checks.
|
||||
row.sourceScriptGuids = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
row.sourceScriptGuids = string.Join(",", sourceScriptGuids.ToArray());
|
||||
}
|
||||
|
||||
row.optionType = Codebase.SerializeType(GetType());
|
||||
row.unitType = Codebase.SerializeType(unitType);
|
||||
row.unit = unit.Serialize().json;
|
||||
|
||||
row.category = category?.fullName;
|
||||
row.labelHuman = labelHuman;
|
||||
row.labelProgrammer = labelProgrammer;
|
||||
row.order = order;
|
||||
row.haystackHuman = haystackHuman;
|
||||
row.haystackProgrammer = haystackProgrammer;
|
||||
row.favoriteKey = favoriteKey;
|
||||
|
||||
row.controlInputCount = controlInputCount;
|
||||
row.controlOutputCount = controlOutputCount;
|
||||
row.valueInputTypes = valueInputTypes.Select(Codebase.SerializeType).ToSeparatedString("|").NullIfEmpty();
|
||||
row.valueOutputTypes = valueOutputTypes.Select(Codebase.SerializeType).ToSeparatedString("|").NullIfEmpty();
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
public virtual void OnPopulate()
|
||||
{
|
||||
if (!filled)
|
||||
{
|
||||
FillFromData();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Prewarm() { }
|
||||
|
||||
|
||||
#region Configuration
|
||||
|
||||
public object value => this;
|
||||
|
||||
public bool parentOnly => false;
|
||||
|
||||
public virtual string headerLabel => label;
|
||||
|
||||
public virtual bool showHeaderIcon => false;
|
||||
|
||||
public virtual bool favoritable => true;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
|
||||
public HashSet<string> sourceScriptGuids { get; protected set; }
|
||||
|
||||
protected string labelHuman { get; set; }
|
||||
|
||||
protected string labelProgrammer { get; set; }
|
||||
|
||||
public string label => BoltCore.Configuration.humanNaming ? labelHuman : labelProgrammer;
|
||||
|
||||
public UnitCategory category { get; private set; }
|
||||
|
||||
public int order { get; private set; }
|
||||
|
||||
public EditorTexture icon { get; private set; }
|
||||
|
||||
protected string haystackHuman { get; set; }
|
||||
|
||||
protected string haystackProgrammer { get; set; }
|
||||
|
||||
public string haystack => BoltCore.Configuration.humanNaming ? haystackHuman : haystackProgrammer;
|
||||
|
||||
public string favoriteKey { get; private set; }
|
||||
|
||||
public string formerHaystack => BoltFlowNameUtility.UnitPreviousTitle(unitType);
|
||||
|
||||
GUIStyle IFuzzyOption.style => Style();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Contextual Filtering
|
||||
|
||||
public int controlInputCount { get; private set; }
|
||||
|
||||
public int controlOutputCount { get; private set; }
|
||||
|
||||
private HashSet<Type> _valueInputTypes;
|
||||
|
||||
private HashSet<Type> _valueOutputTypes;
|
||||
|
||||
// On demand loading for initialization performance (type deserialization is expensive)
|
||||
|
||||
public HashSet<Type> valueInputTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueInputTypes == null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source.valueInputTypes))
|
||||
{
|
||||
_valueInputTypes = new HashSet<Type>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueInputTypes = source.valueInputTypes.Split('|').Select(Codebase.DeserializeType).ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
return _valueInputTypes;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_valueInputTypes = value;
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<Type> valueOutputTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueOutputTypes == null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source.valueOutputTypes))
|
||||
{
|
||||
_valueOutputTypes = new HashSet<Type>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueOutputTypes = source.valueOutputTypes.Split('|').Select(Codebase.DeserializeType).ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
return _valueOutputTypes;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_valueOutputTypes = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Providers
|
||||
|
||||
protected virtual string Label(bool human)
|
||||
{
|
||||
return BoltFlowNameUtility.UnitTitle(unitType, false, true);
|
||||
}
|
||||
|
||||
protected virtual UnitCategory Category()
|
||||
{
|
||||
return unitType.GetAttribute<UnitCategory>();
|
||||
}
|
||||
|
||||
protected virtual int Order()
|
||||
{
|
||||
return unitType.GetAttribute<UnitOrderAttribute>()?.order ?? int.MaxValue;
|
||||
}
|
||||
|
||||
protected virtual string Haystack(bool human)
|
||||
{
|
||||
return Label(human);
|
||||
}
|
||||
|
||||
protected virtual EditorTexture Icon()
|
||||
{
|
||||
return descriptor.Icon();
|
||||
}
|
||||
|
||||
protected virtual GUIStyle Style()
|
||||
{
|
||||
return FuzzyWindow.defaultOptionStyle;
|
||||
}
|
||||
|
||||
protected virtual string FavoriteKey()
|
||||
{
|
||||
return unit.GetType().FullName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Search
|
||||
|
||||
public virtual string SearchResultLabel(string query)
|
||||
{
|
||||
var label = SearchUtility.HighlightQuery(haystack, query);
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
label += $" <color=#{ColorPalette.unityForegroundDim.ToHexString()}>(in {category.fullName})</color>";
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Footer
|
||||
|
||||
private string summary => description.summary;
|
||||
|
||||
public bool hasFooter => !StringUtility.IsNullOrWhiteSpace(summary) || footerPorts.Any();
|
||||
|
||||
protected virtual bool ShowControlInputsInFooter()
|
||||
{
|
||||
return unitType.GetAttribute<UnitFooterPortsAttribute>()?.ControlInputs ?? false;
|
||||
}
|
||||
|
||||
protected virtual bool ShowControlOutputsInFooter()
|
||||
{
|
||||
return unitType.GetAttribute<UnitFooterPortsAttribute>()?.ControlOutputs ?? false;
|
||||
}
|
||||
|
||||
protected virtual bool ShowValueInputsInFooter()
|
||||
{
|
||||
return unitType.GetAttribute<UnitFooterPortsAttribute>()?.ValueInputs ?? true;
|
||||
}
|
||||
|
||||
protected virtual bool ShowValueOutputsInFooter()
|
||||
{
|
||||
return unitType.GetAttribute<UnitFooterPortsAttribute>()?.ValueOutputs ?? true;
|
||||
}
|
||||
|
||||
[DoNotSerialize]
|
||||
protected bool showControlInputsInFooter { get; private set; }
|
||||
|
||||
[DoNotSerialize]
|
||||
protected bool showControlOutputsInFooter { get; private set; }
|
||||
|
||||
[DoNotSerialize]
|
||||
protected bool showValueInputsInFooter { get; private set; }
|
||||
|
||||
[DoNotSerialize]
|
||||
protected bool showValueOutputsInFooter { get; private set; }
|
||||
|
||||
private IEnumerable<IUnitPort> footerPorts
|
||||
{
|
||||
get
|
||||
{
|
||||
if (showControlInputsInFooter)
|
||||
{
|
||||
foreach (var controlInput in unit.controlInputs)
|
||||
{
|
||||
yield return controlInput;
|
||||
}
|
||||
}
|
||||
|
||||
if (showControlOutputsInFooter)
|
||||
{
|
||||
foreach (var controlOutput in unit.controlOutputs)
|
||||
{
|
||||
yield return controlOutput;
|
||||
}
|
||||
}
|
||||
|
||||
if (showValueInputsInFooter)
|
||||
{
|
||||
foreach (var valueInput in unit.valueInputs)
|
||||
{
|
||||
yield return valueInput;
|
||||
}
|
||||
}
|
||||
|
||||
if (showValueOutputsInFooter)
|
||||
{
|
||||
foreach (var valueOutput in unit.valueOutputs)
|
||||
{
|
||||
yield return valueOutput;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetFooterHeight(float width)
|
||||
{
|
||||
var hasSummary = !StringUtility.IsNullOrWhiteSpace(summary);
|
||||
var hasIcon = icon != null;
|
||||
var hasPorts = footerPorts.Any();
|
||||
|
||||
var height = 0f;
|
||||
|
||||
width -= 2 * FooterStyles.padding;
|
||||
|
||||
height += FooterStyles.padding;
|
||||
|
||||
if (hasSummary)
|
||||
{
|
||||
if (hasIcon)
|
||||
{
|
||||
height += Mathf.Max(FooterStyles.unitIconSize, GetFooterSummaryHeight(width - FooterStyles.unitIconSize - FooterStyles.spaceAfterUnitIcon));
|
||||
}
|
||||
else
|
||||
{
|
||||
height += GetFooterSummaryHeight(width);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSummary && hasPorts)
|
||||
{
|
||||
height += FooterStyles.spaceBetweenDescriptionAndPorts;
|
||||
}
|
||||
|
||||
foreach (var port in footerPorts)
|
||||
{
|
||||
height += GetFooterPortHeight(width, port);
|
||||
height += FooterStyles.spaceBetweenPorts;
|
||||
}
|
||||
|
||||
if (hasPorts)
|
||||
{
|
||||
height -= FooterStyles.spaceBetweenPorts;
|
||||
}
|
||||
|
||||
height += FooterStyles.padding;
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
public void OnFooterGUI(Rect position)
|
||||
{
|
||||
var hasSummary = !StringUtility.IsNullOrWhiteSpace(summary);
|
||||
var hasIcon = icon != null;
|
||||
var hasPorts = footerPorts.Any();
|
||||
|
||||
var y = position.y;
|
||||
|
||||
y += FooterStyles.padding;
|
||||
|
||||
position.x += FooterStyles.padding;
|
||||
position.width -= FooterStyles.padding * 2;
|
||||
|
||||
if (hasSummary)
|
||||
{
|
||||
if (hasIcon)
|
||||
{
|
||||
var iconPosition = new Rect
|
||||
(
|
||||
position.x,
|
||||
y,
|
||||
FooterStyles.unitIconSize,
|
||||
FooterStyles.unitIconSize
|
||||
);
|
||||
|
||||
var summaryWidth = position.width - iconPosition.width - FooterStyles.spaceAfterUnitIcon;
|
||||
|
||||
var summaryPosition = new Rect
|
||||
(
|
||||
iconPosition.xMax + FooterStyles.spaceAfterUnitIcon,
|
||||
y,
|
||||
summaryWidth,
|
||||
GetFooterSummaryHeight(summaryWidth)
|
||||
);
|
||||
|
||||
GUI.DrawTexture(iconPosition, icon?[FooterStyles.unitIconSize]);
|
||||
|
||||
OnFooterSummaryGUI(summaryPosition);
|
||||
|
||||
y = Mathf.Max(iconPosition.yMax, summaryPosition.yMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnFooterSummaryGUI(position.VerticalSection(ref y, GetFooterSummaryHeight(position.width)));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSummary && hasPorts)
|
||||
{
|
||||
y += FooterStyles.spaceBetweenDescriptionAndPorts;
|
||||
}
|
||||
|
||||
foreach (var port in footerPorts)
|
||||
{
|
||||
OnFooterPortGUI(position.VerticalSection(ref y, GetFooterPortHeight(position.width, port)), port);
|
||||
y += FooterStyles.spaceBetweenPorts;
|
||||
}
|
||||
|
||||
if (hasPorts)
|
||||
{
|
||||
y -= FooterStyles.spaceBetweenPorts;
|
||||
}
|
||||
|
||||
y += FooterStyles.padding;
|
||||
}
|
||||
|
||||
private float GetFooterSummaryHeight(float width)
|
||||
{
|
||||
return FooterStyles.description.CalcHeight(new GUIContent(summary), width);
|
||||
}
|
||||
|
||||
private void OnFooterSummaryGUI(Rect position)
|
||||
{
|
||||
EditorGUI.LabelField(position, summary, FooterStyles.description);
|
||||
}
|
||||
|
||||
private string GetFooterPortLabel(IUnitPort port)
|
||||
{
|
||||
string type;
|
||||
|
||||
if (port is ValueInput)
|
||||
{
|
||||
type = ((IUnitValuePort)port).type.DisplayName() + " Input";
|
||||
}
|
||||
else if (port is ValueOutput)
|
||||
{
|
||||
type = ((IUnitValuePort)port).type.DisplayName() + " Output";
|
||||
}
|
||||
else if (port is ControlInput)
|
||||
{
|
||||
type = "Trigger Input";
|
||||
}
|
||||
else if (port is ControlOutput)
|
||||
{
|
||||
type = "Trigger Output";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var portDescription = PortDescription(port);
|
||||
|
||||
if (!StringUtility.IsNullOrWhiteSpace(portDescription.summary))
|
||||
{
|
||||
return $"<b>{portDescription.label}:</b> {portDescription.summary} {LudiqGUIUtility.DimString($"({type})")}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"<b>{portDescription.label}:</b> {LudiqGUIUtility.DimString($"({type})")}";
|
||||
}
|
||||
}
|
||||
|
||||
private float GetFooterPortDescriptionHeight(float width, IUnitPort port)
|
||||
{
|
||||
return FooterStyles.portDescription.CalcHeight(new GUIContent(GetFooterPortLabel(port)), width);
|
||||
}
|
||||
|
||||
private void OnFooterPortDescriptionGUI(Rect position, IUnitPort port)
|
||||
{
|
||||
GUI.Label(position, GetFooterPortLabel(port), FooterStyles.portDescription);
|
||||
}
|
||||
|
||||
private float GetFooterPortHeight(float width, IUnitPort port)
|
||||
{
|
||||
var descriptionWidth = width - FooterStyles.portIconSize - FooterStyles.spaceAfterPortIcon;
|
||||
|
||||
return GetFooterPortDescriptionHeight(descriptionWidth, port);
|
||||
}
|
||||
|
||||
private void OnFooterPortGUI(Rect position, IUnitPort port)
|
||||
{
|
||||
var iconPosition = new Rect
|
||||
(
|
||||
position.x,
|
||||
position.y,
|
||||
FooterStyles.portIconSize,
|
||||
FooterStyles.portIconSize
|
||||
);
|
||||
|
||||
var descriptionWidth = position.width - FooterStyles.portIconSize - FooterStyles.spaceAfterPortIcon;
|
||||
|
||||
var descriptionPosition = new Rect
|
||||
(
|
||||
iconPosition.xMax + FooterStyles.spaceAfterPortIcon,
|
||||
position.y,
|
||||
descriptionWidth,
|
||||
GetFooterPortDescriptionHeight(descriptionWidth, port)
|
||||
);
|
||||
|
||||
var portDescription = PortDescription(port);
|
||||
|
||||
var icon = portDescription.icon?[FooterStyles.portIconSize];
|
||||
|
||||
if (icon != null)
|
||||
{
|
||||
GUI.DrawTexture(iconPosition, icon);
|
||||
}
|
||||
|
||||
OnFooterPortDescriptionGUI(descriptionPosition, port);
|
||||
}
|
||||
|
||||
public static class FooterStyles
|
||||
{
|
||||
static FooterStyles()
|
||||
{
|
||||
description = new GUIStyle(EditorStyles.label);
|
||||
description.padding = new RectOffset(0, 0, 0, 0);
|
||||
description.wordWrap = true;
|
||||
description.richText = true;
|
||||
|
||||
portDescription = new GUIStyle(EditorStyles.label);
|
||||
portDescription.padding = new RectOffset(0, 0, 0, 0);
|
||||
portDescription.wordWrap = true;
|
||||
portDescription.richText = true;
|
||||
portDescription.imagePosition = ImagePosition.TextOnly;
|
||||
}
|
||||
|
||||
public static readonly GUIStyle description;
|
||||
public static readonly GUIStyle portDescription;
|
||||
public static readonly float spaceAfterUnitIcon = 7;
|
||||
public static readonly int unitIconSize = IconSize.Medium;
|
||||
public static readonly float spaceAfterPortIcon = 6;
|
||||
public static readonly int portIconSize = IconSize.Small;
|
||||
public static readonly float spaceBetweenDescriptionAndPorts = 8;
|
||||
public static readonly float spaceBetweenPorts = 8;
|
||||
public static readonly float padding = 8;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 37d8f9a231f5545c8aa227756649439d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,345 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UnitOptionFilter : ICloneable
|
||||
{
|
||||
public UnitOptionFilter(bool @default)
|
||||
{
|
||||
NoControlInput = @default;
|
||||
SingleControlInput = @default;
|
||||
MultipleControlInputs = @default;
|
||||
|
||||
NoValueInput = @default;
|
||||
SingleValueInput = @default;
|
||||
MultipleValueInputs = @default;
|
||||
|
||||
NoControlOutput = @default;
|
||||
SingleControlOutput = @default;
|
||||
MultipleControlOutputs = @default;
|
||||
|
||||
NoValueOutput = @default;
|
||||
SingleValueOutput = @default;
|
||||
MultipleValueOutputs = @default;
|
||||
|
||||
Normals = @default;
|
||||
Self = @default;
|
||||
Events = @default;
|
||||
Literals = @default;
|
||||
Variables = @default;
|
||||
Members = @default;
|
||||
Nesters = @default;
|
||||
Expose = @default;
|
||||
Obsolete = false;
|
||||
AllowSelfNestedGraph = false;
|
||||
}
|
||||
|
||||
public bool NoControlInput { get; set; }
|
||||
public bool SingleControlInput { get; set; }
|
||||
public bool MultipleControlInputs { get; set; }
|
||||
|
||||
public bool NoValueInput { get; set; }
|
||||
public bool SingleValueInput { get; set; }
|
||||
public bool MultipleValueInputs { get; set; }
|
||||
|
||||
public bool NoControlOutput { get; set; }
|
||||
public bool SingleControlOutput { get; set; }
|
||||
public bool MultipleControlOutputs { get; set; }
|
||||
|
||||
public bool NoValueOutput { get; set; }
|
||||
public bool SingleValueOutput { get; set; }
|
||||
public bool MultipleValueOutputs { get; set; }
|
||||
|
||||
public bool Normals { get; set; }
|
||||
public bool Self { get; set; }
|
||||
public bool Events { get; set; }
|
||||
public bool Literals { get; set; }
|
||||
public bool Variables { get; set; }
|
||||
public bool Members { get; set; }
|
||||
public bool Nesters { get; set; }
|
||||
public bool Expose { get; set; }
|
||||
public bool Obsolete { get; set; }
|
||||
public bool AllowSelfNestedGraph { get; set; }
|
||||
|
||||
public Type CompatibleInputType { get; set; }
|
||||
public Type CompatibleOutputType { get; set; }
|
||||
|
||||
public int GraphHashCode { get; set; }
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return Clone();
|
||||
}
|
||||
|
||||
public UnitOptionFilter Clone()
|
||||
{
|
||||
return new UnitOptionFilter(true)
|
||||
{
|
||||
NoControlInput = NoControlInput,
|
||||
SingleControlInput = SingleControlInput,
|
||||
MultipleControlInputs = MultipleControlInputs,
|
||||
NoValueInput = NoValueInput,
|
||||
SingleValueInput = SingleValueInput,
|
||||
MultipleValueInputs = MultipleValueInputs,
|
||||
NoControlOutput = NoControlOutput,
|
||||
SingleControlOutput = SingleControlOutput,
|
||||
MultipleControlOutputs = MultipleControlOutputs,
|
||||
NoValueOutput = NoValueOutput,
|
||||
SingleValueOutput = SingleValueOutput,
|
||||
MultipleValueOutputs = MultipleValueOutputs,
|
||||
Normals = Normals,
|
||||
Self = Self,
|
||||
Events = Events,
|
||||
Literals = Literals,
|
||||
Variables = Variables,
|
||||
Members = Members,
|
||||
Nesters = Nesters,
|
||||
Expose = Expose,
|
||||
Obsolete = Obsolete,
|
||||
CompatibleInputType = CompatibleInputType,
|
||||
CompatibleOutputType = CompatibleOutputType,
|
||||
AllowSelfNestedGraph = AllowSelfNestedGraph,
|
||||
GraphHashCode = GraphHashCode
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as UnitOptionFilter;
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return NoControlInput == other.NoControlInput &&
|
||||
SingleControlInput == other.SingleControlInput &&
|
||||
MultipleControlInputs == other.MultipleControlInputs &&
|
||||
NoValueInput == other.NoValueInput &&
|
||||
SingleValueInput == other.SingleValueInput &&
|
||||
MultipleValueInputs == other.MultipleValueInputs &&
|
||||
NoControlOutput == other.NoControlOutput &&
|
||||
SingleControlOutput == other.SingleControlOutput &&
|
||||
MultipleControlOutputs == other.MultipleControlOutputs &&
|
||||
NoValueOutput == other.NoValueOutput &&
|
||||
SingleValueOutput == other.SingleValueOutput &&
|
||||
MultipleValueOutputs == other.MultipleValueOutputs &&
|
||||
Normals == other.Normals &&
|
||||
Self == other.Self &&
|
||||
Events == other.Events &&
|
||||
Literals == other.Literals &&
|
||||
Variables == other.Variables &&
|
||||
Members == other.Members &&
|
||||
Nesters == other.Nesters &&
|
||||
Expose == other.Expose &&
|
||||
Obsolete == other.Obsolete &&
|
||||
CompatibleInputType == other.CompatibleInputType &&
|
||||
CompatibleOutputType == other.CompatibleOutputType &&
|
||||
AllowSelfNestedGraph == other.AllowSelfNestedGraph &&
|
||||
GraphHashCode == other.GraphHashCode;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
|
||||
hash = hash * 23 + NoControlInput.GetHashCode();
|
||||
hash = hash * 23 + SingleControlInput.GetHashCode();
|
||||
hash = hash * 23 + MultipleControlInputs.GetHashCode();
|
||||
|
||||
hash = hash * 23 + NoValueInput.GetHashCode();
|
||||
hash = hash * 23 + SingleValueInput.GetHashCode();
|
||||
hash = hash * 23 + MultipleValueInputs.GetHashCode();
|
||||
|
||||
hash = hash * 23 + NoControlOutput.GetHashCode();
|
||||
hash = hash * 23 + SingleControlOutput.GetHashCode();
|
||||
hash = hash * 23 + MultipleControlOutputs.GetHashCode();
|
||||
|
||||
hash = hash * 23 + NoValueOutput.GetHashCode();
|
||||
hash = hash * 23 + SingleValueOutput.GetHashCode();
|
||||
hash = hash * 23 + MultipleValueOutputs.GetHashCode();
|
||||
|
||||
hash = hash * 23 + Self.GetHashCode();
|
||||
hash = hash * 23 + Events.GetHashCode();
|
||||
hash = hash * 23 + Literals.GetHashCode();
|
||||
hash = hash * 23 + Variables.GetHashCode();
|
||||
hash = hash * 23 + Members.GetHashCode();
|
||||
hash = hash * 23 + Nesters.GetHashCode();
|
||||
hash = hash * 23 + Expose.GetHashCode();
|
||||
hash = hash * 23 + Obsolete.GetHashCode();
|
||||
hash = hash * 23 + AllowSelfNestedGraph.GetHashCode();
|
||||
|
||||
hash = hash * 23 + (CompatibleInputType?.GetHashCode() ?? 0);
|
||||
hash = hash * 23 + (CompatibleOutputType?.GetHashCode() ?? 0);
|
||||
|
||||
hash = hash * 23 + GraphHashCode;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateOption(IUnitOption option)
|
||||
{
|
||||
Ensure.That(nameof(option)).IsNotNull(option);
|
||||
|
||||
if (!NoControlInput && option.controlInputCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SingleControlInput && option.controlInputCount == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!MultipleControlInputs && option.controlInputCount > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NoValueInput && option.valueInputTypes.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SingleValueInput && option.valueInputTypes.Count == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!MultipleValueInputs && option.valueInputTypes.Count > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NoControlOutput && option.controlOutputCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SingleControlOutput && option.controlOutputCount == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!MultipleControlOutputs && option.controlOutputCount > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NoValueOutput && option.valueOutputTypes.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SingleValueOutput && option.valueOutputTypes.Count == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!MultipleValueOutputs && option.valueOutputTypes.Count > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var unitType = option.unitType;
|
||||
|
||||
if (!Normals && !unitType.HasAttribute<SpecialUnitAttribute>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Self && option.UnitIs<This>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Events && option.UnitIs<IEventUnit>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Literals && option.UnitIs<Literal>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Variables && option.UnitIs<IUnifiedVariableUnit>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Members && option.UnitIs<MemberUnit>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Nesters && option.UnitIs<INesterUnit>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Expose && option.UnitIs<Expose>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Obsolete && unitType.HasAttribute<ObsoleteAttribute>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CompatibleInputType != null && !option.HasCompatibleValueInput(CompatibleInputType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CompatibleOutputType != null && !option.HasCompatibleValueOutput(CompatibleOutputType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AllowSelfNestedGraph && option.UnitIs<SuperUnit>())
|
||||
{
|
||||
if (((SuperUnit)option.unit).nest.graph.GetHashCode() == GraphHashCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine($"NoControlInput: {NoControlInput}");
|
||||
sb.AppendLine($"SingleControlInput: {SingleControlInput}");
|
||||
sb.AppendLine($"MultipleControlInputs: {MultipleControlInputs}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"NoValueInput: {NoValueInput}");
|
||||
sb.AppendLine($"SingleValueInput: {SingleValueInput}");
|
||||
sb.AppendLine($"MultipleValueInputs: {MultipleValueInputs}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"NoControlOutput: {NoControlOutput}");
|
||||
sb.AppendLine($"SingleControlOutput: {SingleControlOutput}");
|
||||
sb.AppendLine($"MultipleControlOutputs: {MultipleControlOutputs}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"NoValueOutput: {NoValueOutput}");
|
||||
sb.AppendLine($"SingleValueOutput: {SingleValueOutput}");
|
||||
sb.AppendLine($"MultipleValueOutputs: {MultipleValueOutputs}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Self: {Self}");
|
||||
sb.AppendLine($"Events: {Events}");
|
||||
sb.AppendLine($"Literals: {Literals}");
|
||||
sb.AppendLine($"Variables: {Variables}");
|
||||
sb.AppendLine($"Members: {Members}");
|
||||
sb.AppendLine($"Nesters: {Nesters}");
|
||||
sb.AppendLine($"Expose: {Expose}");
|
||||
sb.AppendLine($"Obsolete: {Obsolete}");
|
||||
sb.AppendLine($"AllowSelfNestedGraph: {AllowSelfNestedGraph}");
|
||||
sb.AppendLine($"GraphHashCode: {GraphHashCode}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static UnitOptionFilter Any => new UnitOptionFilter(true);
|
||||
public static UnitOptionFilter None => new UnitOptionFilter(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5f22a86a3605947029c82ebdf322c9f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,15 @@
|
|||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class XUnitOptionProvider
|
||||
{
|
||||
public static IUnitOption Option(this IUnit unit)
|
||||
{
|
||||
return FuzzyOptionProvider.instance.GetDecorator<IUnitOption>(unit);
|
||||
}
|
||||
|
||||
public static IUnitOption Option<TOption>(this IUnit unit) where TOption : IUnitOption
|
||||
{
|
||||
return FuzzyOptionProvider.instance.GetDecorator<TOption>(unit);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a2b9227c78ca546fcb92bb352fcab92d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using Unity.VisualScripting.Dependencies.Sqlite;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UnitOptionRow
|
||||
{
|
||||
[AutoIncrement, PrimaryKey]
|
||||
public int id { get; set; }
|
||||
|
||||
public string sourceScriptGuids { get; set; }
|
||||
|
||||
public string optionType { get; set; }
|
||||
public string unitType { get; set; }
|
||||
public string labelHuman { get; set; }
|
||||
public string labelProgrammer { get; set; }
|
||||
public string category { get; set; }
|
||||
public int order { get; set; }
|
||||
public string haystackHuman { get; set; }
|
||||
public string haystackProgrammer { get; set; }
|
||||
public string favoriteKey { get; set; }
|
||||
public string tag1 { get; set; }
|
||||
public string tag2 { get; set; }
|
||||
public string tag3 { get; set; }
|
||||
public string unit { get; set; }
|
||||
|
||||
public int controlInputCount { get; set; }
|
||||
public int controlOutputCount { get; set; }
|
||||
public string valueInputTypes { get; set; }
|
||||
public string valueOutputTypes { get; set; }
|
||||
|
||||
public IUnitOption ToOption()
|
||||
{
|
||||
using (ProfilingUtility.SampleBlock("Row to option"))
|
||||
{
|
||||
var optionType = Codebase.DeserializeType(this.optionType);
|
||||
|
||||
IUnitOption option;
|
||||
|
||||
option = (IUnitOption)Activator.CreateInstance(optionType);
|
||||
|
||||
option.Deserialize(this);
|
||||
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 65a6a75d9580f437b84420c6b5e36cbd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,759 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class UnitOptionTree : ExtensibleFuzzyOptionTree
|
||||
{
|
||||
#region Initialization
|
||||
|
||||
public UnitOptionTree(GUIContent label) : base(label)
|
||||
{
|
||||
favorites = new Favorites(this);
|
||||
|
||||
showBackgroundWorkerProgress = true;
|
||||
}
|
||||
|
||||
public override IFuzzyOption Option(object item)
|
||||
{
|
||||
if (item is Namespace @namespace)
|
||||
{
|
||||
return new NamespaceOption(@namespace, true);
|
||||
}
|
||||
|
||||
if (item is Type type)
|
||||
{
|
||||
return new TypeOption(type, true);
|
||||
}
|
||||
|
||||
return base.Option(item);
|
||||
}
|
||||
|
||||
public override void Prewarm()
|
||||
{
|
||||
filter = filter ?? UnitOptionFilter.Any;
|
||||
|
||||
try
|
||||
{
|
||||
options = new HashSet<IUnitOption>(UnitBase.Subset(filter, reference));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to fetch unit options for fuzzy finder (error log below).\nTry rebuilding the unit options from '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}");
|
||||
options = new HashSet<IUnitOption>();
|
||||
}
|
||||
|
||||
typesWithMembers = new HashSet<Type>();
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
if (option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType != null)
|
||||
{
|
||||
typesWithMembers.Add(memberUnitOption.targetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<IUnitOption> options;
|
||||
|
||||
private HashSet<Type> typesWithMembers;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Configuration
|
||||
|
||||
public UnitOptionFilter filter { get; set; }
|
||||
public GraphReference reference { get; set; }
|
||||
public bool includeNone { get; set; }
|
||||
public bool surfaceCommonTypeLiterals { get; set; }
|
||||
public object[] rootOverride { get; set; }
|
||||
|
||||
public FlowGraph graph => reference.graph as FlowGraph;
|
||||
public GameObject self => reference.self;
|
||||
|
||||
public ActionDirection direction { get; set; } = ActionDirection.Any;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Hierarchy
|
||||
|
||||
private readonly FuzzyGroup enumsGroup = new FuzzyGroup("(Enums)", typeof(Enum).Icon());
|
||||
private readonly FuzzyGroup selfGroup = new FuzzyGroup("Self", typeof(GameObject).Icon());
|
||||
|
||||
private IEnumerable<UnitCategory> SpecialCategories()
|
||||
{
|
||||
yield return new UnitCategory("Codebase");
|
||||
yield return new UnitCategory("Events");
|
||||
yield return new UnitCategory("Variables");
|
||||
yield return new UnitCategory("Math");
|
||||
yield return new UnitCategory("Nesting");
|
||||
yield return new UnitCategory("Graphs");
|
||||
}
|
||||
|
||||
public override IEnumerable<object> Root()
|
||||
{
|
||||
if (rootOverride != null && rootOverride.Length > 0)
|
||||
{
|
||||
foreach (var item in rootOverride)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (filter.CompatibleOutputType != null)
|
||||
{
|
||||
var outputType = filter.CompatibleOutputType;
|
||||
|
||||
var outputTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == outputType);
|
||||
|
||||
if (outputTypeLiteral != null)
|
||||
{
|
||||
yield return outputTypeLiteral;
|
||||
}
|
||||
|
||||
HashSet<Type> noSurfaceConstructors = new HashSet<Type>()
|
||||
{
|
||||
typeof(string),
|
||||
typeof(object)
|
||||
};
|
||||
|
||||
if (!noSurfaceConstructors.Contains(outputType))
|
||||
{
|
||||
var outputTypeConstructors = options.Where(option => option is InvokeMemberOption invokeMemberOption &&
|
||||
invokeMemberOption.targetType == outputType &&
|
||||
invokeMemberOption.unit.member.isConstructor);
|
||||
|
||||
foreach (var outputTypeConstructor in outputTypeConstructors)
|
||||
{
|
||||
yield return outputTypeConstructor;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType == typeof(bool))
|
||||
{
|
||||
foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic")))
|
||||
{
|
||||
yield return logicOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType.IsNumeric())
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType == typeof(Vector2))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType == typeof(Vector3))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputType == typeof(Vector4))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (surfaceCommonTypeLiterals)
|
||||
{
|
||||
foreach (var commonType in EditorTypeUtility.commonTypes)
|
||||
{
|
||||
if (commonType == filter.CompatibleOutputType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var commonTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == commonType);
|
||||
|
||||
if (commonTypeLiteral != null)
|
||||
{
|
||||
yield return commonTypeLiteral;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.CompatibleInputType != null)
|
||||
{
|
||||
var inputType = filter.CompatibleInputType;
|
||||
|
||||
if (!inputType.IsPrimitive && inputType != typeof(object))
|
||||
{
|
||||
yield return inputType;
|
||||
}
|
||||
|
||||
if (inputType == typeof(bool))
|
||||
{
|
||||
yield return options.Single(o => o.UnitIs<If>());
|
||||
yield return options.Single(o => o.UnitIs<SelectUnit>());
|
||||
}
|
||||
|
||||
if (inputType == typeof(bool) || inputType.IsNumeric())
|
||||
{
|
||||
foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic")))
|
||||
{
|
||||
yield return logicOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputType.IsNumeric())
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputType == typeof(Vector2))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputType == typeof(Vector3))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputType == typeof(Vector4))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4")))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(inputType) && (inputType != typeof(string) && inputType != typeof(Transform)))
|
||||
{
|
||||
foreach (var mathOperation in CategoryChildren(new UnitCategory("Collections"), false))
|
||||
{
|
||||
yield return mathOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(IList).IsAssignableFrom(inputType))
|
||||
{
|
||||
foreach (var listOperation in CategoryChildren(new UnitCategory("Collections/Lists")))
|
||||
{
|
||||
yield return listOperation;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(IDictionary).IsAssignableFrom(inputType))
|
||||
{
|
||||
foreach (var dictionaryOperation in CategoryChildren(new UnitCategory("Collections/Dictionaries")))
|
||||
{
|
||||
yield return dictionaryOperation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UnityAPI.Await
|
||||
(
|
||||
() =>
|
||||
{
|
||||
if (self != null)
|
||||
{
|
||||
selfGroup.label = self.name;
|
||||
selfGroup.icon = self.Icon();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
)
|
||||
)
|
||||
{
|
||||
yield return selfGroup;
|
||||
}
|
||||
|
||||
foreach (var category in options.Select(option => option.category?.root)
|
||||
.NotNull()
|
||||
.Concat(SpecialCategories())
|
||||
.Distinct()
|
||||
.OrderBy(c => c.name))
|
||||
{
|
||||
yield return category;
|
||||
}
|
||||
|
||||
foreach (var extensionRootItem in base.Root())
|
||||
{
|
||||
yield return extensionRootItem;
|
||||
}
|
||||
|
||||
if (filter.Self)
|
||||
{
|
||||
var self = options.FirstOrDefault(option => option.UnitIs<This>());
|
||||
|
||||
if (self != null)
|
||||
{
|
||||
yield return self;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var unit in CategoryChildren(null))
|
||||
{
|
||||
yield return unit;
|
||||
}
|
||||
|
||||
if (includeNone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<object> Children(object parent)
|
||||
{
|
||||
if (parent is Namespace @namespace)
|
||||
{
|
||||
return NamespaceChildren(@namespace);
|
||||
}
|
||||
else if (parent is Type type)
|
||||
{
|
||||
return TypeChildren(type);
|
||||
}
|
||||
else if (parent == enumsGroup)
|
||||
{
|
||||
return EnumsChildren();
|
||||
}
|
||||
else if (parent == selfGroup)
|
||||
{
|
||||
return SelfChildren();
|
||||
}
|
||||
else if (parent is UnitCategory unitCategory)
|
||||
{
|
||||
return CategoryChildren(unitCategory);
|
||||
}
|
||||
else if (parent is VariableKind variableKind)
|
||||
{
|
||||
return VariableKindChildren(variableKind);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.Children(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> SelfChildren()
|
||||
{
|
||||
yield return typeof(GameObject);
|
||||
|
||||
// Self components can be null if no script is assigned to them
|
||||
// https://support.ludiq.io/forums/5-bolt/topics/817-/
|
||||
foreach (var selfComponentType in UnityAPI.Await(() => self.GetComponents<Component>().NotUnityNull().Select(c => c.GetType())))
|
||||
{
|
||||
yield return selfComponentType;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> CodebaseChildren()
|
||||
{
|
||||
foreach (var rootNamespace in typesWithMembers.Where(t => !t.IsEnum)
|
||||
.Select(t => t.Namespace().Root)
|
||||
.OrderBy(ns => ns.DisplayName(false))
|
||||
.Distinct())
|
||||
{
|
||||
yield return rootNamespace;
|
||||
}
|
||||
|
||||
if (filter.Literals && options.Any(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum))
|
||||
{
|
||||
yield return enumsGroup;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> MathChildren()
|
||||
{
|
||||
foreach (var mathMember in GetMembers(typeof(Mathf)).Where(option => !((MemberUnit)option.unit).member.requiresTarget))
|
||||
{
|
||||
yield return mathMember;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> TimeChildren()
|
||||
{
|
||||
foreach (var timeMember in GetMembers(typeof(Time)).Where(option => !((MemberUnit)option.unit).member.requiresTarget))
|
||||
{
|
||||
yield return timeMember;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> NestingChildren()
|
||||
{
|
||||
foreach (var nester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro == null)
|
||||
.OrderBy(option => option.label))
|
||||
{
|
||||
yield return nester;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> MacroChildren()
|
||||
{
|
||||
foreach (var macroNester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro != null)
|
||||
.OrderBy(option => option.label))
|
||||
{
|
||||
yield return macroNester;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> VariablesChildren()
|
||||
{
|
||||
yield return VariableKind.Flow;
|
||||
yield return VariableKind.Graph;
|
||||
yield return VariableKind.Object;
|
||||
yield return VariableKind.Scene;
|
||||
yield return VariableKind.Application;
|
||||
yield return VariableKind.Saved;
|
||||
}
|
||||
|
||||
private IEnumerable<object> VariableKindChildren(VariableKind kind)
|
||||
{
|
||||
foreach (var variable in options.OfType<IUnifiedVariableUnitOption>()
|
||||
.Where(option => option.kind == kind)
|
||||
.OrderBy(option => option.name))
|
||||
{
|
||||
yield return variable;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> NamespaceChildren(Namespace @namespace)
|
||||
{
|
||||
foreach (var childNamespace in GetChildrenNamespaces(@namespace))
|
||||
{
|
||||
yield return childNamespace;
|
||||
}
|
||||
|
||||
foreach (var type in GetNamespaceTypes(@namespace))
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Namespace> GetChildrenNamespaces(Namespace @namespace)
|
||||
{
|
||||
if (!@namespace.IsGlobal)
|
||||
{
|
||||
foreach (var childNamespace in typesWithMembers.Where(t => !t.IsEnum)
|
||||
.SelectMany(t => t.Namespace().AndAncestors())
|
||||
.Distinct()
|
||||
.Where(ns => ns.Parent == @namespace)
|
||||
.OrderBy(ns => ns.DisplayName(false)))
|
||||
{
|
||||
yield return childNamespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetNamespaceTypes(Namespace @namespace)
|
||||
{
|
||||
foreach (var type in typesWithMembers.Where(t => t.Namespace() == @namespace && !t.IsEnum)
|
||||
.OrderBy(t => t.DisplayName()))
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> TypeChildren(Type type)
|
||||
{
|
||||
foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType == type))
|
||||
{
|
||||
yield return literal;
|
||||
}
|
||||
|
||||
foreach (var expose in options.Where(option => option is ExposeOption exposeOption && exposeOption.exposedType == type))
|
||||
{
|
||||
yield return expose;
|
||||
}
|
||||
|
||||
if (type.IsStruct())
|
||||
{
|
||||
foreach (var createStruct in options.Where(option => option is CreateStructOption createStructOption && createStructOption.structType == type))
|
||||
{
|
||||
yield return createStruct;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var member in GetMembers(type))
|
||||
{
|
||||
yield return member;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IUnitOption> GetMembers(Type type)
|
||||
{
|
||||
foreach (var member in options.Where(option => option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType == type && option.unit.canDefine)
|
||||
.OrderBy(option => BoltCore.Configuration.groupInheritedMembers && ((MemberUnit)option.unit).member.isPseudoInherited)
|
||||
.ThenBy(option => option.order)
|
||||
.ThenBy(option => option.label))
|
||||
{
|
||||
yield return member;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> EnumsChildren()
|
||||
{
|
||||
foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum)
|
||||
.OrderBy(option => option.label))
|
||||
{
|
||||
yield return literal;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> CategoryChildren(UnitCategory category, bool subCategories = true)
|
||||
{
|
||||
if (category != null && subCategories)
|
||||
{
|
||||
foreach (var subCategory in options.SelectMany(option => option.category == null ? Enumerable.Empty<UnitCategory>() : option.category.AndAncestors())
|
||||
.Distinct()
|
||||
.Where(c => c.parent == category)
|
||||
.OrderBy(c => c.name))
|
||||
{
|
||||
yield return subCategory;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var unit in options.Where(option => option.category == category)
|
||||
.Where(option => !option.unitType.HasAttribute<SpecialUnitAttribute>())
|
||||
.OrderBy(option => option.order)
|
||||
.ThenBy(option => option.label))
|
||||
{
|
||||
yield return unit;
|
||||
}
|
||||
|
||||
if (category != null)
|
||||
{
|
||||
if (category.root.name == "Events")
|
||||
{
|
||||
foreach (var eventChild in EventsChildren(category))
|
||||
{
|
||||
yield return eventChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Codebase")
|
||||
{
|
||||
foreach (var codebaseChild in CodebaseChildren())
|
||||
{
|
||||
yield return codebaseChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Variables")
|
||||
{
|
||||
foreach (var variableChild in VariablesChildren())
|
||||
{
|
||||
yield return variableChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Math")
|
||||
{
|
||||
foreach (var mathChild in MathChildren())
|
||||
{
|
||||
yield return mathChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Time")
|
||||
{
|
||||
foreach (var timeChild in TimeChildren())
|
||||
{
|
||||
yield return timeChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Nesting")
|
||||
{
|
||||
foreach (var nestingChild in NestingChildren())
|
||||
{
|
||||
yield return nestingChild;
|
||||
}
|
||||
}
|
||||
else if (category.fullName == "Graphs")
|
||||
{
|
||||
foreach (var macroChild in MacroChildren())
|
||||
{
|
||||
yield return macroChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> EventsChildren(UnitCategory category)
|
||||
{
|
||||
foreach (var unit in options.Where(option => option.UnitIs<IEventUnit>() && option.category == category)
|
||||
.OrderBy(option => option.order)
|
||||
.ThenBy(option => option.label))
|
||||
{
|
||||
yield return unit;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Search
|
||||
|
||||
public override bool searchable { get; } = true;
|
||||
|
||||
public override IEnumerable<ISearchResult> SearchResults(string query, CancellationToken cancellation)
|
||||
{
|
||||
foreach (var typeResult in typesWithMembers.Cancellable(cancellation).OrderableSearchFilter(query, t => t.DisplayName()))
|
||||
{
|
||||
yield return typeResult;
|
||||
}
|
||||
|
||||
foreach (var optionResult in options.Cancellable(cancellation)
|
||||
.OrderableSearchFilter(query, o => o.haystack, o => o.formerHaystack)
|
||||
.WithoutInheritedDuplicates(r => r.result, cancellation))
|
||||
{
|
||||
yield return optionResult;
|
||||
}
|
||||
}
|
||||
|
||||
public override string SearchResultLabel(object item, string query)
|
||||
{
|
||||
if (item is Type type)
|
||||
{
|
||||
return TypeOption.SearchResultLabel(type, query);
|
||||
}
|
||||
else if (item is IUnitOption unitOption)
|
||||
{
|
||||
return unitOption.SearchResultLabel(query);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.SearchResultLabel(item, query);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Favorites
|
||||
|
||||
public override ICollection<object> favorites { get; }
|
||||
|
||||
public override bool CanFavorite(object item)
|
||||
{
|
||||
return (item as IUnitOption)?.favoritable ?? false;
|
||||
}
|
||||
|
||||
public override string FavoritesLabel(object item)
|
||||
{
|
||||
return SearchResultLabel(item, null);
|
||||
}
|
||||
|
||||
public override void OnFavoritesChange()
|
||||
{
|
||||
BoltFlow.Configuration.Save();
|
||||
}
|
||||
|
||||
private class Favorites : ICollection<object>
|
||||
{
|
||||
public Favorites(UnitOptionTree tree)
|
||||
{
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
private UnitOptionTree tree { get; }
|
||||
|
||||
private IEnumerable<IUnitOption> options => tree.options.Where(option => BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey));
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public int Count => BoltFlow.Configuration.favoriteUnitOptions.Count;
|
||||
|
||||
public IEnumerator<object> GetEnumerator()
|
||||
{
|
||||
foreach (var option in options)
|
||||
{
|
||||
yield return option;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Contains(object item)
|
||||
{
|
||||
var option = (IUnitOption)item;
|
||||
|
||||
return BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey);
|
||||
}
|
||||
|
||||
public void Add(object item)
|
||||
{
|
||||
var option = (IUnitOption)item;
|
||||
|
||||
BoltFlow.Configuration.favoriteUnitOptions.Add(option.favoriteKey);
|
||||
}
|
||||
|
||||
public bool Remove(object item)
|
||||
{
|
||||
var option = (IUnitOption)item;
|
||||
|
||||
return BoltFlow.Configuration.favoriteUnitOptions.Remove(option.favoriteKey);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
BoltFlow.Configuration.favoriteUnitOptions.Clear();
|
||||
}
|
||||
|
||||
public void CopyTo(object[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var item in this)
|
||||
{
|
||||
array[i + arrayIndex] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0aff9d906147244d6b9be227b986021a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,7 @@
|
|||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class UnitOptionUtility
|
||||
{
|
||||
public const string GenerateUnitDatabasePath = "Edit > Project Settings > Visual Scripting > Node Library > Regenerate Units";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e22fa013a1894b7f94abb820d303fb35
|
||||
timeCreated: 1605214947
|
Loading…
Add table
Add a link
Reference in a new issue