// 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 #include #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(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(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(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(obj)) { actor->SetName(String(mono_class_get_name(typeClass))); } // Link created managed instance to the unmanaged object if (auto* managedScriptingObject = dynamic_cast(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);