This commit is contained in:
MaidOpi
2026-05-10 22:29:56 +08:00
commit 0428927f5f
54 changed files with 15200 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
---
# Based on LLVM style with custom modifications
BasedOnStyle: LLVM
# Basic settings
Language: Cpp
Standard: c++17
# Indentation
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 160
# Braces
BreakBeforeBraces: Allman
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
# Alignment
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
# Spacing
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
# Line breaks
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
# Pointers and references
DerivePointerAlignment: false
PointerAlignment: Left
# Includes
SortIncludes: false
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^".*\.h"'
Priority: 1
- Regex: '^<.*\.h>'
Priority: 2
- Regex: '^<.*'
Priority: 3
# Other
KeepEmptyLinesAtTheStartOfBlocks: false
# Keep at most one consecutive empty line (avoids stacked blanks).
MaxEmptyLinesToKeep: 1
# Always insert one empty line between adjacent top-level definitions (functions,
# classes, etc.) so function boundaries stay readable; does not target `if`/`for` bodies.
SeparateDefinitionBlocks: Always
NamespaceIndentation: None
ReflowComments: true
+60
View File
@@ -0,0 +1,60 @@
# Local editor state
.vscode/
.vs/
*.suo
*.user
*.userosscache
*.sln.docstates
*.rsuser
# Secrets and local environment
*.env
.env*
!.env.example
# Build output
[Bb]uild/
build*/
[Dd]ebug/
[Rr]elease/
[Rr]elWithDebInfo/
[Mm]in[Ss]ize[Rr]el/
out/
bin/
obj/
godot/build*/
godot/project/bin/
# CMake / native build scratch
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
compile_commands.json
*.ilk
*.lib
*.exp
*.pdb
*.obj
*.idb
*.iobj
*.ipdb
*.pch
*.tlog
*.log
# OS files
.DS_Store
Thumbs.db
.godot
project/leanclr/*.dll
managed/GodotSharpCompat/Generated/
tools/binding_generator/unsupported_api_report.json
src/generated
extension_api.json
tools/binding_generator/__pycache__/generate_bindings.cpython-314.pyc
tools/binding_generator/binding_statistics_report.md
.cache
project/leanclr/live_reload.txt
+3
View File
@@ -0,0 +1,3 @@
[submodule "thirdparty/leanclr"]
path = thirdparty/leanclr
url = https://github.com/focus-creative-games/leanclr.git
+133
View File
@@ -0,0 +1,133 @@
cmake_minimum_required(VERSION 3.19)
project(leanclr_godot LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
option(LEANCLR_GODOT_FETCH_GODOT_CPP "Fetch godot-cpp when GODOT_CPP_ROOT is not provided" ON)
set(GODOT_CPP_ROOT "" CACHE PATH "Path to a checked-out godot-cpp tree")
set(GODOT_CPP_LIBRARY "" CACHE FILEPATH "Path to a prebuilt godot-cpp static library. When set, godot-cpp is imported instead of built.")
set(GODOT_CPP_GEN_INCLUDE_DIR "" CACHE PATH "Path to godot-cpp generated headers, for example <godot-cpp-build>/gen/include")
set(GODOT_CPP_BRANCH "4.4" CACHE STRING "godot-cpp branch or tag to use when fetching")
if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "Select the MSVC runtime library for GDExtension binaries." FORCE)
endif()
function(configure_size_optimized_native_target target_name)
if(NOT TARGET ${target_name})
return()
endif()
set_target_properties(${target_name} PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
if(NOT MSVC)
target_compile_options(${target_name} PRIVATE -ffunction-sections -fdata-sections)
endif()
endfunction()
set(LEANCLR_ROOT "${CMAKE_CURRENT_LIST_DIR}/thirdparty/leanclr" CACHE PATH "Path to the LeanCLR source tree")
add_subdirectory(${LEANCLR_ROOT}/src/runtime leanclr_runtime)
configure_size_optimized_native_target(leanclr)
if(MSVC)
set_target_properties(leanclr PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
endif()
if(GODOT_CPP_LIBRARY)
if(NOT GODOT_CPP_ROOT)
message(FATAL_ERROR "GODOT_CPP_ROOT is required when GODOT_CPP_LIBRARY is set")
endif()
if(NOT GODOT_CPP_GEN_INCLUDE_DIR)
message(FATAL_ERROR "GODOT_CPP_GEN_INCLUDE_DIR is required when GODOT_CPP_LIBRARY is set")
endif()
add_library(godot-cpp STATIC IMPORTED GLOBAL)
set_target_properties(godot-cpp PROPERTIES
IMPORTED_LOCATION "${GODOT_CPP_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${GODOT_CPP_GEN_INCLUDE_DIR};${GODOT_CPP_ROOT}/include"
INTERFACE_COMPILE_FEATURES cxx_std_17
INTERFACE_COMPILE_DEFINITIONS "GDEXTENSION;$<$<CONFIG:Debug>:DEBUG_ENABLED>;THREADS_ENABLED;WINDOWS_ENABLED;TYPED_METHOD_BIND;NOMINMAX"
)
if(MSVC)
target_compile_definitions(godot-cpp INTERFACE _HAS_EXCEPTIONS=0)
target_compile_options(godot-cpp INTERFACE /utf-8)
target_link_options(godot-cpp INTERFACE $<$<CONFIG:Debug>:/DEBUG:FULL>)
endif()
elseif(GODOT_CPP_ROOT)
add_subdirectory(${GODOT_CPP_ROOT} godot_cpp)
else()
if(NOT LEANCLR_GODOT_FETCH_GODOT_CPP)
message(FATAL_ERROR "Set GODOT_CPP_ROOT or enable LEANCLR_GODOT_FETCH_GODOT_CPP")
endif()
include(FetchContent)
FetchContent_Declare(
godot-cpp
GIT_REPOSITORY https://github.com/godotengine/godot-cpp.git
GIT_TAG ${GODOT_CPP_BRANCH}
)
FetchContent_MakeAvailable(godot-cpp)
endif()
configure_size_optimized_native_target(godot-cpp)
add_library(leanclr_godot SHARED
src/generated/godot_api.generated.cpp
src/leanclr_main_node.cpp
src/leanclr_hot_reload_host.cpp
src/leanclr_runtime_bridge.cpp
src/leanclr_script.cpp
src/leanclr_script_language.cpp
src/leanclr_script_loader.cpp
src/leanclr_script_saver.cpp
src/register_types.cpp
)
target_include_directories(leanclr_godot PRIVATE
${CMAKE_CURRENT_LIST_DIR}/src
)
target_link_libraries(leanclr_godot PRIVATE
godot-cpp
leanclr
)
if(MSVC)
target_compile_options(leanclr_godot PRIVATE /W4 /MP /bigobj)
set_target_properties(leanclr_godot PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
else()
target_compile_options(leanclr_godot PRIVATE -Wall -Wextra -Wpedantic)
target_compile_options(leanclr_godot PRIVATE -ffunction-sections -fdata-sections)
if(APPLE)
set(LEANCLR_GODOT_EXPORTED_SYMBOLS "${CMAKE_CURRENT_BINARY_DIR}/leanclr_godot.exports")
file(WRITE "${LEANCLR_GODOT_EXPORTED_SYMBOLS}" "_leanclr_godot_library_init\n")
target_link_options(leanclr_godot PRIVATE
-Wl,-dead_strip
-Wl,-exported_symbols_list,${LEANCLR_GODOT_EXPORTED_SYMBOLS}
)
elseif(UNIX)
target_link_options(leanclr_godot PRIVATE -Wl,--gc-sections)
endif()
endif()
set_target_properties(leanclr_godot PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
set_target_properties(leanclr_godot PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/project/bin/$<CONFIG>"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/project/bin/$<CONFIG>"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/project/bin/$<CONFIG>"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/project/bin/Debug"
LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/project/bin/Debug"
ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/project/bin/Debug"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/project/bin/Release"
LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/project/bin/Release"
ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/project/bin/Release"
)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 MaidOpi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
# LeanCLR Godot
浅尝一下 LeanCLR 跑 Godot 4 脚本。
原版Godot加载GDExtensionC# 代码交给 LeanCLR 解释执行。现在 demo 里可以把 `.cs` 直接挂到节点上,也可以在游戏窗口里的 CodeEdit 改 C#,点运行后编译一个新 assembly,然后不重启切过去。
已完成:
- Godot API 绑定尽量全量生成,热更代码后面会直接用
- C# 脚本资源能在编辑器里加载、保存、挂节点
- 运行时热重载保留字段状态,接近 Python/Lua 那种 reload 手感
- demo 是一个简单 Flappy Bird,用来测输入、Process、状态迁移和重载
先拉子模块:
```bash
git submodule update --init --recursive
```
编 native 扩展:
```bash
cmake -S . -B build-master
cmake --build build-master --config Debug --target leanclr_godot
```
编 demo 的 C#
```bash
dotnet msbuild project/Game.csproj /p:Configuration=Debug
```
运行项目:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --path project
```
几个常看的文件:
`src/` : GDExtension 和 bridge。
`managed/GodotSharpCompat/` : 给 C# 用的 Godot API 外观。
`project/runtime_hot_reload_demo.tscn` : 现在的主 demo。
`project/scripts/HotReloadSmoke.cs` : Flappy 的 C# 脚本。
`project/scripts/RuntimeCSharpEditor.gd` : 游戏里的 C# 编辑器窗口。
`project/leanclr/live_reload.txt` : 热重载 marker。写 `Game` 就回到默认 `Game.dll`,写别的 assembly 名就切到那个 dll。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
namespace Godot
{
internal static class CallableDelegateRegistry
{
private static readonly Dictionary<long, Func<Variant[], Variant>> Delegates = new Dictionary<long, Func<Variant[], Variant>>();
private static long nextId = 1;
internal static long Register(Func<Variant[], Variant> function)
{
long id = nextId++;
Delegates[id] = function;
return id;
}
internal static void Unregister(long delegateId)
{
Delegates.Remove(delegateId);
}
public static Variant Invoke(long delegateId, Variant[] arguments)
{
Func<Variant[], Variant> function;
if (!Delegates.TryGetValue(delegateId, out function))
{
return new Variant();
}
return function(arguments ?? new Variant[0]);
}
internal static T ConvertArgument<T>(Variant[] arguments, int index)
{
Variant value = arguments[index];
Type type = typeof(T);
if (type == typeof(Variant))
{
return (T)(object)value;
}
if (type == typeof(string))
{
return (T)(object)value.ToString();
}
if (type == typeof(int))
{
return (T)(object)(int)value.AsInt64();
}
if (type == typeof(long))
{
return (T)(object)value.AsInt64();
}
if (type == typeof(float))
{
return (T)(object)(float)value.AsDouble();
}
if (type == typeof(double))
{
return (T)(object)value.AsDouble();
}
if (type == typeof(bool))
{
return (T)(object)value.AsBool();
}
if (type == typeof(Vector2))
{
return (T)(object)value.AsVector2();
}
if (type == typeof(Vector3))
{
return (T)(object)value.AsVector3();
}
if (type == typeof(StringName))
{
return (T)(object)value.AsStringName();
}
if (type == typeof(NodePath))
{
return (T)(object)value.AsNodePath();
}
if (type == typeof(RID))
{
return (T)(object)value.AsRID();
}
if (type == typeof(Color))
{
return (T)(object)value.AsColor();
}
if (type == typeof(Quaternion))
{
return (T)(object)value.AsQuaternion();
}
if (type == typeof(Basis))
{
return (T)(object)value.AsBasis();
}
if (type == typeof(Transform3D))
{
return (T)(object)value.AsTransform3D();
}
if (type == typeof(Node))
{
return (T)(object)value.AsObject<Node>();
}
if (type == typeof(Node2D))
{
return (T)(object)value.AsObject<Node2D>();
}
if (type == typeof(GodotObject))
{
return (T)(object)value.AsObject<GodotObject>();
}
return default(T);
}
}
}
@@ -0,0 +1,34 @@
using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Property)]
public sealed class ExportAttribute : Attribute
{
public readonly PropertyHint Hint;
public readonly string HintString;
public readonly PropertyUsageFlags Usage;
public ExportAttribute()
: this(PropertyHint.None, string.Empty, PropertyUsageFlags.PropertyUsageDefault)
{
}
public ExportAttribute(PropertyHint hint)
: this(hint, string.Empty, PropertyUsageFlags.PropertyUsageDefault)
{
}
public ExportAttribute(PropertyHint hint, string hintString)
: this(hint, hintString, PropertyUsageFlags.PropertyUsageDefault)
{
}
public ExportAttribute(PropertyHint hint, string hintString, PropertyUsageFlags usage)
{
Hint = hint;
HintString = hintString ?? string.Empty;
Usage = usage;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
namespace Godot
{
public static class GD
{
public static void Print(string message)
{
PrintInternal(message);
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void PrintInternal(string message);
}
}
+73
View File
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace Godot
{
public partial class GodotObject : System.IDisposable
{
private static readonly Dictionary<IntPtr, WeakReference> NativeInstanceCache = new Dictionary<IntPtr, WeakReference>();
internal System.IntPtr NativePtr;
private int ownedNativeRefCount;
internal static T CreateFromNative<T>(System.IntPtr nativePtr) where T : GodotObject, new()
{
return CreateFromNative<T>(nativePtr, false);
}
internal static T CreateFromNative<T>(System.IntPtr nativePtr, bool ownsNativeRef) where T : GodotObject, new()
{
if (nativePtr == System.IntPtr.Zero)
{
return null;
}
WeakReference weakReference;
if (NativeInstanceCache.TryGetValue(nativePtr, out weakReference))
{
T cached = weakReference.Target as T;
if (cached != null && cached.NativePtr == nativePtr)
{
if (ownsNativeRef)
{
cached.ownedNativeRefCount++;
}
return cached;
}
}
T instance = new T();
instance.NativePtr = nativePtr;
instance.ownedNativeRefCount = ownsNativeRef ? 1 : 0;
NativeInstanceCache[nativePtr] = new WeakReference(instance);
return instance;
}
~GodotObject()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (NativePtr == System.IntPtr.Zero)
{
return;
}
NativeInstanceCache.Remove(NativePtr);
while (ownedNativeRefCount > 0)
{
NativeCalls.GodotObjectReleaseRefCounted(NativePtr);
ownedNativeRefCount--;
}
NativePtr = System.IntPtr.Zero;
}
}
}
@@ -0,0 +1,26 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<AssemblyName>GodotSharpCompat</AssemblyName>
<RootNamespace>Godot</RootNamespace>
<NoStandardLib>true</NoStandardLib>
<NoConfig>true</NoConfig>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<OutputPath>..\..\project\leanclr\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib">
<HintPath>../../thirdparty/leanclr/src/libraries/dotnetframework4.x-linux/mscorlib.dll</HintPath>
</Reference>
<Reference Include="System">
<HintPath>../../thirdparty/leanclr/src/libraries/dotnetframework4.x-linux/System.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+9
View File
@@ -0,0 +1,9 @@
using System;
using System.Runtime.CompilerServices;
namespace Godot
{
internal static partial class NativeCalls
{
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace Godot
{
public partial class Node : GodotObject
{
public virtual void _Ready()
{
}
public virtual void _Process(double delta)
{
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>latest</LangVersion>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<AssemblyName>Game</AssemblyName>
<RootNamespace>Game</RootNamespace>
<NoStandardLib>true</NoStandardLib>
<NoConfig>true</NoConfig>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<OutputPath>leanclr\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="scripts\**\*.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib">
<HintPath>../thirdparty/leanclr/src/libraries/dotnetframework4.x-linux/mscorlib.dll</HintPath>
</Reference>
<Reference Include="System">
<HintPath>../thirdparty/leanclr/src/libraries/dotnetframework4.x-linux/System.dll</HintPath>
</Reference>
<Reference Include="GodotSharpCompat">
<HintPath>leanclr\GodotSharpCompat.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+21
View File
@@ -0,0 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game.csproj", "{6CE2F047-6391-4EE7-87A3-4AD33A189355}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6CE2F047-6391-4EE7-87A3-4AD33A189355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CE2F047-6391-4EE7-87A3-4AD33A189355}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CE2F047-6391-4EE7-87A3-4AD33A189355}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CE2F047-6391-4EE7-87A3-4AD33A189355}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

+43
View File
@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://i0yc5j7xuf21"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+14
View File
@@ -0,0 +1,14 @@
[configuration]
entry_symbol = "leanclr_godot_library_init"
compatibility_minimum = "4.4"
reloadable = true
[libraries]
windows.debug.x86_64 = "res://bin/Debug/leanclr_godot.dll"
windows.release.x86_64 = "res://bin/Release/leanclr_godot.dll"
linux.debug.x86_64 = "res://bin/Debug/libleanclr_godot.so"
linux.release.x86_64 = "res://bin/Release/libleanclr_godot.so"
macos.debug = "res://bin/Debug/libleanclr_godot.dylib"
macos.release = "res://bin/Release/libleanclr_godot.dylib"
+1
View File
@@ -0,0 +1 @@
uid://sbyqrdhnol0s
+11
View File
@@ -0,0 +1,11 @@
extends Node
func _ready() -> void:
var script := ResourceLoader.load("res://hello.lcs")
if script == null:
push_error("Failed to load LeanCLR hello.lcs")
get_tree().quit(1)
return
print("Loaded LeanCLR script: ", script.get_type_name())
get_tree().quit()
+1
View File
@@ -0,0 +1 @@
uid://bkcd2d6cuaxfs
+20
View File
@@ -0,0 +1,20 @@
[gd_scene load_steps=2 format=3 uid="uid://leanclr_godot_main"]
[ext_resource type="Script" path="res://scripts/ScriptMain.cs" id="1_leanclr"]
[node name="Main" type="LeanCLRMain"]
[node name="ScriptLanguageDemo" type="Node2D" parent="."]
script = ExtResource("1_leanclr")
[node name="Child" type="Node" parent="ScriptLanguageDemo"]
[node name="UiLabel" type="Label" parent="ScriptLanguageDemo"]
[node name="SpriteDemo" type="Sprite2D" parent="ScriptLanguageDemo"]
[node name="Node3DDemo" type="Node3D" parent="ScriptLanguageDemo"]
[node name="CpuParticles3DDemo" type="CPUParticles3D" parent="ScriptLanguageDemo"]
[node name="Camera3DDemo" type="Camera3D" parent="ScriptLanguageDemo"]
+19
View File
@@ -0,0 +1,19 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application]
config/name="LeanCLR Godot"
run/main_scene="res://runtime_hot_reload_demo.tscn"
config/features=PackedStringArray("4.6")
+39
View File
@@ -0,0 +1,39 @@
[gd_scene format=3 uid="uid://dynd0csycincd"]
[ext_resource type="Script" uid="uid://b626vql61yl6r" path="res://scripts/RuntimeCSharpEditor.gd" id="1_editor_script"]
[node name="RuntimeCSharpEditor" type="Window" unique_id=440944382]
oversampling_override = 1.0
title = "LeanCLR Runtime C# Editor"
position = Vector2i(560, 36)
size = Vector2i(760, 560)
visible = false
always_on_top = true
script = ExtResource("1_editor_script")
[node name="RuntimeCSharpEditorPanel" type="VBoxContainer" parent="." unique_id=48727115]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="RuntimeCSharpTitle" type="Label" parent="RuntimeCSharpEditorPanel" unique_id=1798922565]
layout_mode = 2
text = "HotReloadSmoke.cs"
[node name="RuntimeCSharpCodeEdit" type="CodeEdit" parent="RuntimeCSharpEditorPanel" unique_id=2041813682]
unique_name_in_owner = true
custom_minimum_size = Vector2(720, 390)
layout_mode = 2
size_flags_vertical = 3
[node name="RuntimeCSharpRunButton" type="Button" parent="RuntimeCSharpEditorPanel" unique_id=1963029537]
unique_name_in_owner = true
layout_mode = 2
text = "Build + Execute Without Restart"
[node name="RuntimeCSharpStatus" type="Label" parent="RuntimeCSharpEditorPanel" unique_id=32725415]
unique_name_in_owner = true
layout_mode = 2
text = "Ready"
+78
View File
@@ -0,0 +1,78 @@
[gd_scene format=3 uid="uid://dom55c3dsfsh6"]
[ext_resource type="Script" uid="uid://bo2r8gbx576k6" path="res://scripts/HotReloadSmoke.cs" id="1_flappy"]
[ext_resource type="Texture2D" uid="uid://i0yc5j7xuf21" path="res://icon.svg" id="2_icon"]
[ext_resource type="PackedScene" uid="uid://dynd0csycincd" path="res://runtime_csharp_editor.tscn" id="3_editor_scene"]
[ext_resource type="Script" uid="uid://dca5khgessig" path="res://scripts/HotReloadInputRelay.gd" id="4_input_relay"]
[node name="RuntimeHotReloadDemo" type="Node2D" unique_id=1212946146]
[node name="GameWorld" type="Node2D" parent="." unique_id=914199522]
[node name="Sky" type="ColorRect" parent="GameWorld" unique_id=90320198]
offset_right = 540.0
offset_bottom = 360.0
color = Color(0.42, 0.78, 0.95, 1)
[node name="CloudA" type="ColorRect" parent="GameWorld" unique_id=1619648798]
offset_left = 76.0
offset_top = 44.0
offset_right = 168.0
offset_bottom = 66.0
color = Color(0.92, 0.97, 1, 1)
[node name="CloudB" type="ColorRect" parent="GameWorld" unique_id=329244074]
offset_left = 318.0
offset_top = 74.0
offset_right = 442.0
offset_bottom = 98.0
color = Color(0.92, 0.97, 1, 1)
[node name="PipeTop" type="ColorRect" parent="GameWorld" unique_id=1793488116]
offset_left = 420.0
offset_right = 480.0
offset_bottom = 92.0
color = Color(0.07, 0.63, 0.23, 1)
[node name="PipeBottom" type="ColorRect" parent="GameWorld" unique_id=422849216]
offset_left = 420.0
offset_top = 208.0
offset_right = 480.0
offset_bottom = 320.0
color = Color(0.07, 0.63, 0.23, 1)
[node name="Ground" type="ColorRect" parent="GameWorld" unique_id=581381780]
offset_top = 320.0
offset_right = 540.0
offset_bottom = 360.0
color = Color(0.68, 0.48, 0.22, 1)
[node name="Bird" type="TextureRect" parent="GameWorld" unique_id=225012823]
offset_left = 108.0
offset_top = 130.0
offset_right = 148.0
offset_bottom = 170.0
pivot_offset = Vector2(20, 20)
texture = ExtResource("2_icon")
expand_mode = 1
[node name="Hud" type="Node" parent="GameWorld" unique_id=441089907]
[node name="DemoStatus" type="Label" parent="GameWorld/Hud" unique_id=204759433]
offset_left = 15.0
offset_top = 328.0
offset_right = 517.0
offset_bottom = 374.0
text = "Running flappy-physics-v1 | score=0 | y=146 | vy=0 | pipeX=420"
[node name="FlappyScript" type="Node" parent="." unique_id=1458328663]
script = ExtResource("1_flappy")
FlapPower = 5
Name = &"FlappyScript"
[node name="LiveHotReloadHost" type="LeanCLRHotReloadHost" parent="." unique_id=465275025]
[node name="HotReloadInputRelay" type="Node" parent="." unique_id=394620894]
script = ExtResource("4_input_relay")
[node name="RuntimeCSharpEditor" parent="." unique_id=56462960 instance=ExtResource("3_editor_scene")]
+11
View File
@@ -0,0 +1,11 @@
using Godot;
namespace Game;
public partial class ClassDbMain : Node
{
public override void _Ready()
{
GD.Print("LeanCLR demo: ClassDB host owner name = " + Name);
}
}
+1
View File
@@ -0,0 +1 @@
uid://b5rn1ql6ugvjj
+10
View File
@@ -0,0 +1,10 @@
extends Node
@onready var hot_reload_host: Node = get_node("../LiveHotReloadHost")
func _ready() -> void:
set_process_input(true)
func _input(event: InputEvent) -> void:
if hot_reload_host != null:
hot_reload_host.forward_input(event)
@@ -0,0 +1 @@
uid://dca5khgessig
+270
View File
@@ -0,0 +1,270 @@
using Godot;
namespace Game;
public partial class HotReloadSmoke : Node
{
private const string Version = "flappy-physics-v1";
private const float WorldWidth = 540.0f;
private const float WorldHeight = 360.0f;
private const float GroundY = 320.0f;
private const float BirdX = 108.0f;
private const float BirdSize = 40.0f;
private const float Gravity = 960.0f;
private const float FlapVelocity = -330.0f;
private const float PipeSpeed = 170.0f;
private const float PipeWidth = 60.0f;
private const float GapHeight = 160.0f;
private const float ResetPipeX = 560.0f;
private const float PipeRecycleX = -80.0f;
private const int MinGapCenter = 118;
private const int MaxGapCenter = 235;
private static readonly Color AliveBirdColor = new Color(1.0f, 0.95f, 0.18f, 1.0f);
private static readonly Color GameOverBirdColor = new Color(1.0f, 0.32f, 0.22f, 1.0f);
[Export(PropertyHint.Range, "1,10,1")]
public int FlapPower { get; set; } = 5;
private float elapsed;
private float birdY;
private float velocityY;
private float pipeX;
private int gapCenter;
private int score;
private bool gameOver;
private bool passedPipe;
private bool processLogged;
private bool runtimeReloadedObject;
public override void _Ready()
{
if (birdY <= 0.0f || pipeX <= 0.0f)
{
ResetGame();
}
ApplyGameState();
GD.Print("LeanCLR flappy reload: version = " + Version);
GD.Print("LeanCLR flappy reload: score = " + score.ToString());
GD.Print("LeanCLR flappy reload: bird y = " + ((int)birdY).ToString());
GD.Print("LeanCLR flappy reload: velocity y = " + ((int)velocityY).ToString());
GD.Print("LeanCLR flappy reload: active marker = " + FileAccess.GetFileAsString("res://leanclr/live_reload.txt").Trim());
}
public override void _Input(InputEvent event_)
{
if (!IsGameplayObjectActive())
{
return;
}
if (event_ != null && event_.IsPressed() && !event_.IsEcho())
{
ForceFlap();
}
}
public void ForceFlap()
{
if (gameOver)
{
ResetGame();
GD.Print("LeanCLR flappy input: restart");
return;
}
velocityY = FlapVelocity - FlapPower * 10.0f;
GD.Print("LeanCLR flappy input: flap velocity = " + ((int)velocityY).ToString());
}
public Variant CaptureHotReloadState()
{
Dictionary state = new Dictionary();
state[new Variant("elapsed")] = new Variant(elapsed);
state[new Variant("birdY")] = new Variant(birdY);
state[new Variant("velocityY")] = new Variant(velocityY);
state[new Variant("pipeX")] = new Variant(pipeX);
state[new Variant("gapCenter")] = new Variant(gapCenter);
state[new Variant("score")] = new Variant(score);
state[new Variant("gameOver")] = new Variant(gameOver);
state[new Variant("passedPipe")] = new Variant(passedPipe);
state[new Variant("processLogged")] = new Variant(processLogged);
GD.Print("LeanCLR hot reload state: captured score = " + score.ToString() + " y = " + ((int)birdY).ToString());
return new Variant(state);
}
public void RestoreHotReloadState(Variant stateVariant)
{
runtimeReloadedObject = true;
Dictionary state = stateVariant.AsDictionary();
if (state != null)
{
elapsed = ReadFloat(state, "elapsed", elapsed);
birdY = ReadFloat(state, "birdY", birdY);
velocityY = ReadFloat(state, "velocityY", velocityY);
pipeX = ReadFloat(state, "pipeX", pipeX);
gapCenter = ReadInt(state, "gapCenter", gapCenter);
score = ReadInt(state, "score", score);
gameOver = ReadBool(state, "gameOver", gameOver);
passedPipe = ReadBool(state, "passedPipe", passedPipe);
processLogged = ReadBool(state, "processLogged", processLogged);
}
GD.Print("LeanCLR hot reload state: restored score = " + score.ToString() + " y = " + ((int)birdY).ToString());
}
public void OnHotReloaded()
{
runtimeReloadedObject = true;
ApplyGameState();
GD.Print("LeanCLR hot reload state: active score after reload = " + score.ToString() + " y = " + ((int)birdY).ToString());
}
public override void _Process(double delta)
{
if (!IsGameplayObjectActive())
{
return;
}
float dt = Clamp((float)delta, 0.0f, 0.033f);
elapsed += dt;
if (!gameOver)
{
velocityY += Gravity * dt;
birdY += velocityY * dt;
pipeX -= PipeSpeed * dt;
if (pipeX < PipeRecycleX)
{
pipeX = ResetPipeX;
gapCenter = NextGapCenter();
passedPipe = false;
}
if (!passedPipe && pipeX + PipeWidth < BirdX)
{
passedPipe = true;
score++;
GD.Print("LeanCLR flappy score: " + score.ToString());
}
if (CheckCollision())
{
gameOver = true;
GD.Print("LeanCLR flappy collision: game over score = " + score.ToString());
}
}
ApplyGameState();
if (!processLogged)
{
processLogged = true;
GD.Print("LeanCLR flappy process: playable physics tick delta = " + delta.ToString());
}
}
private void ResetGame()
{
elapsed = 0.0f;
birdY = 146.0f;
velocityY = 0.0f;
pipeX = 420.0f;
gapCenter = 172;
score = 0;
gameOver = false;
passedPipe = false;
processLogged = false;
}
private bool CheckCollision()
{
if (birdY < 0.0f || birdY + BirdSize > GroundY)
{
return true;
}
bool overlapsPipeX = BirdX + BirdSize > pipeX && BirdX < pipeX + PipeWidth;
if (!overlapsPipeX)
{
return false;
}
float gapTop = gapCenter - GapHeight * 0.5f;
float gapBottom = gapCenter + GapHeight * 0.5f;
return birdY < gapTop || birdY + BirdSize > gapBottom;
}
private void ApplyGameState()
{
TextureRect bird = GetNodeOrNull<TextureRect>("../GameWorld/Bird");
ColorRect pipeTop = GetNodeOrNull<ColorRect>("../GameWorld/PipeTop");
ColorRect pipeBottom = GetNodeOrNull<ColorRect>("../GameWorld/PipeBottom");
Label title = GetNodeOrNull<Label>("../GameWorld/Hud/Title");
Label status = GetNodeOrNull<Label>("../GameWorld/Hud/DemoStatus");
if (bird != null)
{
bird.Position = new Vector2(BirdX, birdY);
bird.Size = new Vector2(BirdSize, BirdSize);
bird.SetModulate(gameOver ? GameOverBirdColor : AliveBirdColor);
bird.SetRotationDegrees(Clamp(velocityY / 18.0f, -24.0f, 58.0f));
}
float gapTop = gapCenter - GapHeight * 0.5f;
float gapBottom = gapCenter + GapHeight * 0.5f;
if (pipeTop != null)
{
pipeTop.Position = new Vector2(pipeX, 0.0f);
pipeTop.Size = new Vector2(PipeWidth, gapTop);
}
if (pipeBottom != null)
{
pipeBottom.Position = new Vector2(pipeX, gapBottom);
pipeBottom.Size = new Vector2(PipeWidth, GroundY - gapBottom);
}
if (title != null)
{
title.Text = gameOver ? "Game Over - press any key/click to restart" : "Flap: any key or mouse click";
}
if (status != null)
{
status.Text = "Running " + Version + " | score=" + score.ToString() + " | y=" + ((int)birdY).ToString() + " | vy=" + ((int)velocityY).ToString() + " | pipeX=" + ((int)pipeX).ToString();
}
}
private int NextGapCenter()
{
int cycle = ((score * 47) + 31) % (MaxGapCenter - MinGapCenter);
return MinGapCenter + cycle;
}
private bool IsGameplayObjectActive()
{
string marker = FileAccess.GetFileAsString("res://leanclr/live_reload.txt").Trim();
return marker == "Game" || runtimeReloadedObject;
}
private float ReadFloat(Dictionary state, string key, float fallback)
{
Variant variantKey = new Variant(key);
return state.ContainsKey(variantKey) ? (float)state[variantKey].AsDouble() : fallback;
}
private int ReadInt(Dictionary state, string key, int fallback)
{
Variant variantKey = new Variant(key);
return state.ContainsKey(variantKey) ? (int)state[variantKey].AsInt64() : fallback;
}
private bool ReadBool(Dictionary state, string key, bool fallback)
{
Variant variantKey = new Variant(key);
return state.ContainsKey(variantKey) ? state[variantKey].AsBool() : fallback;
}
private float Clamp(float value, float min, float max)
{
return value < min ? min : (value > max ? max : value);
}
}
+1
View File
@@ -0,0 +1 @@
uid://bo2r8gbx576k6
+162
View File
@@ -0,0 +1,162 @@
extends Window
const EDIT_SOURCE_PATH := "res://scripts/HotReloadSmoke.cs"
const RELOAD_MARKER_PATH := "res://leanclr/live_reload.txt"
const FRAMEWORK_RELATIVE_PATH := "../thirdparty/leanclr/src/libraries/dotnetframework4.x-linux"
const AUTORUN_ENVIRONMENT := "LEANCLR_RUNTIME_EDITOR_AUTORUN"
@onready var editor: CodeEdit = %RuntimeCSharpCodeEdit
@onready var run_button: Button = %RuntimeCSharpRunButton
@onready var status_label: Label = %RuntimeCSharpStatus
var autorun_started := false
func _ready() -> void:
if _is_editor_scene_context():
hide()
return
editor.syntax_highlighter = _create_csharp_highlighter()
editor.text = FileAccess.get_file_as_string(EDIT_SOURCE_PATH)
run_button.pressed.connect(compile_and_reload)
show()
if OS.has_environment(AUTORUN_ENVIRONMENT) and not autorun_started:
autorun_started = true
var code := editor.text
code = code.replace('private const string Version = "flappy-v1";', 'private const string Version = "flappy-v2";')
code = code.replace('private static readonly Color BirdColor = new Color(1.0f, 0.83f, 0.18f, 1.0f);', 'private static readonly Color BirdColor = new Color(1.0f, 0.35f, 0.18f, 1.0f);')
code = code.replace('private const int GapCenter = 170;', 'private const int GapCenter = 130;')
code = code.replace('public int FlapPower { get; set; } = 4;', 'public int FlapPower { get; set; } = 7;')
editor.text = code
compile_and_reload()
func _exit_tree() -> void:
hide()
func _create_csharp_highlighter() -> CodeHighlighter:
var highlighter := CodeHighlighter.new()
highlighter.number_color = Color(0.72, 0.86, 1.0)
highlighter.symbol_color = Color(0.86, 0.86, 0.82)
highlighter.function_color = Color(0.55, 0.82, 1.0)
highlighter.member_variable_color = Color(0.95, 0.75, 0.45)
for keyword in [
"abstract", "as", "base", "break", "case", "catch", "checked", "class", "const", "continue",
"default", "delegate", "do", "else", "enum", "event", "explicit", "extern", "false", "finally",
"fixed", "for", "foreach", "goto", "if", "implicit", "in", "interface", "internal", "is",
"lock", "namespace", "new", "null", "operator", "out", "override", "params", "private", "protected",
"public", "readonly", "ref", "return", "sealed", "sizeof", "stackalloc", "static", "struct", "switch",
"this", "throw", "true", "try", "typeof", "unchecked", "unsafe", "using", "virtual", "void",
"volatile", "while", "partial", "get", "set", "value", "async", "await", "yield",
]:
highlighter.add_keyword_color(keyword, Color(0.95, 0.48, 0.72))
for type_keyword in [
"bool", "byte", "char", "decimal", "double", "float", "int", "long", "object", "sbyte",
"short", "string", "uint", "ulong", "ushort", "var", "dynamic", "Node", "Color", "Vector2",
"Vector3", "TextureRect", "ColorRect", "Label", "FileAccess", "GD", "System",
]:
highlighter.add_member_keyword_color(type_keyword, Color(0.45, 0.9, 0.7))
highlighter.add_color_region("//", "", Color(0.45, 0.55, 0.48), true)
highlighter.add_color_region("/*", "*/", Color(0.45, 0.55, 0.48), false)
highlighter.add_color_region("\"", "\"", Color(0.98, 0.82, 0.52), false)
highlighter.add_color_region("'", "'", Color(0.98, 0.82, 0.52), false)
return highlighter
func compile_and_reload() -> void:
if editor == null:
_set_status("Editor is not ready.")
return
if not _write_text_file(EDIT_SOURCE_PATH, editor.text):
_set_status("Failed to write HotReloadSmoke.cs")
return
var project_root := ProjectSettings.globalize_path("res://").trim_suffix("/")
var assembly_name := "GameRuntimeEdit" + str(Time.get_unix_time_from_system()).replace(".", "")
var project_file := project_root.path_join("Game.csproj")
var framework_path := project_root.path_join(FRAMEWORK_RELATIVE_PATH).simplify_path()
_set_status("Building " + assembly_name + "...")
if not _build_assembly(project_file, assembly_name, framework_path):
return
var leanclr_dir := project_root.path_join("leanclr")
var versioned_path := leanclr_dir.path_join(assembly_name + ".dll")
var cached_versioned_path := OS.get_user_data_dir().path_join(assembly_name + ".dll")
if not _copy_file(versioned_path, cached_versioned_path):
_set_status("Built " + assembly_name + ", but failed to preserve the reload assembly.")
return
if not _build_assembly(project_file, "Game", framework_path):
return
if not _copy_file(cached_versioned_path, versioned_path):
_set_status("Built Game, but failed to restore " + assembly_name + ".")
return
if not _write_text_file(RELOAD_MARKER_PATH, assembly_name + "\n"):
_set_status("Built " + assembly_name + ", but failed to update reload marker.")
return
_set_status("Loaded marker for " + assembly_name)
print("LeanCLR runtime editor: requested assembly = ", assembly_name)
func _build_assembly(project_file: String, assembly_name: String, framework_path: String) -> bool:
var build_args := [
"msbuild",
project_file,
"/p:Configuration=Debug",
"/p:AssemblyName=" + assembly_name,
"/p:OutputPath=leanclr/",
"/p:FrameworkPathOverride=" + framework_path,
]
print("LeanCLR runtime editor: build command = dotnet ", " ".join(build_args))
var output: Array = []
var exit_code := OS.execute("dotnet", build_args, output, true, false)
if exit_code != 0:
_set_status("Build failed: exit code " + str(exit_code))
printerr("LeanCLR runtime editor: build failed with exit code ", exit_code)
for line in output:
printerr(line)
return false
return true
func _copy_file(source_path: String, target_path: String) -> bool:
var source := FileAccess.open(source_path, FileAccess.READ)
if source == null:
printerr("LeanCLR runtime editor: failed to open copy source ", source_path, " error = ", FileAccess.get_open_error())
return false
var bytes := source.get_buffer(source.get_length())
source.close()
var target := FileAccess.open(target_path, FileAccess.WRITE)
if target == null:
printerr("LeanCLR runtime editor: failed to open copy target ", target_path, " error = ", FileAccess.get_open_error())
return false
target.store_buffer(bytes)
target.flush()
target.close()
return true
func _write_text_file(path: String, text: String) -> bool:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
printerr("LeanCLR runtime editor: failed to open ", path, " error = ", FileAccess.get_open_error())
return false
file.store_string(text)
file.flush()
file.close()
return true
func _set_status(status: String) -> void:
if status_label != null:
status_label.text = status
print("LeanCLR runtime editor: ", status)
func _is_editor_scene_context() -> bool:
if Engine.is_editor_hint():
return true
var tree := get_tree()
return tree != null and tree.edited_scene_root != null
@@ -0,0 +1 @@
uid://b626vql61yl6r
+546
View File
@@ -0,0 +1,546 @@
using Godot;
namespace Game;
public partial class ScriptMain : Node2D
{
[Export(PropertyHint.Range, "0,1000,1")]
public int InspectorNumber { get; set; } = 7;
private bool processPrinted;
private bool physicsProcessPrinted;
private bool inputPrinted;
private bool guiInputPrinted;
public override void _EnterTree()
{
GD.Print("LeanCLR demo: _EnterTree owner name = " + Name);
}
public override void _ExitTree()
{
GD.Print("LeanCLR demo: _ExitTree owner name = " + Name);
}
public override void _Ready()
{
StringName ownerName = Name;
GD.Print("LeanCLR demo: ScriptLanguage owner name = " + ownerName.ToString());
Position = new Vector2(12.0f, 34.0f);
Scale = new Vector2(2.0f, 3.0f);
SetModulate(new Color(0.25f, 0.5f, 0.75f, 1.0f));
Color modulate = GetModulate();
GD.Print("LeanCLR demo: position = " + Position.ToString());
GD.Print("LeanCLR demo: scale = " + Scale.ToString());
GD.Print("LeanCLR demo: modulate color = " + modulate.ToString());
Label label = GetNode<Label>(new NodePath("UiLabel"));
label.Text = "LeanCLR Label";
label.Position = new Vector2(7.0f, 8.0f);
label.Size = new Vector2(120.0f, 24.0f);
label.SetHorizontalAlignment(HorizontalAlignment.Center);
label.SetHSizeFlags(Control.SizeFlags.SizeExpandFill);
label.SetJustificationFlags(TextServer.JustificationFlag.JustificationWordBound | TextServer.JustificationFlag.JustificationTrimEdgeSpaces);
label.Hide();
label.Show();
GD.Print("LeanCLR demo: label text = " + label.Text);
GD.Print("LeanCLR demo: label position = " + label.Position.ToString());
GD.Print("LeanCLR demo: label h alignment = " + label.GetHorizontalAlignment().ToString());
GD.Print("LeanCLR demo: label h size flags = " + label.GetHSizeFlags().ToString());
GD.Print("LeanCLR demo: label justification flags = " + label.GetJustificationFlags().ToString());
GD.Print("LeanCLR demo: label rect = " + label.GetRect().ToString());
GD.Print("LeanCLR demo: node2d transform = " + GetTransform().ToString());
GD.Print("LeanCLR demo: label canvas RID valid = " + label.GetCanvasItem().IsValid().ToString());
Font defaultFont = label.GetThemeDefaultFont();
GD.Print("LeanCLR demo: label default font exists = " + (defaultFont != null).ToString());
Font defaultFontAgain = label.GetThemeDefaultFont();
GD.Print("LeanCLR demo: wrapper identity cache = " + object.ReferenceEquals(defaultFont, defaultFontAgain).ToString());
if (defaultFont != null)
{
defaultFont.Dispose();
}
GD.Print("LeanCLR demo: label visible = " + label.IsVisible().ToString());
GD.Print("LeanCLR demo: script property initial = " + Get(new StringName("InspectorNumber")).AsInt64().ToString());
Set(new StringName("InspectorNumber"), new Variant(314));
GD.Print("LeanCLR demo: script property updated = " + InspectorNumber.ToString());
GodotArray scriptProperties = GetPropertyList();
GD.Print("LeanCLR demo: script property list exists = " + (scriptProperties != null).ToString());
if (scriptProperties != null)
{
for (int i = 0; i < scriptProperties.Count; ++i)
{
Dictionary propertyInfo = scriptProperties[i].AsDictionary();
if (propertyInfo[new Variant("name")].ToString() == "InspectorNumber")
{
GD.Print("LeanCLR demo: script property hint = " + propertyInfo[new Variant("hint")].ToString());
GD.Print("LeanCLR demo: script property hint string = " + propertyInfo[new Variant("hint_string")].ToString());
}
}
scriptProperties.Dispose();
}
string sceneUniqueId = Resource.GenerateSceneUniqueId();
GD.Print("LeanCLR demo: resource id length > 0 = " + (sceneUniqueId.Length > 0).ToString());
Variant metaValue = new Variant("LeanCLR Variant");
SetMeta(new StringName("leanclr_meta"), metaValue);
GD.Print("LeanCLR demo: variant meta exists = " + HasMeta(new StringName("leanclr_meta")).ToString());
Variant metaRoundtrip = GetMeta(new StringName("leanclr_meta"));
GD.Print("LeanCLR demo: variant meta = " + metaRoundtrip.ToString());
metaRoundtrip.Dispose();
metaValue.Dispose();
Variant callMetaKey = new Variant("leanclr_call_meta");
Variant callMetaValue = new Variant("LeanCLR Call Variant");
Call(new StringName("set_meta"), callMetaKey, callMetaValue).Dispose();
Variant callMetaRoundtrip = Call(new StringName("get_meta"), callMetaKey);
GD.Print("LeanCLR demo: object call variant meta = " + callMetaRoundtrip.ToString());
callMetaRoundtrip.Dispose();
callMetaValue.Dispose();
callMetaKey.Dispose();
AddToGroup(new StringName("leanclr_vararg_group"));
Variant groupMetaKey = new Variant("leanclr_group_meta");
Variant groupMetaValue = new Variant("LeanCLR Group Variant");
GetTree().CallGroup(new StringName("leanclr_vararg_group"), new StringName("set_meta"), groupMetaKey, groupMetaValue);
Variant groupMetaRoundtrip = GetMeta(new StringName("leanclr_group_meta"));
GD.Print("LeanCLR demo: call group variant meta = " + groupMetaRoundtrip.ToString());
groupMetaRoundtrip.Dispose();
groupMetaValue.Dispose();
groupMetaKey.Dispose();
Variant jsonValue = new Variant("LeanCLR JSON");
string jsonText = JSON.Stringify(jsonValue);
GD.Print("LeanCLR demo: variant json = " + jsonText);
jsonValue.Dispose();
Variant parsedJson = JSON.ParseString("123");
GD.Print("LeanCLR demo: parsed variant = " + parsedJson.ToString());
parsedJson.Dispose();
GD.Print("LeanCLR demo: project file exists = " + FileAccess.FileExists("res://project.godot").ToString());
GD.Print("LeanCLR demo: project file text length > 0 = " + (FileAccess.GetFileAsString("res://project.godot").Length > 0).ToString());
PackedByteArray projectBytes = FileAccess.GetFileAsBytes("res://project.godot");
Variant packedByteVariant = new Variant(projectBytes);
GD.Print("LeanCLR demo: variant packed byte array type = " + packedByteVariant.VariantType.ToString());
GD.Print("LeanCLR demo: packed byte array wrapper exists = " + (packedByteVariant.AsPackedByteArray() != null).ToString());
packedByteVariant.Dispose();
PackedByteArray manualBytes = new PackedByteArray();
manualBytes.Add(1);
manualBytes.Add(2);
manualBytes[1] = 3;
GD.Print("LeanCLR demo: packed byte manual count = " + manualBytes.Count.ToString());
GD.Print("LeanCLR demo: packed byte manual second = " + manualBytes[1].ToString());
manualBytes.Clear();
GD.Print("LeanCLR demo: packed byte manual cleared = " + manualBytes.Count.ToString());
manualBytes.Dispose();
projectBytes.Dispose();
DirAccess projectDir = DirAccess.Open("res://");
GD.Print("LeanCLR demo: dir access open = " + (projectDir != null).ToString());
GD.Print("LeanCLR demo: dir open error = " + DirAccess.GetOpenError().ToString());
GD.Print("LeanCLR demo: dir drive count >= 0 = " + (DirAccess.GetDriveCount() >= 0).ToString());
if (projectDir != null)
{
projectDir.Dispose();
}
PackedStringArray projectFiles = DirAccess.GetFilesAt("res://");
bool foundProjectFile = false;
for (int i = 0; i < projectFiles.Count; ++i)
{
if (projectFiles[i] == "project.godot")
{
foundProjectFile = true;
}
}
GD.Print("LeanCLR demo: packed files count > 0 = " + (projectFiles.Count > 0).ToString());
GD.Print("LeanCLR demo: packed files include project = " + foundProjectFile.ToString());
projectFiles.Dispose();
PackedInt32Array packedInts = new PackedInt32Array();
packedInts.Add(4);
packedInts[0] = 5;
GD.Print("LeanCLR demo: packed int32 first = " + packedInts[0].ToString());
packedInts.Dispose();
PackedInt64Array packedLongs = new PackedInt64Array();
packedLongs.Add(6L);
GD.Print("LeanCLR demo: packed int64 count = " + packedLongs.Count.ToString());
packedLongs.Dispose();
PackedFloat32Array packedFloats = new PackedFloat32Array();
packedFloats.Add(1.25f);
GD.Print("LeanCLR demo: packed float32 first = " + packedFloats[0].ToString());
packedFloats.Dispose();
PackedFloat64Array packedDoubles = new PackedFloat64Array();
packedDoubles.Add(2.5);
GD.Print("LeanCLR demo: packed float64 first = " + packedDoubles[0].ToString());
packedDoubles.Dispose();
PackedVector2Array packedVector2s = new PackedVector2Array();
packedVector2s.Add(new Vector2(1.0f, 2.0f));
GD.Print("LeanCLR demo: packed vector2 first = " + packedVector2s[0].ToString());
packedVector2s.Dispose();
PackedVector3Array packedVector3s = new PackedVector3Array();
packedVector3s.Add(new Vector3(3.0f, 4.0f, 5.0f));
GD.Print("LeanCLR demo: packed vector3 first = " + packedVector3s[0].ToString());
packedVector3s.Dispose();
PackedColorArray packedColors = new PackedColorArray();
packedColors.Add(new Color(0.6f, 0.7f, 0.8f, 1.0f));
GD.Print("LeanCLR demo: packed color first = " + packedColors[0].ToString());
packedColors.Dispose();
PackedStringArray csvValues = new PackedStringArray(new string[] { "alpha", "beta" });
GD.Print("LeanCLR demo: packed manual first = " + csvValues[0]);
FileAccess csvFile = FileAccess.Open("user://leanclr_packed.csv", FileAccess.ModeFlags.Write);
bool csvStored = csvFile != null && csvFile.StoreCsvLine(csvValues);
GD.Print("LeanCLR demo: packed csv stored = " + csvStored.ToString());
if (csvFile != null)
{
csvFile.Dispose();
}
csvValues.Dispose();
Image image = Image.CreateEmpty(4, 4, false, Image.Format.Rgba8);
image.SetPixel(2, 1, new Color(1.0f, 0.0f, 0.0f, 1.0f));
GD.Print("LeanCLR demo: image used rect = " + image.GetUsedRect().ToString());
image.Dispose();
PhysicsRayQueryParameters3D rayQuery = PhysicsRayQueryParameters3D.Create(new Vector3(1.0f, 2.0f, 3.0f), new Vector3(4.0f, 5.0f, 6.0f));
GD.Print("LeanCLR demo: physics ray 3d query exists = " + (rayQuery != null).ToString());
if (rayQuery != null)
{
GD.Print("LeanCLR demo: physics ray 3d from = " + rayQuery.GetFrom().ToString());
GD.Print("LeanCLR demo: physics ray 3d to = " + rayQuery.GetTo().ToString());
rayQuery.Dispose();
}
Node3D node3D = GetNode<Node3D>(new NodePath("Node3DDemo"));
node3D.SetPosition(new Vector3(7.0f, 8.0f, 9.0f));
node3D.SetQuaternion(Quaternion.Identity);
node3D.SetBasis(Basis.Identity);
node3D.SetTransform(new Transform3D(Basis.Identity, new Vector3(10.0f, 11.0f, 12.0f)));
GD.Print("LeanCLR demo: node3d position = " + node3D.GetPosition().ToString());
GD.Print("LeanCLR demo: node3d quaternion = " + node3D.GetQuaternion().ToString());
GD.Print("LeanCLR demo: node3d basis = " + node3D.GetBasis().ToString());
GD.Print("LeanCLR demo: node3d transform = " + node3D.GetTransform().ToString());
CPUParticles3D particles3D = GetNode<CPUParticles3D>(new NodePath("CpuParticles3DDemo"));
particles3D.SetVisibilityAabb(new Aabb(new Vector3(1.0f, 2.0f, 3.0f), new Vector3(4.0f, 5.0f, 6.0f)));
GD.Print("LeanCLR demo: particles visibility aabb = " + particles3D.GetVisibilityAabb().ToString());
Camera3D camera3D = GetNode<Camera3D>(new NodePath("Camera3DDemo"));
GD.Print("LeanCLR demo: camera transform = " + camera3D.GetCameraTransform().ToString());
Projection cameraProjection = camera3D.GetCameraProjection();
GD.Print("LeanCLR demo: camera projection = " + cameraProjection.ToString());
Variant projectionVariant = new Variant(cameraProjection);
GD.Print("LeanCLR demo: variant projection type = " + projectionVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant projection = " + projectionVariant.AsProjection().ToString());
projectionVariant.Dispose();
Plane demoPlane = new Plane(new Vector3(0.0f, 1.0f, 0.0f), 2.0f);
Variant planeVariant = new Variant(demoPlane);
GD.Print("LeanCLR demo: variant plane type = " + planeVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant plane = " + planeVariant.AsPlane().ToString());
planeVariant.Dispose();
Sprite2D sprite = GetNode<Sprite2D>(new NodePath("SpriteDemo"));
Texture2D spriteTexture = sprite.GetTexture();
sprite.Centered = false;
sprite.Offset = new Vector2(5.0f, 6.0f);
sprite.FlipH = true;
sprite.SetHframes(2);
sprite.SetVframes(2);
sprite.SetFrameCoords(new Vector2i(1, 1));
SetRotationDegrees(15.0f);
GD.Print("LeanCLR demo: sprite centered = " + sprite.Centered.ToString());
GD.Print("LeanCLR demo: sprite texture is null = " + (spriteTexture == null).ToString());
GD.Print("LeanCLR demo: sprite offset = " + sprite.Offset.ToString());
GD.Print("LeanCLR demo: sprite flip h = " + sprite.FlipH.ToString());
GD.Print("LeanCLR demo: sprite frame coords = " + sprite.GetFrameCoords().ToString());
GD.Print("LeanCLR demo: rotation degrees = " + GetRotationDegrees().ToString());
NodePath childPath = new NodePath("Child");
Node child = GetNode<Node>(childPath);
GD.Print("LeanCLR demo: child before QueueFree = " + child.Name);
GD.Print("LeanCLR demo: child parent = " + child.GetParent().Name);
GD.Print("LeanCLR demo: child count = " + GetChildCount().ToString());
GD.Print("LeanCLR demo: inside tree = " + IsInsideTree().ToString());
GD.Print("LeanCLR demo: tree node count > 0 = " + (GetTree().GetNodeCount() > 0).ToString());
SetProcessMode(Node.ProcessMode.Always);
GD.Print("LeanCLR demo: process mode = " + GetProcessMode().ToString());
Variant selfVariant = new Variant(this);
Node2D selfObject = selfVariant.AsObject<Node2D>();
GD.Print("LeanCLR demo: variant object name = " + selfObject.Name.ToString());
selfVariant.Dispose();
Variant vector2Variant = new Variant(new Vector2(3.0f, 4.0f));
GD.Print("LeanCLR demo: variant vector2 = " + vector2Variant.AsVector2().ToString());
GD.Print("LeanCLR demo: variant vector2 stringify = " + vector2Variant.ToString());
vector2Variant.Dispose();
Variant vector3Variant = new Variant(new Vector3(5.0f, 6.0f, 7.0f));
GD.Print("LeanCLR demo: variant vector3 = " + vector3Variant.AsVector3().ToString());
vector3Variant.Dispose();
Variant stringNameVariant = new Variant(new StringName("leanclr_name"));
GD.Print("LeanCLR demo: variant string name type = " + stringNameVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant string name = " + stringNameVariant.AsStringName().ToString());
stringNameVariant.Dispose();
Variant nodePathVariant = new Variant(new NodePath("Child"));
GD.Print("LeanCLR demo: variant node path type = " + nodePathVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant node path = " + nodePathVariant.AsNodePath().ToString());
nodePathVariant.Dispose();
Variant ridVariant = new Variant(new RID(0));
GD.Print("LeanCLR demo: variant rid type = " + ridVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant rid valid = " + ridVariant.AsRID().IsValid().ToString());
ridVariant.Dispose();
PackedStringArray packedStrings = new PackedStringArray(new string[] { "alpha", "beta" });
Variant packedStringArrayVariant = new Variant(packedStrings);
PackedStringArray packedStringsRoundtrip = packedStringArrayVariant.AsPackedStringArray();
GD.Print("LeanCLR demo: variant packed string array type = " + packedStringArrayVariant.VariantType.ToString());
GD.Print("LeanCLR demo: packed string array count = " + packedStringsRoundtrip.Count.ToString());
GD.Print("LeanCLR demo: packed string array first = " + packedStringsRoundtrip[0]);
packedStringsRoundtrip.Dispose();
packedStringArrayVariant.Dispose();
packedStrings.Dispose();
Array demoArray = new Array();
demoArray.Add(new Variant("array first"));
demoArray.Add(new Variant(new Vector2(8.0f, 9.0f)));
Variant arrayVariant = new Variant(demoArray);
GD.Print("LeanCLR demo: variant array type = " + arrayVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant array wrapper exists = " + (arrayVariant.AsArray() != null).ToString());
GD.Print("LeanCLR demo: array count = " + demoArray.Count.ToString());
GD.Print("LeanCLR demo: array first = " + demoArray[0].ToString());
GD.Print("LeanCLR demo: array second = " + demoArray[1].AsVector2().ToString());
Variant arrayInserted = new Variant("array inserted");
demoArray.Insert(1, arrayInserted);
GD.Print("LeanCLR demo: array inserted = " + demoArray[1].ToString());
GD.Print("LeanCLR demo: array contains inserted = " + demoArray.Contains(arrayInserted).ToString());
GD.Print("LeanCLR demo: array inserted index = " + demoArray.IndexOf(arrayInserted).ToString());
int arrayEnumerated = 0;
foreach (Variant item in demoArray)
{
arrayEnumerated++;
}
GD.Print("LeanCLR demo: array enumerated = " + arrayEnumerated.ToString());
demoArray.RemoveAt(1);
GD.Print("LeanCLR demo: array count after remove = " + demoArray.Count.ToString());
demoArray.Clear();
GD.Print("LeanCLR demo: array count after clear = " + demoArray.Count.ToString());
arrayInserted.Dispose();
arrayVariant.Dispose();
demoArray.Dispose();
Dictionary demoDictionary = new Dictionary();
Variant dictionaryKey = new Variant("answer");
demoDictionary[dictionaryKey] = new Variant(42);
Variant dictionaryVariant = new Variant(demoDictionary);
GD.Print("LeanCLR demo: variant dictionary type = " + dictionaryVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant dictionary wrapper exists = " + (dictionaryVariant.AsDictionary() != null).ToString());
GD.Print("LeanCLR demo: dictionary count = " + demoDictionary.Count.ToString());
GD.Print("LeanCLR demo: dictionary has key = " + demoDictionary.ContainsKey(dictionaryKey).ToString());
Variant dictionaryValue = demoDictionary[dictionaryKey];
GD.Print("LeanCLR demo: dictionary value type = " + dictionaryValue.VariantType.ToString());
GD.Print("LeanCLR demo: dictionary value = " + dictionaryValue.ToString());
GD.Print("LeanCLR demo: dictionary value int = " + dictionaryValue.AsInt64().ToString());
Array dictionaryKeys = demoDictionary.Keys;
Array dictionaryValues = demoDictionary.Values;
GD.Print("LeanCLR demo: dictionary keys count = " + dictionaryKeys.Count.ToString());
GD.Print("LeanCLR demo: dictionary values first = " + dictionaryValues[0].ToString());
int dictionaryEnumerated = 0;
foreach (var entry in demoDictionary)
{
if (entry.Key.ToString() == "answer" && entry.Value.AsInt64() == 42)
{
dictionaryEnumerated++;
}
}
GD.Print("LeanCLR demo: dictionary enumerated = " + dictionaryEnumerated.ToString());
GD.Print("LeanCLR demo: dictionary removed = " + demoDictionary.Remove(dictionaryKey).ToString());
GD.Print("LeanCLR demo: dictionary count after remove = " + demoDictionary.Count.ToString());
demoDictionary[dictionaryKey] = new Variant(7);
demoDictionary.Clear();
GD.Print("LeanCLR demo: dictionary count after clear = " + demoDictionary.Count.ToString());
dictionaryKeys.Dispose();
dictionaryValues.Dispose();
dictionaryValue.Dispose();
dictionaryKey.Dispose();
dictionaryVariant.Dispose();
demoDictionary.Dispose();
Callable setMetaCallable = new Callable(this, new StringName("set_meta"));
Variant callableMetaKey = new Variant("leanclr_callable_meta");
Variant callableMetaValue = new Variant("LeanCLR Callable Variant");
setMetaCallable.Call(callableMetaKey, callableMetaValue).Dispose();
Variant callableMetaRoundtrip = GetMeta(new StringName("leanclr_callable_meta"));
GD.Print("LeanCLR demo: callable valid = " + setMetaCallable.IsValid().ToString());
GD.Print("LeanCLR demo: callable method = " + setMetaCallable.GetMethod().ToString());
GD.Print("LeanCLR demo: callable call meta = " + callableMetaRoundtrip.ToString());
callableMetaRoundtrip.Dispose();
callableMetaValue.Dispose();
callableMetaKey.Dispose();
AddUserSignal("leanclr_user_signal");
Signal userSignal = new Signal(this, new StringName("leanclr_user_signal"));
Variant signalMetaKey = new Variant("leanclr_signal_meta");
Variant signalMetaValue = new Variant("LeanCLR Signal Variant");
Callable signalCallable = setMetaCallable.Bind(signalMetaKey, signalMetaValue);
GD.Print("LeanCLR demo: signal name = " + userSignal.GetName().ToString());
GD.Print("LeanCLR demo: signal connect = " + userSignal.Connect(signalCallable, 0).ToString());
userSignal.Emit();
Variant signalMetaRoundtrip = GetMeta(new StringName("leanclr_signal_meta"));
GD.Print("LeanCLR demo: signal emitted meta = " + signalMetaRoundtrip.ToString());
Variant callableVariant = new Variant(signalCallable);
Variant signalVariant = new Variant(userSignal);
GD.Print("LeanCLR demo: variant callable type = " + callableVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant callable valid = " + callableVariant.AsCallable().IsValid().ToString());
GD.Print("LeanCLR demo: variant signal type = " + signalVariant.VariantType.ToString());
GD.Print("LeanCLR demo: variant signal name = " + signalVariant.AsSignal().GetName().ToString());
AddUserSignal("leanclr_managed_signal");
Signal managedSignal = new Signal(this, new StringName("leanclr_managed_signal"));
Callable managedCallable = new Callable(this, new StringName("OnLeanClrManagedSignal"));
GD.Print("LeanCLR demo: managed callable valid = " + managedCallable.IsValid().ToString());
GD.Print("LeanCLR demo: managed signal connect = " + managedSignal.Connect(managedCallable, 0).ToString());
managedSignal.Emit();
Variant managedSignalRoundtrip = GetMeta(new StringName("leanclr_managed_signal_meta"));
GD.Print("LeanCLR demo: managed signal emitted meta = " + managedSignalRoundtrip.ToString());
managedSignalRoundtrip.Dispose();
AddUserSignal("leanclr_managed_arg_signal");
Signal managedArgSignal = new Signal(this, new StringName("leanclr_managed_arg_signal"));
Callable managedArgCallable = new Callable(this, new StringName("OnLeanClrManagedSignalWithArg"));
GD.Print("LeanCLR demo: managed arg callable valid = " + managedArgCallable.IsValid().ToString());
GD.Print("LeanCLR demo: managed arg signal connect = " + managedArgSignal.Connect(managedArgCallable, 0).ToString());
Variant managedArgPayload = new Variant("LeanCLR Managed Signal Payload");
managedArgSignal.Emit(managedArgPayload);
Variant managedArgSignalRoundtrip = GetMeta(new StringName("leanclr_managed_arg_signal_meta"));
GD.Print("LeanCLR demo: managed arg signal emitted meta = " + managedArgSignalRoundtrip.ToString());
managedArgSignalRoundtrip.Dispose();
AddUserSignal("leanclr_managed_multi_signal");
Signal managedMultiSignal = new Signal(this, new StringName("leanclr_managed_multi_signal"));
Callable managedMultiCallable = new Callable(this, new StringName("OnLeanClrManagedSignalWithArgs"));
GD.Print("LeanCLR demo: managed multi callable valid = " + managedMultiCallable.IsValid().ToString());
GD.Print("LeanCLR demo: managed multi signal connect = " + managedMultiSignal.Connect(managedMultiCallable, 0).ToString());
Variant managedMultiFirst = new Variant("LeanCLR");
Variant managedMultiSecond = new Variant("Multi Signal Payload");
managedMultiSignal.Emit(managedMultiFirst, managedMultiSecond);
Variant managedMultiSignalRoundtrip = GetMeta(new StringName("leanclr_managed_multi_signal_meta"));
GD.Print("LeanCLR demo: managed multi signal emitted meta = " + managedMultiSignalRoundtrip.ToString());
managedMultiSignalRoundtrip.Dispose();
Callable typedCallable = new Callable(this, new StringName("OnLeanClrTypedCallable"));
Variant typedCallResult = typedCallable.Call(new Variant("LeanCLR Typed"), new Variant(41), new Variant(1.5), new Variant(new Vector2(2.0f, 3.0f)), new Variant(this));
GD.Print("LeanCLR demo: typed callable result = " + typedCallResult.ToString());
typedCallResult.Dispose();
Callable returnIntCallable = new Callable(this, new StringName("OnLeanClrReturnInt"));
Variant returnIntResult = returnIntCallable.Call(new Variant(41));
GD.Print("LeanCLR demo: return int callable result = " + returnIntResult.ToString());
returnIntResult.Dispose();
returnIntCallable.Dispose();
Callable structCallable = new Callable(this, new StringName("OnLeanClrStructCallable"));
Variant structCallResult = structCallable.Call(new Variant(new StringName("struct_name")), new Variant(new NodePath("Child")), new Variant(new RID(0)), new Variant(new Color(0.1f, 0.2f, 0.3f, 1.0f)), new Variant(Quaternion.Identity), new Variant(Basis.Identity), new Variant(Transform3D.Identity));
GD.Print("LeanCLR demo: struct callable result = " + structCallResult.ToString());
structCallResult.Dispose();
structCallable.Dispose();
Callable returnColorCallable = new Callable(this, new StringName("OnLeanClrReturnColor"));
Variant returnColorResult = returnColorCallable.Call(new Variant(new Color(0.2f, 0.3f, 0.4f, 1.0f)));
GD.Print("LeanCLR demo: return color callable result = " + returnColorResult.AsColor().ToString());
returnColorResult.Dispose();
returnColorCallable.Dispose();
int delegateSignalCount = 0;
Callable delegateCallable = Callable.From(() => { delegateSignalCount = 42; });
AddUserSignal("leanclr_delegate_signal");
Signal delegateSignal = new Signal(this, new StringName("leanclr_delegate_signal"));
GD.Print("LeanCLR demo: delegate callable valid = " + delegateCallable.IsValid().ToString());
GD.Print("LeanCLR demo: delegate signal connect = " + delegateSignal.Connect(delegateCallable, 0).ToString());
delegateSignal.Emit();
GD.Print("LeanCLR demo: delegate signal count = " + delegateSignalCount.ToString());
Callable delegateArgCallable = Callable.From<Variant>((payload) => { SetMeta(new StringName("leanclr_delegate_arg_meta"), payload); });
AddUserSignal("leanclr_delegate_arg_signal");
Signal delegateArgSignal = new Signal(this, new StringName("leanclr_delegate_arg_signal"));
GD.Print("LeanCLR demo: delegate arg signal connect = " + delegateArgSignal.Connect(delegateArgCallable, 0).ToString());
delegateArgSignal.Emit(new Variant("LeanCLR Delegate Payload"));
Variant delegateArgRoundtrip = GetMeta(new StringName("leanclr_delegate_arg_meta"));
GD.Print("LeanCLR demo: delegate arg meta = " + delegateArgRoundtrip.ToString());
delegateArgRoundtrip.Dispose();
delegateArgCallable.Dispose();
delegateArgSignal.Dispose();
delegateCallable.Dispose();
delegateSignal.Emit();
GD.Print("LeanCLR demo: delegate signal count after dispose = " + delegateSignalCount.ToString());
delegateSignal.Dispose();
typedCallable.Dispose();
managedMultiSecond.Dispose();
managedMultiFirst.Dispose();
managedMultiCallable.Dispose();
managedMultiSignal.Dispose();
managedArgPayload.Dispose();
managedArgCallable.Dispose();
managedArgSignal.Dispose();
managedCallable.Dispose();
managedSignal.Dispose();
callableVariant.Dispose();
signalVariant.Dispose();
signalMetaRoundtrip.Dispose();
signalMetaValue.Dispose();
signalMetaKey.Dispose();
signalCallable.Dispose();
userSignal.Dispose();
setMetaCallable.Dispose();
SetPhysicsProcess(true);
SetProcessInput(true);
Call(new StringName("_input"), new Variant((GodotObject)null)).Dispose();
Call(new StringName("_gui_input"), new Variant((GodotObject)null)).Dispose();
child.QueueFree();
}
public void OnLeanClrManagedSignal()
{
SetMeta(new StringName("leanclr_managed_signal_meta"), new Variant("LeanCLR Managed Signal Variant"));
}
public void OnLeanClrManagedSignalWithArg(Variant payload)
{
SetMeta(new StringName("leanclr_managed_arg_signal_meta"), payload);
}
public void OnLeanClrManagedSignalWithArgs(Variant[] payloads)
{
SetMeta(new StringName("leanclr_managed_multi_signal_meta"), new Variant(payloads[0].ToString() + " " + payloads[1].ToString()));
}
public string OnLeanClrTypedCallable(string label, int count, double amount, Vector2 point, Node node)
{
return label + " " + (count + 1).ToString() + " " + amount.ToString() + " " + point.ToString() + " " + node.Name.ToString();
}
public int OnLeanClrReturnInt(int value)
{
return value + 1;
}
public string OnLeanClrStructCallable(StringName name, NodePath path, RID rid, Color color, Quaternion quaternion, Basis basis, Transform3D transform)
{
return name.ToString() + " " + path.ToString() + " " + rid.IsValid().ToString() + " " + color.ToString() + " " + quaternion.ToString() + " " + basis.ToString() + " " + transform.ToString();
}
public Color OnLeanClrReturnColor(Color value)
{
return value;
}
public override void _Process(double delta)
{
if (processPrinted)
{
return;
}
processPrinted = true;
GD.Print("LeanCLR demo: _Process delta > 0 = " + (delta > 0.0).ToString());
}
public override void _PhysicsProcess(float delta)
{
if (physicsProcessPrinted)
{
return;
}
physicsProcessPrinted = true;
GD.Print("LeanCLR demo: _PhysicsProcess delta > 0 = " + (delta > 0.0f).ToString());
}
public override void _Input(InputEvent event_)
{
if (inputPrinted)
{
return;
}
inputPrinted = true;
GD.Print("LeanCLR demo: _Input event wrapper exists = " + (event_ != null).ToString());
}
public void _GuiInput(InputEvent event_)
{
if (guiInputPrinted)
{
return;
}
guiInputPrinted = true;
GD.Print("LeanCLR demo: _GuiInput event wrapper exists = " + (event_ != null).ToString());
}
}
+1
View File
@@ -0,0 +1 @@
uid://c0duyyvvovu5x
+174
View File
@@ -0,0 +1,174 @@
#include "leanclr_hot_reload_host.h"
#include "leanclr_runtime_bridge.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/node_path.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot
{
namespace
{
const char* RELOAD_MARKER_PATH = "res://leanclr/live_reload.txt";
const char* RELOAD_TYPE_NAME = "Game.HotReloadSmoke";
bool is_editor_scene_context()
{
Engine* engine = Engine::get_singleton();
if (engine != nullptr && (engine->is_editor_hint()))
{
return true;
}
PackedStringArray args = OS::get_singleton()->get_cmdline_args();
for (int32_t i = 0; i < args.size(); ++i)
{
if (args[i] == String("--editor") || args[i] == String("-e"))
{
return true;
}
}
return false;
}
}
void LeanCLRHotReloadHost::_bind_methods()
{
ClassDB::bind_method(D_METHOD("forward_input", "event"), &LeanCLRHotReloadHost::forward_input);
}
LeanCLRHotReloadHost::~LeanCLRHotReloadHost()
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
}
void LeanCLRHotReloadHost::_notification(int p_what)
{
if (p_what == NOTIFICATION_READY)
{
if (is_editor_scene_context() || !is_inside_tree() || (get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
set_process(false);
return;
}
set_process(true);
check_reload_marker();
}
else if (p_what == NOTIFICATION_PROCESS)
{
if (is_editor_scene_context() || !is_inside_tree() || (get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
return;
}
const double delta = get_process_delta_time();
if (managed_object != nullptr)
{
LeanCLRRuntimeBridge::invoke_script_process(managed_object, delta);
}
elapsed += delta;
if (elapsed >= 0.25)
{
elapsed = 0.0;
check_reload_marker();
}
}
}
void LeanCLRHotReloadHost::forward_input(const Ref<InputEvent>& p_event)
{
if (managed_object == nullptr || !p_event.is_valid() || is_editor_scene_context() || !is_inside_tree() ||
(get_tree() != nullptr && get_tree()->get_edited_scene_root() != nullptr))
{
return;
}
LeanCLRRuntimeBridge::invoke_script_method(managed_object, "_Input", p_event.ptr());
}
void LeanCLRHotReloadHost::check_reload_marker()
{
String assembly_name = "Game";
if (FileAccess::file_exists(RELOAD_MARKER_PATH))
{
assembly_name = FileAccess::get_file_as_string(RELOAD_MARKER_PATH).strip_edges();
if (assembly_name.is_empty())
{
assembly_name = "Game";
}
}
if (assembly_name == loaded_assembly_name)
{
return;
}
reload_managed_object(assembly_name);
}
void LeanCLRHotReloadHost::reload_managed_object(const String& p_assembly_name)
{
if (p_assembly_name == String("Game"))
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
loaded_assembly_name = p_assembly_name;
UtilityFunctions::print("LeanCLR live reload: using attached script assembly = ", loaded_assembly_name);
return;
}
Object* owner = this;
Node* parent = get_parent();
if (parent != nullptr)
{
Node* script_owner = parent->get_node_or_null(NodePath("FlappyScript"));
if (script_owner != nullptr)
{
owner = script_owner;
}
}
void* previous_object = managed_object != nullptr ? managed_object : LeanCLRRuntimeBridge::get_script_object_for_owner(owner);
Variant custom_state;
const bool has_custom_state = previous_object != nullptr &&
LeanCLRRuntimeBridge::has_script_method(previous_object, "CaptureHotReloadState", 0) &&
LeanCLRRuntimeBridge::invoke_script_method(previous_object, "CaptureHotReloadState", nullptr, 0, &custom_state);
void* next_object = LeanCLRRuntimeBridge::create_script_object(p_assembly_name, RELOAD_TYPE_NAME, owner);
if (next_object == nullptr)
{
UtilityFunctions::printerr("LeanCLR live reload: failed to load ", p_assembly_name, ": ", LeanCLRRuntimeBridge::get_last_error());
return;
}
const int32_t migrated_fields = LeanCLRRuntimeBridge::migrate_script_state(previous_object, next_object);
if (has_custom_state && LeanCLRRuntimeBridge::has_script_method(next_object, "RestoreHotReloadState", 1))
{
LeanCLRRuntimeBridge::invoke_script_method(next_object, "RestoreHotReloadState", custom_state);
}
if (managed_object != nullptr)
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
}
managed_object = next_object;
loaded_assembly_name = p_assembly_name;
UtilityFunctions::print("LeanCLR live reload: loaded assembly = ", loaded_assembly_name);
UtilityFunctions::print("LeanCLR live reload: migrated fields = ", migrated_fields);
LeanCLRRuntimeBridge::invoke_script_ready(managed_object);
LeanCLRRuntimeBridge::invoke_script_method(managed_object, "OnHotReloaded");
}
} // namespace godot
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/variant/string.hpp>
namespace godot
{
class LeanCLRHotReloadHost : public Node
{
GDCLASS(LeanCLRHotReloadHost, Node)
public:
~LeanCLRHotReloadHost();
void forward_input(const Ref<InputEvent>& p_event);
protected:
static void _bind_methods();
void _notification(int p_what);
private:
void check_reload_marker();
void reload_managed_object(const String& p_assembly_name);
void* managed_object = nullptr;
String loaded_assembly_name;
double elapsed = 0.0;
};
} // namespace godot
+40
View File
@@ -0,0 +1,40 @@
#include "leanclr_main_node.h"
#include "leanclr_runtime_bridge.h"
namespace godot
{
void LeanCLRMain::_bind_methods()
{
}
LeanCLRMain::~LeanCLRMain()
{
LeanCLRRuntimeBridge::release_script_object(managed_object);
managed_object = nullptr;
}
void LeanCLRMain::_notification(int p_what)
{
if (p_what != NOTIFICATION_READY || ready_invoked)
{
return;
}
ready_invoked = true;
ensure_managed_object();
LeanCLRRuntimeBridge::invoke_script_ready(managed_object);
}
void LeanCLRMain::ensure_managed_object()
{
if (managed_object != nullptr)
{
return;
}
managed_object = LeanCLRRuntimeBridge::create_script_object("Game", "Game.ClassDbMain", this);
}
} // namespace godot
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <godot_cpp/classes/node.hpp>
namespace godot
{
class LeanCLRMain : public Node
{
GDCLASS(LeanCLRMain, Node)
public:
LeanCLRMain() = default;
~LeanCLRMain();
protected:
static void _bind_methods();
void _notification(int p_what);
private:
void ensure_managed_object();
void* managed_object = nullptr;
bool ready_invoked = false;
};
} // namespace godot
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <godot_cpp/variant/string.hpp>
#include <vector>
namespace godot
{
class Object;
class InputEvent;
class Variant;
struct LeanCLRScriptPropertyInfo
{
String name;
int32_t type = 0;
int32_t hint = 0;
String hint_string;
int32_t usage = 6;
};
class LeanCLRRuntimeBridge
{
public:
static void set_assembly_directory(const String& p_directory);
static String get_assembly_directory();
static bool initialize();
static void shutdown();
static bool is_initialized();
static bool load_assembly(const String& p_assembly_name);
static bool invoke_static_entry(const String& p_assembly_name, const String& p_entry_point);
static void* create_script_object(const String& p_assembly_name, const String& p_type_name, Object* p_owner = nullptr);
static void release_script_object(void* p_managed_object);
static bool has_script_method(void* p_managed_object, const String& p_method, int32_t p_argument_count);
static bool invoke_script_method(void* p_managed_object, const String& p_method);
static bool invoke_script_method(void* p_managed_object, const String& p_method, float p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, double p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant& p_argument);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant* p_arguments, int32_t p_argument_count);
static bool invoke_script_method(void* p_managed_object, const String& p_method, const Variant* p_arguments, int32_t p_argument_count, Variant* r_return);
static bool invoke_script_method(void* p_managed_object, const String& p_method, InputEvent* p_argument);
static bool get_script_property(void* p_managed_object, const String& p_property, Variant* r_value);
static bool set_script_property(void* p_managed_object, const String& p_property, const Variant& p_value);
static bool get_script_property_list(void* p_managed_object, std::vector<LeanCLRScriptPropertyInfo>& r_properties);
static int32_t get_script_property_type(void* p_managed_object, const String& p_property, bool* r_is_valid);
static bool invoke_script_ready(void* p_managed_object);
static bool invoke_script_process(void* p_managed_object, double p_delta);
static void register_script_object(Object* p_owner, void* p_managed_object);
static void unregister_script_object(Object* p_owner, void* p_managed_object);
static void* get_script_object_for_owner(Object* p_owner);
static int32_t migrate_script_state(void* p_source_object, void* p_target_object);
static String get_last_error();
};
} // namespace godot
+773
View File
@@ -0,0 +1,773 @@
#include "leanclr_script.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_script_language.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/script_language.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/gdextension_interface_loader.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/string_name.hpp>
#include <godot_cpp/variant/variant.hpp>
#include <godot_cpp/variant/string.hpp>
#include <gdextension_interface.h>
#include <vector>
namespace godot
{
namespace
{
struct LeanCLRScriptInstance
{
Object* owner = nullptr;
Ref<LeanCLRScript> script;
void* managed_object = nullptr;
bool ready_invoked = false;
};
LeanCLRScriptInstance* as_instance(GDExtensionScriptInstanceDataPtr p_instance)
{
return static_cast<LeanCLRScriptInstance*>(p_instance);
}
String pascal_virtual_name(const StringName& p_godot_method)
{
const String source = String(p_godot_method);
if (!source.begins_with("_"))
{
return String();
}
String result = "_";
PackedStringArray parts = source.substr(1).split("_");
for (int32_t i = 0; i < parts.size(); ++i)
{
String part = parts[i];
if (part.is_empty())
{
continue;
}
if (part == "2d")
{
result += "2D";
}
else if (part == "3d")
{
result += "3D";
}
else if (part == "gui")
{
result += "Gui";
}
else if (part == "rid")
{
result += "Rid";
}
else
{
result += part.substr(0, 1).to_upper() + part.substr(1);
}
}
return result;
}
String managed_virtual_name(const StringName& p_godot_method)
{
return pascal_virtual_name(p_godot_method);
}
bool is_editor_scene_context(Object* p_owner = nullptr)
{
Node* owner_node = Object::cast_to<Node>(p_owner);
if (owner_node != nullptr && owner_node->is_inside_tree() && owner_node->get_tree() != nullptr && owner_node->get_tree()->get_edited_scene_root() != nullptr)
{
return true;
}
Engine* engine = Engine::get_singleton();
if (engine != nullptr && (engine->is_editor_hint()))
{
return true;
}
PackedStringArray args = OS::get_singleton()->get_cmdline_args();
for (int32_t i = 0; i < args.size(); ++i)
{
if (args[i] == String("--editor") || args[i] == String("-e"))
{
return true;
}
}
return false;
}
int32_t managed_virtual_argument_count(const StringName& p_godot_method)
{
if (p_godot_method == StringName("_process") || p_godot_method == StringName("_physics_process") || p_godot_method == StringName("_input") ||
p_godot_method == StringName("_gui_input") || p_godot_method == StringName("_shortcut_input") || p_godot_method == StringName("_unhandled_input") ||
p_godot_method == StringName("_unhandled_key_input") || p_godot_method == StringName("_set_path_cache") ||
p_godot_method == StringName("_make_visible") || p_godot_method == StringName("_edit") || p_godot_method == StringName("_handles") ||
p_godot_method == StringName("_set_state") || p_godot_method == StringName("_get_unsaved_status") || p_godot_method == StringName("_set_window_layout") ||
p_godot_method == StringName("_get_window_layout") || p_godot_method == StringName("_forward_canvas_gui_input") ||
p_godot_method == StringName("_forward_canvas_draw_over_viewport") || p_godot_method == StringName("_forward_canvas_force_draw_over_viewport") ||
p_godot_method == StringName("_forward_3d_draw_over_viewport") || p_godot_method == StringName("_forward_3d_force_draw_over_viewport") ||
p_godot_method == StringName("_run_scene") || p_godot_method == StringName("_has_point") || p_godot_method == StringName("_get_tooltip") ||
p_godot_method == StringName("_get_drag_data") || p_godot_method == StringName("_make_custom_tooltip") ||
p_godot_method == StringName("_get_accessibility_container_name"))
{
return 1;
}
if (p_godot_method == StringName("_forward_3d_gui_input") || p_godot_method == StringName("_structured_text_parser") ||
p_godot_method == StringName("_can_drop_data") || p_godot_method == StringName("_drop_data"))
{
return 2;
}
return 0;
}
GDExtensionBool script_instance_set(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionConstVariantPtr p_value)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
const Variant& value = *reinterpret_cast<const Variant*>(p_value);
return LeanCLRRuntimeBridge::set_script_property(instance->managed_object, String(name), value);
}
GDExtensionBool script_instance_get(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
Variant value;
if (!LeanCLRRuntimeBridge::get_script_property(instance->managed_object, String(name), &value))
{
*reinterpret_cast<Variant*>(r_ret) = Variant();
return false;
}
*reinterpret_cast<Variant*>(r_ret) = value;
return true;
}
const GDExtensionPropertyInfo* script_instance_get_property_list(GDExtensionScriptInstanceDataPtr p_instance, uint32_t* r_count)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
std::vector<LeanCLRScriptPropertyInfo> properties;
if (!LeanCLRRuntimeBridge::get_script_property_list(instance->managed_object, properties) || properties.empty())
{
*r_count = 0;
return nullptr;
}
GDExtensionPropertyInfo* list = new GDExtensionPropertyInfo[properties.size()];
for (size_t i = 0; i < properties.size(); ++i)
{
list[i].type = static_cast<GDExtensionVariantType>(properties[i].type);
list[i].name = reinterpret_cast<GDExtensionStringNamePtr>(new StringName(properties[i].name));
list[i].class_name = reinterpret_cast<GDExtensionStringNamePtr>(new StringName());
list[i].hint = properties[i].hint;
list[i].hint_string = reinterpret_cast<GDExtensionStringPtr>(new String(properties[i].hint_string));
list[i].usage = properties[i].usage;
}
*r_count = static_cast<uint32_t>(properties.size());
return list;
}
void script_instance_free_property_list(GDExtensionScriptInstanceDataPtr p_instance, const GDExtensionPropertyInfo* p_list, uint32_t p_count)
{
(void)p_instance;
for (uint32_t i = 0; i < p_count; ++i)
{
delete reinterpret_cast<StringName*>(p_list[i].name);
delete reinterpret_cast<StringName*>(p_list[i].class_name);
delete reinterpret_cast<String*>(p_list[i].hint_string);
}
delete[] p_list;
}
GDExtensionObjectPtr script_instance_get_owner(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
return instance->owner != nullptr ? instance->owner->_owner : nullptr;
}
void script_instance_get_property_state(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionScriptInstancePropertyStateAdd p_add_func,
void* p_userdata)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
std::vector<LeanCLRScriptPropertyInfo> properties;
LeanCLRRuntimeBridge::get_script_property_list(instance->managed_object, properties);
for (size_t i = 0; i < properties.size(); ++i)
{
StringName name(properties[i].name);
Variant value;
if (LeanCLRRuntimeBridge::get_script_property(instance->managed_object, properties[i].name, &value))
{
p_add_func(&name, &value, p_userdata);
}
}
}
const GDExtensionMethodInfo* script_instance_get_method_list(GDExtensionScriptInstanceDataPtr p_instance, uint32_t* r_count)
{
(void)p_instance;
*r_count = 0;
return nullptr;
}
void script_instance_free_method_list(GDExtensionScriptInstanceDataPtr p_instance, const GDExtensionMethodInfo* p_list, uint32_t p_count)
{
(void)p_instance;
(void)p_list;
(void)p_count;
}
GDExtensionVariantType script_instance_get_property_type(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionBool* r_is_valid)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
bool is_valid = false;
int32_t type = LeanCLRRuntimeBridge::get_script_property_type(instance->managed_object, String(name), &is_valid);
*r_is_valid = is_valid;
return static_cast<GDExtensionVariantType>(type);
}
GDExtensionBool script_instance_has_method(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
String managed_name = managed_virtual_name(name);
if (!managed_name.is_empty())
{
return LeanCLRRuntimeBridge::has_script_method(instance->managed_object, managed_name, managed_virtual_argument_count(name));
}
return LeanCLRRuntimeBridge::has_script_method(instance->managed_object, String(name), 0) ||
LeanCLRRuntimeBridge::has_script_method(instance->managed_object, String(name), 1);
}
void script_instance_invoke_ready(LeanCLRScriptInstance* p_instance)
{
if (p_instance->ready_invoked)
{
return;
}
p_instance->ready_invoked = true;
LeanCLRRuntimeBridge::invoke_script_ready(p_instance->managed_object);
}
GDExtensionInt script_instance_get_method_argument_count(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_name,
GDExtensionBool* r_is_valid)
{
*r_is_valid = script_instance_has_method(p_instance, p_name);
const StringName& name = *reinterpret_cast<const StringName*>(p_name);
String managed_name = managed_virtual_name(name);
if (!managed_name.is_empty())
{
return managed_virtual_argument_count(name);
}
return LeanCLRRuntimeBridge::has_script_method(as_instance(p_instance)->managed_object, String(name), 1) ? 1 : 0;
}
void script_instance_call(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionConstStringNamePtr p_method,
const GDExtensionConstVariantPtr* p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return,
GDExtensionCallError* r_error)
{
const StringName& method = *reinterpret_cast<const StringName*>(p_method);
LeanCLRScriptInstance* instance = as_instance(p_instance);
if (is_editor_scene_context(instance->owner) && !instance->script->_is_tool() && String(method).begins_with("_"))
{
*reinterpret_cast<Variant*>(r_return) = Variant();
r_error->error = GDEXTENSION_CALL_ERROR_INVALID_METHOD;
return;
}
String managed_name = managed_virtual_name(method);
if (managed_name.is_empty())
{
const String method_name = String(method);
if (LeanCLRRuntimeBridge::has_script_method(instance->managed_object, method_name, static_cast<int32_t>(p_argument_count)) ||
(p_argument_count > 1 && LeanCLRRuntimeBridge::has_script_method(instance->managed_object, method_name, 1)))
{
std::vector<Variant> arguments;
arguments.reserve(static_cast<size_t>(p_argument_count));
for (GDExtensionInt i = 0; i < p_argument_count; ++i)
{
arguments.push_back(*reinterpret_cast<const Variant*>(p_args[i]));
}
Variant return_value;
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, method_name, arguments.data(), static_cast<int32_t>(arguments.size()), &return_value);
*reinterpret_cast<Variant*>(r_return) = return_value;
r_error->error = GDEXTENSION_CALL_OK;
return;
}
else
{
r_error->error = GDEXTENSION_CALL_ERROR_INVALID_METHOD;
}
}
else
{
if (method == StringName("_ready") && p_argument_count == 0)
{
script_instance_invoke_ready(instance);
r_error->error = GDEXTENSION_CALL_OK;
}
else
{
std::vector<Variant> arguments;
arguments.reserve(static_cast<size_t>(p_argument_count));
for (GDExtensionInt i = 0; i < p_argument_count; ++i)
{
arguments.push_back(*reinterpret_cast<const Variant*>(p_args[i]));
}
Variant return_value;
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, managed_name, arguments.data(), static_cast<int32_t>(arguments.size()), &return_value);
*reinterpret_cast<Variant*>(r_return) = return_value;
r_error->error = GDEXTENSION_CALL_OK;
return;
}
}
*reinterpret_cast<Variant*>(r_return) = Variant();
}
void script_instance_notification(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what, GDExtensionBool p_reversed)
{
if (p_reversed)
{
return;
}
LeanCLRScriptInstance* instance = as_instance(p_instance);
if (is_editor_scene_context(instance->owner) && !instance->script->_is_tool())
{
return;
}
if (p_what == Node::NOTIFICATION_READY)
{
script_instance_invoke_ready(instance);
}
else if (p_what == Node::NOTIFICATION_EXIT_TREE)
{
LeanCLRRuntimeBridge::invoke_script_method(instance->managed_object, "_ExitTree");
}
}
void script_instance_to_string(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionBool* r_is_valid, GDExtensionStringPtr r_out)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
*reinterpret_cast<String*>(r_out) = "<LeanCLRScriptInstance:" + instance->script->get_type_name() + ">";
*r_is_valid = true;
}
GDExtensionObjectPtr script_instance_get_script(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
return instance->script.ptr() != nullptr ? instance->script->_owner : nullptr;
}
GDExtensionBool script_instance_is_placeholder(GDExtensionScriptInstanceDataPtr p_instance)
{
(void)p_instance;
return false;
}
GDExtensionScriptLanguagePtr script_instance_get_language(GDExtensionScriptInstanceDataPtr p_instance)
{
(void)p_instance;
ScriptLanguage* language = LeanCLRScriptLanguage::get_singleton();
return language != nullptr ? language->_owner : nullptr;
}
void script_instance_free(GDExtensionScriptInstanceDataPtr p_instance)
{
LeanCLRScriptInstance* instance = as_instance(p_instance);
LeanCLRRuntimeBridge::unregister_script_object(instance->owner, instance->managed_object);
LeanCLRRuntimeBridge::release_script_object(instance->managed_object);
delete instance;
}
const GDExtensionScriptInstanceInfo3& script_instance_info()
{
static const GDExtensionScriptInstanceInfo3 info = {
script_instance_set,
script_instance_get,
script_instance_get_property_list,
script_instance_free_property_list,
nullptr,
nullptr,
nullptr,
script_instance_get_owner,
script_instance_get_property_state,
script_instance_get_method_list,
script_instance_free_method_list,
script_instance_get_property_type,
nullptr,
script_instance_has_method,
script_instance_get_method_argument_count,
script_instance_call,
script_instance_notification,
script_instance_to_string,
nullptr,
nullptr,
script_instance_get_script,
script_instance_is_placeholder,
script_instance_set,
script_instance_get,
script_instance_get_language,
script_instance_free,
};
return info;
}
} // namespace
void LeanCLRScript::_bind_methods()
{
ClassDB::bind_method(D_METHOD("get_assembly_name"), &LeanCLRScript::get_assembly_name);
ClassDB::bind_method(D_METHOD("get_type_name"), &LeanCLRScript::get_type_name);
ClassDB::bind_method(D_METHOD("get_entry_point"), &LeanCLRScript::get_entry_point);
}
bool LeanCLRScript::_editor_can_reload_from_file()
{
return true;
}
bool LeanCLRScript::_can_instantiate() const
{
return valid;
}
Ref<Script> LeanCLRScript::_get_base_script() const
{
return Ref<Script>();
}
StringName LeanCLRScript::_get_global_name() const
{
return StringName(type_name);
}
bool LeanCLRScript::_inherits_script(const Ref<Script>& p_script) const
{
return p_script.ptr() == this;
}
StringName LeanCLRScript::_get_instance_base_type() const
{
return base_type;
}
void* LeanCLRScript::_instance_create(Object* p_for_object) const
{
if (!valid || p_for_object == nullptr)
{
return nullptr;
}
void* managed_object = LeanCLRRuntimeBridge::create_script_object(assembly_name, type_name, p_for_object);
if (managed_object == nullptr)
{
return nullptr;
}
LeanCLRScriptInstance* instance = new LeanCLRScriptInstance;
instance->owner = p_for_object;
instance->script = Ref<LeanCLRScript>(const_cast<LeanCLRScript*>(this));
instance->managed_object = managed_object;
LeanCLRRuntimeBridge::register_script_object(p_for_object, managed_object);
void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance);
if (script_instance == nullptr)
{
LeanCLRRuntimeBridge::unregister_script_object(p_for_object, managed_object);
LeanCLRRuntimeBridge::release_script_object(managed_object);
delete instance;
}
return script_instance;
}
void* LeanCLRScript::_placeholder_instance_create(Object* p_for_object) const
{
(void)p_for_object;
return nullptr;
}
bool LeanCLRScript::_has_source_code() const
{
return !source_code.is_empty();
}
String LeanCLRScript::_get_source_code() const
{
return source_code;
}
void LeanCLRScript::_set_source_code(const String& p_code)
{
source_code = p_code;
parse_source();
}
Error LeanCLRScript::_reload(bool p_keep_state)
{
(void)p_keep_state;
parse_source();
return valid ? OK : ERR_PARSE_ERROR;
}
StringName LeanCLRScript::_get_doc_class_name() const
{
return StringName(type_name);
}
TypedArray<Dictionary> LeanCLRScript::_get_documentation() const
{
return TypedArray<Dictionary>();
}
String LeanCLRScript::_get_class_icon_path() const
{
return String();
}
bool LeanCLRScript::_has_method(const StringName& p_method) const
{
return p_method == StringName("_ready") || p_method == StringName("_process");
}
bool LeanCLRScript::_has_static_method(const StringName& p_method) const
{
(void)p_method;
return false;
}
Dictionary LeanCLRScript::_get_method_info(const StringName& p_method) const
{
Dictionary method;
method["name"] = p_method;
return method;
}
bool LeanCLRScript::_is_tool() const
{
return false;
}
bool LeanCLRScript::_is_valid() const
{
return valid;
}
bool LeanCLRScript::_is_abstract() const
{
return false;
}
ScriptLanguage* LeanCLRScript::_get_language() const
{
return LeanCLRScriptLanguage::get_singleton();
}
bool LeanCLRScript::_has_script_signal(const StringName& p_signal) const
{
(void)p_signal;
return false;
}
TypedArray<Dictionary> LeanCLRScript::_get_script_signal_list() const
{
return TypedArray<Dictionary>();
}
bool LeanCLRScript::_has_property_default_value(const StringName& p_property) const
{
(void)p_property;
return false;
}
Variant LeanCLRScript::_get_property_default_value(const StringName& p_property) const
{
(void)p_property;
return Variant();
}
void LeanCLRScript::_update_exports()
{
}
TypedArray<Dictionary> LeanCLRScript::_get_script_method_list() const
{
TypedArray<Dictionary> methods;
Dictionary method;
method["name"] = StringName("_ready");
methods.push_back(method);
Dictionary process_method;
process_method["name"] = StringName("_process");
methods.push_back(process_method);
return methods;
}
TypedArray<Dictionary> LeanCLRScript::_get_script_property_list() const
{
return TypedArray<Dictionary>();
}
int32_t LeanCLRScript::_get_member_line(const StringName& p_member) const
{
(void)p_member;
return -1;
}
Dictionary LeanCLRScript::_get_constants() const
{
return Dictionary();
}
TypedArray<StringName> LeanCLRScript::_get_members() const
{
return TypedArray<StringName>();
}
bool LeanCLRScript::_is_placeholder_fallback_enabled() const
{
return false;
}
Variant LeanCLRScript::_get_rpc_config() const
{
return Variant();
}
void LeanCLRScript::set_path_hint(const String& p_path)
{
path_hint = p_path;
}
String LeanCLRScript::get_assembly_name() const
{
return assembly_name;
}
String LeanCLRScript::get_type_name() const
{
return type_name;
}
String LeanCLRScript::get_entry_point() const
{
return entry_point;
}
void LeanCLRScript::parse_source()
{
assembly_name = String();
type_name = String();
entry_point = String();
base_type = "Node";
const String extension = path_hint.get_extension().to_lower();
if (extension == "cs")
{
assembly_name = "Game";
String namespace_name;
String class_name;
PackedStringArray lines = source_code.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
const String line = lines[i].strip_edges();
if (line.begins_with("// @assembly "))
{
assembly_name = line.substr(12).strip_edges();
}
else if (line.begins_with("// @type "))
{
type_name = line.substr(9).strip_edges();
}
else if (line.begins_with("// @base "))
{
base_type = StringName(line.substr(9).strip_edges());
}
else if (namespace_name.is_empty() && line.begins_with("namespace "))
{
namespace_name = line.substr(10).strip_edges().trim_suffix(";").trim_suffix("{").strip_edges();
}
else if (class_name.is_empty() && (line.find(" class ") >= 0 || line.begins_with("class ")))
{
PackedStringArray tokens = line.replace(":", " : ").split(" ", false);
for (int64_t token_index = 0; token_index + 1 < tokens.size(); ++token_index)
{
if (tokens[token_index] == "class")
{
class_name = tokens[token_index + 1].get_slice("<", 0).strip_edges();
break;
}
}
}
}
if (type_name.is_empty())
{
if (class_name.is_empty() && !path_hint.is_empty())
{
class_name = path_hint.get_file().get_basename();
}
type_name = namespace_name.is_empty() ? class_name : namespace_name + "." + class_name;
}
valid = !assembly_name.is_empty() && !type_name.is_empty();
return;
}
PackedStringArray lines = source_code.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
PackedStringArray pair = lines[i].strip_edges().split("=", false, 1);
if (pair.size() != 2)
{
continue;
}
const String key = pair[0].strip_edges().to_lower();
const String value = pair[1].strip_edges();
if (key == "assembly")
{
assembly_name = value;
}
else if (key == "type")
{
type_name = value;
}
else if (key == "base")
{
base_type = StringName(value);
}
else if (key == "entry")
{
entry_point = value;
}
}
valid = !assembly_name.is_empty() && !type_name.is_empty();
if (!valid && !path_hint.is_empty())
{
type_name = path_hint.get_file().get_basename();
}
}
} // namespace godot
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <godot_cpp/classes/script_language.hpp>
#include <godot_cpp/classes/script_extension.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/typed_array.hpp>
namespace godot
{
class LeanCLRScript : public ScriptExtension
{
GDCLASS(LeanCLRScript, ScriptExtension)
public:
bool _editor_can_reload_from_file() override;
bool _can_instantiate() const override;
Ref<Script> _get_base_script() const override;
StringName _get_global_name() const override;
bool _inherits_script(const Ref<Script>& p_script) const override;
StringName _get_instance_base_type() const override;
void* _instance_create(Object* p_for_object) const override;
void* _placeholder_instance_create(Object* p_for_object) const override;
bool _has_source_code() const override;
String _get_source_code() const override;
void _set_source_code(const String& p_code) override;
Error _reload(bool p_keep_state) override;
StringName _get_doc_class_name() const override;
TypedArray<Dictionary> _get_documentation() const override;
String _get_class_icon_path() const override;
bool _has_method(const StringName& p_method) const override;
bool _has_static_method(const StringName& p_method) const override;
Dictionary _get_method_info(const StringName& p_method) const override;
bool _is_tool() const override;
bool _is_valid() const override;
bool _is_abstract() const override;
ScriptLanguage* _get_language() const override;
bool _has_script_signal(const StringName& p_signal) const override;
TypedArray<Dictionary> _get_script_signal_list() const override;
bool _has_property_default_value(const StringName& p_property) const override;
Variant _get_property_default_value(const StringName& p_property) const override;
void _update_exports() override;
TypedArray<Dictionary> _get_script_method_list() const override;
TypedArray<Dictionary> _get_script_property_list() const override;
int32_t _get_member_line(const StringName& p_member) const override;
Dictionary _get_constants() const override;
TypedArray<StringName> _get_members() const override;
bool _is_placeholder_fallback_enabled() const override;
Variant _get_rpc_config() const override;
void set_path_hint(const String& p_path);
String get_assembly_name() const;
String get_type_name() const;
String get_entry_point() const;
protected:
static void _bind_methods();
private:
void parse_source();
String source_code;
String path_hint;
String assembly_name;
String type_name;
String entry_point;
StringName base_type = "Node";
bool valid = false;
};
} // namespace godot
+457
View File
@@ -0,0 +1,457 @@
#include "leanclr_script_language.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
namespace godot
{
LeanCLRScriptLanguage* LeanCLRScriptLanguage::singleton = nullptr;
void LeanCLRScriptLanguage::_bind_methods()
{
}
void LeanCLRScriptLanguage::init_singleton()
{
if (singleton != nullptr)
{
return;
}
singleton = memnew(LeanCLRScriptLanguage);
Engine::get_singleton()->register_script_language(singleton);
}
void LeanCLRScriptLanguage::deinit_singleton()
{
if (singleton == nullptr)
{
return;
}
Engine::get_singleton()->unregister_script_language(singleton);
memdelete(singleton);
singleton = nullptr;
}
LeanCLRScriptLanguage* LeanCLRScriptLanguage::get_singleton()
{
return singleton;
}
String LeanCLRScriptLanguage::_get_name() const
{
return "LeanCLR C#";
}
void LeanCLRScriptLanguage::_init()
{
}
String LeanCLRScriptLanguage::_get_type() const
{
return "LeanCLRScript";
}
String LeanCLRScriptLanguage::_get_extension() const
{
return "cs";
}
void LeanCLRScriptLanguage::_finish()
{
LeanCLRRuntimeBridge::shutdown();
}
PackedStringArray LeanCLRScriptLanguage::_get_reserved_words() const
{
PackedStringArray words;
words.push_back("assembly");
words.push_back("type");
words.push_back("base");
words.push_back("entry");
return words;
}
bool LeanCLRScriptLanguage::_is_control_flow_keyword(const String& p_keyword) const
{
(void)p_keyword;
return false;
}
PackedStringArray LeanCLRScriptLanguage::_get_comment_delimiters() const
{
PackedStringArray delimiters;
delimiters.push_back("#");
delimiters.push_back("//");
return delimiters;
}
PackedStringArray LeanCLRScriptLanguage::_get_doc_comment_delimiters() const
{
return PackedStringArray();
}
PackedStringArray LeanCLRScriptLanguage::_get_string_delimiters() const
{
PackedStringArray delimiters;
delimiters.push_back("\"");
return delimiters;
}
Ref<Script> LeanCLRScriptLanguage::_make_template(const String& p_template, const String& p_class_name, const String& p_base_class_name) const
{
(void)p_template;
Ref<LeanCLRScript> script;
script.instantiate();
script->_set_source_code("using Godot;\n\nnamespace Game;\n\npublic partial class " + p_class_name + " : " + p_base_class_name + "\n{\n}\n");
return script;
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_built_in_templates(const StringName& p_object) const
{
(void)p_object;
return TypedArray<Dictionary>();
}
bool LeanCLRScriptLanguage::_is_using_templates()
{
return true;
}
Dictionary LeanCLRScriptLanguage::_validate(const String& p_script, const String& p_path, bool p_validate_functions, bool p_validate_errors,
bool p_validate_warnings, bool p_validate_safe_lines) const
{
(void)p_validate_functions;
(void)p_validate_warnings;
(void)p_validate_safe_lines;
const bool is_cs = p_path.get_extension().to_lower() == "cs";
const bool has_assembly = p_script.find("assembly=") >= 0;
const bool has_type = p_script.find("type=") >= 0;
Dictionary result;
result["valid"] = is_cs || (has_assembly && has_type);
if (p_validate_errors && !is_cs && !(has_assembly && has_type))
{
Array errors;
Dictionary error;
error["path"] = p_path;
error["line"] = 1;
error["column"] = 1;
error["message"] = "LeanCLR script requires assembly=<name> and type=<namespace.type>.";
errors.push_back(error);
result["errors"] = errors;
}
return result;
}
String LeanCLRScriptLanguage::_validate_path(const String& p_path) const
{
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs" ? String() : String("LeanCLR scripts must use .cs or .lcs extension.");
}
Object* LeanCLRScriptLanguage::_create_script() const
{
return memnew(LeanCLRScript);
}
bool LeanCLRScriptLanguage::_has_named_classes() const
{
return true;
}
bool LeanCLRScriptLanguage::_supports_builtin_mode() const
{
return false;
}
bool LeanCLRScriptLanguage::_supports_documentation() const
{
return false;
}
bool LeanCLRScriptLanguage::_can_inherit_from_file() const
{
return false;
}
int32_t LeanCLRScriptLanguage::_find_function(const String& p_function, const String& p_code) const
{
(void)p_function;
(void)p_code;
return -1;
}
String LeanCLRScriptLanguage::_make_function(const String& p_class_name, const String& p_function_name, const PackedStringArray& p_function_args) const
{
(void)p_class_name;
(void)p_function_name;
(void)p_function_args;
return String();
}
bool LeanCLRScriptLanguage::_can_make_function() const
{
return false;
}
Error LeanCLRScriptLanguage::_open_in_external_editor(const Ref<Script>& p_script, int32_t p_line, int32_t p_column)
{
(void)p_script;
(void)p_line;
(void)p_column;
return ERR_UNAVAILABLE;
}
bool LeanCLRScriptLanguage::_overrides_external_editor()
{
return false;
}
ScriptLanguage::ScriptNameCasing LeanCLRScriptLanguage::_preferred_file_name_casing() const
{
return SCRIPT_NAME_CASING_PASCAL_CASE;
}
Dictionary LeanCLRScriptLanguage::_complete_code(const String& p_code, const String& p_path, Object* p_owner) const
{
(void)p_code;
(void)p_path;
(void)p_owner;
Dictionary result;
result["result"] = ERR_UNAVAILABLE;
result["force"] = false;
result["call_hint"] = String();
return result;
}
Dictionary LeanCLRScriptLanguage::_lookup_code(const String& p_code, const String& p_symbol, const String& p_path, Object* p_owner) const
{
(void)p_code;
(void)p_symbol;
(void)p_path;
(void)p_owner;
Dictionary result;
result["result"] = ERR_UNAVAILABLE;
result["type"] = LOOKUP_RESULT_SCRIPT_LOCATION;
return result;
}
String LeanCLRScriptLanguage::_auto_indent_code(const String& p_code, int32_t p_from_line, int32_t p_to_line) const
{
(void)p_from_line;
(void)p_to_line;
return p_code;
}
void LeanCLRScriptLanguage::_thread_enter()
{
}
void LeanCLRScriptLanguage::_thread_exit()
{
}
String LeanCLRScriptLanguage::_debug_get_error() const
{
return LeanCLRRuntimeBridge::get_last_error();
}
int32_t LeanCLRScriptLanguage::_debug_get_stack_level_count() const
{
return 0;
}
int32_t LeanCLRScriptLanguage::_debug_get_stack_level_line(int32_t p_level) const
{
(void)p_level;
return -1;
}
String LeanCLRScriptLanguage::_debug_get_stack_level_function(int32_t p_level) const
{
(void)p_level;
return String();
}
String LeanCLRScriptLanguage::_debug_get_stack_level_source(int32_t p_level) const
{
(void)p_level;
return String();
}
Dictionary LeanCLRScriptLanguage::_debug_get_stack_level_locals(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
Dictionary LeanCLRScriptLanguage::_debug_get_stack_level_members(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
void* LeanCLRScriptLanguage::_debug_get_stack_level_instance(int32_t p_level)
{
(void)p_level;
return nullptr;
}
Dictionary LeanCLRScriptLanguage::_debug_get_globals(int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_max_subitems;
(void)p_max_depth;
return Dictionary();
}
String LeanCLRScriptLanguage::_debug_parse_stack_level_expression(int32_t p_level, const String& p_expression, int32_t p_max_subitems, int32_t p_max_depth)
{
(void)p_level;
(void)p_expression;
(void)p_max_subitems;
(void)p_max_depth;
return String();
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_debug_get_current_stack_info()
{
return TypedArray<Dictionary>();
}
void LeanCLRScriptLanguage::_reload_all_scripts()
{
}
void LeanCLRScriptLanguage::_reload_scripts(const Array& p_scripts, bool p_soft_reload)
{
(void)p_scripts;
(void)p_soft_reload;
}
void LeanCLRScriptLanguage::_reload_tool_script(const Ref<Script>& p_script, bool p_soft_reload)
{
(void)p_script;
(void)p_soft_reload;
}
PackedStringArray LeanCLRScriptLanguage::_get_recognized_extensions() const
{
PackedStringArray extensions;
extensions.push_back("cs");
extensions.push_back("lcs");
return extensions;
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_public_functions() const
{
return TypedArray<Dictionary>();
}
Dictionary LeanCLRScriptLanguage::_get_public_constants() const
{
return Dictionary();
}
TypedArray<Dictionary> LeanCLRScriptLanguage::_get_public_annotations() const
{
return TypedArray<Dictionary>();
}
void LeanCLRScriptLanguage::_profiling_start()
{
}
void LeanCLRScriptLanguage::_profiling_stop()
{
}
void LeanCLRScriptLanguage::_profiling_set_save_native_calls(bool p_enable)
{
(void)p_enable;
}
int32_t LeanCLRScriptLanguage::_profiling_get_accumulated_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max)
{
(void)p_info_array;
(void)p_info_max;
return 0;
}
int32_t LeanCLRScriptLanguage::_profiling_get_frame_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max)
{
(void)p_info_array;
(void)p_info_max;
return 0;
}
void LeanCLRScriptLanguage::_frame()
{
}
bool LeanCLRScriptLanguage::_handles_global_class_type(const String& p_type) const
{
return p_type == "LeanCLRScript";
}
Dictionary LeanCLRScriptLanguage::_get_global_class_name(const String& p_path) const
{
Dictionary result;
const String extension = p_path.get_extension().to_lower();
if (extension != "cs" && extension != "lcs")
{
return result;
}
const String source = FileAccess::get_file_as_string(p_path);
Ref<LeanCLRScript> script;
script.instantiate();
script->set_path_hint(p_path);
script->_set_source_code(source);
if (script->_is_valid())
{
result["name"] = script->get_type_name();
result["base_type"] = script->_get_instance_base_type();
return result;
}
PackedStringArray lines = source.split("\n");
for (int64_t i = 0; i < lines.size(); ++i)
{
PackedStringArray pair = lines[i].strip_edges().split("=", false, 1);
if (pair.size() != 2)
{
continue;
}
const String key = pair[0].strip_edges().to_lower();
const String value = pair[1].strip_edges();
if (key == "type")
{
result["name"] = value;
}
else if (key == "base")
{
result["base_type"] = value;
}
}
return result;
}
} // namespace godot
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <godot_cpp/classes/script_language_extension.hpp>
namespace godot
{
class LeanCLRScriptLanguage : public ScriptLanguageExtension
{
GDCLASS(LeanCLRScriptLanguage, ScriptLanguageExtension)
public:
static void init_singleton();
static void deinit_singleton();
static LeanCLRScriptLanguage* get_singleton();
String _get_name() const override;
void _init() override;
String _get_type() const override;
String _get_extension() const override;
void _finish() override;
PackedStringArray _get_reserved_words() const override;
bool _is_control_flow_keyword(const String& p_keyword) const override;
PackedStringArray _get_comment_delimiters() const override;
PackedStringArray _get_doc_comment_delimiters() const override;
PackedStringArray _get_string_delimiters() const override;
Ref<Script> _make_template(const String& p_template, const String& p_class_name, const String& p_base_class_name) const override;
TypedArray<Dictionary> _get_built_in_templates(const StringName& p_object) const override;
bool _is_using_templates() override;
Dictionary _validate(const String& p_script, const String& p_path, bool p_validate_functions, bool p_validate_errors,
bool p_validate_warnings, bool p_validate_safe_lines) const override;
String _validate_path(const String& p_path) const override;
Object* _create_script() const override;
bool _has_named_classes() const override;
bool _supports_builtin_mode() const override;
bool _supports_documentation() const override;
bool _can_inherit_from_file() const override;
int32_t _find_function(const String& p_function, const String& p_code) const override;
String _make_function(const String& p_class_name, const String& p_function_name, const PackedStringArray& p_function_args) const override;
bool _can_make_function() const override;
Error _open_in_external_editor(const Ref<Script>& p_script, int32_t p_line, int32_t p_column) override;
bool _overrides_external_editor() override;
ScriptLanguage::ScriptNameCasing _preferred_file_name_casing() const override;
Dictionary _complete_code(const String& p_code, const String& p_path, Object* p_owner) const override;
Dictionary _lookup_code(const String& p_code, const String& p_symbol, const String& p_path, Object* p_owner) const override;
String _auto_indent_code(const String& p_code, int32_t p_from_line, int32_t p_to_line) const override;
void _thread_enter() override;
void _thread_exit() override;
String _debug_get_error() const override;
int32_t _debug_get_stack_level_count() const override;
int32_t _debug_get_stack_level_line(int32_t p_level) const override;
String _debug_get_stack_level_function(int32_t p_level) const override;
String _debug_get_stack_level_source(int32_t p_level) const override;
Dictionary _debug_get_stack_level_locals(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth) override;
Dictionary _debug_get_stack_level_members(int32_t p_level, int32_t p_max_subitems, int32_t p_max_depth) override;
void* _debug_get_stack_level_instance(int32_t p_level) override;
Dictionary _debug_get_globals(int32_t p_max_subitems, int32_t p_max_depth) override;
String _debug_parse_stack_level_expression(int32_t p_level, const String& p_expression, int32_t p_max_subitems, int32_t p_max_depth) override;
TypedArray<Dictionary> _debug_get_current_stack_info() override;
void _reload_all_scripts() override;
void _reload_scripts(const Array& p_scripts, bool p_soft_reload) override;
void _reload_tool_script(const Ref<Script>& p_script, bool p_soft_reload) override;
PackedStringArray _get_recognized_extensions() const override;
TypedArray<Dictionary> _get_public_functions() const override;
Dictionary _get_public_constants() const override;
TypedArray<Dictionary> _get_public_annotations() const override;
void _profiling_start() override;
void _profiling_stop() override;
void _profiling_set_save_native_calls(bool p_enable) override;
int32_t _profiling_get_accumulated_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max) override;
int32_t _profiling_get_frame_data(ScriptLanguageExtensionProfilingInfo* p_info_array, int32_t p_info_max) override;
void _frame() override;
bool _handles_global_class_type(const String& p_type) const override;
Dictionary _get_global_class_name(const String& p_path) const override;
protected:
static void _bind_methods();
private:
static LeanCLRScriptLanguage* singleton;
};
} // namespace godot
+54
View File
@@ -0,0 +1,54 @@
#include "leanclr_script_loader.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/core/class_db.hpp>
namespace godot
{
void LeanCLRScriptLoader::_bind_methods()
{
}
PackedStringArray LeanCLRScriptLoader::_get_recognized_extensions() const
{
PackedStringArray extensions;
extensions.push_back("cs");
extensions.push_back("lcs");
return extensions;
}
bool LeanCLRScriptLoader::_handles_type(const StringName& p_type) const
{
return p_type == StringName("Script") || p_type == StringName("LeanCLRScript") || p_type == StringName();
}
String LeanCLRScriptLoader::_get_resource_type(const String& p_path) const
{
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs" ? String("LeanCLRScript") : String();
}
Variant LeanCLRScriptLoader::_load(const String& p_path, const String& p_original_path, bool p_use_sub_threads, int32_t p_cache_mode) const
{
(void)p_original_path;
(void)p_use_sub_threads;
(void)p_cache_mode;
Ref<LeanCLRScript> script;
script.instantiate();
script->set_path_hint(p_path);
script->_set_source_code(FileAccess::get_file_as_string(p_path));
const Error reload_error = script->_reload(false);
if (reload_error != OK)
{
return reload_error;
}
return script;
}
} // namespace godot
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <godot_cpp/classes/resource_format_loader.hpp>
namespace godot
{
class LeanCLRScriptLoader : public ResourceFormatLoader
{
GDCLASS(LeanCLRScriptLoader, ResourceFormatLoader)
public:
PackedStringArray _get_recognized_extensions() const override;
bool _handles_type(const StringName& p_type) const override;
String _get_resource_type(const String& p_path) const override;
Variant _load(const String& p_path, const String& p_original_path, bool p_use_sub_threads, int32_t p_cache_mode) const override;
protected:
static void _bind_methods();
};
} // namespace godot
+79
View File
@@ -0,0 +1,79 @@
#include "leanclr_script_saver.h"
#include "leanclr_script.h"
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot
{
void LeanCLRScriptSaver::_bind_methods()
{
}
Error LeanCLRScriptSaver::_save(const Ref<Resource>& p_resource, const String& p_path, uint32_t p_flags)
{
(void)p_flags;
LeanCLRScript* script = Object::cast_to<LeanCLRScript>(p_resource.ptr());
if (script == nullptr || p_path.is_empty())
{
return ERR_INVALID_PARAMETER;
}
String source_code = script->_get_source_code();
if (source_code.is_empty() && FileAccess::file_exists(p_path))
{
source_code = FileAccess::get_file_as_string(p_path);
}
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);
if (!file.is_valid())
{
const Error error = FileAccess::get_open_error();
UtilityFunctions::printerr("LeanCLR script saver: failed to open ", p_path, " error = ", static_cast<int64_t>(error));
return error;
}
file->store_string(source_code);
file->flush();
file->close();
return OK;
}
Error LeanCLRScriptSaver::_set_uid(const String& p_path, int64_t p_uid)
{
(void)p_path;
(void)p_uid;
return OK;
}
bool LeanCLRScriptSaver::_recognize(const Ref<Resource>& p_resource) const
{
return Object::cast_to<LeanCLRScript>(p_resource.ptr()) != nullptr;
}
PackedStringArray LeanCLRScriptSaver::_get_recognized_extensions(const Ref<Resource>& p_resource) const
{
PackedStringArray extensions;
if (_recognize(p_resource))
{
extensions.push_back("cs");
extensions.push_back("lcs");
}
return extensions;
}
bool LeanCLRScriptSaver::_recognize_path(const Ref<Resource>& p_resource, const String& p_path) const
{
if (!_recognize(p_resource))
{
return false;
}
const String extension = p_path.get_extension().to_lower();
return extension == "cs" || extension == "lcs";
}
} // namespace godot
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <godot_cpp/classes/resource_format_saver.hpp>
namespace godot
{
class LeanCLRScriptSaver : public ResourceFormatSaver
{
GDCLASS(LeanCLRScriptSaver, ResourceFormatSaver)
public:
Error _save(const Ref<Resource>& p_resource, const String& p_path, uint32_t p_flags) override;
Error _set_uid(const String& p_path, int64_t p_uid) override;
bool _recognize(const Ref<Resource>& p_resource) const override;
PackedStringArray _get_recognized_extensions(const Ref<Resource>& p_resource) const override;
bool _recognize_path(const Ref<Resource>& p_resource, const String& p_path) const override;
protected:
static void _bind_methods();
};
} // namespace godot
+96
View File
@@ -0,0 +1,96 @@
#include "register_types.h"
#include "leanclr_runtime_bridge.h"
#include "leanclr_main_node.h"
#include "leanclr_hot_reload_host.h"
#include "leanclr_script.h"
#include "leanclr_script_language.h"
#include "leanclr_script_loader.h"
#include "leanclr_script_saver.h"
#include <gdextension_interface.h>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/resource_saver.hpp>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/godot.hpp>
namespace godot
{
namespace
{
Ref<LeanCLRScriptLoader>* script_loader = nullptr;
Ref<LeanCLRScriptSaver>* script_saver = nullptr;
} // namespace
void initialize_leanclr_godot_module(ModuleInitializationLevel p_level)
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE)
{
return;
}
GDREGISTER_CLASS(LeanCLRScript);
GDREGISTER_CLASS(LeanCLRScriptLanguage);
GDREGISTER_CLASS(LeanCLRScriptLoader);
GDREGISTER_CLASS(LeanCLRScriptSaver);
GDREGISTER_CLASS(LeanCLRMain);
GDREGISTER_CLASS(LeanCLRHotReloadHost);
LeanCLRScriptLanguage::init_singleton();
script_loader = memnew(Ref<LeanCLRScriptLoader>);
script_loader->instantiate();
ResourceLoader::get_singleton()->add_resource_format_loader(*script_loader, true);
script_saver = memnew(Ref<LeanCLRScriptSaver>);
script_saver->instantiate();
ResourceSaver::get_singleton()->add_resource_format_saver(*script_saver, true);
}
void uninitialize_leanclr_godot_module(ModuleInitializationLevel p_level)
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE)
{
return;
}
if (script_saver != nullptr && script_saver->is_valid())
{
ResourceSaver::get_singleton()->remove_resource_format_saver(*script_saver);
script_saver->unref();
memdelete(script_saver);
script_saver = nullptr;
}
if (script_loader != nullptr && script_loader->is_valid())
{
ResourceLoader::get_singleton()->remove_resource_format_loader(*script_loader);
script_loader->unref();
memdelete(script_loader);
script_loader = nullptr;
}
LeanCLRScriptLanguage::deinit_singleton();
LeanCLRRuntimeBridge::shutdown();
}
} // namespace godot
extern "C"
{
GDExtensionBool GDE_EXPORT leanclr_godot_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization* r_initialization)
{
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(godot::initialize_leanclr_godot_module);
init_obj.register_terminator(godot::uninitialize_leanclr_godot_module);
init_obj.set_minimum_library_initialization_level(godot::MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <godot_cpp/core/class_db.hpp>
namespace godot
{
void initialize_leanclr_godot_module(ModuleInitializationLevel p_level);
void uninitialize_leanclr_godot_module(ModuleInitializationLevel p_level);
} // namespace godot
Vendored Submodule
+1
Submodule thirdparty/leanclr added at dbe2516ac6
File diff suppressed because it is too large Load Diff