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