Static variables in scripts are now reset when leaving play mode #351

Merged
Pycorax merged 7 commits from SP3-6-CSharpStaticReset into main 2023-02-20 11:12:09 +08:00
11 changed files with 177 additions and 24 deletions

View File

@ -0,0 +1,37 @@
using SHADE;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SHADE_Scripting
{
public class StaticTest
{
public static int x;
static StaticTest()
{
x = 5;
Debug.Log("Static Constructor!");
}
}
public class ImplicitStaticTest : Script
{
public static int x = 5;
static ImplicitStaticTest()
{
Debug.Log("Static Constructor!");
}
protected override void awake()
{
Debug.LogWarning($"Before Add: x = {x}");
++x;
Debug.LogWarning($"After Add: x = {x}");
}
}
}

View File

@ -0,0 +1,3 @@
Name: StaticTest
ID: 159057282
Type: 9

View File

@ -387,6 +387,7 @@ namespace SHADE
{ {
auto eventData = reinterpret_cast<const SHEventSpec<SHEditorStateChangeEvent>*>(eventPtr.get()); auto eventData = reinterpret_cast<const SHEventSpec<SHEditorStateChangeEvent>*>(eventPtr.get());
csScriptRemoveAllForAllNow(true); csScriptRemoveAllForAllNow(true);
csEngineReloadScripts();
return eventData->handle; return eventData->handle;
} }
@ -569,7 +570,7 @@ namespace SHADE
SHEventManager::SubscribeTo(SH_ENTITY_DESTROYED_EVENT, std::dynamic_pointer_cast<SHEventReceiver>(destroyedEventReceiver)); SHEventManager::SubscribeTo(SH_ENTITY_DESTROYED_EVENT, std::dynamic_pointer_cast<SHEventReceiver>(destroyedEventReceiver));
/* Editor */ /* Editor */
// Register for editor state change event // Register for editor state change events
std::shared_ptr<SHEventReceiverSpec<SHScriptEngine>> destroyedSceneEventReceiver std::shared_ptr<SHEventReceiverSpec<SHScriptEngine>> destroyedSceneEventReceiver
{ {
std::make_shared<SHEventReceiverSpec<SHScriptEngine>>(this, &SHScriptEngine::onSceneDestroyed) std::make_shared<SHEventReceiverSpec<SHScriptEngine>>(this, &SHScriptEngine::onSceneDestroyed)

View File

@ -233,6 +233,10 @@ namespace SHADE
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
void OpenFile(const std::filesystem::path& path); void OpenFile(const std::filesystem::path& path);
/// <summary>
/// Resets all static data in the loaded assemblies to their default values.
/// </summary>
static void ResetStaticDataInLoadedAssembly();
private: private:
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/

View File

@ -81,7 +81,8 @@ namespace SHADE
// Add the script // Add the script
Script^ script; Script^ script;
ScriptStore::AddScriptViaNameWithRef(entity, type->Name, script); ScriptStore::AddScriptViaNameWithRef(entity, type->Name, script);
registerUndoScriptAddAction(entity, script); // TODO: Re-enable when undo-redo is fixed
// registerUndoScriptAddAction(entity, script);
break; break;
} }
} }
@ -375,7 +376,8 @@ namespace SHADE
{ {
// Mark script for removal // Mark script for removal
ScriptStore::RemoveScript(entity, script); ScriptStore::RemoveScript(entity, script);
registerUndoScriptRemoveAction(entity, script, scriptIndex); // TODO: Re-enable when undo-redo is fixed
// registerUndoScriptRemoveAction(entity, script, scriptIndex);
} }
SHEditorUI::EndPopup(); SHEditorUI::EndPopup();
} }

View File

