Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd6fd6310ecb2405fae7d988941661de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a03c32ec404b64125b88769f42e5b8a5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bc17db44a4104432a5abc99035fe920
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37d8f9a231f5545c8aa227756649439d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f22a86a3605947029c82ebdf322c9f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2b9227c78ca546fcb92bb352fcab92d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65a6a75d9580f437b84420c6b5e36cbd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0aff9d906147244d6b9be227b986021a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public static class UnitOptionUtility
{
public const string GenerateUnitDatabasePath = "Edit > Project Settings > Visual Scripting > Node Library > Regenerate Units";
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e22fa013a1894b7f94abb820d303fb35
timeCreated: 1605214947