Skip to content

Commit

Permalink
First pass at recursion support
Browse files Browse the repository at this point in the history
- Adds RecursiveMethod attribute and handling for generating and managing an `object[]` stack which stores the values of locals in a method along with intermediate values that are important to a function call.
MerlinVR committed Dec 25, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 0cb722a commit aa79a63
Showing 11 changed files with 1,670 additions and 40 deletions.
52 changes: 50 additions & 2 deletions Assets/UdonSharp/Editor/UdonSharpASTVisitor.cs
Original file line number Diff line number Diff line change
@@ -29,6 +29,10 @@ public class ASTVisitorContext
public JumpLabel returnLabel = null;
public SymbolDefinition returnJumpTarget = null;
public SymbolDefinition returnSymbol = null;
public bool isRecursiveMethod = false;
public int maxMethodFrameSize = 0; // The maximum size for a "stack frame" for a method. This is used to initialize the correct default size of the artificial stack so that we know we only need to double the size of it at most.
public SymbolDefinition artificalStackSymbol = null;
public SymbolDefinition stackAddressSymbol = null;
#if UDON_BETA_SDK
public bool requiresVRCReturn = false;
#endif
@@ -288,6 +292,23 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node)

Visit(node.BaseList);

bool hasRecursiveMethods = false;
foreach (MethodDefinition definition in visitorContext.definedMethods)
{
if (definition.declarationFlags.HasFlag(MethodDeclFlags.RecursiveMethod))
{
hasRecursiveMethods = true;
break;
}
}

if (hasRecursiveMethods)
{
visitorContext.artificalStackSymbol = visitorContext.topTable.CreateNamedSymbol("usharpValueStack", typeof(object[]), SymbolDeclTypeFlags.Internal);
visitorContext.stackAddressSymbol = visitorContext.topTable.CreateNamedSymbol("usharpStackAddress", typeof(int), SymbolDeclTypeFlags.Internal);
visitorContext.stackAddressSymbol.symbolDefaultValue = (int)0;
}

visitorContext.topTable.CreateReflectionSymbol("udonTypeID", typeof(long), Internal.UdonSharpInternalUtility.GetTypeID(visitorContext.behaviourUserType));
visitorContext.topTable.CreateReflectionSymbol("udonTypeName", typeof(string), Internal.UdonSharpInternalUtility.GetTypeName(visitorContext.behaviourUserType));

@@ -299,6 +320,9 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node)
}

visitorContext.uasmBuilder.AppendLine(".code_end", 0);

if (hasRecursiveMethods)
visitorContext.artificalStackSymbol.symbolDefaultValue = new object[visitorContext.maxMethodFrameSize];
}

public override void VisitBlock(BlockSyntax node)
@@ -607,7 +631,11 @@ public override void VisitVariableDeclaration(VariableDeclarationSyntax node)
{
UpdateSyntaxNode(node);

visitorContext.topTable.EnterExpressionScope();

HandleVariableDeclaration(node, SymbolDeclTypeFlags.Local, UdonSyncMode.NotSynced);

visitorContext.topTable.ExitExpressionScope();
}

public override void VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)
@@ -656,6 +684,8 @@ public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
UpdateSyntaxNode(node);

visitorContext.topTable.EnterExpressionScope();

bool isSimpleAssignment = node.OperatorToken.Kind() == SyntaxKind.SimpleAssignmentExpression || node.OperatorToken.Kind() == SyntaxKind.EqualsToken;
ExpressionCaptureScope topScope = visitorContext.topCaptureScope;

@@ -759,6 +789,8 @@ public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
}
}
}

visitorContext.topTable.ExitExpressionScope();
}

public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
@@ -999,6 +1031,8 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node)

MethodDefinition definition = visitorContext.definedMethods.Where(e => e.originalMethodName == node.Identifier.ValueText).First();

visitorContext.isRecursiveMethod = definition.declarationFlags.HasFlag(MethodDeclFlags.RecursiveMethod);

string functionName = node.Identifier.ValueText;
bool isBuiltinEvent = visitorContext.resolverContext.ReplaceInternalEventName(ref functionName);

@@ -1135,6 +1169,7 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
visitorContext.uasmBuilder.AppendLine("");

visitorContext.returnLabel = null;
visitorContext.isRecursiveMethod = false;
}

