Files
FlaxEngine/Source/Tools/Flax.Build/Build/Plugins/VisualScriptingPlugin.cs
Wojtek Figat 8938f13a0b Add support for compiling and running engine without C# scripting
(configurable via `EngineConfiguration.UseCSharp` in Flax.Build)
2021-10-23 16:43:15 +02:00

116 lines
5.7 KiB
C#

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System.Text;
using Flax.Build.Bindings;
namespace Flax.Build.Plugins
{
/// <summary>
/// Flax.Build plugin for Visual Scripting support. Generates required bindings glue code for overriding native methods with Visual Scripts.
/// </summary>
/// <seealso cref="Flax.Build.Plugin" />
sealed class VisualScriptingPlugin : Plugin
{
/// <inheritdoc />
public override void Init()
{
base.Init();
BindingsGenerator.GenerateCppScriptWrapperFunction += OnGenerateCppScriptWrapperFunction;
BindingsGenerator.ScriptingLangInfos.Add(new BindingsGenerator.ScriptingLangInfo
{
Enabled = true,
VirtualWrapperMethodsPostfix = "_VisualScriptWrapper",
});
}
private void OnGenerateCppScriptWrapperFunction(Builder.BuildData buildData, VirtualClassInfo classInfo, FunctionInfo functionInfo, int scriptVTableSize, int scriptVTableIndex, StringBuilder contents)
{
// Generate C++ wrapper function to invoke Visual Script instead of overridden native function (with support for base method callback)
BindingsGenerator.CppIncludeFiles.Add("Engine/Content/Assets/VisualScript.h");
contents.AppendFormat(" {0} {1}_VisualScriptWrapper(", functionInfo.ReturnType, functionInfo.UniqueName);
var separator = false;
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (separator)
contents.Append(", ");
separator = true;
contents.Append(parameterInfo.Type);
contents.Append(' ');
contents.Append(parameterInfo.Name);
}
contents.Append(')');
contents.AppendLine();
contents.AppendLine(" {");
string scriptVTableOffset;
if (classInfo.IsInterface)
{
contents.AppendLine($" auto object = ScriptingObject::FromInterface(this, {classInfo.NativeName}::TypeInitializer);");
contents.AppendLine(" if (object == nullptr)");
contents.AppendLine(" {");
contents.AppendLine($" LOG(Error, \"Failed to cast interface {{0}} to scripting object\", TEXT(\"{classInfo.Name}\"));");
BindingsGenerator.GenerateCppReturn(buildData, contents, " ", functionInfo.ReturnType);
contents.AppendLine(" }");
contents.AppendLine($" const int32 scriptVTableOffset = {scriptVTableIndex} + object->GetType().GetInterface({classInfo.NativeName}::TypeInitializer)->ScriptVTableOffset;");
scriptVTableOffset = "scriptVTableOffset";
}
else
{
contents.AppendLine($" auto object = ({classInfo.NativeName}*)this;");
scriptVTableOffset = scriptVTableIndex.ToString();
}
contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;");
contents.AppendLine(" if (WrapperCallInstance == object)");
contents.AppendLine(" {");
contents.AppendLine(" // Prevent stack overflow by calling base method");
contents.AppendLine(" const auto scriptVTableBase = object->GetType().Script.ScriptVTableBase;");
contents.Append($" return (this->**({functionInfo.UniqueName}_Internal_Signature*)&scriptVTableBase[{scriptVTableOffset} + 2])(");
separator = false;
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (separator)
contents.Append(", ");
separator = true;
contents.Append(parameterInfo.Name);
}
contents.AppendLine(");");
contents.AppendLine(" }");
contents.AppendLine(" auto scriptVTable = (VisualScript::Method**)object->GetType().Script.ScriptVTable;");
contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableOffset}]);");
if (functionInfo.Parameters.Count != 0)
{
contents.AppendLine($" Variant parameters[{functionInfo.Parameters.Count}];");
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
contents.AppendLine($" parameters[{i}] = {BindingsGenerator.GenerateCppWrapperNativeToVariant(buildData, parameterInfo.Type, classInfo, parameterInfo.Name)};");
}
}
else
{
contents.AppendLine(" Variant* parameters = nullptr;");
}
contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;");
contents.AppendLine(" WrapperCallInstance = object;");
contents.AppendLine($" auto __result = VisualScripting::Invoke(scriptVTable[{scriptVTableOffset}], object, Span<Variant>(parameters, {functionInfo.Parameters.Count}));");
contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;");
if (!functionInfo.ReturnType.IsVoid)
{
contents.AppendLine($" return {BindingsGenerator.GenerateCppWrapperVariantToNative(buildData, functionInfo.ReturnType, classInfo, "__result")};");
}
contents.AppendLine(" }");
contents.AppendLine();
}
}
}