feat(scripting): add managed SRP runtime bridge
This commit is contained in:
@@ -148,6 +148,272 @@ MonoScriptRuntime* GetActiveMonoScriptRuntime() {
|
||||
return dynamic_cast<MonoScriptRuntime*>(ScriptEngine::Get().GetRuntime());
|
||||
}
|
||||
|
||||
bool TryUnboxManagedBoolean(MonoObject* boxedValue, bool& outValue) {
|
||||
outValue = false;
|
||||
if (!boxedValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void* const rawValue = mono_object_unbox(boxedValue);
|
||||
if (!rawValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = (*static_cast<mono_bool*>(rawValue)) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MonoManagedRenderPipelineStageRecorder final
|
||||
: public Rendering::RenderPipelineStageRecorder {
|
||||
public:
|
||||
MonoManagedRenderPipelineStageRecorder(
|
||||
MonoScriptRuntime* runtime,
|
||||
std::weak_ptr<void> runtimeLifetime,
|
||||
Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor)
|
||||
: m_runtime(runtime)
|
||||
, m_runtimeLifetime(std::move(runtimeLifetime))
|
||||
, m_descriptor(std::move(descriptor)) {
|
||||
}
|
||||
|
||||
~MonoManagedRenderPipelineStageRecorder() override {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Initialize(const Rendering::RenderContext&) override {
|
||||
return EnsureManagedPipeline();
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
ReleaseManagedObjects();
|
||||
m_supportsStageMethod = nullptr;
|
||||
m_recordStageMethod = nullptr;
|
||||
m_pipelineCreationAttempted = false;
|
||||
}
|
||||
|
||||
bool SupportsStageRenderGraph(Rendering::CameraFrameStage stage) const override {
|
||||
if (!Rendering::SupportsCameraFramePipelineGraphRecording(stage) ||
|
||||
!EnsureManagedPipeline()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const pipelineObject =
|
||||
m_runtime->GetManagedObject(m_pipelineHandle);
|
||||
MonoMethod* const method = ResolveSupportsStageMethod(pipelineObject);
|
||||
if (!pipelineObject || !method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t managedStage = static_cast<int32_t>(stage);
|
||||
void* args[1] = { &managedStage };
|
||||
MonoObject* result = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
pipelineObject,
|
||||
method,
|
||||
args,
|
||||
&result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool supportsStage = false;
|
||||
return TryUnboxManagedBoolean(result, supportsStage) && supportsStage;
|
||||
}
|
||||
|
||||
bool RecordStageRenderGraph(
|
||||
const Rendering::RenderPipelineStageRenderGraphContext& context) override {
|
||||
if (!EnsureManagedPipeline()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const pipelineObject =
|
||||
m_runtime->GetManagedObject(m_pipelineHandle);
|
||||
MonoMethod* const method = ResolveRecordStageMethod(pipelineObject);
|
||||
if (!pipelineObject || !method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t managedStage = static_cast<int32_t>(context.stage);
|
||||
void* args[1] = { &managedStage };
|
||||
MonoObject* result = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
pipelineObject,
|
||||
method,
|
||||
args,
|
||||
&result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool recorded = false;
|
||||
return TryUnboxManagedBoolean(result, recorded) && recorded;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsRuntimeAlive() const {
|
||||
return m_runtime != nullptr &&
|
||||
!m_runtimeLifetime.expired() &&
|
||||
m_runtime->m_initialized;
|
||||
}
|
||||
|
||||
bool EnsureManagedPipeline() const {
|
||||
if (m_pipelineHandle != 0) {
|
||||
return true;
|
||||
}
|
||||
if (m_pipelineCreationAttempted || !IsRuntimeAlive() ||
|
||||
!m_descriptor.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pipelineCreationAttempted = true;
|
||||
|
||||
MonoClass* assetClass = nullptr;
|
||||
if (!m_runtime->ResolveManagedClass(
|
||||
m_descriptor.assemblyName,
|
||||
m_descriptor.namespaceName,
|
||||
m_descriptor.className,
|
||||
assetClass) ||
|
||||
assetClass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMonoClassOrSubclass(
|
||||
assetClass,
|
||||
m_runtime->m_scriptableRenderPipelineAssetClass)) {
|
||||
m_runtime->SetError(
|
||||
"Managed render pipeline asset must derive from ScriptableRenderPipelineAsset: " +
|
||||
m_descriptor.GetFullName() + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t assetHandle = 0;
|
||||
if (!m_runtime->CreateExternalManagedObject(assetClass, assetHandle) ||
|
||||
assetHandle == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* const assetObject = m_runtime->GetManagedObject(assetHandle);
|
||||
MonoMethod* const createPipelineMethod =
|
||||
m_runtime->ResolveManagedMethod(assetObject, "CreatePipeline", 0);
|
||||
if (!assetObject || !createPipelineMethod) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* pipelineObject = nullptr;
|
||||
if (!m_runtime->InvokeManagedMethod(
|
||||
assetObject,
|
||||
createPipelineMethod,
|
||||
nullptr,
|
||||
&pipelineObject) ||
|
||||
pipelineObject == nullptr) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMonoClassOrSubclass(
|
||||
mono_object_get_class(pipelineObject),
|
||||
m_runtime->m_scriptableRenderPipelineClass)) {
|
||||
m_runtime->SetError(
|
||||
"Managed render pipeline asset returned a non-ScriptableRenderPipeline instance: " +
|
||||
m_descriptor.GetFullName() + ".");
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t pipelineHandle =
|
||||
m_runtime->RetainExternalManagedObject(pipelineObject);
|
||||
if (pipelineHandle == 0) {
|
||||
m_runtime->DestroyExternalManagedObject(assetHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_assetHandle = assetHandle;
|
||||
m_pipelineHandle = pipelineHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReleaseManagedObjects() {
|
||||
if (!IsRuntimeAlive()) {
|
||||
m_assetHandle = 0;
|
||||
m_pipelineHandle = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pipelineHandle != 0) {
|
||||
m_runtime->DestroyExternalManagedObject(m_pipelineHandle);
|
||||
m_pipelineHandle = 0;
|
||||
}
|
||||
if (m_assetHandle != 0) {
|
||||
m_runtime->DestroyExternalManagedObject(m_assetHandle);
|
||||
m_assetHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
MonoMethod* ResolveSupportsStageMethod(MonoObject* pipelineObject) const {
|
||||
if (m_supportsStageMethod == nullptr) {
|
||||
m_supportsStageMethod =
|
||||
m_runtime->ResolveManagedMethod(
|
||||
pipelineObject,
|
||||
"SupportsStageRenderGraph",
|
||||
1);
|
||||
}
|
||||
|
||||
return m_supportsStageMethod;
|
||||
}
|
||||
|
||||
MonoMethod* ResolveRecordStageMethod(MonoObject* pipelineObject) const {
|
||||
if (m_recordStageMethod == nullptr) {
|
||||
m_recordStageMethod =
|
||||
m_runtime->ResolveManagedMethod(
|
||||
pipelineObject,
|
||||
"RecordStageRenderGraph",
|
||||
1);
|
||||
}
|
||||
|
||||
return m_recordStageMethod;
|
||||
}
|
||||
|
||||
MonoScriptRuntime* m_runtime = nullptr;
|
||||
std::weak_ptr<void> m_runtimeLifetime;
|
||||
Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor m_descriptor;
|
||||
mutable uint32_t m_assetHandle = 0;
|
||||
mutable uint32_t m_pipelineHandle = 0;
|
||||
mutable MonoMethod* m_supportsStageMethod = nullptr;
|
||||
mutable MonoMethod* m_recordStageMethod = nullptr;
|
||||
mutable bool m_pipelineCreationAttempted = false;
|
||||
};
|
||||
|
||||
class MonoManagedRenderPipelineBridge final
|
||||
: public Rendering::Pipelines::ManagedRenderPipelineBridge {
|
||||
public:
|
||||
MonoManagedRenderPipelineBridge(
|
||||
MonoScriptRuntime* runtime,
|
||||
std::weak_ptr<void> runtimeLifetime)
|
||||
: m_runtime(runtime)
|
||||
, m_runtimeLifetime(std::move(runtimeLifetime)) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Rendering::RenderPipelineStageRecorder> CreateStageRecorder(
|
||||
const Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor) const override {
|
||||
if (m_runtime == nullptr ||
|
||||
m_runtimeLifetime.expired() ||
|
||||
!descriptor.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<MonoManagedRenderPipelineStageRecorder>(
|
||||
m_runtime,
|
||||
m_runtimeLifetime,
|
||||
descriptor);
|
||||
}
|
||||
|
||||
private:
|
||||
MonoScriptRuntime* m_runtime = nullptr;
|
||||
std::weak_ptr<void> m_runtimeLifetime;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoClass* monoClass) {
|
||||
ManagedComponentTypeInfo typeInfo;
|
||||
if (!monoClass) {
|
||||
@@ -2003,12 +2269,19 @@ bool MonoScriptRuntime::Initialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_runtimeLifetimeToken = std::make_shared<int>(0);
|
||||
m_initialized = true;
|
||||
Rendering::Pipelines::SetManagedRenderPipelineBridge(
|
||||
std::make_shared<MonoManagedRenderPipelineBridge>(
|
||||
this,
|
||||
m_runtimeLifetimeToken));
|
||||
return true;
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::Shutdown() {
|
||||
Rendering::Pipelines::ClearManagedRenderPipelineBridge();
|
||||
ClearManagedInstances();
|
||||
ClearExternalManagedObjects();
|
||||
ClearClassCache();
|
||||
|
||||
m_coreAssembly = nullptr;
|
||||
@@ -2019,6 +2292,8 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_behaviourClass = nullptr;
|
||||
m_gameObjectClass = nullptr;
|
||||
m_monoBehaviourClass = nullptr;
|
||||
m_scriptableRenderPipelineAssetClass = nullptr;
|
||||
m_scriptableRenderPipelineClass = nullptr;
|
||||
m_serializeFieldAttributeClass = nullptr;
|
||||
m_gameObjectConstructor = nullptr;
|
||||
m_managedGameObjectUUIDField = nullptr;
|
||||
@@ -2030,6 +2305,7 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_activeScene = nullptr;
|
||||
GetInternalCallScene() = nullptr;
|
||||
GetInternalCallDeltaTime() = 0.0f;
|
||||
m_runtimeLifetimeToken.reset();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
@@ -2614,6 +2890,24 @@ bool MonoScriptRuntime::DiscoverScriptClasses() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelineAssetClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
"ScriptableRenderPipelineAsset");
|
||||
if (!m_scriptableRenderPipelineAssetClass) {
|
||||
SetError("Failed to locate the managed ScriptableRenderPipelineAsset base type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scriptableRenderPipelineClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
"ScriptableRenderPipeline");
|
||||
if (!m_scriptableRenderPipelineClass) {
|
||||
SetError("Failed to locate the managed ScriptableRenderPipeline base type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_serializeFieldAttributeClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
@@ -2917,6 +3211,117 @@ const MonoScriptRuntime::ClassMetadata* MonoScriptRuntime::FindClassMetadata(
|
||||
return it != m_classes.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::ResolveManagedClass(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
MonoClass*& outClass) const {
|
||||
outClass = nullptr;
|
||||
if (!m_initialized || assemblyName.empty() || className.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoImage* image = nullptr;
|
||||
if (assemblyName == m_settings.coreAssemblyName) {
|
||||
image = m_coreImage;
|
||||
} else if (assemblyName == m_settings.appAssemblyName) {
|
||||
image = m_appImage;
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
outClass = mono_class_from_name(
|
||||
image,
|
||||
namespaceName.c_str(),
|
||||
className.c_str());
|
||||
return outClass != nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::CreateExternalManagedObject(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
uint32_t& outHandle) {
|
||||
outHandle = 0;
|
||||
|
||||
MonoClass* monoClass = nullptr;
|
||||
if (!ResolveManagedClass(
|
||||
assemblyName,
|
||||
namespaceName,
|
||||
className,
|
||||
monoClass)) {
|
||||
SetError(
|
||||
"Managed class was not found: " +
|
||||
assemblyName + "|" +
|
||||
BuildFullClassName(namespaceName, className));
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateExternalManagedObject(monoClass, outHandle);
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::CreateExternalManagedObject(
|
||||
MonoClass* monoClass,
|
||||
uint32_t& outHandle) {
|
||||
outHandle = 0;
|
||||
if (!m_initialized || !monoClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoObject* const instance = mono_object_new(m_appDomain, monoClass);
|
||||
if (!instance) {
|
||||
SetError(
|
||||
"Mono failed to allocate a managed object for " +
|
||||
BuildFullClassName(
|
||||
SafeString(mono_class_get_namespace(monoClass)),
|
||||
SafeString(mono_class_get_name(monoClass))) + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
mono_runtime_object_init(instance);
|
||||
outHandle = RetainExternalManagedObject(instance);
|
||||
return outHandle != 0;
|
||||
}
|
||||
|
||||
uint32_t MonoScriptRuntime::RetainExternalManagedObject(MonoObject* instance) {
|
||||
if (!m_initialized || !instance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
const uint32_t gcHandle = mono_gchandle_new(instance, false);
|
||||
if (gcHandle == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_externalManagedObjects[gcHandle] = ExternalManagedObjectData{
|
||||
mono_object_get_class(instance),
|
||||
gcHandle
|
||||
};
|
||||
return gcHandle;
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::DestroyExternalManagedObject(uint32_t gcHandle) {
|
||||
if (gcHandle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = m_externalManagedObjects.find(gcHandle);
|
||||
if (it == m_externalManagedObjects.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
mono_gchandle_free(gcHandle);
|
||||
m_externalManagedObjects.erase(it);
|
||||
}
|
||||
|
||||
MonoScriptRuntime::InstanceData* MonoScriptRuntime::FindInstance(const ScriptRuntimeContext& context) {
|
||||
const auto it = m_instances.find(InstanceKey{context.gameObjectUUID, context.scriptComponentUUID});
|
||||
return it != m_instances.end() ? &it->second : nullptr;
|
||||
@@ -2948,6 +3353,43 @@ MonoObject* MonoScriptRuntime::GetManagedObject(const InstanceData& instanceData
|
||||
return mono_gchandle_get_target(instanceData.gcHandle);
|
||||
}
|
||||
|
||||
MonoObject* MonoScriptRuntime::GetManagedObject(uint32_t gcHandle) const {
|
||||
if (gcHandle == 0 ||
|
||||
m_externalManagedObjects.find(gcHandle) == m_externalManagedObjects.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
return mono_gchandle_get_target(gcHandle);
|
||||
}
|
||||
|
||||
MonoMethod* MonoScriptRuntime::ResolveManagedMethod(
|
||||
MonoClass* monoClass,
|
||||
const char* methodName,
|
||||
int parameterCount) const {
|
||||
if (!monoClass || !methodName) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
return mono_class_get_method_from_name(
|
||||
monoClass,
|
||||
methodName,
|
||||
parameterCount);
|
||||
}
|
||||
|
||||
MonoMethod* MonoScriptRuntime::ResolveManagedMethod(
|
||||
MonoObject* instance,
|
||||
const char* methodName,
|
||||
int parameterCount) const {
|
||||
return instance != nullptr
|
||||
? ResolveManagedMethod(
|
||||
mono_object_get_class(instance),
|
||||
methodName,
|
||||
parameterCount)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::ApplyContextFields(const ScriptRuntimeContext& context, MonoObject* instance) {
|
||||
if (!instance) {
|
||||
return false;
|
||||
@@ -3552,24 +3994,49 @@ void MonoScriptRuntime::ClearManagedInstances() {
|
||||
m_instances.clear();
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::ClearExternalManagedObjects() {
|
||||
for (auto& [gcHandle, objectData] : m_externalManagedObjects) {
|
||||
(void)objectData;
|
||||
if (gcHandle != 0) {
|
||||
mono_gchandle_free(gcHandle);
|
||||
}
|
||||
}
|
||||
|
||||
m_externalManagedObjects.clear();
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::ClearClassCache() {
|
||||
m_classes.clear();
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::InvokeManagedMethod(MonoObject* instance, MonoMethod* method, void** args) {
|
||||
bool MonoScriptRuntime::InvokeManagedMethod(
|
||||
MonoObject* instance,
|
||||
MonoMethod* method,
|
||||
void** args,
|
||||
MonoObject** outReturnValue) {
|
||||
if (!instance || !method) {
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoObject* exception = nullptr;
|
||||
mono_runtime_invoke(method, instance, args, &exception);
|
||||
MonoObject* returnValue = mono_runtime_invoke(method, instance, args, &exception);
|
||||
if (exception) {
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = nullptr;
|
||||
}
|
||||
RecordException(exception);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outReturnValue) {
|
||||
*outReturnValue = returnValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user