public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
@@ -1612,6 +1647,8 @@ public override void VisitReturnStatement(ReturnStatementSyntax node)
{
UpdateSyntaxNode(node);

visitorContext.topTable.EnterExpressionScope();

if (visitorContext.returnSymbol != null)
{
using (ExpressionCaptureScope returnCaptureScope = new ExpressionCaptureScope(visitorContext, null, visitorContext.returnSymbol))
@@ -1642,6 +1679,8 @@ public override void VisitReturnStatement(ReturnStatementSyntax node)

visitorContext.uasmBuilder.AddReturnSequence(visitorContext.returnJumpTarget, "Explicit return sequence");
//visitorContext.uasmBuilder.AddJumpToExit();

visitorContext.topTable.ExitExpressionScope();
}

public override void VisitBreakStatement(BreakStatementSyntax node)
@@ -1861,7 +1900,7 @@ public override void VisitForEachStatement(ForEachStatementSyntax node)

SymbolDefinition valueSymbol = null;

SymbolDefinition indexSymbol = visitorContext.topTable.CreateUnnamedSymbol(typeof(int), SymbolDeclTypeFlags.Internal | SymbolDeclTypeFlags.Local);
SymbolDefinition indexSymbol = visitorContext.topTable.CreateUnnamedSymbol(typeof(int), SymbolDeclTypeFlags.Internal | SymbolDeclTypeFlags.Local | SymbolDeclTypeFlags.NeedsRecursivePush);

SymbolDefinition arraySymbol = null;

@@ -1917,6 +1956,7 @@ public override void VisitForEachStatement(ForEachStatementSyntax node)
lengthGetterScope.ResolveAccessToken("childCount");

arrayLengthSymbol = lengthGetterScope.ExecuteGet();
arrayLengthSymbol.declarationType |= SymbolDeclTypeFlags.NeedsRecursivePush;
}

JumpLabel loopExitLabel = visitorContext.labelTable.GetNewJumpLabel("foreachLoopExit");
@@ -2235,6 +2275,8 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
return;
}

visitorContext.topTable.EnterExpressionScope();

SymbolDefinition requestedDestination = visitorContext.requestedDestination;

// Grab the external scope so that the method call can propagate its output upwards
@@ -2259,7 +2301,7 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
for (int i = 0; i < node.ArgumentList.Arguments.Count; i++)
{
ArgumentSyntax argument = node.ArgumentList.Arguments[i];
SymbolDefinition argDestination = argDestinations != null ? argDestinations[i] : null;
SymbolDefinition argDestination = argDestinations != null && !visitorContext.isRecursiveMethod ? argDestinations[i] : null;

using (ExpressionCaptureScope captureScope = new ExpressionCaptureScope(visitorContext, null, argDestination))
{
@@ -2283,13 +2325,17 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)

invocationArgs.ForEach((arg) => arg.Dispose());
}

visitorContext.topTable.ExitExpressionScope();
}

// Constructors
public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
{
UpdateSyntaxNode(node);

visitorContext.topTable.EnterExpressionScope();

SymbolDefinition requestedDestination = visitorContext.requestedDestination;

System.Type newType = null;
@@ -2345,6 +2391,8 @@ public override void VisitObjectCreationExpression(ObjectCreationExpressionSynta
val.Dispose();
}
}

visitorContext.topTable.ExitExpressionScope();
}

public override void VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node)
339 changes: 327 additions & 12 deletions Assets/UdonSharp/Editor/UdonSharpExpressionCapture.cs

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions Assets/UdonSharp/Editor/UdonSharpMethod.cs
Original file line number Diff line number Diff line change
@@ -7,10 +7,7 @@ public enum MethodDeclFlags
None = 0,
Public = 1,
Private = 2,
AllowRecursion = 4, // Not implemented yet
// Not implemented yet, will be an attribute which enforces that the method has 0 arguments, returns void, and has no other methods that share the same name.
// In exchange the name won't be mangled so it can be reliably called from Udon graphs and other non-UdonSharp based programs
GraphEventExport = 8,
RecursiveMethod = 4,
}

