// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "BinaryModule.h" #include "Scripting.h" #include "StdTypesContainer.h" #include "ScriptingType.h" #include "FlaxEngine.Gen.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/ThreadLocal.h" #include "Engine/Threading/IRunnable.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/File.h" #include "Engine/Debug/DebugLog.h" #if USE_EDITOR #include "Engine/Level/Level.h" #include "Editor/Scripting/ScriptsBuilder.h" #endif #include "ManagedCLR/MAssembly.h" #include "ManagedCLR/MClass.h" #include "ManagedCLR/MMethod.h" #include "ManagedCLR/MDomain.h" #include "ManagedCLR/MCore.h" #include "MException.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Asset.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Serialization/JsonTools.h" #if USE_MONO #include #include #endif extern void registerFlaxEngineInternalCalls(); class ScriptingService : public EngineService { public: ScriptingService() : EngineService(TEXT("Scripting"), -20) { } bool Init() override; void Update() override; void LateUpdate() override; void FixedUpdate() override; void Draw() override; void BeforeExit() override; void Dispose() override; }; namespace { MDomain* _rootDomain = nullptr; MDomain* _scriptsDomain = nullptr; CriticalSection _objectsLocker; #define USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING 0 #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING struct ScriptingObjectData { ScriptingObject* Ptr; StringAnsi TypeName; ScriptingObjectData() { Ptr = nullptr; } ScriptingObjectData(ScriptingObject* ptr) { Ptr = ptr; if (ptr && ptr->GetTypeHandle() && ptr->GetTypeHandle().TypeIndex < ptr->GetTypeHandle().Module->Types.Count()) TypeName = ptr->GetType().Fullname; } ScriptingObject* operator->() const { return Ptr; } explicit operator ScriptingObject*() { return Ptr; } operator ScriptingObject*() const { return Ptr; } }; Dictionary _objectsDictionary(1024 * 16); #else Dictionary _objectsDictionary(1024 * 16); #endif bool _isEngineAssemblyLoaded = false; bool _hasGameModulesLoaded = false; MMethod* _method_Update = nullptr; MMethod* _method_LateUpdate = nullptr; MMethod* _method_FixedUpdate = nullptr; MMethod* _method_Draw = nullptr; MMethod* _method_Exit = nullptr; Array> _nonNativeModules; #if USE_EDITOR bool LastBinariesLoadTriggeredCompilation = false; #endif } Delegate Scripting::BinaryModuleLoaded; Action Scripting::ScriptsLoaded; Action Scripting::ScriptsUnload; Action Scripting::ScriptsReloading; Action Scripting::ScriptsReloaded; ThreadLocal Scripting::ObjectsLookupIdMapping; ScriptingService ScriptingServiceInstance; bool initFlaxEngine(); // Assembly events void onEngineLoaded(MAssembly* assembly); void onEngineUnloading(MAssembly* assembly); bool ScriptingService::Init() { const auto startTime = DateTime::NowUTC(); // Link for assemblies events auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; engineAssembly->Loaded.Bind(onEngineLoaded); engineAssembly->Unloading.Bind(onEngineUnloading); // Initialize managed runtime if (MCore::LoadEngine()) { LOG(Fatal, "Mono initialization failed."); return true; } // Cache root domain _rootDomain = MCore::GetRootDomain(); #if USE_SCRIPTING_SINGLE_DOMAIN // Use single root domain auto domain = _rootDomain; #else // Create Mono domain for scripts auto domain = MCore::CreateDomain("Scripts Domain"); #endif domain->SetCurrentDomain(true); _scriptsDomain = domain; // Add internal calls registerFlaxEngineInternalCalls(); // Load assemblies if (Scripting::Load()) { LOG(Fatal, "Scripting Engine initialization failed."); return true; } auto endTime = DateTime::NowUTC(); LOG(Info, "Scripting Engine initializated! (time: {0}ms)", (int32)((endTime - startTime).GetTotalMilliseconds())); return false; } #if COMPILE_WITHOUT_CSHARP #define INVOKE_EVENT(name) #else #define INVOKE_EVENT(name) \ if (!_isEngineAssemblyLoaded) return; \ if (_method_##name == nullptr) \ { \ MClass* mclass = Scripting::GetStaticClass(); \ if (mclass == nullptr) \ { \ LOG(Fatal, "Missing Scripting class."); \ return; \ } \ _method_##name = mclass->GetMethod("Internal_" #name, 0); \ if (_method_##name == nullptr) \ { \ LOG(Fatal, "Missing Scripting method " #name "."); \ return; \ } \ } \ MObject* exception = nullptr; \ _method_##name->Invoke(nullptr, nullptr, &exception); \ DebugLog::LogException(exception) #endif void ScriptingService::Update() { PROFILE_CPU_NAMED("Scripting::Update"); INVOKE_EVENT(Update); } void ScriptingService::LateUpdate() { PROFILE_CPU_NAMED("Scripting::LateUpdate"); INVOKE_EVENT(LateUpdate); } void ScriptingService::FixedUpdate() { PROFILE_CPU_NAMED("Scripting::FixedUpdate"); INVOKE_EVENT(FixedUpdate); } void ScriptingService::Draw() { PROFILE_CPU_NAMED("Scripting::Draw"); INVOKE_EVENT(Draw); } void ScriptingService::BeforeExit() { PROFILE_CPU_NAMED("Scripting::BeforeExit"); INVOKE_EVENT(Exit); } MDomain* Scripting::GetRootDomain() { return _rootDomain; } MDomain* Scripting::GetScriptsDomain() { return _scriptsDomain; } void Scripting::ProcessBuildInfoPath(String& path, const String& projectFolderPath) { if (path.IsEmpty()) return; if (path.StartsWith(TEXT("$(EnginePath)"))) path = Globals::StartupFolder / path.Substring(14); else if (path.StartsWith(TEXT("$(ProjectPath)"))) path = projectFolderPath / path.Substring(14); else if (FileSystem::IsRelative(path)) path = projectFolderPath / path; } bool Scripting::LoadBinaryModules(const String& path, const String& projectFolderPath) { PROFILE_CPU_NAMED("LoadBinaryModules"); LOG(Info, "Loading binary modules from build info file {0}", path); // Read file contents Array fileData; if (File::ReadAllBytes(path, fileData)) { LOG(Error, "Failed to read file contents."); return true; } // Parse Json data rapidjson_flax::Document document; { PROFILE_CPU_NAMED("Json.Parse"); document.Parse((char*)fileData.Get(), fileData.Count()); } if (document.HasParseError()) { LOG(Error, "Failed to file contents."); return true; } // TODO: validate Name, Platform, Architecture, Configuration from file // Load all references auto referencesMember = document.FindMember("References"); if (referencesMember != document.MemberEnd() && referencesMember->value.IsArray()) { auto& referencesArray = referencesMember->value; for (rapidjson::SizeType i = 0; i < referencesArray.Size(); i++) { auto& reference = referencesArray[i]; String referenceProjectPath = JsonTools::GetString(reference, "ProjectPath", String::Empty); if (referenceProjectPath == TEXT("$(EnginePath)/Flax.flaxproj")) continue; // Skip reference to engine String referencePath = JsonTools::GetString(reference, "Path", String::Empty); if (referenceProjectPath.IsEmpty() || referencePath.IsEmpty()) { LOG(Error, "Empty reference."); return true; } ProcessBuildInfoPath(referenceProjectPath, projectFolderPath); ProcessBuildInfoPath(referencePath, projectFolderPath); String referenceProjectFolderPath = StringUtils::GetDirectoryName(referenceProjectPath); if (LoadBinaryModules(referencePath, referenceProjectFolderPath)) { LOG(Error, "Failed to load reference."); return true; } } } // Load all binary modules auto binaryModulesMember = document.FindMember("BinaryModules"); if (binaryModulesMember != document.MemberEnd() && binaryModulesMember->value.IsArray()) { auto& binaryModulesArray = binaryModulesMember->value; for (rapidjson::SizeType i = 0; i < binaryModulesArray.Size(); i++) { auto& binaryModule = binaryModulesArray[i]; const auto nameMember = binaryModule.FindMember("Name"); if (nameMember == binaryModule.MemberEnd()) { LOG(Error, "Failed to process file."); return true; } String name = nameMember->value.GetText(); StringAnsi nameAnsi(nameMember->value.GetString(), nameMember->value.GetStringLength()); String nativePath = JsonTools::GetString(binaryModule, "NativePath", String::Empty); String managedPath = JsonTools::GetString(binaryModule, "ManagedPath", String::Empty); ProcessBuildInfoPath(nativePath, projectFolderPath); ProcessBuildInfoPath(managedPath, projectFolderPath); LOG(Info, "Loading binary module {0}", name); // Check if that module has been already registered BinaryModule* module = BinaryModule::GetModule(nameAnsi); if (!module) { // C++ if (nativePath.HasChars()) { // Check if this module has been statically linked auto& staticallyLinkedBinaryModules = StaticallyLinkedBinaryModuleInitializer::GetStaticallyLinkedBinaryModules(); for (auto getter : staticallyLinkedBinaryModules) { module = getter(); if (module && module->GetName() == nameAnsi) break; module = nullptr; } if (!module) { // Load library const auto startTime = DateTime::NowUTC(); #if PLATFORM_ANDROID // On Android all native binaries are side-by-side with the app nativePath = StringUtils::GetDirectoryName(Platform::GetExecutableFilePath()); nativePath /= StringUtils::GetFileName(nativePath); #endif auto library = Platform::LoadLibrary(nativePath.Get()); if (!library) { LOG(Error, "Failed to load library '{0}' for binary module {1}.", nativePath, name); return true; } char getBinaryFuncName[512]; StringAnsiView getBinaryFuncNamePrefix("GetBinaryModule"); ASSERT(getBinaryFuncNamePrefix.Length() + nameAnsi.Length() < ARRAY_COUNT(getBinaryFuncName)); Platform::MemoryCopy(getBinaryFuncName, getBinaryFuncNamePrefix.Get(), getBinaryFuncNamePrefix.Length()); Platform::MemoryCopy(getBinaryFuncName + getBinaryFuncNamePrefix.Length(), nameAnsi.Get(), nameAnsi.Length()); *(getBinaryFuncName + getBinaryFuncNamePrefix.Length() + nameAnsi.Length()) = '\0'; const auto getBinaryFunc = (GetBinaryModuleFunc)Platform::GetProcAddress(library, getBinaryFuncName); if (!getBinaryFunc) { Platform::FreeLibrary(library); LOG(Error, "Failed to setup library '{0}' for binary module {1}.", nativePath, name); return true; } const auto endTime = DateTime::NowUTC(); LOG(Info, "Module {0} loaded in {1}ms", name, (int32)(endTime - startTime).GetTotalMilliseconds()); // Get the binary module module = getBinaryFunc(); if (!module) { Platform::FreeLibrary(library); LOG(Error, "Failed to get binary module {0}.", name); return true; } ((NativeBinaryModule*)module)->Library = library; } } else { // Create module if native library is not used module = New(nameAnsi, MAssemblyOptions()); _nonNativeModules.Add(module); } } #if !COMPILE_WITHOUT_CSHARP // C# if (managedPath.HasChars() && !((ManagedBinaryModule*)module)->Assembly->IsLoaded()) { if (((ManagedBinaryModule*)module)->Assembly->Load(managedPath)) { LOG(Error, "Failed to load C# assembly '{0}' for binary module {1}.", managedPath, name); return true; } } #endif BinaryModuleLoaded(module); } } return false; } bool Scripting::Load() { PROFILE_CPU(); // Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads) ASSERT(IsInMainThread()); #if !COMPILE_WITHOUT_CSHARP // Load C# core assembly if (GetBinaryModuleCorlib()->Assembly->Load(mono_get_corlib())) { LOG(Error, "Failed to load corlib C# assembly."); return true; } #endif // Load FlaxEngine const String flaxEnginePath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll"); if (((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->Load(flaxEnginePath)) { LOG(Error, "Failed to load FlaxEngine C# assembly."); return true; } #if USE_EDITOR // Skip loading game modules in Editor on startup - Editor loads them later during splash screen (eg. after first compilation) static bool SkipFirstLoad = true; if (SkipFirstLoad) { SkipFirstLoad = false; return false; } // Flax.Build outputs the .Build.json with binary modules to use for game scripting const Char *target, *platform, *architecture, *configuration; ScriptsBuilder::GetBinariesConfiguration(target, platform, architecture, configuration); if (StringUtils::Length(target) == 0) { LOG(Info, "Missing EditorTarget in project. Not using game script modules."); _hasGameModulesLoaded = true; return false; } const String targetBuildInfo = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration / target + TEXT(".Build.json"); // Call compilation if game target build info is missing if (!FileSystem::FileExists(targetBuildInfo)) { LOG(Info, "Missing target build info ({0})", targetBuildInfo); if (LastBinariesLoadTriggeredCompilation) return false; LastBinariesLoadTriggeredCompilation = true; ScriptsBuilder::Compile(); return false; } #else const String targetBuildInfo = Globals::BinariesFolder / TEXT("Game.Build.json"); #endif // Load game binary modules if (LoadBinaryModules(targetBuildInfo, Globals::ProjectFolder)) { LOG(Error, "Failed to load Game assemblies."); return true; } _hasGameModulesLoaded = true; // End ScriptsLoaded(); return false; } void Scripting::Release() { PROFILE_CPU(); // Note: this action can be called from main thread (due to Mono problems with assemblies actions from other threads) ASSERT(IsInMainThread()); // Fire event ScriptsUnload(); // Cleanup ObjectsRemovalService::Flush(); // Cleanup some managed objects MCore::GC::Collect(); MCore::GC::WaitForPendingFinalizers(); // Release managed objects instances for persistent objects (assets etc.) _objectsLocker.Lock(); { for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { auto obj = i->Value; #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); #endif obj->OnScriptingDispose(); } } _objectsLocker.Unlock(); // Unload assemblies (from back to front) { LOG(Info, "Unloading binary modules"); auto modules = BinaryModule::GetModules(); for (int32 i = modules.Count() - 1; i >= 0; i--) { auto module = modules[i]; if (module == GetBinaryModuleCorlib() || module == GetBinaryModuleFlaxEngine()) { // Just C# assembly unload for in-build modules ((ManagedBinaryModule*)module)->Assembly->Unload(); continue; } module->Destroy(false); } _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; } // Cleanup MCore::GC::Collect(); MCore::GC::WaitForPendingFinalizers(); // Flush objects ObjectsRemovalService::Flush(); // Switch domain auto rootDomain = MCore::GetRootDomain(); if (rootDomain) { if (!rootDomain->SetCurrentDomain(false)) { LOG(Error, "Failed to set current domain to root"); } } #if !USE_SCRIPTING_SINGLE_DOMAIN MCore::UnloadDomain("Scripts Domain"); #endif } #if USE_EDITOR void Scripting::Reload(bool canTriggerSceneReload) { // By default we allow to call it only from the main thread and when no scene is loaded. // Otherwise call scene manager to perform clear scripts reload. // It will call this method back on main thread without scenes loaded, see SceneActionType::ReloadScripts. if (!IsInMainThread() || Level::IsAnySceneLoaded()) { if (canTriggerSceneReload) { // Call scene to reload scripts Level::ReloadScriptsAsync(); } else { LOG(Warning, "Cannot reload scene on scripting reload. Flag is not set."); } return; } PROFILE_CPU(); // Ideally we would call Release and Load but this would also reload Editor objects which we want avoid // Editor is also referencing assets and other managed objects so we should force reload everything. // However Reload is called when no scene is loaded. // Faster path - if no game assembly loaded yet if (!_hasGameModulesLoaded) { // Just load missing assemblies Load(); return; } LOG(Info, "Start user scripts reload"); ScriptsReloading(); // Flush cache (some objects may be deleted after reload start event) ObjectsRemovalService::Flush(); // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect(); MCore::GC::WaitForPendingFinalizers(); // Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload) _objectsLocker.Lock(); { const auto flaxModule = GetBinaryModuleFlaxEngine(); for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { auto obj = i->Value; if (obj->GetTypeHandle().Module == flaxModule) continue; #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); #endif obj->OnScriptingDispose(); } } _objectsLocker.Unlock(); // Unload all game modules LOG(Info, "Unloading game binary modules"); auto modules = BinaryModule::GetModules(); for (int32 i = modules.Count() - 1; i >= 0; i--) { BinaryModule* module = modules[i]; if (module == GetBinaryModuleCorlib() || module == GetBinaryModuleFlaxEngine()) continue; module->Destroy(true); } modules.Clear(); _nonNativeModules.ClearDelete(); _hasGameModulesLoaded = false; // Give GC a try to cleanup old user objects and the other mess MCore::GC::Collect(); MCore::GC::WaitForPendingFinalizers(); // Load all game modules if (Load()) { LOG(Error, "User assemblies reload failed."); } ScriptsReloaded(); LOG(Info, "End user scripts reload"); } #endif MClass* Scripting::FindClass(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return nullptr; PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) { auto managedModule = dynamic_cast(module); if (managedModule && managedModule->Assembly->IsLoaded()) { MClass* result = managedModule->Assembly->GetClass(fullname); if (result != nullptr) return result; } } return nullptr; } #if USE_MONO MClass* Scripting::FindClass(MonoClass* monoClass) { if (monoClass == nullptr) return nullptr; PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) { auto managedModule = dynamic_cast(module); if (managedModule && managedModule->Assembly->IsLoaded()) { MClass* result = managedModule->Assembly->GetClass(monoClass); if (result != nullptr) return result; } } return nullptr; } MonoClass* Scripting::FindClassNative(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return nullptr; PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) { auto managedModule = dynamic_cast(module); if (managedModule && managedModule->Assembly->IsLoaded()) { MClass* result = managedModule->Assembly->GetClass(fullname); if (result != nullptr) return result->GetNative(); } } return nullptr; } #endif ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname) { if (fullname.IsEmpty()) return ScriptingTypeHandle(); PROFILE_CPU(); auto& modules = BinaryModule::GetModules(); for (auto module : modules) { int32 typeIndex; if (module->FindScriptingType(fullname, typeIndex)) { return ScriptingTypeHandle(module, typeIndex); } } return ScriptingTypeHandle(); } ScriptingObject* Scripting::NewObject(const ScriptingTypeHandle& type) { if (!type) { LOG(Error, "Invalid type."); return nullptr; } const ScriptingType& scriptingType = type.GetType(); // Create unmanaged object const ScriptingObjectSpawnParams params(Guid::New(), type); ScriptingObject* obj = scriptingType.Script.Spawn(params); if (obj == nullptr) LOG(Error, "Failed to spawn object of type \'{0}\'.", scriptingType.ToString()); return obj; } ScriptingObject* Scripting::NewObject(const MClass* type) { if (type == nullptr) { LOG(Error, "Invalid type."); return nullptr; } #if USE_MONO // Get the assembly with that class MonoClass* typeClass = type->GetNative(); auto module = ManagedBinaryModule::FindModule(typeClass); 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.Script.Spawn(params); if (obj == nullptr) LOG(Error, "Failed to spawn object of type \'{0}\'.", scriptingType.ToString()); return obj; #else LOG(Error, "Not supported object creation from Managed class."); return nullptr; #endif } FLAXENGINE_API ScriptingObject* FindObject(const Guid& id, MClass* type) { return Scripting::FindObject(id, type); } ScriptingObject* Scripting::FindObject(Guid id, MClass* type) { if (!id.IsValid()) return nullptr; PROFILE_CPU(); // Try to map object id const auto idsMapping = ObjectsLookupIdMapping.Get(); if (idsMapping) { idsMapping->TryGet(id, id); } // Try to find it #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING ScriptingObjectData data; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, data); _objectsLocker.Unlock(); auto result = data.Ptr; #else ScriptingObject* result = nullptr; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, result); _objectsLocker.Unlock(); #endif if (result) { // Check type if (!type || result->Is(type)) return result; LOG(Warning, "Found scripting object with ID={0} of type {1} that doesn't match type {2}.", id, String(result->GetType().Fullname), String(type->GetFullName())); return nullptr; } // Check if object can be an asset and try to load it if (!type) { result = Content::LoadAsync(id); if (!result) LOG(Warning, "Unable to find scripting object with ID={0}", id); return result; } if (type == ScriptingObject::GetStaticClass() || type->IsSubClassOf(Asset::GetStaticClass())) { Asset* asset = Content::LoadAsync(id, type); if (asset) return asset; } LOG(Warning, "Unable to find scripting object with ID={0}. Required type {1}.", id, String(type->GetFullName())); return nullptr; } ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) { if (!id.IsValid()) return nullptr; PROFILE_CPU(); // Try to map object id const auto idsMapping = ObjectsLookupIdMapping.Get(); if (idsMapping) { idsMapping->TryGet(id, id); } // Try to find it #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING ScriptingObjectData data; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, data); _objectsLocker.Unlock(); auto result = data.Ptr; #else ScriptingObject* result = nullptr; _objectsLocker.Lock(); _objectsDictionary.TryGet(id, result); _objectsLocker.Unlock(); #endif // Check type if (result && type && !result->Is(type)) { result = nullptr; } return result; } ScriptingObject* Scripting::TryFindObject(MClass* mclass) { if (mclass == nullptr) return nullptr; ScopeLock lock(_objectsLocker); for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { const auto obj = i->Value; if (obj->GetClass() == mclass) return obj; } return nullptr; } ScriptingObject* Scripting::FindObject(const MObject* managedInstance) { if (managedInstance == nullptr) return nullptr; PROFILE_CPU(); // TODO: optimize it by reading the unmanagedPtr or _internalId from managed Object property ScopeLock lock(_objectsLocker); for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) { const auto obj = i->Value; if (obj->GetManagedInstance() == managedInstance) return obj; } return nullptr; } void Scripting::OnManagedInstanceDeleted(ScriptingObject* obj) { PROFILE_CPU(); ASSERT(obj); // Validate if object still exists _objectsLocker.Lock(); if (_objectsDictionary.ContainsValue(obj)) { #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING LOG(Info, "[OnManagedInstanceDeleted] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); #endif obj->OnManagedInstanceDeleted(); } else { //LOG(Warning, "Object finalization called for already removed object (address={0:x})", (uint64)obj); } _objectsLocker.Unlock(); } bool Scripting::HasGameModulesLoaded() { return _hasGameModulesLoaded; } bool Scripting::IsEveryAssemblyLoaded() { auto& modules = BinaryModule::GetModules(); for (BinaryModule* module : modules) { if (!module->IsLoaded()) return false; } return true; } bool Scripting::IsTypeFromGameScripts(MClass* type) { const auto binaryModule = ManagedBinaryModule::GetModule(type ? type->GetAssembly() : nullptr); return binaryModule && binaryModule != GetBinaryModuleCorlib() && binaryModule != GetBinaryModuleFlaxEngine(); } void Scripting::RegisterObject(ScriptingObject* obj) { ScopeLock lock(_objectsLocker); //ASSERT(!_objectsDictionary.ContainsValue(obj)); #if ENABLE_ASSERTION #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING ScriptingObjectData other; if (_objectsDictionary.TryGet(obj->GetID(), other)) #else ScriptingObject* other; if (_objectsDictionary.TryGet(obj->GetID(), other)) #endif { // Something went wrong... LOG(Error, "Objects registry already contains object with ID={0} (type '{3}')! Trying to register object {1} (type '{2}').", obj->GetID(), obj->ToString(), String(obj->GetClass()->GetFullName()), String(other->GetClass()->GetFullName())); _objectsDictionary.Remove(obj->GetID()); } #else ASSERT(!_objectsDictionary.ContainsKey(obj->_id)); #endif #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING LOG(Info, "[RegisterObject] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); #endif _objectsDictionary.Add(obj->GetID(), obj); } void Scripting::UnregisterObject(ScriptingObject* obj) { ScopeLock lock(_objectsLocker); //ASSERT(!obj->_id.IsValid() || _objectsDictionary.ContainsValue(obj)); #if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING LOG(Info, "[UnregisterObject] obj = 0x{0:x}, {1}", (uint64)obj, String(ScriptingObjectData(obj).TypeName)); #endif _objectsDictionary.Remove(obj->GetID()); } void Scripting::OnObjectIdChanged(ScriptingObject* obj, const Guid& oldId) { ScopeLock lock(_objectsLocker); ASSERT(obj && oldId.IsValid()); ASSERT(obj->GetID() != oldId); ASSERT(_objectsDictionary.ContainsKey(oldId)); //ASSERT(_objectsDictionary.ContainsValue(obj)); ASSERT(!_objectsDictionary.ContainsKey(obj->GetID())); _objectsDictionary.Remove(oldId); _objectsDictionary.Add(obj->GetID(), obj); } bool initFlaxEngine() { // Cache common types if (StdTypesContainer::Instance()->Gather()) return true; #if !COMPILE_WITHOUT_CSHARP // Init C# class library { auto scriptingClass = Scripting::GetStaticClass(); ASSERT(scriptingClass); const auto initMethod = scriptingClass->GetMethod("Init"); ASSERT(initMethod); MObject* exception = nullptr; initMethod->Invoke(nullptr, nullptr, &exception); if (exception) { MException ex(exception); ex.Log(LogType::Fatal, TEXT("FlaxEngine.Scripting.Init")); return true; } } #endif // TODO: move it somewhere to game instance class or similar MainRenderTask::Instance = New(); return false; } void onEngineLoaded(MAssembly* assembly) { if (initFlaxEngine()) { LOG(Fatal, "Failed to initialize Flax Engine runtime."); } // Set flag _isEngineAssemblyLoaded = true; } void onEngineUnloading(MAssembly* assembly) { // Clear flag _isEngineAssemblyLoaded = false; // Clear cached methods _method_Update = nullptr; _method_LateUpdate = nullptr; _method_FixedUpdate = nullptr; _method_Exit = nullptr; StdTypesContainer::Instance()->Clear(); } void ScriptingService::Dispose() { Scripting::Release(); MCore::UnloadEngine(); }