Skip to content

Commit

Permalink
actions generator can now also generate a YSLS file for your methods.
Browse files Browse the repository at this point in the history
This is controlled via the project settings window.
This is off by default.
  • Loading branch information
McJones committed Feb 1, 2024
1 parent 6736a02 commit 5d89d94
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 9 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- A Unity Project scoped settings that allows you to override some of the default behaviours of Yarn Spinner
- settings are saved to a file in `ProjectSettings\Packages\dev.yarnspinner\YarnSpinnerProjectSettings.json`
- these can be changed via `Edit -> Project Settings -> Yarn Spinner`
- currently supports two convenience features of Yarn Spinner:
- currently supports three convenience features of Yarn Spinner:
- automatically associating assets with localisations
- automatically linking YarnCommand and YarnFunction attributed methods to the dialogue runner
- enabling/disabling C# linking will force an entire C# reimport
- generating a `ysls` file for all of your Yarn attributed methods
- this is save to `ProjectSettings\Packages\dev.yarnspinner\generated.ysls.json`
- this is an experimental feature to support better editor integration down the line
- as such this defaults to *not* being generated
- enabling/disabling C# linking or ysls generation will force an entire C# reimport
- enabling/disabling asset linking will force a reimport of all `yarnprojects`
- `Yarn.Unity.ActionAnalyser.Action` now has a `MethodIdentifierName` property, which is the short form of the method name.
- `LineView` now will add in line breaks when it encounters a self closing `[br /]` marker.
Expand Down
259 changes: 256 additions & 3 deletions Editor/Analysis/ActionsRegistrationGenerator~/ActionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public void Execute(GeneratorExecutionContext context)
// so what we do is we use the included Compilation Assembly additional file that Unity gives us.
// This file if opened has the path of the Unity project, which we can then use to get the settings
// if any stage of this fails then we bail out and assume that codegen is desired
string projectPath = null;
Yarn.Unity.Editor.YarnSpinnerProjectSettings settings = null;
if (context.AdditionalFiles.Any())
{
var relevants = context.AdditionalFiles.Where(i => i.Path.Contains($"{context.Compilation.AssemblyName}.AdditionalFile.txt"));
Expand All @@ -36,11 +38,12 @@ public void Execute(GeneratorExecutionContext context)
{
try
{
var projectPath = File.ReadAllText(arsgacsaf.Path);
projectPath = File.ReadAllText(arsgacsaf.Path);
var fullPath = Path.Combine(projectPath, Yarn.Unity.Editor.YarnSpinnerProjectSettings.YarnSpinnerProjectSettingsPath);
output.WriteLine($"Attempting to read settings file at {fullPath}");

if (!Yarn.Unity.Editor.YarnSpinnerProjectSettings.GetOrCreateSettings(projectPath, output).automaticallyLinkAttributedYarnCommandsAndFunctions)
settings = Yarn.Unity.Editor.YarnSpinnerProjectSettings.GetOrCreateSettings(projectPath, output);
if (!settings.automaticallyLinkAttributedYarnCommandsAndFunctions)
{
output.WriteLine("Skipping codegen due to settings.");
output.Dispose();
Expand Down Expand Up @@ -190,7 +193,7 @@ public void Execute(GeneratorExecutionContext context)
}

// removing any actions that failed validation
actions.Where(x => !removals.Contains(x.Name)).ToList();
actions = actions.Where(x => !removals.Contains(x.Name)).ToList();

output.Write($"Generating source code...");

Expand All @@ -208,6 +211,50 @@ public void Execute(GeneratorExecutionContext context)

context.AddSource($"YarnActionRegistration-{compilation.AssemblyName}.Generated.cs", sourceText);

if (settings != null)
{
if (settings.generateYSLSFile)
{
output.Write($"Generating ysls...");
// generating the ysls
YSLSGenerator generator = new YSLSGenerator();
generator.logger = output;
foreach (var action in actions)
{
generator.AddAction(action);
}
var ysls = generator.Serialise();
output.WriteLine($"Done.");

if (!string.IsNullOrEmpty(projectPath))
{
output.Write($"Writing generated ysls...");

var fullPath = Path.Combine(projectPath, Yarn.Unity.Editor.YarnSpinnerProjectSettings.YarnSpinnerGeneratedYSLSPath);
try
{
System.IO.File.WriteAllText(fullPath, ysls);
output.WriteLine($"Done.");
}
catch (Exception e)
{
output.WriteLine($"Unable to write ysls to disk: {e.Message}");
}
}
else
{
output.WriteLine("unable to identify project path, ysls will not be written to disk");
}
}
else
{
output.WriteLine($"skipping ysls generation due to settings");
}
}
else
{
output.WriteLine($"skipping ysls generation due to settings not being found");
}

return;

Expand Down Expand Up @@ -486,3 +533,209 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
}
}
}