@ -22,6 +22,7 @@ of DigiPen Institute of Technology is prohibited.
#include "Utility/Debug.hxx" #include "Utility/Debug.hxx"
#include "Utility/Convert.hxx" #include "Utility/Convert.hxx"
#include "Scripts/ScriptStore.hxx" #include "Scripts/ScriptStore.hxx"
#include "Serialisation/SerialisationUtilities.hxx"
namespace SHADE namespace SHADE
{ {
@ -266,8 +267,10 @@ namespace SHADE
/* ScriptAddCommand - Constructor */ /* ScriptAddCommand - Constructor */
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
ScriptAddCommand::ScriptAddCommand(EntityID id, Script^ script) ScriptAddCommand::ScriptAddCommand(EntityID id, Script^ script)
: entity { id } : entity { id }
, addedScript { script } , typeName { script->GetType()->FullName }
, serialisedScript { SerialisationUtilities::Serialise(script) }
, insertedIndex { ScriptStore::GetScriptIndex(script) }
{} {}
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
@ -275,12 +278,20 @@ namespace SHADE
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
bool ScriptAddCommand::Execute() bool ScriptAddCommand::Execute()
{ {
return ScriptStore::AddScript(entity, addedScript) != nullptr; Script^ script = nullptr;
if (ScriptStore::AddScriptViaNameWithRef(entity, typeName, script))
{
SerialisationUtilities::Deserialise(script, serialisedScript);
insertedIndex = ScriptStore::GetScriptIndex(script);
return true;
}
return false;
} }
bool ScriptAddCommand::Unexceute() bool ScriptAddCommand::Unexceute()
{ {
return ScriptStore::RemoveScript(entity, addedScript); return ScriptStore::RemoveScript(entity, insertedIndex);
} }
bool ScriptAddCommand::Merge(ICommand^) bool ScriptAddCommand::Merge(ICommand^)
@ -293,8 +304,9 @@ namespace SHADE
/* ScriptRemoveCommand - Constructor */ /* ScriptRemoveCommand - Constructor */
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
ScriptRemoveCommand::ScriptRemoveCommand(EntityID id, Script^ script, int index) ScriptRemoveCommand::ScriptRemoveCommand(EntityID id, Script^ script, int index)
: entity { id } : entity{ id }
, removedScript { script } , typeName{ script->GetType()->FullName }
, serialisedScript{ SerialisationUtilities::Serialise(script) }
, originalIndex { index } , originalIndex { index }
{} {}
@ -303,12 +315,19 @@ namespace SHADE
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
bool ScriptRemoveCommand::Execute() bool ScriptRemoveCommand::Execute()
{ {
return ScriptStore::RemoveScript(entity, removedScript); return ScriptStore::RemoveScript(entity, originalIndex);
} }
bool ScriptRemoveCommand::Unexceute() bool ScriptRemoveCommand::Unexceute()
{ {
return ScriptStore::AddScript(entity, removedScript, originalIndex) != nullptr; Script^ script = nullptr;
if (ScriptStore::AddScriptViaNameWithRef(entity, typeName, script, originalIndex))
{
SerialisationUtilities::Deserialise(script, serialisedScript);
return true;
}
return false;
} }
bool ScriptRemoveCommand::Merge(ICommand^) bool ScriptRemoveCommand::Merge(ICommand^)

View File

@ -114,7 +114,9 @@ namespace SHADE
private: private:
EntityID entity; EntityID entity;
Script^ addedScript; System::String^ typeName;
System::String^ serialisedScript;
int insertedIndex;
}; };
private ref class ScriptRemoveCommand sealed : public ICommand private ref class ScriptRemoveCommand sealed : public ICommand
@ -126,9 +128,10 @@ namespace SHADE
bool Unexceute() override; bool Unexceute() override;
bool Merge(ICommand^ command) override; bool Merge(ICommand^ command) override;
private: private:
EntityID entity; EntityID entity;
Script^ removedScript; System::String^ typeName;
System::String^ serialisedScript;
int originalIndex; int originalIndex;
}; };

View File

