Skip to content
This repository has been archived by the owner on Jan 22, 2022. It is now read-only.

Commit

Permalink
Merge pull request #87 from MerlinVR/variable-callbacks
Browse files Browse the repository at this point in the history
Variable callbacks
  • Loading branch information
MerlinVR authored Jul 9, 2021
2 parents 14e56ce + 3a50ff7 commit 9418913
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 48 deletions.
49 changes: 14 additions & 35 deletions Assets/UdonSharp/Editor/UdonSharpASTVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ASTVisitorContext
public List<MethodDefinition> definedMethods;

public List<PropertyDefinition> definedProperties;
public Dictionary<string, FieldDefinition> onModifyCallbackFields = new Dictionary<string, FieldDefinition>();

// Tracking labels for the current function and flow control
public JumpLabel returnLabel = null;
Expand Down Expand Up @@ -531,6 +532,19 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
var setter = definition.setter;

// Handle VRC field modification callbacks
if (visitorContext.onModifyCallbackFields.TryGetValue(definition.originalPropertyName, out FieldDefinition targetField))
{
string exportStr = VRC.Udon.Common.VariableChangedEvent.EVENT_PREFIX + targetField.fieldSymbol.symbolUniqueName;
visitorContext.uasmBuilder.AppendLine($".export {exportStr}", 1);
visitorContext.uasmBuilder.AppendLine($"{exportStr}:", 1);

SymbolDefinition oldPropertyVal = visitorContext.topTable.GetGlobalSymbolTable().CreateNamedSymbol($"{VRC.Udon.Common.VariableChangedEvent.OLD_VALUE_PREFIX}{targetField.fieldSymbol.symbolUniqueName}", targetField.fieldSymbol.userCsType, SymbolDeclTypeFlags.Private);

visitorContext.uasmBuilder.AddCopy(setter.paramSymbol, targetField.fieldSymbol);
visitorContext.uasmBuilder.AddCopy(targetField.fieldSymbol, oldPropertyVal);
}

if ((node.Modifiers.HasModifier("public") && setter.declarationFlags == PropertyDeclFlags.None) || setter.declarationFlags == PropertyDeclFlags.Public)
{
visitorContext.uasmBuilder.AppendLine($".export {setter.accessorName}", 1);
Expand Down Expand Up @@ -2473,41 +2487,6 @@ public override void VisitSwitchStatement(SwitchStatementSyntax node)
visitorContext.breakLabelStack.Pop();
}

private void HandleNameOfExpression(InvocationExpressionSyntax node)
{
SyntaxNode currentNode = node.ArgumentList.Arguments[0].Expression;
string currentName = "";

while (currentNode != null)
{
switch (currentNode.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
MemberAccessExpressionSyntax memberNode = (MemberAccessExpressionSyntax)currentNode;
currentName = memberNode.Name.ToString();
currentNode = memberNode.Name;
break;
case SyntaxKind.IdentifierName:
IdentifierNameSyntax identifierName = (IdentifierNameSyntax)currentNode;
currentName = identifierName.ToString();
currentNode = null;
break;
default:
currentNode = null;
break;
}

if (currentNode != null)
UpdateSyntaxNode(currentNode);
}

if (currentName == "")
throw new System.ArgumentException("Expression does not have a name");

if (visitorContext.topCaptureScope != null)
visitorContext.topCaptureScope.SetToLocalSymbol(visitorContext.topTable.CreateConstSymbol(typeof(string), currentName));
}

