Files
Test/Library/PackageCache/com.unity.graph-authoring@cfd2588851ee/Editor/ShaderImplementation/Provider/ExpressionProvider.cs
T
2026-11-01 23:39:55 +02:00

144 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
using Unity.GraphAuthoring.Editor.ProviderSystem.Hints;
namespace Unity.GraphAuthoring.Editor.ProviderSystem
{
[Serializable]
[ScriptedProvider]
[MovedFrom(false, "UnityEditor.ShaderGraph.ProviderSystem", "Unity.ShaderGraph.Editor")]
internal class ExpressionProvider : IProvider<IShaderFunction>
{
// Expression provider is domain agnostic, but we need some sort of keyword filtering that is domain specific.
internal interface IKeywordFilter
{
bool IsReserved(string name);
}
internal const string kExpressionProviderKey = "unity:shadergraph:scripted:Expression";
public string ProviderKey => kExpressionProviderKey;
public GUID AssetID => default;
private static readonly string[] kNamespace = { "unity_sg_expression" };
internal string Expression => m_expression;
internal string ShaderType => m_type;
public IShaderFunction Definition
{
get
{
if (m_definition == null || !m_definition.IsValid)
Reload();
return m_definition;
}
}
[SerializeField]
string m_name;
[SerializeField]
string m_expression;
[SerializeField]
string m_type;
[SerializeReference]
IKeywordFilter m_keywordFilter;
[NonSerialized]
IShaderFunction m_definition;
internal ExpressionProvider() : this("Expression", "A", "float") { }
internal ExpressionProvider(string name, string expression, string type, IKeywordFilter filter = null)
{
UpdateExpression(name, expression, type, filter);
}
public void Reload()
{
m_definition = ExpressionToShaderFunction(m_name, m_expression, m_type, out _, m_keywordFilter);
}
static IReadOnlyCollection<IStrongHint> BuildFuncHints() =>
new IStrongHint[]
{
new DisplayName("Expression", true),
new ProviderKey(kExpressionProviderKey),
new SearchName("Expression"),
new SearchCategory("Utility"),
new SearchTerms(new[] { "equation", "calculation", "inline", "code" }),
new Precision(true),
};
static IReadOnlyCollection<IStrongHint> BuildParamHints(string displayName) =>
new IStrongHint[]
{
new DisplayName(displayName, true)
};
internal void UpdateExpression(string name, string expression, string type, IKeywordFilter filter = null)
{
m_name = name;
m_type = type;
m_expression = expression;
m_keywordFilter = filter;
Reload();
}
public IProvider Clone()
=> new ExpressionProvider(m_name, m_expression, m_type, m_keywordFilter);
internal static IShaderFunction ExpressionToShaderFunction(string name, string expression, string type, out string finalExpression, IKeywordFilter keywordFilter = null)
{
// don't create fields from comments.
const string kCommentRegex = @"(\/\*.*?\*\/)|(\/\/.*)";
// pattern for getting valid identifiers that aren't functions or members,
// includes expanded european character set.
const string kIdentifierRegex = @"(?<!\.)\b[a-zA-ZŽžÀ-ÿ_][a-zA-ZŽžÀ-ÿ0-9_]*\b(?!\s*[\(])";
List<string> orderedNames = new();
HashSet<string> usedNames = new();
// gather the list of identifiers, deduplicate them, and ignore if they are reserved keywords.
string HandleName(string name)
{
if (!usedNames.Contains(name) && keywordFilter?.IsReserved(name) != true)
{
usedNames.Add(name);
orderedNames.Add(name);
}
return name;
}
// clean out comments.
expression = Regex.Replace(expression, kCommentRegex, "");
// process identifiers.
expression = Regex.Replace(expression, kIdentifierRegex, e => HandleName(e.Value));
finalExpression = expression;
// default initialize if there is no expression left.
if (string.IsNullOrWhiteSpace(expression))
expression = $"({type})0";
IShaderType shaderType = new ShaderType(type);
List<IShaderField> parameters = new();
foreach (var paramName in orderedNames)
parameters.Add(new ShaderField(paramName, true, false, shaderType, BuildParamHints(paramName)));
// trust that the name and type is valid.
return new ShaderFunction(name, kNamespace, parameters, shaderType, $"return {expression};", BuildFuncHints());
}
}
}