feat(scripting): add mono csharp runtime foundation
This commit is contained in:
@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.15)
|
||||
project(XCEngineLib)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(XCENGINE_VULKAN_SDK_HINT "$ENV{VULKAN_SDK}")
|
||||
if(NOT EXISTS "${XCENGINE_VULKAN_SDK_HINT}/Lib/vulkan-1.lib")
|
||||
file(GLOB XCENGINE_VULKAN_SDK_DIRS "D:/VulkanSDK/*")
|
||||
@@ -18,8 +20,6 @@ endif()
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(XCEngine STATIC
|
||||
# Core (Types, RefCounted, SmartPtr, Event)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Types.h
|
||||
@@ -149,6 +149,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12ResourceView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12QueryHeap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12RenderPass.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12Framebuffer.cpp
|
||||
|
||||
# Vulkan RHI
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommon.h
|
||||
@@ -171,7 +172,6 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSwapChain.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDevice.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanScreenshot.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12Framebuffer.cpp
|
||||
|
||||
# OpenGL RHI
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/OpenGL/OpenGLBuffer.h
|
||||
@@ -332,8 +332,10 @@ add_library(XCEngine STATIC
|
||||
|
||||
# Scene
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/SceneRuntime.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/SceneManager.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/Scene.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneRuntime.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scene/SceneManager.cpp
|
||||
|
||||
# Platform
|
||||
@@ -413,6 +415,49 @@ target_link_libraries(XCEngine PUBLIC
|
||||
Vulkan::Vulkan
|
||||
)
|
||||
|
||||
if(XCENGINE_ENABLE_MONO_SCRIPTING)
|
||||
set(XCENGINE_MONO_INCLUDE_DIR "${XCENGINE_MONO_ROOT_DIR}/include")
|
||||
set(XCENGINE_MONO_STATIC_LIBRARY "${XCENGINE_MONO_ROOT_DIR}/lib/libmono-static-sgen.lib")
|
||||
set(XCENGINE_MONO_POSIX_HELPER_LIBRARY "${XCENGINE_MONO_ROOT_DIR}/lib/MonoPosixHelper.lib")
|
||||
|
||||
foreach(XCENGINE_MONO_REQUIRED_PATH
|
||||
"${XCENGINE_MONO_INCLUDE_DIR}"
|
||||
"${XCENGINE_MONO_STATIC_LIBRARY}"
|
||||
"${XCENGINE_MONO_POSIX_HELPER_LIBRARY}")
|
||||
if(NOT EXISTS "${XCENGINE_MONO_REQUIRED_PATH}")
|
||||
message(FATAL_ERROR "Required Mono dependency is missing: ${XCENGINE_MONO_REQUIRED_PATH}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_library(XCEMono STATIC IMPORTED)
|
||||
set_target_properties(XCEMono PROPERTIES
|
||||
IMPORTED_LOCATION "${XCENGINE_MONO_STATIC_LIBRARY}"
|
||||
)
|
||||
|
||||
target_sources(XCEngine PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Scripting/Mono/MonoScriptRuntime.cpp
|
||||
)
|
||||
|
||||
target_include_directories(XCEngine PRIVATE
|
||||
${XCENGINE_MONO_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(XCEngine PUBLIC
|
||||
XCEMono
|
||||
${XCENGINE_MONO_POSIX_HELPER_LIBRARY}
|
||||
ws2_32
|
||||
bcrypt
|
||||
version
|
||||
iphlpapi
|
||||
winmm
|
||||
)
|
||||
|
||||
target_compile_definitions(XCEngine PRIVATE
|
||||
XCENGINE_ENABLE_MONO_SCRIPTING
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(XCEngine PRIVATE /W3 /FS)
|
||||
else()
|
||||
|
||||
26
engine/include/XCEngine/Scene/SceneRuntime.h
Normal file
26
engine/include/XCEngine/Scene/SceneRuntime.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
class SceneRuntime {
|
||||
public:
|
||||
void Start(Scene* scene);
|
||||
void Stop();
|
||||
|
||||
void FixedUpdate(float fixedDeltaTime);
|
||||
void Update(float deltaTime);
|
||||
void LateUpdate(float deltaTime);
|
||||
|
||||
bool IsRunning() const { return m_running; }
|
||||
Scene* GetScene() const { return m_scene; }
|
||||
|
||||
private:
|
||||
Scene* m_scene = nullptr;
|
||||
bool m_running = false;
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
185
engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h
Normal file
185
engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Scripting/IScriptRuntime.h>
|
||||
#include <XCEngine/Scripting/ScriptField.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
typedef struct _MonoDomain MonoDomain;
|
||||
typedef struct _MonoAssembly MonoAssembly;
|
||||
typedef struct _MonoImage MonoImage;
|
||||
typedef struct _MonoClass MonoClass;
|
||||
typedef struct _MonoObject MonoObject;
|
||||
typedef struct _MonoMethod MonoMethod;
|
||||
typedef struct _MonoClassField MonoClassField;
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Scripting {
|
||||
|
||||
class ScriptComponent;
|
||||
|
||||
class MonoScriptRuntime : public IScriptRuntime {
|
||||
public:
|
||||
struct Settings {
|
||||
std::filesystem::path assemblyDirectory;
|
||||
std::filesystem::path corlibDirectory;
|
||||
std::filesystem::path coreAssemblyPath;
|
||||
std::filesystem::path appAssemblyPath;
|
||||
std::string coreAssemblyName = "XCEngine.ScriptCore";
|
||||
std::string appAssemblyName = "GameScripts";
|
||||
std::string baseNamespace = "XCEngine";
|
||||
std::string baseClassName = "MonoBehaviour";
|
||||
};
|
||||
|
||||
explicit MonoScriptRuntime(Settings settings = {});
|
||||
~MonoScriptRuntime() override;
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
const Settings& GetSettings() const { return m_settings; }
|
||||
const std::string& GetLastError() const { return m_lastError; }
|
||||
|
||||
bool IsClassAvailable(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className) const;
|
||||
std::vector<std::string> GetScriptClassNames(const std::string& assemblyName = std::string()) const;
|
||||
|
||||
bool HasManagedInstance(const ScriptComponent* component) const;
|
||||
size_t GetManagedInstanceCount() const { return m_instances.size(); }
|
||||
|
||||
bool TryGetFieldValue(
|
||||
const ScriptComponent* component,
|
||||
const std::string& fieldName,
|
||||
ScriptFieldValue& outValue) const;
|
||||
|
||||
template<typename T>
|
||||
bool TryGetFieldValue(const ScriptComponent* component, const std::string& fieldName, T& outValue) const {
|
||||
ScriptFieldValue value;
|
||||
if (!TryGetFieldValue(component, fieldName, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* typedValue = std::get_if<T>(&value);
|
||||
if (!typedValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = *typedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnRuntimeStart(Components::Scene* scene) override;
|
||||
void OnRuntimeStop(Components::Scene* scene) override;
|
||||
bool CreateScriptInstance(const ScriptRuntimeContext& context) override;
|
||||
void DestroyScriptInstance(const ScriptRuntimeContext& context) override;
|
||||
void InvokeMethod(const ScriptRuntimeContext& context, ScriptLifecycleMethod method, float deltaTime) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t LifecycleMethodCount = 8;
|
||||
|
||||
struct FieldMetadata {
|
||||
ScriptFieldType type = ScriptFieldType::None;
|
||||
MonoClassField* field = nullptr;
|
||||
};
|
||||
|
||||
struct ClassMetadata {
|
||||
std::string assemblyName;
|
||||
std::string namespaceName;
|
||||
std::string className;
|
||||
std::string fullName;
|
||||
MonoClass* monoClass = nullptr;
|
||||
std::array<MonoMethod*, LifecycleMethodCount> lifecycleMethods{};
|
||||
std::unordered_map<std::string, FieldMetadata> fields;
|
||||
};
|
||||
|
||||
struct InstanceKey {
|
||||
uint64_t gameObjectUUID = 0;
|
||||
uint64_t scriptComponentUUID = 0;
|
||||
|
||||
bool operator==(const InstanceKey& other) const {
|
||||
return gameObjectUUID == other.gameObjectUUID
|
||||
&& scriptComponentUUID == other.scriptComponentUUID;
|
||||
}
|
||||
};
|
||||
|
||||
struct InstanceKeyHasher {
|
||||
size_t operator()(const InstanceKey& key) const;
|
||||
};
|
||||
|
||||
struct InstanceData {
|
||||
const ClassMetadata* classMetadata = nullptr;
|
||||
uint32_t gcHandle = 0;
|
||||
};
|
||||
|
||||
void ResolveSettings();
|
||||
bool InitializeRootDomain();
|
||||
bool CreateAppDomain();
|
||||
void DestroyAppDomain();
|
||||
void SetCurrentDomain() const;
|
||||
|
||||
bool LoadAssemblies();
|
||||
bool DiscoverScriptClasses();
|
||||
void DiscoverScriptClassesInImage(const std::string& assemblyName, MonoImage* image);
|
||||
bool IsMonoBehaviourSubclass(MonoClass* monoClass) const;
|
||||
|
||||
ScriptFieldType MapMonoFieldType(MonoClassField* field) const;
|
||||
|
||||
static const char* ToLifecycleMethodName(ScriptLifecycleMethod method);
|
||||
|
||||
const ClassMetadata* FindClassMetadata(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className) const;
|
||||
|
||||
InstanceData* FindInstance(const ScriptRuntimeContext& context);
|
||||
const InstanceData* FindInstance(const ScriptRuntimeContext& context) const;
|
||||
const InstanceData* FindInstance(const ScriptComponent* component) const;
|
||||
|
||||
MonoObject* GetManagedObject(const InstanceData& instanceData) const;
|
||||
|
||||
bool ApplyContextFields(const ScriptRuntimeContext& context, MonoObject* instance);
|
||||
bool ApplyStoredFields(const ScriptRuntimeContext& context, const ClassMetadata& metadata, MonoObject* instance);
|
||||
MonoObject* CreateManagedGameObject(uint64_t gameObjectUUID);
|
||||
bool TryExtractGameObjectReference(MonoObject* managedObject, GameObjectReference& outReference) const;
|
||||
bool TrySetFieldValue(MonoObject* instance, const FieldMetadata& fieldMetadata, const ScriptFieldValue& value);
|
||||
bool TryReadFieldValue(MonoObject* instance, const FieldMetadata& fieldMetadata, ScriptFieldValue& outValue) const;
|
||||
|
||||
void ClearManagedInstances();
|
||||
void ClearClassCache();
|
||||
bool InvokeManagedMethod(MonoObject* instance, MonoMethod* method);
|
||||
void RecordException(MonoObject* exception);
|
||||
void SetError(const std::string& error);
|
||||
|
||||
Settings m_settings;
|
||||
Components::Scene* m_activeScene = nullptr;
|
||||
|
||||
bool m_initialized = false;
|
||||
std::string m_lastError;
|
||||
|
||||
MonoDomain* m_appDomain = nullptr;
|
||||
MonoAssembly* m_coreAssembly = nullptr;
|
||||
MonoAssembly* m_appAssembly = nullptr;
|
||||
MonoImage* m_coreImage = nullptr;
|
||||
MonoImage* m_appImage = nullptr;
|
||||
MonoClass* m_componentClass = nullptr;
|
||||
MonoClass* m_behaviourClass = nullptr;
|
||||
MonoClass* m_gameObjectClass = nullptr;
|
||||
MonoClass* m_monoBehaviourClass = nullptr;
|
||||
MonoMethod* m_gameObjectConstructor = nullptr;
|
||||
MonoClassField* m_managedGameObjectUUIDField = nullptr;
|
||||
MonoClassField* m_gameObjectUUIDField = nullptr;
|
||||
MonoClassField* m_scriptComponentUUIDField = nullptr;
|
||||
|
||||
std::unordered_map<std::string, ClassMetadata> m_classes;
|
||||
std::unordered_map<InstanceKey, InstanceData, InstanceKeyHasher> m_instances;
|
||||
};
|
||||
|
||||
} // namespace Scripting
|
||||
} // namespace XCEngine
|
||||
@@ -228,30 +228,27 @@ void TransformComponent::LookAt(const Math::Vector3& target, const Math::Vector3
|
||||
void TransformComponent::Rotate(const Math::Vector3& eulers, Space relativeTo) {
|
||||
Math::Quaternion rotation = Math::Quaternion::FromEulerAngles(eulers * Math::DEG_TO_RAD);
|
||||
if (relativeTo == Space::Self) {
|
||||
m_localRotation = m_localRotation * rotation;
|
||||
SetRotation(GetRotation() * rotation);
|
||||
} else {
|
||||
m_localRotation = rotation * m_localRotation;
|
||||
SetRotation(rotation * GetRotation());
|
||||
}
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
void TransformComponent::Rotate(const Math::Vector3& axis, float angle, Space relativeTo) {
|
||||
Math::Quaternion rotation = Math::Quaternion::FromAxisAngle(axis, angle * Math::DEG_TO_RAD);
|
||||
if (relativeTo == Space::Self) {
|
||||
m_localRotation = m_localRotation * rotation;
|
||||
SetRotation(GetRotation() * rotation);
|
||||
} else {
|
||||
m_localRotation = rotation * m_localRotation;
|
||||
SetRotation(rotation * GetRotation());
|
||||
}
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
void TransformComponent::Translate(const Math::Vector3& translation, Space relativeTo) {
|
||||
if (relativeTo == Space::Self) {
|
||||
m_localPosition = m_localPosition + translation;
|
||||
SetPosition(GetPosition() + TransformDirection(translation));
|
||||
} else {
|
||||
m_localPosition = m_localPosition + translation;
|
||||
SetPosition(GetPosition() + translation);
|
||||
}
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
Math::Vector3 TransformComponent::TransformPoint(const Math::Vector3& point) const {
|
||||
@@ -317,4 +314,4 @@ void TransformComponent::Deserialize(std::istream& is) {
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
64
engine/src/Scene/SceneRuntime.cpp
Normal file
64
engine/src/Scene/SceneRuntime.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "Scene/SceneRuntime.h"
|
||||
|
||||
#include "Scripting/ScriptEngine.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
|
||||
void SceneRuntime::Start(Scene* scene) {
|
||||
if (m_running && m_scene == scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scene = scene;
|
||||
m_running = true;
|
||||
Scripting::ScriptEngine::Get().OnRuntimeStart(scene);
|
||||
}
|
||||
|
||||
void SceneRuntime::Stop() {
|
||||
if (!m_running) {
|
||||
m_scene = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
Scripting::ScriptEngine::Get().OnRuntimeStop();
|
||||
m_running = false;
|
||||
m_scene = nullptr;
|
||||
}
|
||||
|
||||
void SceneRuntime::FixedUpdate(float fixedDeltaTime) {
|
||||
if (!m_running || !m_scene || !m_scene->IsActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scripts run first so their state changes are visible to native components in the same frame.
|
||||
Scripting::ScriptEngine::Get().OnFixedUpdate(fixedDeltaTime);
|
||||
m_scene->FixedUpdate(fixedDeltaTime);
|
||||
}
|
||||
|
||||
void SceneRuntime::Update(float deltaTime) {
|
||||
if (!m_running || !m_scene || !m_scene->IsActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scripting::ScriptEngine::Get().OnUpdate(deltaTime);
|
||||
m_scene->Update(deltaTime);
|
||||
}
|
||||
|
||||
void SceneRuntime::LateUpdate(float deltaTime) {
|
||||
if (!m_running || !m_scene || !m_scene->IsActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scripting::ScriptEngine::Get().OnLateUpdate(deltaTime);
|
||||
m_scene->LateUpdate(deltaTime);
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
} // namespace XCEngine
|
||||
1794
engine/src/Scripting/Mono/MonoScriptRuntime.cpp
Normal file
1794
engine/src/Scripting/Mono/MonoScriptRuntime.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user