init
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "thirdparty/leanclr"]
|
||||
path = thirdparty/leanclr
|
||||
url = https://github.com/focus-creative-games/leanclr.git
|
||||
+133
@@ -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"
|
||||
)
|
||||
@@ -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.
|
||||
@@ -0,0 +1,52 @@
|
||||
# LeanCLR Godot
|
||||
|
||||
浅尝一下 LeanCLR 跑 Godot 4 脚本。
|
||||
|
||||
原版Godot加载GDExtension,C# 代码交给 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
internal static partial class NativeCalls
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Godot
|
||||
{
|
||||
public partial class Node : GodotObject
|
||||
{
|
||||
public virtual void _Ready()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void _Process(double delta)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
uid://sbyqrdhnol0s
|
||||
@@ -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()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bkcd2d6cuaxfs
|
||||
@@ -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"]
|
||||
@@ -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")
|
||||
@@ -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"
|
||||
@@ -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")]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b5rn1ql6ugvjj
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bo2r8gbx576k6
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://c0duyyvvovu5x
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
+1
Submodule thirdparty/leanclr added at dbe2516ac6
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user