@ -100,9 +100,14 @@ namespace SHADE
} }
bool ScriptStore::AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, Script^% createdScript) bool ScriptStore::AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, Script^% createdScript)
{
return AddScriptViaNameWithRef(entity, scriptName, createdScript, System::Int32::MaxValue);
}
bool ScriptStore::AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, [System::Runtime::InteropServices::Out] Script^% createdScript, int index)
{ {
// Check if we are set up to get scripts // Check if we are set up to get scripts
if (addScriptMethod == nullptr) if (addScriptMethod == nullptr)
{ {
Debug::LogError("[ScriptStore] Native AddScript<T>() was not loaded. Unable to add scripts."); Debug::LogError("[ScriptStore] Native AddScript<T>() was not loaded. Unable to add scripts.");
return false; return false;
@ -120,17 +125,18 @@ namespace SHADE
return false; return false;
} }
// Otherwise, add the script // Add the script
System::Reflection::MethodInfo^ method = addScriptMethod->MakeGenericMethod(scriptType); System::Reflection::MethodInfo^ method = addScriptMethod->MakeGenericMethod(scriptType);
try try
{ {
array<Object^>^ params = gcnew array<Object^>{entity}; // Create the script and add it in
createdScript = safe_cast<Script^>(method->Invoke(nullptr, params)); createdScript = safe_cast<Script^>(System::Activator::CreateInstance(scriptType));
AddScript(entity, createdScript, index);
} }
catch (System::Exception^ e) catch (System::Exception^ e)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "[ScriptStore] Failed to add Script named \"" << Convert::ToNative(scriptName) oss << "[ScriptStore] Failed to add Script named \"" << Convert::ToNative(scriptType->Name)
<< "\" to Entity #" << entity << "! (" << Convert::ToNative(e->GetType()->Name) << ")"; << "\" to Entity #" << entity << "! (" << Convert::ToNative(e->GetType()->Name) << ")";
oss << Convert::ToNative(e->ToString()); oss << Convert::ToNative(e->ToString());
Debug::LogError(oss.str()); Debug::LogError(oss.str());
@ -321,6 +327,19 @@ namespace SHADE
} }
return nullptr; return nullptr;
} }
int ScriptStore::GetScriptIndex(Script^ script)
{
// Check if entity exists in the script storage
if (!scripts.ContainsKey(script->Owner.EntityId))
{
Debug::LogError("[ScriptStore] Attempted to query a Script that does not belong to the ScriptStore.");
return -1;
}
return scripts[script->Owner.EntityId]->IndexOf(script);
}
generic<typename T> generic<typename T>
void ScriptStore::RemoveScript(Entity entity) void ScriptStore::RemoveScript(Entity entity)
{ {
@ -376,6 +395,35 @@ namespace SHADE
removeScript(script); removeScript(script);
return true; return true;
} }
bool ScriptStore::RemoveScript(Entity entity, int index)
{
// Check if entity exists
if (!EntityUtils::IsValid(entity))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script from an invalid Entity!");
return false;
}
// Check if entity exists in the script storage
if (!scripts.ContainsKey(entity))
{
Debug::LogError("[ScriptStore] Attempted to remove a Script that does not belong to the specified Entity!");
return false;
}
// Check if the script index is out of bounds
if (index < 0 || index >= scripts[entity]->Count)
{
Debug::LogError("[ScriptStore] Attempted to remove a Script from an out of range index!");
return false;
}
// Script found, queue it for deletion
removeScript((*scripts[entity])[index]);
return true;
}
void ScriptStore::RemoveAllScripts(Entity entity) void ScriptStore::RemoveAllScripts(Entity entity)
{ {
SAFE_NATIVE_CALL_BEGIN SAFE_NATIVE_CALL_BEGIN

View File

@ -82,9 +82,9 @@ namespace SHADE
/// <summary> /// <summary>
/// Adds a Script to a specified Entity. /// Adds a Script to a specified Entity.
/// <br/> /// <br/>
/// This function is meant for consumption from native code or for serialisation /// This function is meant for deserialisation purposes. If you are writing in
/// purposes. If you are writing in C# or C++/CLI and not doing serialisation, /// C# or C++/CLI and not doing serialisation, use AddScript&lt;T&gt;() instead
/// use AddScript&lt;T&gt;() instead as it is faster. /// as it is faster.
/// </summary> /// </summary>
/// <param name="entity">The entity to add a script to.</param> /// <param name="entity">The entity to add a script to.</param>
/// <param name="scriptName">The entity to add a script to.</param> /// <param name="scriptName">The entity to add a script to.</param>
@ -96,6 +96,7 @@ namespace SHADE
/// console. /// console.
/// </returns> /// </returns>
static bool AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, [System::Runtime::InteropServices::Out] Script^% createdScript); static bool AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, [System::Runtime::InteropServices::Out] Script^% createdScript);
static bool AddScriptViaNameWithRef(Entity entity, System::String^ scriptName, [System::Runtime::InteropServices::Out] Script^% createdScript, int index);
/// <summary> /// <summary>
/// Retrieves the first Script from the specified Entity that matches the /// Retrieves the first Script from the specified Entity that matches the
/// specified type. /// specified type.
@ -190,6 +191,12 @@ namespace SHADE
/// </returns> /// </returns>
static System::Collections::Generic::IEnumerable<Script^>^ GetAllScripts(Entity entity); static System::Collections::Generic::IEnumerable<Script^>^ GetAllScripts(Entity entity);
/// <summary> /// <summary>
/// Retrieves the index of a Script within the list of it's Entity's script list.
/// </summary>
/// <param name="script">Script to get the index of.</param>
/// <returns>Script index if valid. Otherwise -1.</returns>
static int GetScriptIndex(Script^ script);
/// <summary>
/// Removes all Scripts of the specified type from the specified Entity. /// Removes all Scripts of the specified type from the specified Entity.
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
@ -201,7 +208,7 @@ namespace SHADE
/// If the specified Entity is invalid. /// If the specified Entity is invalid.
/// </exception> /// </exception>
generic<typename T> where T : ref class, Script generic<typename T> where T : ref class, Script
static void RemoveScript(Entity entity); static void RemoveScript(Entity entity);
/// <summary> /// <summary>
/// Removes a specific script from the specified entity. /// Removes a specific script from the specified entity.
/// </summary> /// </summary>
@ -210,6 +217,13 @@ namespace SHADE
/// <returns>True if successfully removed. False otherwise.</returns> /// <returns>True if successfully removed. False otherwise.</returns>
static bool RemoveScript(Entity entity, Script^ script); static bool RemoveScript(Entity entity, Script^ script);
/// <summary> /// <summary>
/// Removes a script at a specified index from the specified entity.
/// </summary>
/// <param name="entity">The entity to remove the script from.</param>
/// <param name="index">Index of the script to remove.</param>
/// <returns>True if successfully removed. False otherwise.</returns>
static bool RemoveScript(Entity entity, int index);
/// <summary>
/// Removes all Scripts attached to the specified Entity. Does not do anything /// Removes all Scripts attached to the specified Entity. Does not do anything
/// if the specified Entity is invalid or does not have any Scripts /// if the specified Entity is invalid or does not have any Scripts
/// attached. /// attached.