public class ParameterDefinition
26 changes: 26 additions & 0 deletions Assets/UdonSharp/Editor/UdonSharpMethodVisitor.cs
Original file line number Diff line number Diff line change
@@ -26,13 +26,39 @@ public MethodVisitor(ResolverContext resolver, SymbolTable rootTable, LabelTable
"GetUdonTypeName",
};

bool HasRecursiveMethodAttribute(MethodDeclarationSyntax node)
{
if (node.AttributeLists != null)
{
foreach (AttributeListSyntax attributeList in node.AttributeLists)
{
foreach (AttributeSyntax attribute in attributeList.Attributes)
{
using (ExpressionCaptureScope attributeTypeCapture = new ExpressionCaptureScope(visitorContext, null))
{
attributeTypeCapture.isAttributeCaptureScope = true;
Visit(attribute.Name);

if (attributeTypeCapture.captureType == typeof(RecursiveMethodAttribute))
return true;
}
}
}
}

return false;
}

public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
UpdateSyntaxNode(node);

MethodDefinition methodDefinition = new MethodDefinition();

methodDefinition.declarationFlags = node.Modifiers.HasModifier("public") ? MethodDeclFlags.Public : MethodDeclFlags.Private;
if (HasRecursiveMethodAttribute(node))
methodDefinition.declarationFlags |= MethodDeclFlags.RecursiveMethod;

methodDefinition.methodUdonEntryPoint = visitorContext.labelTable.GetNewJumpLabel("udonMethodEntryPoint");
methodDefinition.methodUserCallStart = visitorContext.labelTable.GetNewJumpLabel("userMethodCallEntry");
methodDefinition.methodReturnPoint = visitorContext.labelTable.GetNewJumpLabel("methodReturnPoint");
81 changes: 81 additions & 0 deletions Assets/UdonSharp/Editor/UdonSharpSymbolTable.cs
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ public enum SymbolDeclTypeFlags
Reflection = 128, // Metadata information for type checking and other editor time info
Readonly = 256, // Symbols marked as either const or readonly by the user, treat them the same for now.
MethodParameter = 512, // Symbols used for passing around method parameters
NeedsRecursivePush = 1024, // Internal symbols used for tracking flow control and such which need to be pushed to the recursive stack when a method is recursive. An example of this is the int counter for a foreach loop and the size of the array the foreach is iterating.
}

[Serializable]
@@ -164,6 +165,8 @@ public COWValueInternal(ASTVisitorContext visitorContext, SymbolDefinition symbo
this.visitorContext = visitorContext;

tableCreationScope = visitorContext.topTable;

tableCreationScope.AddSymbolCOW(this);
}

public void AddRef(COWValue holder)
@@ -304,6 +307,10 @@ public class SymbolTable

private List<(SymbolTable, Dictionary<string, int>)> initialSymbolCounters = new List<(SymbolTable, Dictionary<string, int>)>();

private List<SymbolDefinition.COWValueInternal> scopeCOWValues = new List<SymbolDefinition.COWValueInternal>();

int expressionScopeDepth = 0;

public SymbolTable GetGlobalSymbolTable()
{
SymbolTable currentTable = this;
@@ -350,6 +357,35 @@ public void CloseSymbolTable()
IsTableReadOnly = true;

ValidateParentTableCounters();

Debug.Assert(expressionScopeDepth == 0, "Symbol table scope depth must be 0");
Debug.Assert(scopeCOWValues.Count == 0, "Symbol table COW values must be empty");
}

public void EnterExpressionScope()
{
++expressionScopeDepth;
}

public void ExitExpressionScope()
{
--expressionScopeDepth;

if (expressionScopeDepth == 0)
scopeCOWValues.Clear();

Debug.Assert(expressionScopeDepth >= 0, "Expression scope cannot be negative");
}

public IEnumerable<SymbolDefinition> GetOpenCOWSymbols()
{
return scopeCOWValues.Where(e => e.symbol != null).Select(e => e.symbol);
}

internal void AddSymbolCOW(SymbolDefinition.COWValueInternal value)
{
if (expressionScopeDepth > 0)
scopeCOWValues.Add(value);
}

protected int IncrementUniqueNameCounter(string symbolName)
@@ -478,6 +514,51 @@ public List<SymbolDefinition> GetAllSymbols(bool includeInternal = false)
return foundSymbols;
}

