You're breathtaking!
This commit is contained in:
556
Source/Engine/Scripting/ScriptingObject.cpp
Normal file
556
Source/Engine/Scripting/ScriptingObject.cpp
Normal file
@@ -0,0 +1,556 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ScriptingObject.h"
|
||||
#include "Scripting.h"
|
||||
#include "BinaryModule.h"
|
||||
#include "InternalCalls.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include "ManagedCLR/MAssembly.h"
|
||||
#include "ManagedCLR/MClass.h"
|
||||
#include "ManagedCLR/MUtils.h"
|
||||
#include "ManagedCLR/MField.h"
|
||||
#include "ManagedCLR/MUtils.h"
|
||||
#if PLATFORM_LINUX
|
||||
#include "ManagedCLR/MCore.h"
|
||||
#endif
|
||||
#include "FlaxEngine.Gen.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
|
||||
#define ScriptingObject_unmanagedPtr "__unmanagedPtr"
|
||||
#define ScriptingObject_id "__internalId"
|
||||
|
||||
ScriptingObject::ScriptingObject(const SpawnParams& params)
|
||||
: _gcHandle(0)
|
||||
, _type(params.Type)
|
||||
, _id(params.ID)
|
||||
{
|
||||
// Managed objects must have valid and unique ID
|
||||
ASSERT(_id.IsValid());
|
||||
}
|
||||
|
||||
ScriptingObject::~ScriptingObject()
|
||||
{
|
||||
// Ensure that GC handle is empty
|
||||
ASSERT(_gcHandle == 0);
|
||||
|
||||
// Ensure that object has been already unregistered
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
|
||||
Deleted(this);
|
||||
|
||||
// Handle custom scripting objects removing
|
||||
if (Flags & ObjectFlags::IsCustomScriptingType)
|
||||
{
|
||||
_type.Module->OnObjectDeleted(this);
|
||||
}
|
||||
}
|
||||
|
||||
MonoObject* ScriptingObject::GetManagedInstance() const
|
||||
{
|
||||
return _gcHandle ? mono_gchandle_get_target(_gcHandle) : nullptr;
|
||||
}
|
||||
|
||||
MClass* ScriptingObject::GetClass() const
|
||||
{
|
||||
return _type ? _type.GetType().ManagedClass : nullptr;
|
||||
}
|
||||
|
||||
ScriptingObject* ScriptingObject::ToNative(MonoObject* obj)
|
||||
{
|
||||
ScriptingObject* ptr = nullptr;
|
||||
if (obj)
|
||||
{
|
||||
// TODO: cache the field offset from object and read directly from object pointer
|
||||
const auto ptrField = mono_class_get_field_from_name(mono_object_get_class(obj), ScriptingObject_unmanagedPtr);
|
||||
CHECK_RETURN(ptrField, nullptr);
|
||||
mono_field_get_value(obj, ptrField, &ptr);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void ScriptingObject::ChangeID(const Guid& newId)
|
||||
{
|
||||
ASSERT(newId.IsValid() && newId != _id);
|
||||
|
||||
const Guid prevId = _id;
|
||||
_id = newId;
|
||||
|
||||
// Update managed instance
|
||||
const auto managedInstance = GetManagedInstance();
|
||||
const auto monoClass = GetClass();
|
||||
if (managedInstance && monoClass)
|
||||
{
|
||||
const MField* monoIdField = monoClass->GetField(ScriptingObject_id);
|
||||
if (monoIdField)
|
||||
monoIdField->SetValue(managedInstance, &_id);
|
||||
}
|
||||
|
||||
// Update scripting
|
||||
if (IsRegistered())
|
||||
Scripting::OnObjectIdChanged(this, prevId);
|
||||
_type.GetType().Module->OnObjectIdChanged(this, prevId);
|
||||
}
|
||||
|
||||
void ScriptingObject::OnManagedInstanceDeleted()
|
||||
{
|
||||
// Release the handle
|
||||
if (_gcHandle)
|
||||
{
|
||||
mono_gchandle_free(_gcHandle);
|
||||
_gcHandle = 0;
|
||||
}
|
||||
|
||||
// Unregister object
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
|
||||
// Self destruct
|
||||
DeleteObject();
|
||||
}
|
||||
|
||||
void ScriptingObject::OnScriptingDispose()
|
||||
{
|
||||
// Delete C# object
|
||||
DestroyManaged();
|
||||
|
||||
// Delete C++ object
|
||||
DeleteObject();
|
||||
}
|
||||
|
||||
MonoObject* ScriptingObject::CreateManagedInternal()
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
ASSERT(!HasManagedInstance());
|
||||
#endif
|
||||
|
||||
// Get class
|
||||
MClass* monoClass = GetClass();
|
||||
if (monoClass == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing managed class for objecct '{0}'.", ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate managed instance
|
||||
MonoObject* managedInstance = mono_object_new(mono_domain_get(), monoClass->GetNative());
|
||||
if (managedInstance == nullptr)
|
||||
{
|
||||
LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName()));
|
||||
}
|
||||
|
||||
// Set handle to unmanaged object
|
||||
const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr);
|
||||
if (monoUnmanagedPtrField)
|
||||
{
|
||||
const void* value = this;
|
||||
monoUnmanagedPtrField->SetValue(managedInstance, &value);
|
||||
}
|
||||
|
||||
// Set object id
|
||||
const MField* monoIdField = monoClass->GetField(ScriptingObject_id);
|
||||
if (monoIdField)
|
||||
{
|
||||
monoIdField->SetValue(managedInstance, (void*)&_id);
|
||||
}
|
||||
|
||||
// Initialize managed instance (calls constructor)
|
||||
mono_runtime_object_init(managedInstance);
|
||||
|
||||
return managedInstance;
|
||||
}
|
||||
|
||||
void ScriptingObject::DestroyManaged()
|
||||
{
|
||||
// Get managed instance
|
||||
const auto managedInstance = GetManagedInstance();
|
||||
|
||||
// Reset managed to unmanaged pointer
|
||||
if (managedInstance)
|
||||
{
|
||||
if (const auto monoClass = GetClass())
|
||||
{
|
||||
const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr);
|
||||
if (monoUnmanagedPtrField)
|
||||
{
|
||||
void* param = nullptr;
|
||||
monoUnmanagedPtrField->SetValue(managedInstance, ¶m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the handle
|
||||
if (_gcHandle)
|
||||
{
|
||||
mono_gchandle_free(_gcHandle);
|
||||
_gcHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingObject::RegisterObject()
|
||||
{
|
||||
ASSERT(!IsRegistered());
|
||||
|
||||
Flags |= ObjectFlags::IsRegistered;
|
||||
Scripting::RegisterObject(this);
|
||||
}
|
||||
|
||||
void ScriptingObject::UnregisterObject()
|
||||
{
|
||||
ASSERT(IsRegistered());
|
||||
|
||||
Flags &= ~ObjectFlags::IsRegistered;
|
||||
Scripting::UnregisterObject(this);
|
||||
}
|
||||
|
||||
bool ScriptingObject::CanCast(MClass* from, MClass* to)
|
||||
{
|
||||
if (!from && !to)
|
||||
return true;
|
||||
CHECK_RETURN(from && to, false);
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
// Cannot enter GC unsafe region if the thread is not attached
|
||||
MCore::AttachThread();
|
||||
#endif
|
||||
|
||||
return from->IsSubClassOf(to);
|
||||
}
|
||||
|
||||
void ScriptingObject::OnDeleteObject()
|
||||
{
|
||||
// Cleanup managed object
|
||||
DestroyManaged();
|
||||
|
||||
// Unregister
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
|
||||
// Base
|
||||
RemovableObject::OnDeleteObject();
|
||||
}
|
||||
|
||||
String ScriptingObject::ToString() const
|
||||
{
|
||||
return GetClass()->ToString();
|
||||
}
|
||||
|
||||
ManagedScriptingObject::ManagedScriptingObject(const SpawnParams& params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
void ManagedScriptingObject::CreateManaged()
|
||||
{
|
||||
MonoObject* managedInstance = CreateManagedInternal();
|
||||
if (managedInstance)
|
||||
{
|
||||
// Cache the GC handle to the object (used to track the target object because it can be moved in a memory)
|
||||
_gcHandle = mono_gchandle_new_weakref(managedInstance, false);
|
||||
|
||||
// Ensure to be registered
|
||||
if (!IsRegistered())
|
||||
RegisterObject();
|
||||
}
|
||||
}
|
||||
|
||||
PersistentScriptingObject::PersistentScriptingObject(const SpawnParams& params)
|
||||
: ScriptingObject(params)
|
||||
{
|
||||
}
|
||||
|
||||
PersistentScriptingObject::~PersistentScriptingObject()
|
||||
{
|
||||
PersistentScriptingObject::DestroyManaged();
|
||||
}
|
||||
|
||||
void PersistentScriptingObject::OnManagedInstanceDeleted()
|
||||
{
|
||||
// Cleanup
|
||||
if (_gcHandle)
|
||||
{
|
||||
mono_gchandle_free(_gcHandle);
|
||||
_gcHandle = 0;
|
||||
}
|
||||
|
||||
// But do not delete itself
|
||||
}
|
||||
|
||||
void PersistentScriptingObject::OnScriptingDispose()
|
||||
{
|
||||
// Delete C# object
|
||||
if (IsRegistered())
|
||||
UnregisterObject();
|
||||
DestroyManaged();
|
||||
|
||||
// Don't delete C++ object
|
||||
}
|
||||
|
||||
void PersistentScriptingObject::CreateManaged()
|
||||
{
|
||||
MonoObject* managedInstance = CreateManagedInternal();
|
||||
if (managedInstance)
|
||||
{
|
||||
// Prevent form object GC destruction and moving
|
||||
_gcHandle = mono_gchandle_new(managedInstance, false);
|
||||
|
||||
// Ensure to be registered
|
||||
if (!IsRegistered())
|
||||
RegisterObject();
|
||||
}
|
||||
}
|
||||
|
||||
class ScriptingObjectInternal
|
||||
{
|
||||
public:
|
||||
|
||||
static MonoObject* Create1(MonoReflectionType* type)
|
||||
{
|
||||
// Peek class for that type (handle generic class cases)
|
||||
if (!type)
|
||||
DebugLog::ThrowArgumentNull("type");
|
||||
MonoType* monoType = mono_reflection_type_get_type(type);
|
||||
const int32 monoTypeType = mono_type_get_type(monoType);
|
||||
if (monoTypeType == MONO_TYPE_GENERICINST)
|
||||
{
|
||||
LOG(Error, "Generic scripts are not supported.");
|
||||
return nullptr;
|
||||
}
|
||||
MonoClass* typeClass = mono_type_get_class(monoType);
|
||||
if (typeClass == nullptr)
|
||||
{
|
||||
LOG(Error, "Invalid type.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the assembly with that class
|
||||
// TODO: consider caching lookup table MonoImage* -> ManagedBinaryModule*
|
||||
ManagedBinaryModule* module = nullptr;
|
||||
MonoImage* mImage = mono_class_get_image(typeClass);
|
||||
auto& modules = BinaryModule::GetModules();
|
||||
for (auto e : modules)
|
||||
{
|
||||
auto managedModule = dynamic_cast<ManagedBinaryModule*>(e);
|
||||
if (managedModule && managedModule->Assembly->GetMonoImage() == mImage)
|
||||
{
|
||||
module = managedModule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (module == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot find scripting assembly for type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to find the scripting type for this class
|
||||
int32 typeIndex;
|
||||
if (!module->ClassToTypeIndex.TryGet(typeClass, typeIndex))
|
||||
{
|
||||
LOG(Error, "Cannot spawn objects of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return nullptr;
|
||||
}
|
||||
const ScriptingType& scriptingType = module->Types[typeIndex];
|
||||
|
||||
// Create unmanaged object
|
||||
const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
|
||||
ScriptingObject* obj = scriptingType.Class.Spawn(params);
|
||||
if (obj == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set default name for actors
|
||||
if (auto* actor = dynamic_cast<Actor*>(obj))
|
||||
{
|
||||
actor->SetName(String(mono_class_get_name(typeClass)));
|
||||
}
|
||||
|
||||
// Create managed object
|
||||
obj->CreateManaged();
|
||||
MonoObject* managedInstance = obj->GetManagedInstance();
|
||||
if (managedInstance == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot create managed instance for type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
Delete(obj);
|
||||
}
|
||||
|
||||
return managedInstance;
|
||||
}
|
||||
|
||||
static MonoObject* Create2(MonoString* typeNameObj)
|
||||
{
|
||||
// Get typename
|
||||
if (typeNameObj == nullptr)
|
||||
DebugLog::ThrowArgumentNull("typeName");
|
||||
const StringAsANSI<> typeNameData((const Char*)mono_string_chars(typeNameObj), (int32)mono_string_length(typeNameObj));
|
||||
const StringAnsiView typeName(typeNameData.Get(), (int32)mono_string_length(typeNameObj));
|
||||
|
||||
// Try to find the scripting type for this typename
|
||||
const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName);
|
||||
if (!type)
|
||||
{
|
||||
LOG(Error, "Cannot find scripting type for \'{0}\'.", String(typeName));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create unmanaged object
|
||||
const ScriptingObjectSpawnParams params(Guid::New(), type);
|
||||
ScriptingObject* obj = type.GetType().Class.Spawn(params);
|
||||
if (obj == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to spawn object of type \'{0}\'.", String(typeName));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create managed object
|
||||
obj->CreateManaged();
|
||||
MonoObject* managedInstance = obj->GetManagedInstance();
|
||||
if (managedInstance == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeName));
|
||||
Delete(obj);
|
||||
}
|
||||
|
||||
return managedInstance;
|
||||
}
|
||||
|
||||
static void ManagedInstanceCreated(MonoObject* managedInstance)
|
||||
{
|
||||
MonoClass* typeClass = mono_object_get_class(managedInstance);
|
||||
|
||||
// Get the assembly with that class
|
||||
// TODO: consider caching lookup table MonoImage* -> ManagedBinaryModule*
|
||||
ManagedBinaryModule* module = nullptr;
|
||||
MonoImage* mimage = mono_class_get_image(typeClass);
|
||||
auto& modules = BinaryModule::GetModules();
|
||||
for (auto e : modules)
|
||||
{
|
||||
auto managedModule = dynamic_cast<ManagedBinaryModule*>(e);
|
||||
if (managedModule && managedModule->Assembly->GetMonoImage() == mimage)
|
||||
{
|
||||
module = managedModule;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (module == nullptr)
|
||||
{
|
||||
LOG(Error, "Cannot find scripting assembly for type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find the scripting type for this class
|
||||
int32 typeIndex;
|
||||
if (!module->ClassToTypeIndex.TryGet(typeClass, typeIndex))
|
||||
{
|
||||
LOG(Error, "Cannot spawn objects of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return;
|
||||
}
|
||||
const ScriptingType& scriptingType = module->Types[typeIndex];
|
||||
|
||||
// Create unmanaged object
|
||||
const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
|
||||
ScriptingObject* obj = scriptingType.Class.Spawn(params);
|
||||
if (obj == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set default name for actors
|
||||
if (auto* actor = dynamic_cast<Actor*>(obj))
|
||||
{
|
||||
actor->SetName(String(mono_class_get_name(typeClass)));
|
||||
}
|
||||
|
||||
// Link created managed instance to the unmanaged object
|
||||
if (auto* managedScriptingObject = dynamic_cast<ManagedScriptingObject*>(obj))
|
||||
{
|
||||
// Managed
|
||||
managedScriptingObject->_gcHandle = mono_gchandle_new_weakref(managedInstance, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Persistent
|
||||
obj->_gcHandle = mono_gchandle_new(managedInstance, false);
|
||||
}
|
||||
|
||||
MClass* monoClass = obj->GetClass();
|
||||
|
||||
// Set handle to unmanaged object
|
||||
const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr);
|
||||
if (monoUnmanagedPtrField)
|
||||
{
|
||||
const void* value = obj;
|
||||
monoUnmanagedPtrField->SetValue(managedInstance, &value);
|
||||
}
|
||||
|
||||
// Set object id
|
||||
const MField* monoIdField = monoClass->GetField(ScriptingObject_id);
|
||||
if (monoIdField)
|
||||
{
|
||||
monoIdField->SetValue(managedInstance, (void*)&obj->_id);
|
||||
}
|
||||
|
||||
// Register object
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
}
|
||||
|
||||
static void ManagedInstanceDeleted(ScriptingObject* obj)
|
||||
{
|
||||
Scripting::OnManagedInstanceDeleted(obj);
|
||||
}
|
||||
|
||||
static void Destroy(ManagedScriptingObject* obj, float timeLeft)
|
||||
{
|
||||
// Use scaled game time for removing actors/scripts by the user (maybe expose it to the api?)
|
||||
const bool useGameTime = timeLeft > ZeroTolerance;
|
||||
|
||||
if (obj)
|
||||
obj->DeleteObject(timeLeft, useGameTime);
|
||||
}
|
||||
|
||||
static MonoString* GetTypeName(ManagedScriptingObject* obj)
|
||||
{
|
||||
INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
|
||||
return MUtils::ToString(obj->GetType().Fullname);
|
||||
}
|
||||
|
||||
static MonoObject* FindObject(Guid* id, MonoReflectionType* type)
|
||||
{
|
||||
ScriptingObject* obj = Scripting::FindObject(*id, Scripting::FindClass(MUtils::GetClass(type)));
|
||||
return obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
}
|
||||
|
||||
static MonoObject* TryFindObject(Guid* id, MonoReflectionType* type)
|
||||
{
|
||||
ScriptingObject* obj = Scripting::TryFindObject(*id, Scripting::FindClass(MUtils::GetClass(type)));
|
||||
return obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
}
|
||||
|
||||
static void ChangeID(ManagedScriptingObject* obj, Guid* id)
|
||||
{
|
||||
INTERNAL_CALL_CHECK(obj);
|
||||
obj->ChangeID(*id);
|
||||
}
|
||||
|
||||
static void InitRuntime()
|
||||
{
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Create1", &Create1);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Create2", &Create2);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceCreated", &ManagedInstanceCreated);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &ManagedInstanceDeleted);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Destroy", &Destroy);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetTypeName", &GetTypeName);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &FindObject);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_TryFindObject", &TryFindObject);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ChangeID", &ChangeID);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_SCRIPTING_TYPE_NO_SPAWN(ScriptingObject, FlaxEngine, "FlaxEngine.Object", nullptr, nullptr);
|
||||
Reference in New Issue
Block a user