public override void VisitCaseSwitchLabel(CaseSwitchLabelSyntax node)
{
UpdateSyntaxNode(node);
Expand Down
22 changes: 12 additions & 10 deletions Assets/UdonSharp/Editor/UdonSharpCompilationModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ public CompileTaskResult Compile(List<ClassDefinition> classDefinitions, Microso
debugInfo = new ClassDebugInfo(sourceCode, settings == null || settings.includeInlineCode);
}

UdonSharpFieldVisitor fieldVisitor = new UdonSharpFieldVisitor(fieldsWithInitializers, resolver, moduleSymbols, moduleLabels, classDefinitions, debugInfo);
PropertyVisitor propertyVisitor = new PropertyVisitor(resolver, moduleSymbols, moduleLabels);

try
{
fieldVisitor.Visit(syntaxTree.GetRoot());
propertyVisitor.Visit(syntaxTree.GetRoot());
}
catch (System.Exception e)
{
LogException(result, e, fieldVisitor.visitorContext.currentNode, out string logMessage);
LogException(result, e, propertyVisitor.visitorContext.currentNode, out string logMessage);

programAsset.compileErrors.Add(logMessage);

Expand All @@ -104,15 +104,16 @@ public CompileTaskResult Compile(List<ClassDefinition> classDefinitions, Microso
if (ErrorCount > 0)
return result;

MethodVisitor methodVisitor = new MethodVisitor(resolver, moduleSymbols, moduleLabels);
UdonSharpFieldVisitor fieldVisitor = new UdonSharpFieldVisitor(fieldsWithInitializers, resolver, moduleSymbols, moduleLabels, classDefinitions, debugInfo);
fieldVisitor.visitorContext.definedProperties = propertyVisitor.definedProperties;

try
{
methodVisitor.Visit(syntaxTree.GetRoot());
fieldVisitor.Visit(syntaxTree.GetRoot());
}
catch (System.Exception e)
{
LogException(result, e, methodVisitor.visitorContext.currentNode, out string logMessage);
LogException(result, e, fieldVisitor.visitorContext.currentNode, out string logMessage);

programAsset.compileErrors.Add(logMessage);

Expand All @@ -122,15 +123,15 @@ public CompileTaskResult Compile(List<ClassDefinition> classDefinitions, Microso
if (ErrorCount > 0)
return result;

PropertyVisitor propertyVisitor = new PropertyVisitor(resolver, moduleSymbols, moduleLabels);
MethodVisitor methodVisitor = new MethodVisitor(resolver, moduleSymbols, moduleLabels);

try
{
propertyVisitor.Visit(syntaxTree.GetRoot());
methodVisitor.Visit(syntaxTree.GetRoot());
}
catch (System.Exception e)
{
LogException(result, e, propertyVisitor.visitorContext.currentNode, out string logMessage);
LogException(result, e, methodVisitor.visitorContext.currentNode, out string logMessage);

programAsset.compileErrors.Add(logMessage);

Expand All @@ -141,6 +142,7 @@ public CompileTaskResult Compile(List<ClassDefinition> classDefinitions, Microso
return result;

ASTVisitor visitor = new ASTVisitor(resolver, moduleSymbols, moduleLabels, methodVisitor.definedMethods, propertyVisitor.definedProperties, classDefinitions, debugInfo);
visitor.visitorContext.onModifyCallbackFields = fieldVisitor.visitorContext.onModifyCallbackFields;

try
{
Expand Down
3 changes: 3 additions & 0 deletions Assets/UdonSharp/Editor/UdonSharpExpressionCapture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,9 @@ public void ExecuteSet(SymbolDefinition value, bool explicitCast = false)
}
else if (captureArchetype == ExpressionCaptureArchetype.ExternUserField)
{
if (visitorContext.onModifyCallbackFields.Values.Any(e => e.fieldSymbol.symbolUniqueName == captureExternUserField.fieldSymbol.symbolUniqueName))
throw new System.InvalidOperationException($"Cannot set field with {nameof(FieldChangeCallbackAttribute)}, use a property or SetProgramVariable");

using (ExpressionCaptureScope setVariableMethodScope = new ExpressionCaptureScope(visitorContext, null))
{
setVariableMethodScope.SetToLocalSymbol(accessSymbol);
Expand Down
26 changes: 26 additions & 0 deletions Assets/UdonSharp/Editor/UdonSharpFieldVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace UdonSharp.Compiler
Expand Down Expand Up @@ -49,11 +50,14 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)

List<System.Attribute> fieldAttributes = GetFieldAttributes(node);


bool isPublic = (node.Modifiers.Any(SyntaxKind.PublicKeyword) || fieldAttributes.Find(e => e is SerializeField) != null) && fieldAttributes.Find(e => e is System.NonSerializedAttribute) == null;
bool isConst = (node.Modifiers.Any(SyntaxKind.ConstKeyword) || node.Modifiers.Any(SyntaxKind.ReadOnlyKeyword));
SymbolDeclTypeFlags flags = (isPublic ? SymbolDeclTypeFlags.Public : SymbolDeclTypeFlags.Private) |
(isConst ? SymbolDeclTypeFlags.Readonly : 0);

FieldChangeCallbackAttribute varChange = fieldAttributes.OfType<FieldChangeCallbackAttribute>().FirstOrDefault();

List<SymbolDefinition> fieldSymbols = HandleVariableDeclaration(node.Declaration, flags, fieldSyncMode);
foreach (SymbolDefinition fieldSymbol in fieldSymbols)
{
Expand All @@ -77,6 +81,28 @@ public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
}

visitorContext.localFieldDefinitions.Add(fieldSymbol.symbolUniqueName, fieldDefinition);

if (varChange != null)
{
string targetProperty = varChange.CallbackPropertyName;

if (variables.Count > 1 || visitorContext.onModifyCallbackFields.ContainsKey(targetProperty))
throw new System.Exception($"Only one field may target property '{targetProperty}'");

PropertyDefinition foundProperty = visitorContext.definedProperties.FirstOrDefault(e => e.originalPropertyName == targetProperty);

if (foundProperty == null)
throw new System.ArgumentException($"Invalid target property for {nameof(FieldChangeCallbackAttribute)} on {node.Declaration}");

PropertyDefinition property = visitorContext.definedProperties.FirstOrDefault(e => e.originalPropertyName == targetProperty);
if (property == null)
throw new System.ArgumentException($"Property not found for '{targetProperty}'");

if (property.type != fieldDefinition.fieldSymbol.userCsType)
throw new System.Exception($"Types must match between property and variable change field");

visitorContext.onModifyCallbackFields.Add(targetProperty, fieldDefinition);
}
}
}

Expand Down
46 changes: 46 additions & 0 deletions Assets/UdonSharp/Editor/UdonSharpSyntaxWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,52 @@ public override void VisitUsingDirective(UsingDirectiveSyntax node)
}
}

protected void HandleNameOfExpression(InvocationExpressionSyntax node)
{
SyntaxNode currentNode = node.ArgumentList.Arguments[0].Expression;
string currentName = "";

while (currentNode != null)
{
switch (currentNode.Kind())
{
case SyntaxKind.SimpleMemberAccessExpression:
MemberAccessExpressionSyntax memberNode = (MemberAccessExpressionSyntax)currentNode;
currentName = memberNode.Name.ToString();
currentNode = memberNode.Name;
break;
case SyntaxKind.IdentifierName:
IdentifierNameSyntax identifierName = (IdentifierNameSyntax)currentNode;
currentName = identifierName.ToString();
currentNode = null;
break;
default:
currentNode = null;
break;
}

if (currentNode != null)
UpdateSyntaxNode(currentNode);
}

if (currentName == "")
throw new System.ArgumentException("Expression does not have a name");

if (visitorContext.topCaptureScope != null)
visitorContext.topCaptureScope.SetToLocalSymbol(visitorContext.topTable.CreateConstSymbol(typeof(string), currentName));
}

public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
UpdateSyntaxNode(node);

if (node.Expression != null && node.Expression.ToString() == "nameof") // nameof is not a dedicated node and the Kind of the node isn't the nameof kind for whatever reason...
{
HandleNameOfExpression(node);
return;
}
}

public override void VisitIdentifierName(IdentifierNameSyntax node)
{
UpdateSyntaxNode(node);
Expand Down
18 changes: 18 additions & 0 deletions Assets/UdonSharp/Scripts/UdonSharpAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,23 @@ public class RecursiveMethodAttribute : Attribute
public RecursiveMethodAttribute()
{ }
}

/// <summary>
/// Calls the target property's setter when the marked field is modified by network sync or SetProgramVariable().
/// Fields marked with this will instead have the target property's setter called. The setter is expected to set the field if you want the field to change.
/// </summary>
[PublicAPI]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class FieldChangeCallbackAttribute : Attribute
{
public string CallbackPropertyName { get; private set; }

private FieldChangeCallbackAttribute() { }

public FieldChangeCallbackAttribute(string targetPropertyName)
{
CallbackPropertyName = targetPropertyName;
}
}
}

21 changes: 20 additions & 1 deletion Assets/UdonSharp/Scripts/UdonSharpBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,26 @@ public void SetProgramVariable(string name, object value)

if (variableField != null)
{
variableField.SetValue(this, value);
FieldChangeCallbackAttribute fieldChangeCallback = variableField.GetCustomAttribute<FieldChangeCallbackAttribute>();

if (fieldChangeCallback != null)
{
PropertyInfo targetProperty = variableField.DeclaringType.GetProperty(fieldChangeCallback.CallbackPropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

if (targetProperty == null)
return;

MethodInfo setMethod = targetProperty.GetSetMethod(true);

if (setMethod == null)
return;

setMethod.Invoke(this, new object[] { value });
}
else
{
variableField.SetValue(this, value);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Assets/UdonSharp/Tests/IntegrationTestScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -8356,7 +8356,7 @@ MonoBehaviour:
serializedProgramAsset: {fileID: 11400000, guid: c341eeb82008276418f85fd69d4dd8b3,
type: 2}
programSource: {fileID: 11400000, guid: e6b13e0cbf910824dbf27af03fe07b7f, type: 2}
serializedPublicVariablesBytesString: Ai8AAAAAATIAAABWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgAuAFUAZABvAG4AVgBhAHIAaQBhAGIAbABlAFQAYQBiAGwAZQAsACAAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4AAAAAAAYBAAAAAAAAACcBBAAAAHQAeQBwAGUAAWgAAABTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBMAGkAcwB0AGAAMQBbAFsAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4ALgBJAG4AdABlAHIAZgBhAGMAZQBzAC4ASQBVAGQAbwBuAFYAYQByAGkAYQBiAGwAZQAsACAAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4AXQBdACwAIABtAHMAYwBvAHIAbABpAGIAAQEJAAAAVgBhAHIAaQBhAGIAbABlAHMALwEAAAABaAAAAFMAeQBzAHQAZQBtAC4AQwBvAGwAbABlAGMAdABpAG8AbgBzAC4ARwBlAG4AZQByAGkAYwAuAEwAaQBzAHQAYAAxAFsAWwBWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgAuAEkAbgB0AGUAcgBmAGEAYwBlAHMALgBJAFUAZABvAG4AVgBhAHIAaQBhAGIAbABlACwAIABWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgBdAF0ALAAgAG0AcwBjAG8AcgBsAGkAYgABAAAABgAAAAAAAAAABwUHBQ==
serializedPublicVariablesBytesString: Ai8AAAAAATIAAABWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgAuAFUAZABvAG4AVgBhAHIAaQBhAGIAbABlAFQAYQBiAGwAZQAsACAAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4AAAAAAAYBAAAAAAAAACcBBAAAAHQAeQBwAGUAAWgAAABTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBMAGkAcwB0AGAAMQBbAFsAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4ALgBJAG4AdABlAHIAZgBhAGMAZQBzAC4ASQBVAGQAbwBuAFYAYQByAGkAYQBiAGwAZQAsACAAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4AXQBdACwAIABtAHMAYwBvAHIAbABpAGIAAQEJAAAAVgBhAHIAaQBhAGIAbABlAHMALwEAAAABaAAAAFMAeQBzAHQAZQBtAC4AQwBvAGwAbABlAGMAdABpAG8AbgBzAC4ARwBlAG4AZQByAGkAYwAuAEwAaQBzAHQAYAAxAFsAWwBWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgAuAEkAbgB0AGUAcgBmAGEAYwBlAHMALgBJAFUAZABvAG4AVgBhAHIAaQBhAGIAbABlACwAIABWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgBdAF0ALAAgAG0AcwBjAG8AcgBsAGkAYgABAAAABgEAAAAAAAAAAi8CAAAAAUoAAABWAFIAQwAuAFUAZABvAG4ALgBDAG8AbQBtAG8AbgAuAFUAZABvAG4AVgBhAHIAaQBhAGIAbABlAGAAMQBbAFsAUwB5AHMAdABlAG0ALgBTAGkAbgBnAGwAZQAsACAAbQBzAGMAbwByAGwAaQBiAF0AXQAsACAAVgBSAEMALgBVAGQAbwBuAC4AQwBvAG0AbQBvAG4AAgAAAAYCAAAAAAAAACcBBAAAAHQAeQBwAGUAARcAAABTAHkAcwB0AGUAbQAuAFMAdAByAGkAbgBnACwAIABtAHMAYwBvAHIAbABpAGIAJwEKAAAAUwB5AG0AYgBvAGwATgBhAG0AZQABFwAAAGIAYQBjAGsAaQBuAGcAQwBhAGwAbABiAGEAYwBrAFAAcgBvAHAAZQByAHQAeQAnAQQAAAB0AHkAcABlAAEXAAAAUwB5AHMAdABlAG0ALgBTAGkAbgBnAGwAZQAsACAAbQBzAGMAbwByAGwAaQBiAB8BBQAAAFYAYQBsAHUAZQAAAAAABwUHBQcF
publicVariablesUnityEngineObjects: []
publicVariablesSerializationDataFormat: 0
--- !u!4 &1954857073
Expand Down
65 changes: 64 additions & 1 deletion Assets/UdonSharp/Tests/TestScripts/Core/PropertyTest.asset
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ MonoBehaviour:
Data:
- Name:
Entry: 12
Data: 4
Data: 5
- Name:
Entry: 7
Data:
Expand Down Expand Up @@ -297,6 +297,69 @@ MonoBehaviour:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: backingCallbackfield
- Name: $v
Entry: 7
Data: 18|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: fieldSymbol
Entry: 7
Data: 19|UdonSharp.Compiler.SymbolDefinition, UdonSharp.Editor
- Name: internalType
Entry: 9
Data: 9
- Name: declarationType
Entry: 3
Data: 1
- Name: syncMode
Entry: 3
Data: 0
- Name: symbolResolvedTypeName
Entry: 1
Data: SystemSingle
- Name: symbolOriginalName
Entry: 1
Data: backingCallbackfield
- Name: symbolUniqueName
Entry: 1
Data: backingCallbackfield
- Name: symbolDefaultValue
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: fieldAttributes
Entry: 7
Data: 20|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 1
- Name:
Entry: 7
Data: 21|UdonSharp.FieldChangeCallbackAttribute, UdonSharp.Runtime
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name: userBehaviourSource
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
Expand Down
Loading

0 comments on commit 9418913

Please sign in to comment.