internal class YSLSGenerator
{
struct YarnActionParameter
{
internal string Name;
internal string Type;
internal bool IsParamsArray;

internal Dictionary<string, object> ToDictionary()
{
var dict = new Dictionary<string, object>();
dict["Name"] = Name;
dict["Type"] = Type;
dict["IsParamsArray"] = IsParamsArray;
return dict;
}
}
struct YarnActionCommand
{
internal string YarnName;
internal string DefinitionName;
internal string Signature;
internal string FileName;
internal YarnActionParameter[] Parameters;

internal Dictionary<string, object> ToDictionary()
{
var dict = new Dictionary<string, object>();
dict["YarnName"] = YarnName;
dict["DefinitionName"] = DefinitionName;
dict["Signature"] = Signature;
dict["Language"] = "csharp";

if (!string.IsNullOrEmpty(FileName))
{
dict["FileName"] = FileName;
}

if (Parameters.Length > 0)
{
var pl = new List<Dictionary<string, object>>();
foreach (var p in Parameters)
{
pl.Add(p.ToDictionary());
}
dict["Parameters"] = pl;
}
return dict;
}
}
struct YarnActionFunction
{
internal string YarnName;
internal string DefinitionName;
internal string Signature;
internal YarnActionParameter[] Parameters;
internal string ReturnType;
internal string FileName;

internal Dictionary<string, object> ToDictionary()
{
var dict = new Dictionary<string, object>();
dict["YarnName"] = YarnName;
dict["DefinitionName"] = DefinitionName;
dict["Signature"] = Signature;
dict["ReturnType"] = ReturnType;
dict["Language"] = "csharp";

if (!string.IsNullOrEmpty(FileName))
{
dict["FileName"] = FileName;
}

if (Parameters.Length > 0)
{
var pl = new List<Dictionary<string, object>>();
foreach (var p in Parameters)
{
pl.Add(p.ToDictionary());
}
dict["Parameters"] = pl;
}
return dict;
}
}

internal Yarn.Unity.ILogger logger;
List<YarnActionCommand> commands = new List<YarnActionCommand>();
List<YarnActionFunction> functions = new List<YarnActionFunction>();

internal string Serialise()
{
var commandLine = "\"Commands\":[]";
var functionLine = "\"Functions\":[]";
// do we have any commands?
if (commands.Count() > 0)
{
var result = string.Join(",", commands.Select(c => Yarn.Unity.Editor.Json.Serialize(c.ToDictionary())));
commandLine = $"\"Commands\":[{result}]";
}
// do we have any functions?
if (functions.Count() > 0)
{
var result = string.Join(",", functions.Select(f => Yarn.Unity.Editor.Json.Serialize(f.ToDictionary())));
functionLine = $"\"Functions\":[{result}]";
}
return $"{{{commandLine},{functionLine}}}";
}
internal void AddAction(YarnAction action)
{
switch (action.Type)
{
case ActionType.Command:
AddCommand(action);
break;
case ActionType.Function:
AddFunction(action);
break;
case ActionType.NotAnAction:
logger.WriteLine($"attempted to make a ysls string for {action.Name}, but it is not a command or function");
break;
}
}
private void AddFunction(YarnAction action)
{
var parameters = GenerateParams(action.Parameters);
var Signature = $"{action.Name}({string.Join(", ", parameters.Select(p => p.Name))})";
if (parameters.Length == 0)
{
Signature = $"{action.Name}()";
}
var function = new YarnActionFunction
{
YarnName = action.Name,
DefinitionName = action.MethodIdentifierName,
Signature = Signature,
Parameters = parameters,
ReturnType = InternalTypeToYarnType(action.MethodSymbol.ReturnType),
FileName = action.SourceFileName,
};
functions.Add(function);
}
private void AddCommand(YarnAction action)
{
var parameters = GenerateParams(action.Parameters);
var Signature = $"<<{action.Name} {string.Join(" ", parameters.Select(p => p.Name))}>>";
if (parameters.Length == 0)
{
Signature = $"<<{action.Name}>>";
}
var command = new YarnActionCommand
{
YarnName = action.Name,
DefinitionName = action.MethodIdentifierName,
Signature = Signature,
Parameters = parameters,
FileName = action.SourceFileName,
};
commands.Add(command);
}
private YarnActionParameter[] GenerateParams(List<Parameter> parameters)
{
List<YarnActionParameter> parameterList = new List<YarnActionParameter>();
foreach (var param in parameters)
{
var paramType = InternalTypeToYarnType(param.Type);

var parameter = new YarnActionParameter
{
Name = param.Name,
Type = paramType,
IsParamsArray = false,
};
parameterList.Add(parameter);
}
return parameterList.ToArray();
}
private string InternalTypeToYarnType(ITypeSymbol symbol)
{
var type = "any";
switch (symbol.SpecialType)
{
case SpecialType.System_Boolean:
type = "boolean";
break;
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Decimal:
case SpecialType.System_Single:
case SpecialType.System_Double:
type = "number";
break;
case SpecialType.System_String:
type = "string";
break;
}
return type;
}
}
Loading

0 comments on commit 5d89d94

Please sign in to comment.