// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Script.h" #include "Engine/Core/Log.h" #if USE_EDITOR #include "Internal/StdTypesContainer.h" #include "ManagedCLR/MClass.h" #include "Editor/Editor.h" #endif #include "Scripting.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Level.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Threading/Threading.h" #if USE_EDITOR #define CHECK_EXECUTE_IN_EDITOR if (Editor::IsPlayMode || _executeInEditor) #else #define CHECK_EXECUTE_IN_EDITOR #endif Script::Script(const SpawnParams& params) : SceneObject(params) , _enabled(true) , _tickFixedUpdate(false) , _tickUpdate(false) , _tickLateUpdate(false) , _tickLateFixedUpdate(false) , _wasStartCalled(false) , _wasEnableCalled(false) { #if USE_EDITOR _executeInEditor = GetClass()->HasAttribute(StdTypesContainer::Instance()->ExecuteInEditModeAttribute); #endif } void Script::SetEnabled(bool value) { // Check if value will change if (GetEnabled() != value) { // Change state _enabled = value; // Call event for the script if (_parent == nullptr || (_parent->IsDuringPlay() && _parent->IsActiveInHierarchy())) { if (value) { if (!_wasEnableCalled) { Start(); Enable(); } } else if (_wasEnableCalled) { Disable(); } } } } Actor* Script::GetActor() const { return _parent; } void Script::SetActor(Actor* value) { SetParent(value, true); } void Script::SetParent(Actor* value, bool canBreakPrefabLink) { // Check if value won't change if (_parent == value) return; if (IsDuringPlay() && !IsInMainThread()) { LOG(Error, "Editing scene hierarchy is only allowed on a main thread."); return; } // Unlink from the old one if (_parent) { if (!value && _parent->IsDuringPlay() && _parent->IsActiveInHierarchy() && GetEnabled()) { // Call disable when script is removed from actor (new actor is null) Disable(); } _parent->Scripts.RemoveKeepOrder(this); } // Set value const auto previous = _parent; _parent = value; // Link to the new one if (_parent) { _parent->Scripts.Add(this); } // Break prefab link for prefab instance objects if (HasPrefabLink() && IsDuringPlay() && canBreakPrefabLink) { BreakPrefabLink(); } // Check if actor is already in play but script isn't if (value && value->IsDuringPlay() && !IsDuringPlay()) { // Prepare for gameplay Initialize(); { SceneBeginData beginData; BeginPlay(&beginData); beginData.OnDone(); } // Fire events for scripting if (GetEnabled()) { Start(); Enable(); } } else if (!previous && value && value->IsDuringPlay() && value->IsActiveInHierarchy() && GetEnabled()) { // Call enable when script is added to actor (previous actor was null) Enable(); } } int32 Script::GetOrderInParent() const { return _parent ? _parent->Scripts.Find((Script*)this) : INVALID_INDEX; } void Script::SetOrderInParent(int32 index) { if (!_parent) return; // Cache data auto& parentScripts = _parent->Scripts; const int32 currentIndex = parentScripts.Find(this); ASSERT(currentIndex != INVALID_INDEX); // Check if index will change if (currentIndex != index) { parentScripts.RemoveAtKeepOrder(currentIndex); // Check if index is invalid if (index < 0 || index >= parentScripts.Count()) { // Append at the end parentScripts.Add(this); } else { // Change order parentScripts.Insert(index, this); } } } void Script::SetupType() { // Enable tick functions based on the method overriden in C# or Visual Script ScriptingTypeHandle typeHandle = GetTypeHandle(); while (typeHandle != Script::TypeInitializer) { auto& type = typeHandle.GetType(); if (type.Script.ScriptVTable) { _tickUpdate |= type.Script.ScriptVTable[8] != nullptr; _tickLateUpdate |= type.Script.ScriptVTable[9] != nullptr; _tickFixedUpdate |= type.Script.ScriptVTable[10] != nullptr; _tickLateFixedUpdate |= type.Script.ScriptVTable[11] != nullptr; } typeHandle = type.GetBaseType(); } } void Script::Start() { if (_wasStartCalled) return; _wasStartCalled = 1; CHECK_EXECUTE_IN_EDITOR { OnStart(); } } void Script::Enable() { ASSERT(GetEnabled()); ASSERT(!_wasEnableCalled); if (_parent && _parent->GetScene()) { _parent->GetScene()->Ticking.AddScript(this); _wasEnableCalled = 1; } CHECK_EXECUTE_IN_EDITOR { OnEnable(); } } void Script::Disable() { ASSERT(_wasEnableCalled); CHECK_EXECUTE_IN_EDITOR { OnDisable(); } if (_parent && _parent->GetScene()) { _wasEnableCalled = 0; _parent->GetScene()->Ticking.RemoveScript(this); } } String Script::ToString() const { const auto& type = GetType(); return type.ToString(); } void Script::OnDeleteObject() { // Ensure to unlink from the parent (it will call Disable event if required) SetParent(nullptr); // Check if remove object from game if (IsDuringPlay()) { CHECK_EXECUTE_IN_EDITOR { OnDestroy(); } EndPlay(); } // Base SceneObject::OnDeleteObject(); } const Guid& Script::GetSceneObjectId() const { return GetID(); } void Script::Initialize() { ASSERT(!IsDuringPlay()); if (EnumHasAnyFlags(Flags, ObjectFlags::IsManagedType | ObjectFlags::IsCustomScriptingType)) SetupType(); // Use lazy creation for the managed instance, just register the object if (!IsRegistered()) RegisterObject(); CHECK_EXECUTE_IN_EDITOR { OnAwake(); } } void Script::BeginPlay(SceneBeginData* data) { // Perform additional verification ASSERT(!IsDuringPlay()); // Set flag Flags |= ObjectFlags::IsDuringPlay; } void Script::EndPlay() { // Clear flag Flags &= ~ObjectFlags::IsDuringPlay; // Cleanup managed object DestroyManaged(); if (IsRegistered()) UnregisterObject(); } void Script::Serialize(SerializeStream& stream, const void* otherObj) { // Base SceneObject::Serialize(stream, otherObj); SERIALIZE_GET_OTHER_OBJ(Script); SERIALIZE_BIT_MEMBER(Enabled, _enabled); } void Script::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { // Base SceneObject::Deserialize(stream, modifier); DESERIALIZE_BIT_MEMBER(Enabled, _enabled); DESERIALIZE_MEMBER(PrefabID, _prefabID); { const auto member = SERIALIZE_FIND_MEMBER(stream, "ParentID"); if (member != stream.MemberEnd()) { Guid parentId; Serialization::Deserialize(member->value, parentId, modifier); const auto parent = Scripting::FindObject(parentId); if (_parent != parent) { if (IsDuringPlay()) { SetParent(parent, false); } else { if (_parent) _parent->Scripts.RemoveKeepOrder(this); _parent = parent; if (_parent) _parent->Scripts.Add(this); } } else if (!parent && parentId.IsValid()) { LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString()); } } } }