Merge pull request #203 from SHADE-DP/SP3-6-c-scripting

Script QoL Improvements
Added

GetComponentsInChildren() for GameObject
GetScriptsInChildren() for GameObject
GetComponentsInChildren() for Scripts
GetScriptsInChildren() for Scripts
Fixed

Bug causing crash if a Renderable is added without any mesh or materials
Serialization failure caused by null objects in Scripts
This commit is contained in:
XiaoQiDigipen 2022-11-14 17:57:54 +08:00 committed by GitHub
commit 45ec617c3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 307 additions and 6 deletions

View File

@ -34,8 +34,11 @@ namespace SHADE
void SHRenderable::OnDestroy() void SHRenderable::OnDestroy()
{ {
// Remove from SuperBatch // Remove from SuperBatch
Handle<SHSuperBatch> superBatch = sharedMaterial->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass()->GetSuperBatch(); if (sharedMaterial)
superBatch->Remove(this); {
Handle<SHSuperBatch> superBatch = sharedMaterial->GetBaseMaterial()->GetPipeline()->GetPipelineState().GetSubpass()->GetSuperBatch();
superBatch->Remove(this);
}
// Free resources // Free resources
if (material) if (material)

View File

@ -288,7 +288,15 @@ namespace YAML
{ {
YAML::Node node; YAML::Node node;
node[MESH_YAML_TAG.data()] = SHResourceManager::GetAssetID<SHMesh>(rhs.GetMesh()).value_or(0); node[MESH_YAML_TAG.data()] = SHResourceManager::GetAssetID<SHMesh>(rhs.GetMesh()).value_or(0);
node[MAT_YAML_TAG.data()] = SHResourceManager::GetAssetID<SHMaterial>(rhs.GetMaterial()->GetBaseMaterial()).value_or(0); auto mat = rhs.GetMaterial();
if (mat)
{
node[MAT_YAML_TAG.data()] = SHResourceManager::GetAssetID<SHMaterial>(rhs.GetMaterial()->GetBaseMaterial()).value_or(0);
}
else
{
node[MAT_YAML_TAG.data()] = 0;
}
return node; return node;
} }
static bool decode(YAML::Node const& node, SHRenderable& rhs) static bool decode(YAML::Node const& node, SHRenderable& rhs)

View File

@ -27,6 +27,7 @@ of DigiPen Institute of Technology is prohibited.
#include "Scene/SHSceneManager.h" #include "Scene/SHSceneManager.h"
#include "Scene/SHSceneGraph.h" #include "Scene/SHSceneGraph.h"
#include "Tools/SHLog.h" #include "Tools/SHLog.h"
#include "Graphics\MiddleEnd\Interface\SHRenderable.h"
// Project Headers // Project Headers
#include "Utility/Convert.hxx" #include "Utility/Convert.hxx"
#include "Utility/Debug.hxx" #include "Utility/Debug.hxx"
@ -36,6 +37,7 @@ of DigiPen Institute of Technology is prohibited.
#include "Components/Camera.hxx" #include "Components/Camera.hxx"
#include "Components/CameraArm.hxx" #include "Components/CameraArm.hxx"
#include "Components/Light.hxx" #include "Components/Light.hxx"
#include "Components\Renderable.hxx"
namespace SHADE namespace SHADE
{ {
@ -166,6 +168,70 @@ namespace SHADE
return T(); return T();
} }
generic<typename T>
System::Collections::Generic::IEnumerable<T>^ ECS::GetComponentsInChildren(EntityID entity)
{
System::Type^ componentType = T::typeid;
// Check if entity is correct
if (!SHEntityManager::IsValidEID(entity))
{
std::ostringstream oss;
oss << "[ECS] Attempted to retrieve Component \""
<< Convert::ToNative(componentType->Name)
<< "\" from invalid Entity.";
Debug::LogError(oss.str());
return nullptr;
}
// Search all elements via a iterative breadth first search
System::Collections::Generic::List<T>^ results;
System::Collections::Generic::Queue<Entity>^ searchSpace = gcnew System::Collections::Generic::Queue<Entity>();
// Start off with direct children
SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity);
if (entityNode == nullptr)
{
std::ostringstream oss;
oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!";
SHLog::Warning(oss.str());
}
for (const auto& child : entityNode->GetChildren())
{
searchSpace->Enqueue(child->GetEntityID());
}
// Continue with all subsequent children
while (searchSpace->Count > 0)
{
// Check if this entity has the component we need
Entity curr = searchSpace->Dequeue();
T component = GetComponent<T>(curr);
if (component != nullptr)
{
// We only construct if we need to
if (results == nullptr)
results = gcnew System::Collections::Generic::List<T>();
results->Add(component);
}
// Add children to the queue
SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(curr);
if (sceneGraphNode == nullptr)
{
std::ostringstream oss;
oss << "[ECS_CLI] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!";
SHLog::Warning(oss.str());
continue;
}
for (const auto& child : sceneGraphNode->GetChildren())
{
searchSpace->Enqueue(child->GetEntityID());
}
}
// None here
return results;
}
generic <typename T> generic <typename T>
T ECS::EnsureComponent(EntityID entity) T ECS::EnsureComponent(EntityID entity)
{ {
@ -249,6 +315,7 @@ namespace SHADE
static ECS::ECS() static ECS::ECS()
{ {
componentMap.Add(createComponentSet<SHTransformComponent, Transform>()); componentMap.Add(createComponentSet<SHTransformComponent, Transform>());
componentMap.Add(createComponentSet<SHRenderable, Renderable>());
componentMap.Add(createComponentSet<SHColliderComponent, Collider>()); componentMap.Add(createComponentSet<SHColliderComponent, Collider>());
componentMap.Add(createComponentSet<SHRigidBodyComponent, RigidBody>()); componentMap.Add(createComponentSet<SHRigidBodyComponent, RigidBody>());
componentMap.Add(createComponentSet<SHCameraComponent, Camera>()); componentMap.Add(createComponentSet<SHCameraComponent, Camera>());

View File

@ -51,9 +51,9 @@ namespace SHADE
/// specified Component. /// specified Component.
/// </returns> /// </returns>
generic<typename T> where T : BaseComponent generic<typename T> where T : BaseComponent
static T GetComponent(EntityID entity); static T GetComponent(EntityID entity);
/// <summary> /// <summary>
/// Retrieves the first Component from the specified GameObjectt's children that /// Retrieves the first Component from the specified GameObject's children that
/// matches the specified type. /// matches the specified type.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam> /// <typeparam name="T">Type of the Component to get.</typeparam>
@ -65,6 +65,20 @@ namespace SHADE
generic<typename T> where T : BaseComponent generic<typename T> where T : BaseComponent
static T GetComponentInChildren(EntityID entity); static T GetComponentInChildren(EntityID entity);
/// <summary> /// <summary>
/// Retrieves a list of Components from the specified GameObject's children that
/// matches the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetComponentInChildren() instead.
/// This does not search the specified entity.
/// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam>
/// <param name="entity"> Entity object to get the Component from. </param>
/// <returns>
/// Newly allocated List of components. Will be null if no components are found.
/// </returns>
generic<typename T> where T : BaseComponent
static System::Collections::Generic::IEnumerable<T>^ GetComponentsInChildren(EntityID entity);
/// <summary>
/// Ensures a Component on the specified Entity. /// Ensures a Component on the specified Entity.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the Component to ensure.</typeparam> /// <typeparam name="T">Type of the Component to ensure.</typeparam>

View File

@ -170,6 +170,14 @@ namespace SHADE
return ECS::GetComponentInChildren<T>(entity); return ECS::GetComponentInChildren<T>(entity);
} }
generic<typename T>
System::Collections::Generic::IEnumerable<T>^ GameObject::GetComponentsInChildren()
{
if (!valid)
throw gcnew System::NullReferenceException();
return ECS::GetComponentsInChildren<T>(entity);
}
generic <typename T> generic <typename T>
T GameObject::EnsureComponent() T GameObject::EnsureComponent()
{ {
@ -212,6 +220,13 @@ namespace SHADE
throw gcnew System::NullReferenceException(); throw gcnew System::NullReferenceException();
return ScriptStore::GetScriptInChildren<T>(entity); return ScriptStore::GetScriptInChildren<T>(entity);
} }
generic <typename T>
System::Collections::Generic::IEnumerable<T>^ GameObject::GetScriptsInChildren()
{
if (!valid)
throw gcnew System::NullReferenceException();
return ScriptStore::GetScriptsInChildren<T>(entity);
}
generic <typename T> generic <typename T>
System::Collections::Generic::IEnumerable<T>^ GameObject::GetScripts() System::Collections::Generic::IEnumerable<T>^ GameObject::GetScripts()

View File

@ -153,6 +153,7 @@ namespace SHADE
/// <summary> /// <summary>
/// Retrieves the first Component from this GameObject's children that matches /// Retrieves the first Component from this GameObject's children that matches
/// the specified type. /// the specified type.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam> /// <typeparam name="T">Type of the Component to get.</typeparam>
/// <returns> /// <returns>
@ -162,6 +163,19 @@ namespace SHADE
generic<typename T> where T : BaseComponent generic<typename T> where T : BaseComponent
T GetComponentInChildren(); T GetComponentInChildren();
/// <summary> /// <summary>
/// Retrieves a list of Components from this GameObject's children that matches
/// the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetComponentInChildren() instead.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam>
/// <returns>
/// Newly allocated List of components. Will be null if no components are found.
/// </returns>
generic<typename T> where T : BaseComponent
System::Collections::Generic::IEnumerable<T>^ GetComponentsInChildren();
/// <summary>
/// Ensures a Component on this GameObject. /// Ensures a Component on this GameObject.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the Component to ensure.</typeparam> /// <typeparam name="T">Type of the Component to ensure.</typeparam>
@ -201,12 +215,26 @@ namespace SHADE
/// Retrieves a Script of the specified type from child GameObjects. /// Retrieves a Script of the specified type from child GameObjects.
/// If multiple Scripts of the same specified type are added on the same /// If multiple Scripts of the same specified type are added on the same
/// child GameObject, this will retrieve the first one added. /// child GameObject, this will retrieve the first one added.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary> /// </summary>
/// <typeparam name="T">Type of Script to retrieve.</typeparam> /// <typeparam name="T">Type of Script to retrieve.</typeparam>
/// <returns>Reference to the Script to retrieve.</returns> /// <returns>Reference to the Script to retrieve.</returns>
generic<typename T> where T : ref class, Script generic<typename T> where T : ref class, Script
T GetScriptInChildren(); T GetScriptInChildren();
/// <summary> /// <summary>
/// Retrieves a list of Scripts from this GameObject's children that matches
/// the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetComponentInChildren() instead.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam>
/// <returns>
/// Newly allocated List of components. Will be null if no components are found.
/// </returns>
generic<typename T> where T : ref class, Script
System::Collections::Generic::IEnumerable<T>^ GetScriptsInChildren();
/// <summary>
/// Retrieves a immutable list of Scripts of the specified type from this /// Retrieves a immutable list of Scripts of the specified type from this
/// GameObject. /// GameObject.
/// </summary> /// </summary>

View File

@ -42,6 +42,12 @@ namespace SHADE
return owner.GetComponentInChildren<T>(); return owner.GetComponentInChildren<T>();
} }
generic<typename T>
System::Collections::Generic::IEnumerable<T>^ Script::GetComponentsInChildren()
{
return owner.GetComponentsInChildren<T>();
}
generic <typename T> generic <typename T>
T Script::EnsureComponent() T Script::EnsureComponent()
{ {
@ -72,6 +78,11 @@ namespace SHADE
{ {
return ScriptStore::GetScriptInChildren<T>(owner.GetEntity()); return ScriptStore::GetScriptInChildren<T>(owner.GetEntity());
} }
generic <typename T>
System::Collections::Generic::IEnumerable<T>^ Script::GetScriptsInChildren()
{
return ScriptStore::GetScriptsInChildren<T>(owner.GetEntity());
}
generic <typename T> generic <typename T>
System::Collections::Generic::IEnumerable<T>^ Script::GetScripts() System::Collections::Generic::IEnumerable<T>^ Script::GetScripts()

View File

@ -69,6 +69,7 @@ namespace SHADE
/// <summary> /// <summary>
/// Retrieves the first Component from this GameObject's children that matches /// Retrieves the first Component from this GameObject's children that matches
/// the specified type. /// the specified type.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// Type of the Component to get. Must be derived from BaseComponent. /// Type of the Component to get. Must be derived from BaseComponent.
@ -77,6 +78,19 @@ namespace SHADE
generic<typename T> where T : BaseComponent generic<typename T> where T : BaseComponent
T GetComponentInChildren(); T GetComponentInChildren();
/// <summary> /// <summary>
/// Retrieves a list of Components from this GameObject's children that
/// matches the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetComponentInChildren() instead.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam>
/// <returns>
/// Newly allocated List of components. Will be null if no components are found.
/// </returns>
generic<typename T> where T : BaseComponent
System::Collections::Generic::IEnumerable<T>^ GetComponentsInChildren();
/// <summary>
/// Ensures a Component on the GameObject that this Script belongs to. /// Ensures a Component on the GameObject that this Script belongs to.
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
@ -121,6 +135,7 @@ namespace SHADE
/// <summary> /// <summary>
/// Retrieves the first Script from this GameObject's children that matches the /// Retrieves the first Script from this GameObject's children that matches the
/// specified type. /// specified type.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// Type of script to get. /// Type of script to get.
@ -130,6 +145,19 @@ namespace SHADE
generic<typename T> where T : ref class, Script generic<typename T> where T : ref class, Script
T GetScriptInChildren(); T GetScriptInChildren();
/// <summary> /// <summary>
/// Retrieves a list of Scripts from this GameObject's children that matches
/// the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetComponentInChildren() instead.
/// Unlike Unity, we do not search this GameObject, only the children.
/// </summary>
/// <typeparam name="T">Type of the Component to get.</typeparam>
/// <returns>
/// Newly allocated List of components. Will be null if no components are found.
/// </returns>
generic<typename T> where T : ref class, Script
System::Collections::Generic::IEnumerable<T>^ GetScriptsInChildren();
/// <summary>
/// Retrieves a immutable list of scripts from the specified Entity that /// Retrieves a immutable list of scripts from the specified Entity that
/// matches the specified type. /// matches the specified type.
/// <br/> /// <br/>

View File

@ -211,6 +211,70 @@ namespace SHADE
return T(); return T();
} }
generic<typename T>
System::Collections::Generic::IEnumerable<T>^ ScriptStore::GetScriptsInChildren(Entity entity)
{
System::Type^ componentType = T::typeid;
// Check if entity is correct
if (!SHEntityManager::IsValidEID(entity))
{
std::ostringstream oss;
oss << "[ScriptStore] Attempted to retrieve Script \""
<< Convert::ToNative(componentType->Name)
<< "\" from invalid Entity.";
Debug::LogError(oss.str());
return nullptr;
}
// Search all elements via a iterative breadth first search
System::Collections::Generic::List<T>^ results;
System::Collections::Generic::Queue<Entity>^ searchSpace = gcnew System::Collections::Generic::Queue<Entity>();
// Start off with direct children
SHSceneNode* entityNode = SHSceneManager::GetCurrentSceneGraph().GetNode(entity);
if (entityNode == nullptr)
{
std::ostringstream oss;
oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!";
SHLog::Warning(oss.str());
}
for (const auto& child : entityNode->GetChildren())
{
searchSpace->Enqueue(child->GetEntityID());
}
// Continue with all subsequent children
while (searchSpace->Count > 0)
{
// Check if this entity has the component we need
Entity curr = searchSpace->Dequeue();
T script = GetScript<T>(curr);
if (script != nullptr)
{
// We only construct if we need to
if (results == nullptr)
results = gcnew System::Collections::Generic::List<T>();
results->Add(script);
}
// Add children to the queue
SHSceneNode* sceneGraphNode = SHSceneManager::GetCurrentSceneGraph().GetNode(curr);
if (sceneGraphNode == nullptr)
{
std::ostringstream oss;
oss << "[ScriptStore] Failed to retrieve SceneGraphNode of entity #" << entity << ". This should not happen!";
SHLog::Warning(oss.str());
continue;
}
for (const auto& child : sceneGraphNode->GetChildren())
{
searchSpace->Enqueue(child->GetEntityID());
}
}
// None here
return results;
}
generic <typename T> generic <typename T>
System::Collections::Generic::IEnumerable<T>^ ScriptStore::GetScripts(Entity entity) System::Collections::Generic::IEnumerable<T>^ ScriptStore::GetScripts(Entity entity)
{ {

View File

@ -137,6 +137,29 @@ namespace SHADE
generic<typename T> where T : ref class, Script generic<typename T> where T : ref class, Script
static T GetScriptInChildren(Entity entity); static T GetScriptInChildren(Entity entity);
/// <summary> /// <summary>
/// Retrieves the list of Scripts from the specified Entity and the Entity's
/// children that matches the specified type.
/// This function performs allocations. If expecting only 1 component, use
/// GetScriptInChildren() instead.
/// This does not search the specified entity.
/// </summary>
/// <typeparam name="T">
/// Type of script to get.
/// This needs to be a default constructable Script.
/// </typeparam>
/// <param name="entity">
/// The entity which the script to retrieve is attached.
/// </param>
/// <returns>
/// Reference to the script. This can be null if no script of the specified
/// type is attached.
/// </returns>
/// <exception cref="ArgumentException">
/// If the specified Entity is invalid.
/// </exception>
generic<typename T> where T : ref class, Script
static System::Collections::Generic::IEnumerable<T>^ GetScriptsInChildren(Entity entity);
/// <summary>
/// Retrieves a immutable list of scripts from the specified Entity that /// Retrieves a immutable list of scripts from the specified Entity that
/// matches the specified type. /// matches the specified type.
/// <br/> /// <br/>

View File

@ -28,7 +28,47 @@ namespace SHADE
template<typename FieldType> template<typename FieldType>
bool SerialisationUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode) bool SerialisationUtilities::fieldInsertYaml(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, YAML::Node& fieldNode)
{ {
return varInsertYamlInternal<FieldType>(fieldInfo->GetValue(object), fieldNode); // Handle null objects
System::Object^ fieldObject = fieldInfo->GetValue(object);
if (fieldObject == nullptr)
{
// Default construct if null
if (fieldInfo->FieldType == FieldType::typeid)
{
if constexpr (std::is_same_v<FieldType, System::Enum>)
{
fieldNode = 0;
}
else if constexpr (std::is_same_v<FieldType, System::String>)
{
fieldNode = "";
}
else if constexpr (std::is_same_v<FieldType, Vector2>)
{
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(0.0f);
fieldNode.push_back(0.0f);
}
else if constexpr (std::is_same_v<FieldType, Vector3>)
{
fieldNode.SetStyle(YAML::EmitterStyle::Flow);
fieldNode.push_back(0.0f);
fieldNode.push_back(0.0f);
fieldNode.push_back(0.0f);
}
else if constexpr (std::is_same_v<FieldType, GameObject>)
{
fieldNode = MAX_EID;
}
else
{
fieldNode = FieldType();
}
return true;
}
return false;
}
return varInsertYamlInternal<FieldType>(fieldObject, fieldNode);
} }
template<typename FieldType> template<typename FieldType>
bool SerialisationUtilities::varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode) bool SerialisationUtilities::varInsertYamlInternal(System::Object^ object, YAML::Node& fieldNode)