View File

@ -22,6 +22,7 @@ of DigiPen Institute of Technology is prohibited.
#include "Assets/MaterialAsset.hxx" #include "Assets/MaterialAsset.hxx"
#include "Assets/MeshAsset.hxx" #include "Assets/MeshAsset.hxx"
#include "Scripts/Script.hxx" #include "Scripts/Script.hxx"
#include "Scripts/ScriptStore.hxx"
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
/* File-Level Constants */ /* File-Level Constants */
@ -79,6 +80,19 @@ namespace SHADE
scriptListNode.push_back(scriptNode); scriptListNode.push_back(scriptNode);
} }
System::String^ SerialisationUtilities::Serialise(Script^ script)
{
YAML::Node node;
node.SetStyle(YAML::EmitterStyle::Block);
Serialise(script, node);
YAML::Emitter emitter;
emitter << YAML::BeginMap;
emitter << node;
emitter << YAML::EndMap;
return Convert::ToCLI(emitter.c_str());
}
void SerialisationUtilities::Deserialise(Object^ object, YAML::Node& yamlNode) void SerialisationUtilities::Deserialise(Object^ object, YAML::Node& yamlNode)
{ {
using namespace System::Reflection; using namespace System::Reflection;
@ -135,6 +149,12 @@ namespace SHADE
} }
} }
} }
void SerialisationUtilities::Deserialise(Script^ script, System::String^ yamlString)
{
Deserialise(script, YAML::Load(Convert::ToNative(yamlString)));
}
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
/* Serialization Helper Functions */ /* Serialization Helper Functions */
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/

View File

@ -39,6 +39,7 @@ namespace SHADE
/// </summary> /// </summary>
/// <param name="object">The object to serialise.</param> /// <param name="object">The object to serialise.</param>
static void Serialise(System::Object^ object, YAML::Node& yamlNode); static void Serialise(System::Object^ object, YAML::Node& yamlNode);
static System::String^ Serialise(Script^ script);
/// <summary> /// <summary>
/// Deserialises a YAML node that contains a map of Scripts and copies the /// Deserialises a YAML node that contains a map of Scripts and copies the
/// deserialised data into the specified object if there are matching fields. /// deserialised data into the specified object if there are matching fields.
@ -48,6 +49,7 @@ namespace SHADE
/// </param> /// </param>
/// <param name="object">The object to copy deserialised data into.</param> /// <param name="object">The object to copy deserialised data into.</param>
static void Deserialise(System::Object^ object, YAML::Node& yamlNode); static void Deserialise(System::Object^ object, YAML::Node& yamlNode);
static void Deserialise(Script^ script, System::String^ yamlString);
private: private:
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/