public List<SymbolDefinition> GetAllLocalSymbols()
{
List<SymbolDefinition> foundSymbols = new List<SymbolDefinition>();

SymbolTable currentTable = this;

while (currentTable != null && !currentTable.IsGlobalSymbolTable)
{
foundSymbols.AddRange(currentTable.symbolDefinitions.Where(e => !e.declarationType.HasFlag(SymbolDeclTypeFlags.Internal) && e.declarationType.HasFlag(SymbolDeclTypeFlags.Local)));
currentTable = currentTable.parentSymbolTable;
}

return foundSymbols;
}

public List<SymbolDefinition> GetAllRecursiveSymbols()
{
List<SymbolDefinition> foundSymbols = new List<SymbolDefinition>();

SymbolTable currentTable = this;

while (currentTable != null && !currentTable.IsGlobalSymbolTable)
{
foundSymbols.AddRange(currentTable.symbolDefinitions.Where(e => (!e.declarationType.HasFlag(SymbolDeclTypeFlags.Internal) || e.declarationType.HasFlag(SymbolDeclTypeFlags.NeedsRecursivePush)) && e.declarationType.HasFlag(SymbolDeclTypeFlags.Local)));
currentTable = currentTable.parentSymbolTable;
}

return foundSymbols;
}

public List<SymbolDefinition> GetCurrentMethodParameters()
{
List<SymbolDefinition> foundSymbols = new List<SymbolDefinition>();

SymbolTable currentTable = this;

while (currentTable != null && !currentTable.IsGlobalSymbolTable)
{
foundSymbols.AddRange(currentTable.symbolDefinitions.Where(e => e.declarationType.HasFlag(SymbolDeclTypeFlags.MethodParameter)));
currentTable = currentTable.parentSymbolTable;
}

return foundSymbols;
}

/// <summary>
/// Tries to find a global constant that already has a given constant value to avoid duplication
/// </summary>
12 changes: 12 additions & 0 deletions Assets/UdonSharp/Scripts/UdonSharpAttributes.cs
Original file line number Diff line number Diff line change
@@ -42,5 +42,17 @@ public UdonBehaviourSyncModeAttribute(BehaviourSyncMode behaviourSyncMode)
}
}
#endif

/// <summary>
/// Marks a method that can be called recursively in U#.
/// This should be used on the methods that are being called recursively, you do not need to mark methods that are calling recursive methods with this.
/// This attribute has a performance overhead which makes the marked method perform slower and usually generate more garbage. So use it only on methods that **need** to be called recursively.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class RecursiveMethodAttribute : Attribute
{
public RecursiveMethodAttribute()
{ }
}
}

747 changes: 725 additions & 22 deletions Assets/UdonSharp/Tests/IntegrationTestScene.unity

Large diffs are not rendered by default.

184 changes: 184 additions & 0 deletions Assets/UdonSharp/Tests/TestScripts/FlowControl/RecursionTest.asset
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c333ccfdd0cbdbc4ca30cef2dd6e6b9b, type: 3}
m_Name: RecursionTest
m_EditorClassIdentifier:
serializedUdonProgramAsset: {fileID: 11400000, guid: 989e6fff91f0e554687b35b4ab4ad384,
type: 2}
udonAssembly:
assemblyError:
sourceCsScript: {fileID: 11500000, guid: 41aa655b07e37a84292568dd0dc1aac5, type: 3}
behaviourIDHeapVarName: __refl_const_intnl_udonTypeID
compileErrors: []
hasInteractEvent: 0
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects:
- {fileID: 11500000, guid: 11d8d463c5030e74bbaa9da5236e94e9, type: 3}
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: fieldDefinitions
Entry: 7
Data: 0|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[UdonSharp.Compiler.FieldDefinition,
UdonSharp.Editor]], mscorlib
- Name: comparer
Entry: 7
Data: 1|System.Collections.Generic.GenericEqualityComparer`1[[System.String,
mscorlib]], mscorlib
- Name:
Entry: 8
Data:
- Name:
Entry: 12
Data: 2
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: tester
- Name: $v
Entry: 7
Data: 2|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: fieldSymbol
Entry: 7
Data: 3|UdonSharp.Compiler.SymbolDefinition, UdonSharp.Editor
- Name: internalType
Entry: 7
Data: 4|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: UdonSharp.Tests.IntegrationTestSuite, Assembly-CSharp
- Name:
Entry: 8
Data:
- Name: declarationType
Entry: 3
Data: 2
- Name: syncMode
Entry: 3
Data: 0
- Name: symbolResolvedTypeName
Entry: 1
Data: VRCUdonUdonBehaviour
- Name: symbolOriginalName
Entry: 1
Data: tester
- Name: symbolUniqueName
Entry: 1
Data: tester
- Name: symbolDefaultValue
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: fieldAttributes
Entry: 7
Data: 5|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 1
- Name:
Entry: 7
Data: 6|System.NonSerializedAttribute, mscorlib
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name: userBehaviourSource
Entry: 10
Data: 0
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: externChildCount
- Name: $v
Entry: 7
Data: 7|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: fieldSymbol
Entry: 7
Data: 8|UdonSharp.Compiler.SymbolDefinition, UdonSharp.Editor
- Name: internalType
Entry: 7
Data: 9|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: System.Int32, mscorlib
- Name:
Entry: 8
Data:
- Name: declarationType
Entry: 3
Data: 2
- Name: syncMode
Entry: 3
Data: 0
- Name: symbolResolvedTypeName
Entry: 1
Data: SystemInt32
- Name: symbolOriginalName
Entry: 1
Data: externChildCount
- Name: symbolUniqueName
Entry: 1
Data: externChildCount
- Name: symbolDefaultValue
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: fieldAttributes
Entry: 7
Data: 10|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 0
- 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:
- Name:
Entry: 8
Data:
245 changes: 245 additions & 0 deletions Assets/UdonSharp/Tests/TestScripts/FlowControl/RecursionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

namespace UdonSharp.Tests
{
[AddComponentMenu("Udon Sharp/Tests/RecursionTest")]
public class RecursionTest : UdonSharpBehaviour
{
[System.NonSerialized]
public IntegrationTestSuite tester;

[RecursiveMethod]
int Factorial(int input)
{
if (input == 1)
return 1;

return input * Factorial(input - 1);
}

int Partition(int[] arr, int left, int right)
{
int pivot;
pivot = arr[left];
while (true)
{
while (arr[left] < pivot)
{
left++;
}
while (arr[right] > pivot)
{
right--;
}
if (left < right)
{
int temp = arr[right];
arr[right] = arr[left];
arr[left] = temp;
}
else
{
return right;
}
}
}

// Copy paste from https://www.tutorialspoint.com/chash-program-to-perform-quick-sort-using-recursion
[RecursiveMethod]
public void QuickSort(int[] arr, int left, int right)
{
int pivot;
if (left < right)
{
pivot = Partition(arr, left, right);

if (pivot > 1)
QuickSort(arr, left, pivot - 1);

if (pivot + 1 < right)
QuickSort(arr, pivot + 1, right);
}

arr = null; // Just throw a curveball with something that should be handled, but could break stuff if it isn't handled
}

// https://www.geeksforgeeks.org/iterative-quick-sort/
// Just a test for relative performance of using recursive vs iterative
//void QuickSortIterative(int[] arr, int l, int h)
//{
// int[] stack = new int[h - l + 1];

// int top = -1;

// stack[++top] = l;
// stack[++top] = h;

// while (top >= 0)
// {
// h = stack[top--];
// l = stack[top--];

// int p = Partition(arr, l, h);

// if (p - 1 > l)
// {
// stack[++top] = l;
// stack[++top] = p - 1;
// }

// if (p + 1 < h)
// {
// stack[++top] = p + 1;
// stack[++top] = h;
// }
// }
//}

int[] InitTestArray(int size)
{
int[] testArray = new int[size];

for (int i = 0; i < testArray.Length; ++i)
testArray[i] = i;

return testArray;
}

void ShuffleArray(int[] shuffleArray)
{
Random.InitState(1337);

int n = shuffleArray.Length - 1;
for (int i = 0; i < n; ++i)
{
int r = Random.Range(i + 1, n);
int flipVal = shuffleArray[r];
shuffleArray[r] = shuffleArray[i];
shuffleArray[i] = flipVal;
}
}

bool IsSorted(int[] array)
{
for (int i = 0; i < array.Length; ++i)
{
if (array[i] != i)
{
return false;
}
}

return true;
}

[RecursiveMethod]
public string CombineStrings(int count, string a, string b)
{
if (count == 0)
return "";

return string.Concat(a, CombineStrings(count - 1, b, a), CombineStrings(count - 1, a, b));
}

[RecursiveMethod]
public string CombineStringsExtern(int count, string a, string b)
{
if (count == 0)
return "";

RecursionTest self = this;

//Debug.Log($"count: {count}, a: {a}, b: {b}"/*, a result: {aResult}, b result: {bResult}"*/);

return string.Concat(a, self.CombineStringsExtern(count - 1, b, a), self.CombineStringsExtern(count - 1, a, b));
}

[RecursiveMethod]
public string CombineStringsParams(int count, string a, string b, string c, string d, string e)
{
if (count == 0)
return "";

return string.Concat(a, CombineStringsParams(count - 1, e, d, c, b, a), CombineStringsParams(count - 1, a, b, c, d, e), CombineStringsParams(count - 1, a, a, c, d, e), CombineStringsParams(count - 1, a, b, b, e, e), CombineStringsParams(count - 1, a, b, a, d, e));
}

[RecursiveMethod]
public string CombineStringsNested(int count, string a, string b)
{
if (count == 0)
return "";

return string.Concat(a, CombineStringsNested(count - 1, CombineStringsNested(count - 1, b, a), CombineStringsNested(count - 1, a, b)), CombineStringsNested(count - 1, CombineStringsNested(count - 1, a, b), CombineStringsNested(count - 1, b, a)), "c");
}

//public void Start()
//{
// ExecuteTests();
//}

[RecursiveMethod]
int CountChildren(Transform transformToCount)
{
int childCount = transformToCount.childCount;

foreach (Transform child in transformToCount)
childCount += CountChildren(child);

return childCount;
}

int externChildCount;

[RecursiveMethod]
void CountChildrenExternalCount(Transform transformToCount)
{
externChildCount += transformToCount.childCount;

foreach (Transform child in transformToCount)
CountChildrenExternalCount(child);
}

public void ExecuteTests()
{
tester.TestAssertion("Basic recursion 4!", Factorial(4) == 24);
tester.TestAssertion("Basic recursion 5!", Factorial(5) == 120);
tester.TestAssertion("Basic recursion 12!", Factorial(12) == 479001600);

int arraySize = Random.Range(10000, 11000);
int[] shuffleArray = InitTestArray(arraySize); // Fuzz a little

ShuffleArray(shuffleArray);
QuickSort(shuffleArray, 0, shuffleArray.Length - 1);

bool sorted = IsSorted(shuffleArray);
if (!sorted)
Debug.LogWarning($"Array size that failed {arraySize}");

tester.TestAssertion("Quicksort recursion", sorted);

RecursionTest self = this;

ShuffleArray(shuffleArray);
self.QuickSort(shuffleArray, 0, shuffleArray.Length - 1);

tester.TestAssertion("Quicksort external call", IsSorted(shuffleArray));

tester.TestAssertion("Function parameter swap recursion", CombineStrings(6, "a", "b") == "abababababababababababababababababababababababababababababababa");

tester.TestAssertion("Function parameter swap recursion external call", self.CombineStringsExtern(6, "a", "b") == "abababababababababababababababababababababababababababababababa");
tester.TestAssertion("Params array recursion", CombineStringsParams(4, "a", "b", "c", "d", "e") == "aeaeaaaaeaeeeeeaeeeeeaeeeeeaeeeeaeaeeeeaeaaaaaeaaaaaeaaaaaeaaaaaeaeeeeaeaaaaaeaaaaaeaaaaaeaaaaaeaeeeeaeaaaaaeaaaaaeaaaaaeaaaaaeaeeeeaeaaaaaeaaaaaeaaaaaeaaaa");
tester.TestAssertion("Nested call recursion", CombineStringsNested(3, "a", "b") == "abaccbcccabccacccccbaccbccccccabccacccbaccbcccccabccaccccccc");

tester.TestAssertion("Count children recursively foreach", CountChildren(transform) == 20);

externChildCount = 0;
CountChildrenExternalCount(transform);

tester.TestAssertion("Count children recursively foreach external counter", externChildCount == 20);
}
}
}

0 comments on commit aa79a63

Please sign in to comment.