Преглед на файлове

Implemented texture and model asset automatic garbage collector by tracking reference counters of each unique asset
Added asset loader update methods that are called each frame that process the asset release queues in Engine, LoaderBase, ModelLoader, TextureLoader
Added a way to release buffers, models, textures and shaders from GPU memory in GraphicsDataSets, RendererScene, RendererBackend, RendererFrontend
Added command buffers and methods for unloading objects in RendererBackend, RendererFrontend
Added a way to create an entity (and a popup with its settings) in Entity Hierarchy window in EditorWindow
Added checks for memory leaks if unhandled orphaned void data pointer wasn't deleted in receiveData in all Scenes
Added ways for creating and deleting components by sending data notifications to WorldScene
Added more Config variables
Fixed a bug of failed entity hierarchy list populating, because of storing pointers to vector elements when vector elements are rearranged during push back, by instead having the underlying vectors hold pointers, in EditorWindow
Fixed a bug of UniqueObjects in LoaderBase (asset objects) reference counter being set below 0, because default copy and move assignment constructors were called that did not track references, by defining all needed constructors and assignment operators in LoaderBase
Fixed a bug of ModelComponent not loading the given texture, because mesh was set to not present, by checking all given textures and texture data individually no matter the mesh present flag, in ModelComponent
Fixed a bug of a string variable not being destructed in a union, because the union does not call non-trivial destructors, by calling the destructor explicitly if the variable type is string, in PropertySet
Fixed a bug of explicitly deleting the collision shape of a rigid body after it has been deleted by the Bullets dynamic world, in RigidBodyComponent
Fixed a bug of default textures being marked for removal, by always storing a Texture2DHandle to keep the reference counter from reaching 0, in TextureLoader

Paul A преди 2 години
родител
ревизия
5fa0eb648b
променени са 33 файла, в които са добавени 928 реда и са изтрити 344 реда
  1. 4 4
      Praxis3D/Data/Maps/componentTest.pmap
  2. 4 0
      Praxis3D/Source/AudioScene.cpp
  3. 8 0
      Praxis3D/Source/CommonDefinitions.h
  4. 1 0
      Praxis3D/Source/Config.cpp
  5. 20 13
      Praxis3D/Source/Config.h
  6. 231 36
      Praxis3D/Source/EditorWindow.cpp
  7. 35 8
      Praxis3D/Source/EditorWindow.h
  8. 12 7
      Praxis3D/Source/Engine.cpp
  9. 4 2
      Praxis3D/Source/Engine.h
  10. 2 0
      Praxis3D/Source/EngineState.h
  11. 1 1
      Praxis3D/Source/ErrorCodes.h
  12. 37 27
      Praxis3D/Source/GUIScene.cpp
  13. 80 45
      Praxis3D/Source/GraphicsDataSets.h
  14. 50 40
      Praxis3D/Source/LoaderBase.h
  15. 1 1
      Praxis3D/Source/LuaScript.cpp
  16. 26 7
      Praxis3D/Source/ModelComponent.h
  17. 11 1
      Praxis3D/Source/ModelLoader.cpp
  18. 14 2
      Praxis3D/Source/ModelLoader.h
  19. 4 0
      Praxis3D/Source/PhysicsScene.cpp
  20. 6 1
      Praxis3D/Source/PropertySet.h
  21. 15 0
      Praxis3D/Source/RendererBackend.cpp
  22. 19 0
      Praxis3D/Source/RendererBackend.h
  23. 34 11
      Praxis3D/Source/RendererFrontend.cpp
  24. 30 0
      Praxis3D/Source/RendererFrontend.h
  25. 36 20
      Praxis3D/Source/RendererScene.cpp
  26. 3 0
      Praxis3D/Source/RendererScene.h
  27. 0 31
      Praxis3D/Source/RigidBodyComponent.h
  28. 3 6
      Praxis3D/Source/SceneLoader.cpp
  29. 4 0
      Praxis3D/Source/ScriptScene.cpp
  30. 53 45
      Praxis3D/Source/TextureLoader.cpp
  31. 54 24
      Praxis3D/Source/TextureLoader.h
  32. 79 10
      Praxis3D/Source/WorldScene.cpp
  33. 47 2
      Praxis3D/imgui.ini

+ 4 - 4
Praxis3D/Data/Maps/componentTest.pmap

@@ -401,7 +401,7 @@
 		{
 			"ID": "5",
 			"Name": "Sphere 2",
-			"Parent": "0",
+			"Parent": "4",
 			"World":
 			{
 				"SpatialComponent":
@@ -542,7 +542,7 @@
 		{
 			"ID": "13",
 			"Name": "PointLight 2",
-			"Parent": "0",
+			"Parent": "12",
 			"World":
 			{
 				"SpatialComponent":
@@ -563,7 +563,7 @@
 		{
 			"ID": "14",
 			"Name": "PointLight 3",
-			"Parent": "0",
+			"Parent": "13",
 			"World":
 			{
 				"SpatialComponent":
@@ -584,7 +584,7 @@
 		{
 			"ID": "15",
 			"Name": "PointLight 4",
-			"Parent": "0",
+			"Parent": "13",
 			"World":
 			{
 				"SpatialComponent":

+ 4 - 0
Praxis3D/Source/AudioScene.cpp

@@ -670,6 +670,10 @@ void AudioScene::receiveData(const DataType p_dataType, void *p_data, const bool
 					delete componentData;
 			}
 			break;
+
+		default:
+			assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
+			break;
 	}
 }
 

+ 8 - 0
Praxis3D/Source/CommonDefinitions.h

@@ -120,6 +120,14 @@ enum LoadObjectType : unsigned int
 	LoadObject_TextureCube,
 	LoadObject_Model
 };
+enum UnloadObjectType : unsigned int
+{
+	UnloadObjectType_VAO,
+	UnloadObjectType_Buffer,
+	UnloadObjectType_Shader,
+	UnloadObjectType_Texture,
+	UnloadObjectType_NumOfTypes
+};
 enum LoadableObjectType : unsigned int
 {
 	LoadableObj_Null,

+ 1 - 0
Praxis3D/Source/Config.cpp

@@ -94,6 +94,7 @@ void Config::init()
 	AddVariablePredef(m_engineVar, glsl_version);
 	AddVariablePredef(m_engineVar, gl_context_major_version);
 	AddVariablePredef(m_engineVar, gl_context_minor_version);
+	AddVariablePredef(m_engineVar, loaders_num_of_unload_per_frame);
 	AddVariablePredef(m_engineVar, object_directory_init_pool_size);
 	AddVariablePredef(m_engineVar, smoothing_tick_samples);
 	AddVariablePredef(m_engineVar, task_scheduler_clock_frequency);

+ 20 - 13
Praxis3D/Source/Config.h

@@ -28,23 +28,28 @@ enum DataType : uint32_t
 {
 	DataType_Null = 0,
 	// General
-	DataType_CreateComponent,
-	DataType_DeleteComponent,
+	DataType_CreateComponent,			// ComponentsConstructionInfo
+	DataType_DeleteComponent,			// EntityAndComponent
+	DataType_CreateEntity,				// ComponentsConstructionInfo
+	DataType_DeleteEntity,				// EntityAndComponent
 	// Graphics
-	DataType_GUIPassFunctors,
-	DataType_RenderToTexture,
-	DataType_RenderToTextureResolution,
-	DataType_Texture2D,
-	DataType_Texture3D,
-	DataType_ModelsProperties,
+	DataType_GUIPassFunctors,			// FunctorSequence
+	DataType_RenderToTexture,			// bool
+	DataType_RenderToTextureResolution, // glm::ivec2
+	DataType_LoadTexture2D,				// TextureLoader2D::Texture2DHandle
+	DataType_UnloadTexture2D,			// TextureLoader2D::Texture2DHandle
+	DataType_LoadTexture3D,
+	DataType_UnloadTexture3D,
+	DataType_UnloadModel,
+	DataType_ModelsProperties,			// ModelComponent::ModelsProperties
 	// GUI
-	DataType_EnableGUISequence,
-	DataType_EditorWindow,
-	DataType_FileBrowserDialog,
+	DataType_EnableGUISequence,			// bool
+	DataType_EditorWindow,				// EditorWindowSettings
+	DataType_FileBrowserDialog,			// FileBrowserDialog
 	// Physics
-	DataType_SimulationActive,
+	DataType_SimulationActive,			// bool
 	// Scripting
-	DataType_EnableLuaScripting
+	DataType_EnableLuaScripting			// bool
 };
 
 namespace Systems
@@ -680,6 +685,7 @@ public:
 			glsl_version = 430;
 			gl_context_major_version = 3;
 			gl_context_minor_version = 3;
+			loaders_num_of_unload_per_frame = 1;
 			object_directory_init_pool_size = 1000;
 			smoothing_tick_samples = 100;
 			task_scheduler_clock_frequency = 120;
@@ -699,6 +705,7 @@ public:
 		int glsl_version;
 		int gl_context_major_version;
 		int gl_context_minor_version;
+		int loaders_num_of_unload_per_frame;
 		int object_directory_init_pool_size;
 		int smoothing_tick_samples;
 		int task_scheduler_clock_frequency;

+ 231 - 36
Praxis3D/Source/EditorWindow.cpp

@@ -137,9 +137,6 @@ void EditorWindow::update(const float p_deltaTime)
     }
 
     // Update ImGui style
-    //m_imguiStyle = ImGui::GetStyle();
-    //const float fontSize = ImGui::GetFontSize();
-    //const ImVec2 openReloadButtonSize = ImVec2(fontSize, fontSize);
     ImVec2 mainMenuBarSize;
     ImGuiViewport *mainViewport = ImGui::GetMainViewport();
 
@@ -254,7 +251,8 @@ void EditorWindow::update(const float p_deltaTime)
 
             if(ImGui::MenuItem("Close editor"))
             {
-                Config::m_engineVar.engineState = EngineStateType::EngineStateType_MainMenu;
+                // Send a notification to the engine to change the current engine state back to MainMenu
+                m_systemScene->getSceneLoader()->getChangeController()->sendEngineChange(EngineChangeData(EngineChangeType::EngineChangeType_StateChange, EngineStateType::EngineStateType_MainMenu));
             }
 
             if(ImGui::MenuItem("Exit"))
@@ -442,14 +440,126 @@ void EditorWindow::update(const float p_deltaTime)
                 ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 8.0f);
 
                 // Draw every entry from the hierarchy list
-                for(decltype(m_entityHierarchy.size()) size = m_entityHierarchy.size(), i = 0; i < size; i++)
-                {
-                    drawEntityHierarchyEntry(m_entityHierarchy[i]);
-                }
+                if(m_rootEntityHierarchyEntry.m_entityID != NULL_ENTITY_ID)
+                    drawEntityHierarchyEntry(&m_rootEntityHierarchyEntry);
+                else
+                    ImGui::Text("No root entity");
 
                 // Pop indent spacing
                 ImGui::PopStyleVar();
 
+                //ImGui::NewLine();
+                ImGui::Separator();
+
+                const std::string componentTypeSelectionPopupName = "##AddEntityPopup";
+
+                // Calculate button size
+                const char *buttonLabel = "Add entity";
+                float buttonWidth = ImGui::CalcTextSize(buttonLabel).x * Config::GUIVar().editor_inspector_button_width_multiplier;
+
+                // Set the button position to the right-most side
+                ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - buttonWidth);
+
+                // Draw ADD ENTITY button
+                if(ImGui::Button(buttonLabel, ImVec2(buttonWidth, 0.0f)))
+                {
+                    if(m_newEntityConstructionInfo == nullptr)
+                    {
+                        m_newEntityConstructionInfo = new ComponentsConstructionInfo();
+                        m_newEntityConstructionInfo->m_name = "New entity";
+                        m_newEntityConstructionInfo->m_id = 0;
+
+                        // Open the pop-up with the new entity settings
+                        m_openNewEntityPopup = true;
+                    }
+                }
+
+                if(m_openNewEntityPopup)
+                {
+                    ImGui::OpenPopup(componentTypeSelectionPopupName.c_str());
+                    m_openNewEntityPopup = false;
+                }
+
+
+                // Draw COMPONENT TYPE LIST
+                if(ImGui::BeginPopup(componentTypeSelectionPopupName.c_str()))
+                {
+                    // Calculate widget offset used to draw a label on the left and a widget on the right (opposite of how ImGui draws it)
+                    float inputWidgetOffset = ImGui::GetCursorPosX() + ImGui::CalcItemWidth() * 0.5f + ImGui::GetStyle().ItemInnerSpacing.x;
+
+                    // Draw NAME
+                    ImGui::Text("Name:");
+                    ImGui::SameLine();
+                    ImGui::SetCursorPosX(inputWidgetOffset);
+                    if(ImGui::InputText("##NewEntityNameStringInput", &m_newEntityConstructionInfo->m_name, ImGuiInputTextFlags_EnterReturnsTrue))
+                    {
+                    }
+
+                    // Draw ENTITY ID
+                    ImGui::Text("Entity ID:");
+                    ImGui::SameLine();
+                    ImGui::SetCursorPosX(inputWidgetOffset);
+                    if(ImGui::InputScalar("##NewEntityEntityIDInput", ImGuiDataType_U32, &m_newEntityConstructionInfo->m_id))
+                    {
+                    }
+
+                    // Draw PARENT
+                    ImGui::Text("Parent ID:");
+                    ImGui::SameLine();
+                    ImGui::SetCursorPosX(inputWidgetOffset);
+                    if(ImGui::BeginCombo("##NewEntityParentIDCombo", Utilities::toString(m_newEntityConstructionInfo->m_parent).c_str()))
+                    {
+                        // Go over all existing entities
+                        for(decltype(m_entityList.size()) i = 0, size = m_entityList.size(); i < size; i++)
+                        {
+                            // Mark which parent ID is selected
+                            const bool is_selected = (m_newEntityConstructionInfo->m_parent == m_entityList[i].m_entityID);
+
+                            // Don't show entities own ID as a parent ID selection
+                            if(m_entityList[i].m_entityID != m_newEntityConstructionInfo->m_id)
+                            {
+                                if(ImGui::Selectable(Utilities::toString(m_entityList[i].m_entityID).c_str(), is_selected))
+                                {
+                                    m_newEntityConstructionInfo->m_parent = m_entityList[i].m_entityID;
+                                }
+                            }
+
+                            // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+                            if(is_selected)
+                                ImGui::SetItemDefaultFocus();
+                        }
+                        ImGui::EndCombo();
+                    }
+
+                    // Draw CREATE button
+                    if(ImGui::Button("Create"))
+                    {
+                        m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::World), DataType::DataType_CreateEntity, (void *)m_newEntityConstructionInfo, false);
+
+                        m_componentConstructionInfoPool.push_back(m_newEntityConstructionInfo);
+                        m_newEntityConstructionInfo = nullptr;
+                        ImGui::CloseCurrentPopup();
+                    }
+
+                    // Draw CANCEL button
+                    if(ImGui::Button("Cancel"))
+                    {
+                        delete m_newEntityConstructionInfo;
+                        m_newEntityConstructionInfo = nullptr;
+                        ImGui::CloseCurrentPopup();
+                    }
+
+                    ImGui::EndPopup();
+                }
+                else
+                {
+                    if(m_newEntityConstructionInfo != nullptr)
+                    {
+                        delete m_newEntityConstructionInfo;
+                        m_newEntityConstructionInfo = nullptr;
+                    }
+                }
+
                 ImGui::EndTabItem();
             }
             if(ImGui::BeginTabItem("Entities"))
@@ -534,6 +644,10 @@ void EditorWindow::update(const float p_deltaTime)
                         {
                             // Get the current entity name
                             m_selectedEntity.m_componentData.m_name = metadataComponent->getName();
+                            m_selectedEntity.m_componentData.m_id = metadataComponent->getEntityID();
+                            m_selectedEntity.m_componentData.m_parent = metadataComponent->getParentEntityID();
+
+                            const std::string parentSelectionPopupName = "##ParentSelectionPopup";
 
                             // Draw NAME
                             drawLeftAlignedLabelText("Name:", inputWidgetOffset);
@@ -542,6 +656,39 @@ void EditorWindow::update(const float p_deltaTime)
                                 // If the entity name was changed, send a notification to the Metadata Component
                                 m_systemScene->getSceneLoader()->getChangeController()->sendChange(this, metadataComponent, Systems::Changes::Generic::Name);
                             }
+
+                            // Draw ENTITY ID
+                            drawLeftAlignedLabelText("Entity ID:", inputWidgetOffset);
+                            if(ImGui::InputScalar("##EntityIDInput", ImGuiDataType_U32, &m_selectedEntity.m_componentData.m_id))
+                            {
+
+                            }
+
+                            // Draw PARENT
+                            drawLeftAlignedLabelText("Parent ID:", inputWidgetOffset);        
+                            if(ImGui::BeginCombo("##ParentIDCombo", Utilities::toString(m_selectedEntity.m_componentData.m_parent).c_str()))
+                            {
+                                // Go over all existing entities
+                                for(decltype(m_entityList.size()) i = 0, size = m_entityList.size(); i < size; i++)
+                                {
+                                    // Mark which parent ID is selected
+                                    const bool is_selected = (m_selectedEntity.m_componentData.m_parent == m_entityList[i].m_entityID);
+
+                                    // Don't show entities own ID as a parent ID selection
+                                    if(m_entityList[i].m_entityID != m_selectedEntity.m_componentData.m_id)
+                                    {
+                                        if(ImGui::Selectable(Utilities::toString(m_entityList[i].m_entityID).c_str(), is_selected))
+                                        {
+                                            m_selectedEntity.m_componentData.m_parent = m_entityList[i].m_entityID;
+                                        }
+                                    }
+
+                                    // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+                                    if(is_selected)
+                                        ImGui::SetItemDefaultFocus();
+                                }
+                                ImGui::EndCombo();
+                            }
                         }
                     }
                     auto *spatialComponent = entityRegistry.try_get<SpatialComponent>(m_selectedEntity.m_entityID);
@@ -1030,6 +1177,7 @@ void EditorWindow::update(const float p_deltaTime)
                                             {
                                                 // If the texture filename was changed, set the modified flag
                                                 m_selectedEntity.m_modelDataModified = true;
+                                                modelEntry.m_present[meshIndex] = true;
                                             }
 
                                             // Draw TEXTURE OPEN button
@@ -1115,6 +1263,7 @@ void EditorWindow::update(const float p_deltaTime)
                                                         {
                                                             // Set the selected texture
                                                             modelEntry.m_meshMaterials[meshIndex][materialIndex] = m_textureAssets[i].second;
+                                                            modelEntry.m_present[meshIndex] = true;
 
                                                             // Set the modified flag
                                                             m_selectedEntity.m_modelDataModified = true;
@@ -1140,6 +1289,8 @@ void EditorWindow::update(const float p_deltaTime)
                                 ImGui::PopStyleVar(); // ImGuiStyleVar_SeparatorTextAlign
                             }
 
+                            ImGui::Separator();
+
                             // Calculate button size
                             const char *addModelButtonLabel = "Add model";
                             float addModelButtonWidth = ImGui::CalcTextSize(addModelButtonLabel).x * Config::GUIVar().editor_inspector_button_width_multiplier;
@@ -1856,11 +2007,11 @@ void EditorWindow::update(const float p_deltaTime)
                         }
                     }
 
-                    const std::string componentTypeSelectionPopupName = "##ComponentTypeSelectionPopup";
-
                     ImGui::NewLine();
                     ImGui::Separator();
 
+                    const std::string componentTypeSelectionPopupName = "##ComponentTypeSelectionPopup";
+
                     // Calculate button size
                     const char *addComponentButtonLabel = "Add component";
                     float addComponentButtonWidth = ImGui::CalcTextSize(addComponentButtonLabel).x * Config::GUIVar().editor_inspector_button_width_multiplier;
@@ -1901,6 +2052,8 @@ void EditorWindow::update(const float p_deltaTime)
                                 std::cout << componentTypes[i].first << std::endl;
 
                                 ComponentsConstructionInfo *newComponentInfo = new ComponentsConstructionInfo();
+                                newComponentInfo->m_id = m_selectedEntity.m_entityID;
+                                newComponentInfo->m_name = m_selectedEntity.m_componentData.m_name;
                                 bool newComponentInfoSet = true;
 
                                 switch(componentTypes[i].second)
@@ -1969,8 +2122,9 @@ void EditorWindow::update(const float p_deltaTime)
 
                                 if(newComponentInfoSet)
                                 {
-                                    if(worldScene->addComponents(m_selectedEntity.m_entityID, *newComponentInfo) == ErrorCode::Success)
-                                        std::cout << "success" << std::endl;
+                                    m_componentConstructionInfoPool.push_back(newComponentInfo);
+
+                                    m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::World), DataType::DataType_CreateComponent, (void *)newComponentInfo, false);
                                 }
                                 else
                                     delete newComponentInfo;
@@ -2770,7 +2924,7 @@ void EditorWindow::setup(EditorWindowSettings &p_editorWindowSettings)
 
     // Load button textures to GPU
     for(decltype(m_buttonTextures.size()) size = m_buttonTextures.size(), i = 0; i < size; i++)
-        m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_Texture2D, (void *)&m_buttonTextures[i]);
+        m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_LoadTexture2D, (void *)&m_buttonTextures[i]);
 
     // Tell the renderer to draw the scene to a texture instead of the screen
     m_systemScene->getSceneLoader()->getChangeController()->sendData(m_systemScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_RenderToTexture, (void *)true);
@@ -3028,21 +3182,59 @@ void EditorWindow::drawSceneData(SceneData &p_sceneData, const bool p_sendChange
     ImGui::PopStyleColor(); // ImGuiCol_Border
 }
 
-void EditorWindow::drawEntityHierarchyEntry(EntityHierarchyEntry &p_entityEntry)
+void EditorWindow::drawEntityHierarchyEntry(EntityHierarchyEntry *p_entityEntry)
 {
     static ImGuiTreeNodeFlags baseNodeFlags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
 
-    ImGuiTreeNodeFlags flags = p_entityEntry.m_children.empty() ? baseNodeFlags | ImGuiTreeNodeFlags_Leaf : baseNodeFlags;
+    ImGuiTreeNodeFlags flags = p_entityEntry->m_children.empty() ? baseNodeFlags | ImGuiTreeNodeFlags_Leaf : baseNodeFlags;
 
-    if(ImGui::TreeNodeEx(p_entityEntry.m_combinedEntityIdAndName.c_str(), 
-        m_selectedEntity.m_entityID == p_entityEntry.m_entityID ? flags | ImGuiTreeNodeFlags_Selected : flags))
+    // Calculate the offset for the collapsing header that is drawn after the delete component button of each component type
+    const float headerOffsetAfterDeleteButton = m_buttonSizedByFont.x * 2.0f + m_imguiStyle.FramePadding.x * 6.0f;
+
+    ImGui::SetNextItemAllowOverlap();
+    if(ImGui::TreeNodeEx(p_entityEntry->m_combinedEntityIdAndName.c_str(), 
+        m_selectedEntity.m_entityID == p_entityEntry->m_entityID ? flags | ImGuiTreeNodeFlags_Selected : flags))
     {
         if(ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
-            m_selectedEntity.setEntity(p_entityEntry.m_entityID);
+            m_selectedEntity.setEntity(p_entityEntry->m_entityID);
+
+        // Make button background transparent and remove frame padding to make the buttons smaller
+        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
 
-        for(decltype(p_entityEntry.m_children.size()) size = p_entityEntry.m_children.size(), i = 0; i < size; i++)
+        // Draw ENTITY ADD CHILD button
+        ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - m_buttonSizedByFont.x * 2.0f - m_imguiStyle.ItemInnerSpacing.x);
+        if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_Add], ("##" + Utilities::toString(p_entityEntry->m_entityID) + "EntityAddChildButton").c_str(), ("Add child entity to \"" + p_entityEntry->m_combinedEntityIdAndName + "\"").c_str()))
         {
-            drawEntityHierarchyEntry(p_entityEntry.m_children[i]);
+            if(m_newEntityConstructionInfo == nullptr)
+            {
+                m_newEntityConstructionInfo = new ComponentsConstructionInfo();
+                m_newEntityConstructionInfo->m_name = "New entity";
+                m_newEntityConstructionInfo->m_id = 0;
+                m_newEntityConstructionInfo->m_parent = p_entityEntry->m_entityID;
+
+                // Open the pop-up with the new entity settings
+                m_openNewEntityPopup = true;
+            }
+        }
+
+        // Do not allow the deletion of root entity
+        if(p_entityEntry->m_entityID != 0)
+        {
+            // Draw ENTITY DELETE button
+            ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - m_buttonSizedByFont.x);
+            if(drawTextSizedButton(m_buttonTextures[ButtonTextureType::ButtonTextureType_DeleteEntry], ("##" + Utilities::toString(p_entityEntry->m_entityID) + "EntityDeleteButton").c_str(), ("Delete \"" + p_entityEntry->m_combinedEntityIdAndName + "\" entity").c_str()))
+            {
+
+            }
+        }
+
+        ImGui::PopStyleVar(); // ImGuiStyleVar_FramePadding
+        ImGui::PopStyleColor(); // ImGuiCol_Button
+
+        for(decltype(p_entityEntry->m_children.size()) size = p_entityEntry->m_children.size(), i = 0; i < size; i++)
+        {
+            drawEntityHierarchyEntry(p_entityEntry->m_children[i]);
         }
 
         ImGui::TreePop();
@@ -3107,58 +3299,58 @@ void EditorWindow::updateEntityList()
 void EditorWindow::updateHierarchyList()
 {
     // Clear the old hierarchy list
-    m_entityHierarchy.clear();
+    m_rootEntityHierarchyEntry.clear();
 
-    // Define a root entity and a temp entity list
-    EntityHierarchyEntry *rootEntity = nullptr;
-    std::list<EntityListEntry> parentlessEntityList;
+    // Define a root entity flag and a temp entity list
+    bool rootEntryPresent = false;
+    std::list<EntityListEntry*> parentlessEntityList;
 
     // Find the root entity and add all non-root entities to the temp entity list
     for(decltype(m_entityList.size()) size = m_entityList.size(), i = 0; i < size; i++)
     {
         if(m_entityList[i].m_entityID == 0 && m_entityList[i].m_parentEntityID == 0)
         {
-            m_entityHierarchy.emplace_back(m_entityList[i].m_entityID, m_entityList[i].m_parentEntityID, m_entityList[i].m_name, m_entityList[i].m_combinedEntityIdAndName, m_entityList[i].m_componentFlag);
-            rootEntity = &m_entityHierarchy.front();
+            m_rootEntityHierarchyEntry.init(m_entityList[i].m_entityID, m_entityList[i].m_parentEntityID, m_entityList[i].m_name, m_entityList[i].m_combinedEntityIdAndName, m_entityList[i].m_componentFlag);
+            rootEntryPresent = true;
         }
         else
         {
-            parentlessEntityList.push_back(m_entityList[i]);
+            parentlessEntityList.push_back(&m_entityList[i]);
         }
     }
 
     // Check if the root entity is present
-    if(rootEntity != nullptr)
+    if(rootEntryPresent)
     {
         // Define a list that hold all the children that have been added during the last loop
         // and add the root entity to it
         std::vector<EntityHierarchyEntry*> newlyAddedChildren;
-        newlyAddedChildren.push_back(rootEntity);
+        newlyAddedChildren.push_back(&m_rootEntityHierarchyEntry);
 
         // Continue the loop until there are no newly added children
         while(!newlyAddedChildren.empty())
         {
             // Save a copy of the children list that have been generated during the last loop
             // and clear the children list for the current loop
-            auto newChildrenFromPreviousRun = newlyAddedChildren;
+            std::vector<EntityHierarchyEntry *> newChildrenFromPreviousRun = newlyAddedChildren;
             newlyAddedChildren.clear();
 
             // Go over each new children from the previous loop
             for(decltype(newChildrenFromPreviousRun.size()) childrenSize = newChildrenFromPreviousRun.size(), childrenIndex = 0; childrenIndex < childrenSize; childrenIndex++)
             {
                 // Go over each entity left, that haven't been added as children
-                auto parentlessEntity = parentlessEntityList.begin();
+                auto &parentlessEntity = parentlessEntityList.begin();
                 while(parentlessEntity != parentlessEntityList.end())
                 {
                     // If the entity ID of the current child and the parent entity ID of the parent-less entity matches, add the parent-less entity as a child
                     // if it doesn't match, continue to the next entity
-                    if(newChildrenFromPreviousRun[childrenIndex]->m_entityID == parentlessEntity->m_parentEntityID)
+                    if(newChildrenFromPreviousRun[childrenIndex]->m_entityID == (*parentlessEntity)->m_parentEntityID)
                     {
                         // Add the parent-less entity as a child
-                        newChildrenFromPreviousRun[childrenIndex]->m_children.emplace_back(parentlessEntity->m_entityID, parentlessEntity->m_parentEntityID, parentlessEntity->m_name, parentlessEntity->m_combinedEntityIdAndName, parentlessEntity->m_componentFlag);
+                        newChildrenFromPreviousRun[childrenIndex]->addChild((*parentlessEntity)->m_entityID, (*parentlessEntity)->m_parentEntityID, (*parentlessEntity)->m_name, (*parentlessEntity)->m_combinedEntityIdAndName, (*parentlessEntity)->m_componentFlag);
 
                         // Add the newly created child to the child list for the next loop
-                        newlyAddedChildren.push_back(&newChildrenFromPreviousRun[childrenIndex]->m_children.back());
+                        newlyAddedChildren.push_back(newChildrenFromPreviousRun[childrenIndex]->m_children.back());
 
                         // Remove the former parent-less entity, as it has been added as a child
                         parentlessEntityList.erase(parentlessEntity++);
@@ -3176,12 +3368,15 @@ void EditorWindow::updateHierarchyList()
 
             for(auto const &i : parentlessEntityList)
             {
-                std::cout << i.m_entityID << std::endl;
+                std::cout << i->m_entityID << std::endl;
             }
         }
     }
     else
-        std::cout << "NO ROOT ENTRY" << std::endl;
+    {
+        m_rootEntityHierarchyEntry.m_entityID = NULL_ENTITY_ID;
+        m_rootEntityHierarchyEntry.m_parent = NULL_ENTITY_ID;
+    }
 
     return;
 }

+ 35 - 8
Praxis3D/Source/EditorWindow.h

@@ -51,6 +51,9 @@ public:
 		for(unsigned int i = 0; i < RenderPassType::RenderPassType_NumOfTypes; i++)
 			m_renderingPassesTypeText.push_back(GetString(static_cast<RenderPassType>(i)));
 
+		m_newEntityConstructionInfo = nullptr;
+		m_openNewEntityPopup = false;
+
 		m_newSceneSettingsTabFlags = 0;
 
 		m_fontSize = ImGui::GetFontSize();
@@ -392,6 +395,14 @@ private:
 	{
 		EntityHierarchyEntry() : m_entityID(NULL_ENTITY_ID), m_parent(NULL_ENTITY_ID), m_componentFlag(Systems::AllComponentTypes::None) { }
 		EntityHierarchyEntry(const EntityID p_entityID, const EntityID p_parent, const std::string &p_name, const std::string &p_combinedEntityIdAndName, const BitMask p_componentFlag) : m_entityID(p_entityID), m_parent(p_parent), m_name(p_name), m_combinedEntityIdAndName(p_combinedEntityIdAndName), m_componentFlag(p_componentFlag){ }
+		void init(const EntityID p_entityID, const EntityID p_parent, const std::string &p_name, const std::string &p_combinedEntityIdAndName, const BitMask p_componentFlag)
+		{
+			m_entityID = p_entityID;
+			m_parent = p_parent;
+			m_name = p_name;
+			m_combinedEntityIdAndName = p_combinedEntityIdAndName;
+			m_componentFlag = p_componentFlag;
+		}
 
 		bool operator==(const EntityHierarchyEntry &p_childEntityEntry) { return m_entityID == p_childEntityEntry.m_entityID; }
 
@@ -408,7 +419,7 @@ private:
 			{
 				for(decltype(m_children.size()) size = m_children.size(), i = 0; i < size; i++)
 				{
-					if(m_children[i].findParentAndAddChild(p_childEntityEntry))
+					if(m_children[i]->findParentAndAddChild(p_childEntityEntry))
 					{
 						parentFound = true;
 						break;
@@ -420,20 +431,34 @@ private:
 		}
 		void addChild(const EntityHierarchyEntry &p_childEntityEntry)
 		{
-			m_children.push_back(p_childEntityEntry);
+			m_children.push_back(new EntityHierarchyEntry(p_childEntityEntry));
+		}
+		void addChild(const EntityID p_entityID, const EntityID p_parent, const std::string &p_name, const std::string &p_combinedEntityIdAndName, const BitMask p_componentFlag)
+		{
+			m_children.push_back(new EntityHierarchyEntry(p_entityID, p_parent, p_name, p_combinedEntityIdAndName, p_componentFlag));
 		}
 		void removeChild(const EntityHierarchyEntry &p_childEntityEntry)
 		{
-			std::vector<EntityHierarchyEntry>::iterator childPosition = std::find(m_children.begin(), m_children.end(), p_childEntityEntry);
+			std::vector<EntityHierarchyEntry *>::iterator childPosition = std::find_if(m_children.begin(), m_children.end(), [&](EntityHierarchyEntry *e) { return *e == p_childEntityEntry; });// std::find(m_children.begin(), m_children.end(), p_childEntityEntry);
 			if(childPosition != m_children.end())
 				m_children.erase(childPosition);
 		}
 		bool containsChildren() const { return !m_children.empty(); }
-		void getEntries(std::vector<EntityHierarchyEntry *> &p_entriesList)
+		void getEntries(std::vector<EntityHierarchyEntry*> &p_entriesList)
 		{
 			p_entriesList.push_back(this);
 			for(decltype(m_children.size()) size = m_children.size(), i = 0; i < size; i++)
-				m_children[i].getEntries(p_entriesList);
+				m_children[i]->getEntries(p_entriesList);
+		}
+		void clear()
+		{
+			for(auto *child : m_children)
+			{
+				child->clear();
+				delete child;
+			}
+
+			m_children.clear();
 		}
 
 		EntityID m_entityID;
@@ -441,7 +466,7 @@ private:
 		BitMask m_componentFlag;
 		std::string m_name;
 		std::string m_combinedEntityIdAndName;
-		std::vector<EntityHierarchyEntry> m_children;
+		std::vector<EntityHierarchyEntry*> m_children;
 	};
 	struct SelectedEntity
 	{
@@ -616,7 +641,7 @@ private:
 	};
 
 	void drawSceneData(SceneData &p_sceneData, const bool p_sendChanges = false);
-	void drawEntityHierarchyEntry(EntityHierarchyEntry &p_entityEntry);
+	void drawEntityHierarchyEntry(EntityHierarchyEntry *p_entityEntry);
 	inline void drawLeftAlignedLabelText(const char *p_labelText, float p_nextWidgetOffset)
 	{
 		ImGui::AlignTextToFramePadding();
@@ -871,13 +896,15 @@ private:
 	// Scene entities
 	std::vector<ComponentListEntry> m_componentList;
 	std::vector<EntityListEntry> m_entityList;
-	std::vector<EntityHierarchyEntry> m_entityHierarchy;
+	EntityHierarchyEntry m_rootEntityHierarchyEntry;
 	SelectedEntity m_selectedEntity;
 	SceneData m_currentSceneData;
 
 	// Used to hold entity and component data for component creation / deletion until the next frame, after sending the data as a change
 	std::vector<EntityAndComponent*> m_entityAndComponentPool;
 	std::vector<ComponentsConstructionInfo*> m_componentConstructionInfoPool;
+	ComponentsConstructionInfo *m_newEntityConstructionInfo;
+	bool m_openNewEntityPopup;
 
 	// New scene settings
 	SceneData m_newSceneData;

+ 12 - 7
Praxis3D/Source/Engine.cpp

@@ -105,18 +105,15 @@ void Engine::run()
 		// Handle window and input events
 		m_window->handleEvents();
 
+		// Update all loaders
+		updateLoaders();
+
 		// If engine is still running
 		if(Config::engineVar().running == true)
 		{
+			// Process any queued changes
 			processEngineChanges();
 
-			// Load a different engine state, if it has been changed
-			//if(m_currentStateType != Config::engineVar().engineState)
-			//{
-			//	m_currentStateType = Config::engineVar().engineState;
-			//	setCurrentStateType();
-			//}
-
 			// Call update on the current engine state
 			m_engineStates[m_currentStateType]->update(*this);
 
@@ -216,6 +213,14 @@ void Engine::processEngineChanges()
 	}
 }
 
+void Engine::updateLoaders()
+{
+	Loaders::model().processReleaseQueue(m_engineStates[m_currentStateType]->getSceneLoader());
+	Loaders::texture2D().processReleaseQueue(m_engineStates[m_currentStateType]->getSceneLoader());
+	// Not in use for the moment
+	//Loaders::textureCubemap().processReleaseQueue(); 
+}
+
 ErrorCode Engine::initServices()
 {
 	ErrorCode returnError = ErrorCode::Success;

+ 4 - 2
Praxis3D/Source/Engine.h

@@ -29,6 +29,9 @@ private:
 	// Gets engine changes (if there are any) from the Universal scene of the current Engine State and process them
 	void processEngineChanges();
 
+	// Update all loaders that are derived from LoaderBase and track their objects with reference counters
+	void updateLoaders();
+
 	// Get all engine systems. Return a pointer to an array the size of Systems::NumberOfSystems
 	SystemBase **getSystems() { return m_systems; }
 
@@ -243,5 +246,4 @@ private:
 
 	// Make sure there is only one instance running
 	static int m_instances;
-};
-
+};

+ 2 - 0
Praxis3D/Source/EngineState.h

@@ -34,6 +34,8 @@ public:
 
 	inline UniversalScene *getChangeControllerScene() { return m_changeCtrlScene; }
 
+	inline SceneLoader &getSceneLoader() { return m_sceneLoader; }
+
 	const inline bool isInitialized() const { return m_initialized; }
 
 	inline void setSceneFilename(const std::string &p_filename) { m_sceneFilename = p_filename; }

+ 1 - 1
Praxis3D/Source/ErrorCodes.h

@@ -33,7 +33,7 @@ DECLARE_ENUM(ErrorType, ERROR_TYPES)
 
 #define ERROR_CODES(Code) \
 	/* General errors */ \
-    Code(Undefined,) \
+    Code(Undefined, = 0) \
     Code(Success,) \
     Code(Failure,) \
     Code(Initialize_success,) \

+ 37 - 27
Praxis3D/Source/GUIScene.cpp

@@ -329,46 +329,56 @@ void GUIScene::receiveData(const DataType p_dataType, void *p_data, const bool p
 			}
 			break;
 		case DataType_EnableGUISequence:
-			setGUISequenceEnabled(static_cast<bool>(p_data));
+			{
+				setGUISequenceEnabled(static_cast<bool>(p_data));
+			}
 			break;
 
 		case DataType_FileBrowserDialog:
-			// Cast the sent data into the intended type and add it to the file-browser dialog queue
-			m_fileBrowserDialogs.push(static_cast<FileBrowserDialog *>(p_data));
+			{
+				// Cast the sent data into the intended type and add it to the file-browser dialog queue
+				m_fileBrowserDialogs.push(static_cast<FileBrowserDialog *>(p_data));
+			}
 			break;
 
 		case DataType_EditorWindow:
-			// Cast the sent data into the intended type
-			EditorWindowSettings *editorWindowSettings = static_cast<EditorWindowSettings *>(p_data);
-
-			// If the editor window should be enabled
-			if(editorWindowSettings->m_enabled)
 			{
-				// If the editor window doesn't exist, create it
-				if(m_editorWindow == nullptr)
+				// Cast the sent data into the intended type
+				EditorWindowSettings *editorWindowSettings = static_cast<EditorWindowSettings *>(p_data);
+
+				// If the editor window should be enabled
+				if(editorWindowSettings->m_enabled)
 				{
-					m_editorWindow = new EditorWindow(this, Config::GUIVar().gui_editor_window_name, 0);
-					m_editorWindow->init();
-					m_editorWindow->setup(*editorWindowSettings);
-				}
-				else // If the editor window does exist, just send the settings to it
-					m_editorWindow->setup(*editorWindowSettings);
+					// If the editor window doesn't exist, create it
+					if(m_editorWindow == nullptr)
+					{
+						m_editorWindow = new EditorWindow(this, Config::GUIVar().gui_editor_window_name, 0);
+						m_editorWindow->init();
+						m_editorWindow->setup(*editorWindowSettings);
+					}
+					else // If the editor window does exist, just send the settings to it
+						m_editorWindow->setup(*editorWindowSettings);
 
-				GUIHandlerLocator::get().enableDocking();
-			}
-			else // If the editor should be disabled
-			{
-				// If the editor window exist, delete it
-				if(m_editorWindow != nullptr)
+					GUIHandlerLocator::get().enableDocking();
+				}
+				else // If the editor should be disabled
 				{
-					delete m_editorWindow;
-					m_editorWindow = nullptr;
+					// If the editor window exist, delete it
+					if(m_editorWindow != nullptr)
+					{
+						delete m_editorWindow;
+						m_editorWindow = nullptr;
+					}
 				}
+
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete editorWindowSettings;
 			}
+			break;
 
-			// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
-			if(p_deleteAfterReceiving)
-				delete editorWindowSettings;
+		default:
+			assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
 			break;
 	}
 }

+ 80 - 45
Praxis3D/Source/GraphicsDataSets.h

@@ -2,6 +2,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <variant>
 
 #include "Math.h"
 #include "Loaders.h"
@@ -159,74 +160,108 @@ struct RenderableObjectData
 // A simple container storing a union which contains one of the loadable objects; uses an enum to distinguish which object is currently being stored
 struct LoadableObjectsContainer
 {
-	enum LoadableObjectType
+	enum LoadableObjectType : unsigned int
 	{
+		LoadableObjectType_Shader = 0,
 		LoadableObjectType_Model,
-		LoadableObjectType_Shader,
 		LoadableObjectType_Texture
 	};
 
-	LoadableObjectsContainer(ModelLoader::ModelHandle p_model)				: m_loadableObject(p_model), m_objectType(LoadableObjectType::LoadableObjectType_Model) { }
-	LoadableObjectsContainer(ShaderLoader::ShaderProgram *p_shader)			: m_loadableObject(p_shader), m_objectType(LoadableObjectType::LoadableObjectType_Shader) { }
-	LoadableObjectsContainer(TextureLoader2D::Texture2DHandle p_texture)	: m_loadableObject(p_texture), m_objectType(LoadableObjectType::LoadableObjectType_Texture) { }
+	LoadableObjectsContainer(const LoadableObjectsContainer &p_arg) noexcept
+	{
+		switch(p_arg.m_loadableObject.index())
+		{
+			case LoadableObjectsContainer::LoadableObjectType_Shader:
+				m_loadableObject = std::get<ShaderLoader::ShaderProgram *>(p_arg.m_loadableObject);
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Model:
+				m_loadableObject = std::get<ModelLoader::ModelHandle>(p_arg.m_loadableObject);
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Texture:
+				m_loadableObject = std::get<TextureLoader2D::Texture2DHandle>(p_arg.m_loadableObject);
+				break;
+		}
+	}
+	LoadableObjectsContainer(LoadableObjectsContainer &&p_arg) noexcept
+	{
+		m_loadableObject = std::move(p_arg.m_loadableObject);
+	}
+	LoadableObjectsContainer(ModelLoader::ModelHandle p_model)				: m_loadableObject(p_model) {}//, m_objectType(LoadableObjectType::LoadableObjectType_Model) { }
+	LoadableObjectsContainer(ShaderLoader::ShaderProgram *p_shader)			: m_loadableObject(p_shader) {}//, m_objectType(LoadableObjectType::LoadableObjectType_Shader) { }
+	LoadableObjectsContainer(TextureLoader2D::Texture2DHandle p_texture)	: m_loadableObject(p_texture) {}//, m_objectType(LoadableObjectType::LoadableObjectType_Texture) { }
 	~LoadableObjectsContainer() { }
 
-	union m_loadableObject
+	inline const bool isLoadedToVideoMemory() const
 	{
-		m_loadableObject(ModelLoader::ModelHandle p_model)				: m_model(p_model)  { }
-		m_loadableObject(ShaderLoader::ShaderProgram *p_shader)			: m_shader(p_shader) { }
-		m_loadableObject(TextureLoader2D::Texture2DHandle p_texture)	: m_texture(p_texture) { }
-		~m_loadableObject() { }
+		return std::visit([](auto &&p_arg) { return isLoadedToVideoMemory(p_arg); }, m_loadableObject);
+
+		//switch(m_loadableObject.index())
+		//{
+		//	case LoadableObjectsContainer::LoadableObjectType_Shader:
+		//		return std::get<ShaderLoader::ShaderProgram *>(m_loadableObject)->isLoadedToVideoMemory();
+		//		break;
+		//	case LoadableObjectsContainer::LoadableObjectType_Model:
+		//		return std::get<ModelLoader::ModelHandle>(m_loadableObject).isLoadedToVideoMemory();
+		//		break;
+		//	case LoadableObjectsContainer::LoadableObjectType_Texture:
+		//		return std::get<TextureLoader2D::Texture2DHandle>(m_loadableObject).isLoadedToVideoMemory();
+		//		break;
+		//}
+
+		//return true;
+	}
 
-		ModelLoader::ModelHandle m_model;
-		ShaderLoader::ShaderProgram *m_shader;
-		TextureLoader2D::Texture2DHandle m_texture;
-	} m_loadableObject;
+	static const bool isLoadedToVideoMemory(const ShaderLoader::ShaderProgram *p_shader) { return p_shader->isLoadedToVideoMemory(); }
+	static const bool isLoadedToVideoMemory(const ModelLoader::ModelHandle &p_model) { return p_model.isLoadedToVideoMemory(); }
+	static const bool isLoadedToVideoMemory(const TextureLoader2D::Texture2DHandle &p_texture) { return p_texture.isLoadedToVideoMemory(); }
 
-	inline const bool isLoadedToVideoMemory() const
+	inline const LoadableObjectType getType() const { return static_cast<LoadableObjectType>(m_loadableObject.index()); }
+
+	// Get shader program; UNSAFE: MUST make sure the underlying variant is of ShaderProgram* type
+	inline ShaderLoader::ShaderProgram *getShaderProgram() { return std::get<ShaderLoader::ShaderProgram *>(m_loadableObject); }
+
+	// Get model handle; UNSAFE: MUST make sure the underlying variant is of ModelHandle type
+	inline ModelLoader::ModelHandle &getModelHandle() { return std::get<ModelLoader::ModelHandle>(m_loadableObject); }
+
+	// Get texture handle; UNSAFE: MUST make sure the underlying variant is of Texture2DHandle type
+	inline TextureLoader2D::Texture2DHandle &getTextureHandle() { return std::get<TextureLoader2D::Texture2DHandle>(m_loadableObject); }
+
+	// Copy assignment
+	LoadableObjectsContainer &operator=(const LoadableObjectsContainer &p_arg) noexcept
 	{
-		switch(m_objectType)
+		// Guard for self assignment
+		if(this == &p_arg)
+			return *this;
+
+		//m_objectType = p_arg.m_objectType;
+		m_loadableObject = p_arg.m_loadableObject;
+		switch(p_arg.m_loadableObject.index())
 		{
-		case LoadableObjectsContainer::LoadableObjectType_Model:
-			return m_loadableObject.m_model.isLoadedToVideoMemory();
-			break;
-		case LoadableObjectsContainer::LoadableObjectType_Shader:
-			return m_loadableObject.m_shader->isLoadedToVideoMemory();
-			break;
-		case LoadableObjectsContainer::LoadableObjectType_Texture:
-			return m_loadableObject.m_texture.isLoadedToVideoMemory();
-			break;
+			case LoadableObjectsContainer::LoadableObjectType_Shader:
+				m_loadableObject = std::get<ShaderLoader::ShaderProgram *>(p_arg.m_loadableObject);
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Model:
+				m_loadableObject = std::get<ModelLoader::ModelHandle>(p_arg.m_loadableObject);
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Texture:
+				m_loadableObject = std::get<TextureLoader2D::Texture2DHandle>(p_arg.m_loadableObject);
+				break;
 		}
 
-		return true;
+		return *this;
 	}
 
-	// copy assignment
-	LoadableObjectsContainer &operator=(const LoadableObjectsContainer &p_arg)
+	LoadableObjectsContainer &operator=(LoadableObjectsContainer &&p_arg) noexcept
 	{
-		// Guard self assignment
+		// Guard for self assignment
 		if(this == &p_arg)
 			return *this;
 
-		m_objectType = p_arg.m_objectType;
-
-		switch(p_arg.m_objectType)
-		{
-		case LoadableObjectsContainer::LoadableObjectType_Model:
-			m_loadableObject.m_model = p_arg.m_loadableObject.m_model;
-			break;
-		case LoadableObjectsContainer::LoadableObjectType_Shader:
-			m_loadableObject.m_shader = p_arg.m_loadableObject.m_shader;
-			break;
-		case LoadableObjectsContainer::LoadableObjectType_Texture:
-			m_loadableObject.m_texture = p_arg.m_loadableObject.m_texture;
-			break;
-		}
-
+		m_loadableObject = std::move(p_arg.m_loadableObject);
 		return *this;
 	}
 
-	LoadableObjectType m_objectType;
+	std::variant<ShaderLoader::ShaderProgram *, ModelLoader::ModelHandle, TextureLoader2D::Texture2DHandle> m_loadableObject;
 };
 
 struct RenderableMeshData

+ 50 - 40
Praxis3D/Source/LoaderBase.h

@@ -29,20 +29,21 @@ public:
 		}
 		virtual ~UniqueObject()
 		{
-			unload();
+			//unload(*this);
 		}
 
 		// Increments reference counter
-		inline void incRefCounter() { m_refCounter++; }
+		inline void incRefCounter() noexcept { m_refCounter++; }
 
 		// Decrements reference counter, if it goes to 0, object is put into unload queue
-		inline void decRefCounter()
+		inline void decRefCounter() noexcept
 		{
+			assert(m_refCounter > 0 && "m_refCounter of UniqueObject of LoaderBase was set to below zero (constructor-destructor were not paired)");
+
 			m_refCounter--;
 			if(m_refCounter == 0)
 			{
-				// TODO: queue texture unload upon reference counter reaching zero
-				//m_loaderBase->queueUnload(*this);
+				m_loaderBase->queueUnload(*this);
 			}
 		}
 
@@ -52,16 +53,15 @@ public:
 		inline void setUniqueID(unsigned int p_uniqueID)	{ m_uniqueID = p_uniqueID;			}
 
 		// Getters
-		inline const bool isLoadedToMemory() const		{ return m_loadedToMemory;		}
-		inline const bool isLoadedToVideoMemory() const { return m_loadedToVideoMemory; }
-		inline const unsigned int getUniqueID()	const	{ return m_uniqueID;			}
-		inline const std::string &getFilename() const	{ return m_filename;			}
+		inline const bool isLoadedToMemory() const		{ return m_loadedToMemory;			}
+		inline const bool isLoadedToVideoMemory() const { return m_loadedToVideoMemory;		}
+		inline const unsigned int getUniqueID()	const	{ return (unsigned int)m_uniqueID;	}
+		inline const std::string &getFilename() const	{ return m_filename;				}
 
 		// Equality operator; compares filenames
 		inline bool operator==(std::string p_string) { return m_filename == p_string; }
 
-		inline ErrorCode unload()		 { return static_cast<TObject*>(this)->unloadMemory();		}
-
+		//inline ErrorCode unload() { return static_cast<TObject *>(this)->unloadMemory(); }
 	protected:
 		inline const bool isBeingLoaded() { return m_beingLoaded; }
 
@@ -79,7 +79,7 @@ public:
 		LoaderBase *m_loaderBase;
 	};
 
-	LoaderBase() : m_queueIsEmpty(false) { }
+	LoaderBase() : m_queueIsEmpty(true) { }
 	~LoaderBase()
 	{
 		// Swap the queue with an empty one, effectively clearing it
@@ -95,37 +95,54 @@ public:
 	}
 
 	// Unloads the oldest object in the queue (if there are any)
-	// Only process one object, and should be called once per frame, to level out performance
-	inline void processReleaseQueue()
+	// Only process one object, and should be called once per frame, to level out performance degradation
+	inline void processReleaseQueue(SceneLoader &p_sceneLoader)
 	{
 		// First check if the queue isn't empty
 		if(!m_queueIsEmpty)
 		{
-			// Check if the reference counter is less than 0. If it's not, that means that
-			// something is still using the object, therefore it got on the unload queue by mistake,
-			// so just remove it without deleting it
-			if(m_objectUnloadQueue.front()->m_refCounter < 1)
+			for(decltype(Config::engineVar().loaders_num_of_unload_per_frame) i = 0, max = Config::engineVar().loaders_num_of_unload_per_frame; i < max;)
 			{
-				// Unload the object from RAM and VRAM
-				m_objectUnloadQueue.front()->unloadMemory();
-				m_objectUnloadQueue.front()->unloadVideoMemory();
-				removeObject(m_objectUnloadQueue.front());
-				//delete m_objectUnloadQueue.front();
+				auto *object = m_objectUnloadQueue.front();
+
+				// Check if the reference counter is less than 0. If it's not, that means that
+				// something is still using the object, therefore it got on the unload queue by mistake,
+				// so just remove it without deleting it
+				if(object->m_refCounter < 1)
+				{
+					// Unload the object from RAM and VRAM
+					unload(*static_cast<TObject *>(object), p_sceneLoader);
+					//m_objectUnloadQueue.front()->unload();
+					//m_objectUnloadQueue.front()->unloadMemory();
+					//m_objectUnloadQueue.front()->unloadVideoMemory();
+					//removeObject(*object);
+					m_objectRemoveQueue.push_back(object);
+					i++;
+				}
+
+				// Remove object from queue
+				m_objectUnloadQueue.pop();
+
+				if(m_objectUnloadQueue.empty())
+				{
+					m_queueIsEmpty = true;
+					return;
+				}
 			}
+		}
 
-			// Remove object from queue
-			m_objectUnloadQueue.pop();
+		if(!m_objectRemoveQueue.empty())
+		{
+			for(decltype(m_objectRemoveQueue.size()) i = 0, size = m_objectRemoveQueue.size(); i < size; i++)
+				removeObject(*m_objectRemoveQueue[i]);
 
-			if(m_objectUnloadQueue.size() < 1)
-				m_queueIsEmpty = true;
+			m_objectRemoveQueue.clear();
 		}
 	}
 
 	// Preload all the objects to memory, in parallel; returns after the loading has been completed
 	void preloadAllToMemory()
 	{
-		//static_cast<TDerived*>(this)->preloadAllToMemory();
-
 		TaskManagerLocator::get().parallelFor(0, m_objectPool.size(), 1, [=](decltype(m_objectPool.size()) i)
 		{
 			// Increment the reference counter before loading the object, as a safety precaution
@@ -139,10 +156,9 @@ public:
 	// Retrieve a const reference to an internal object pool
 	const std::vector<TObject *> &getObjectPool() const { return m_objectPool; }
 
-	// Returns false if there are any objects in the LoadToVideoMemory queue
-	//const inline bool isLoadToVideoMemoryQueueEmpty() const { return m_objectLoadToVideoMemoryQueue.empty(); }
-
 protected:
+	virtual void unload(TObject &p_object, SceneLoader &p_sceneLoader) { }
+
 	// Queue and object to be removed from memory
 	inline void queueUnload(UniqueObject &p_object)
 	{
@@ -150,12 +166,6 @@ protected:
 		m_queueIsEmpty = false;
 	}
 
-	// Put an object into a queue for loading it into video memory
-	//inline void queueLoadToVideoMemory(TObject &p_object)
-	//{
-	//	m_objectLoadToVideoMemoryQueue.push(&p_object);
-	//}
-
 	// Swap the object with the last element of vector and pop_back
 	inline void removeObject(UniqueObject &p_object)
 	{
@@ -184,6 +194,6 @@ private:
 	// so checking a bool instead of .size() will be a bit faster
 	bool m_queueIsEmpty;
 	
-	std::queue<UniqueObject*> m_objectUnloadQueue;
-	//std::queue<TObject*> m_objectLoadToVideoMemoryQueue;
+	std::queue<UniqueObject *> m_objectUnloadQueue;
+	std::vector<UniqueObject *> m_objectRemoveQueue;
 };

+ 1 - 1
Praxis3D/Source/LuaScript.cpp

@@ -678,7 +678,7 @@ void LuaScript::setUsertypes()
 	// Graphics types
 	m_luaState.new_usertype<TextureLoader2D::Texture2DHandle>("Texture2DHandle",
 		"loadToMemory", &TextureLoader2D::Texture2DHandle::loadToMemory,
-		"loadToVideoMemory", [this](TextureLoader2D::Texture2DHandle &p_v1) -> void { m_scriptScene->getSceneLoader()->getChangeController()->sendData(m_scriptScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_Texture2D, (void*)&p_v1); },
+		"loadToVideoMemory", [this](TextureLoader2D::Texture2DHandle &p_v1) -> void { m_scriptScene->getSceneLoader()->getChangeController()->sendData(m_scriptScene->getSceneLoader()->getSystemScene(Systems::Graphics), DataType::DataType_LoadTexture2D, (void*)&p_v1); },
 		"isLoadedToMemory", &TextureLoader2D::Texture2DHandle::isLoadedToMemory,
 		"isLoadedToVideoMemory", &TextureLoader2D::Texture2DHandle::isLoadedToVideoMemory,
 		"getTextureHeight", &TextureLoader2D::Texture2DHandle::getTextureHeight,

+ 26 - 7
Praxis3D/Source/ModelComponent.h

@@ -38,15 +38,15 @@ public:
 
 				// Resize mesh material alpha threshold and initialize each element to 0.0f
 				if(p_size > m_alphaThreshold.size())
-					m_alphaThreshold.resize(p_size, 0.0f);
+					m_alphaThreshold.resize(p_size, Config::graphicsVar().alpha_threshold);
 
 				// Resize mesh material emissive intensity initialize each element to 0.0f
 				if(p_size > m_emissiveIntensity.size())
-					m_emissiveIntensity.resize(p_size, 0.0f);
+					m_emissiveIntensity.resize(p_size, Config::graphicsVar().emissive_multiplier);
 
 				// Resize mesh material height scale and initialize each element to 1.0f
 				if(p_size > m_heightScale.size())
-					m_heightScale.resize(p_size, 1.0f);
+					m_heightScale.resize(p_size, Config::graphicsVar().height_scale);
 
 				// Resize the "mesh is present" array and initialize each element to false
 				if(p_size > m_present.size())
@@ -247,7 +247,7 @@ public:
 				// Go over each mesh
 				for(decltype(newModelData.m_model.getMeshSize()) meshIndex = 0, meshSize = newModelData.m_model.getMeshSize(); meshIndex < meshSize; meshIndex++)
 				{
-					if(m_modelsProperties->m_models[modelIndex].m_meshMaterials.size() > meshIndex && m_modelsProperties->m_models[modelIndex].m_present[meshIndex] == true)
+					if(m_modelsProperties->m_models[modelIndex].m_meshMaterials.size() > meshIndex)// && m_modelsProperties->m_models[modelIndex].m_present[meshIndex] == true)
 					{
 						MaterialData materials[MaterialType::MaterialType_NumOfTypes];
 
@@ -267,15 +267,30 @@ public:
 								m_texturesNeedLoading = true;
 
 							materials[iMatType].m_textureScale = m_modelsProperties->m_models[modelIndex].m_meshMaterialsScale[meshIndex][iMatType];
+
+							if(materials[iMatType].m_textureScale == glm::vec2(0.0f, 0.0f))
+								materials[iMatType].m_textureScale = glm::vec2(Config::graphicsVar().texture_tiling_factor, Config::graphicsVar().texture_tiling_factor);
 						}
 
+						float heightScale = Config::graphicsVar().height_scale;
+						if(m_modelsProperties->m_models[modelIndex].m_heightScale.size() > meshIndex)
+							heightScale = m_modelsProperties->m_models[modelIndex].m_heightScale[meshIndex];
+
+						float alphaThreshold = Config::graphicsVar().alpha_threshold;
+						if(m_modelsProperties->m_models[modelIndex].m_alphaThreshold.size() > meshIndex)
+							alphaThreshold = m_modelsProperties->m_models[modelIndex].m_alphaThreshold[meshIndex];
+
+						float emissiveIntensity = Config::graphicsVar().emissive_multiplier;
+						if(m_modelsProperties->m_models[modelIndex].m_emissiveIntensity.size() > meshIndex)
+							emissiveIntensity = m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex];
+
 						// Add the data for this mesh. Include materials loaded from the model itself, if they were present, otherwise, include default textures instead
 						newModelData.m_meshes.push_back(MeshData(
 							newModelData.m_model.getMeshArray()[meshIndex],
 							materials,
-							m_modelsProperties->m_models[modelIndex].m_heightScale[meshIndex],
-							m_modelsProperties->m_models[modelIndex].m_alphaThreshold[meshIndex],
-							m_modelsProperties->m_models[modelIndex].m_emissiveIntensity[meshIndex]));
+							heightScale,
+							alphaThreshold,
+							emissiveIntensity));
 					}
 					else
 					{
@@ -394,6 +409,10 @@ public:
 					m_loadPending = true;
 				}
 				break;
+
+			default:
+				assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
+				break;
 		}
 	}
 

+ 11 - 1
Praxis3D/Source/ModelLoader.cpp

@@ -7,6 +7,7 @@
 #include "Config.h"
 #include "ErrorHandlerLocator.h"
 #include "ModelLoader.h"
+#include "SceneLoader.h"
 #include "TaskManagerLocator.h"
 
 #include "Loaders.h"
@@ -376,4 +377,13 @@ ModelLoader::ModelHandle ModelLoader::load(std::string p_filename, bool p_startB
 
 	// Return the new texture
 	return ModelHandle(*model);
-}
+}
+
+void ModelLoader::unload(Model &p_object, SceneLoader &p_sceneLoader)
+{
+	// Create new model handle
+	ModelHandle *modelHandle = new ModelHandle(p_object);
+
+	// Send a notification to graphics scene to unload the model; set deleteAfterReceiving flag to true, to transfer the ownership of the model handle pointer to the graphics scene (so it will be responsible for deleting it)
+	p_sceneLoader.getChangeController()->sendData(p_sceneLoader.getSystemScene(Systems::Graphics), DataType::DataType_UnloadModel, (void *)modelHandle, true);
+}

+ 14 - 2
Praxis3D/Source/ModelLoader.h

@@ -223,6 +223,9 @@ public:
 		friend class ModelLoader;
 		friend class RendererFrontend;
 	public:
+		// Increment the reference counter when creating a handle
+		ModelHandle(const ModelHandle &p_modelHandle) : m_model(p_modelHandle.m_model) { m_model->incRefCounter(); }
+		ModelHandle(ModelHandle &&p_modelHandle) noexcept : m_model(p_modelHandle.m_model) { m_model->incRefCounter(); }
 		~ModelHandle() { m_model->decRefCounter(); }
 		
 		// Loads data from HDD to RAM and restructures it to be used to fill buffers later
@@ -245,10 +248,17 @@ public:
 			return returnError;
 		}
 		
-		// Assignment operator
+		// Copy assignment operator
 		ModelHandle &operator=(const ModelHandle &p_modelHandle)
 		{
-			m_model->decRefCounter();
+			m_model = p_modelHandle.m_model;
+			m_model->incRefCounter();
+			return *this;
+		}
+
+		// Move assignment operator
+		ModelHandle &operator=(ModelHandle &&p_modelHandle) noexcept
+		{
 			m_model = p_modelHandle.m_model;
 			m_model->incRefCounter();
 			return *this;
@@ -290,4 +300,6 @@ public:
 
 	virtual ModelHandle load(std::string p_filename, bool p_startBackgroundLoading = true);
 
+	private:
+		void unload(Model &p_object, SceneLoader &p_sceneLoader);
 };

+ 4 - 0
Praxis3D/Source/PhysicsScene.cpp

@@ -587,5 +587,9 @@ void PhysicsScene::receiveData(const DataType p_dataType, void *p_data, const bo
 		case DataType_SimulationActive:
 			m_simulationRunning = static_cast<bool>(p_data);
 			break;
+
+		default:
+			assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
+			break;
 	}
 }

+ 6 - 1
Praxis3D/Source/PropertySet.h

@@ -111,6 +111,11 @@ public:
 			break;
 		}
 	}
+	~Property()
+	{
+		if(m_variableType == PropertyVariableType::Type_string)
+			m_variable.m_string.~basic_string();
+	}
 
 	// Getters for each type
 	const inline bool getBool() const noexcept
@@ -507,7 +512,7 @@ public:
 	const inline bool operator==(const Property &p_property) const noexcept { return (m_propertyID == p_property.m_propertyID); }
 	const inline bool operator<(const Property &p_property) const noexcept { return (m_propertyID < p_property.m_propertyID); }
 
-	// Assignment operator
+	// Cope assignment operator
 	inline Property &operator=(const Property &p_property) noexcept
 	{
 		m_propertyID = p_property.m_propertyID;

+ 15 - 0
Praxis3D/Source/RendererBackend.cpp

@@ -64,6 +64,21 @@ void RendererBackend::processLoading(LoadCommands &p_loadCommands, const Uniform
 	}
 }
 
+void RendererBackend::processUnloading(UnloadCommands &p_unloadCommands)
+{
+	// Declare an array for each type of unloadable object
+	std::vector<unsigned int> unloadArrays[UnloadObjectType::UnloadObjectType_NumOfTypes];
+
+	// Go over each unload command and put them inside the corresponding array
+	for(decltype(p_unloadCommands.size()) i = 0, size = p_unloadCommands.size(); i < size; i++)
+		unloadArrays[p_unloadCommands[i].first].push_back(p_unloadCommands[i].second);
+
+	// Go over each type of unloadable object arrays, and if it contains any elements, pass the whole array to be unloaded in a batch
+	for(unsigned int i = 0; i < UnloadObjectType::UnloadObjectType_NumOfTypes; i++)
+		if(!unloadArrays[i].empty())
+			processCommand(static_cast<UnloadObjectType>(i), (int)unloadArrays[i].size(), unloadArrays[i].data());
+}
+
 void RendererBackend::processDrawing(const DrawCommands &p_drawCommands, const UniformFrameData &p_frameData)
 {
 	resetVAO();

+ 19 - 0
Praxis3D/Source/RendererBackend.h

@@ -406,6 +406,7 @@ public:
 	typedef std::vector<std::pair<int64_t, ScreenSpaceDrawCommand>> ScreenSpaceDrawCommands;
 
 	typedef std::vector<LoadCommand> LoadCommands;
+	typedef std::vector<std::pair<UnloadObjectType, unsigned int>> UnloadCommands;
 	typedef std::vector<BufferUpdateCommand> BufferUpdateCommands;
 	typedef std::vector<ComputeDispatchCommand> ComputeDispatchCommands;
 
@@ -425,6 +426,7 @@ public:
 	
 	void processUpdate(const BufferUpdateCommands &p_updateCommands, const UniformFrameData &p_frameData);
 	void processLoading(LoadCommands &p_loadCommands, const UniformFrameData &p_frameData);
+	void processUnloading(UnloadCommands &p_unloadCommands);
 	void processDrawing(const DrawCommands &p_drawCommands, const UniformFrameData &p_frameData);
 	void processDrawing(const ScreenSpaceDrawCommands &p_screenSpaceDrawCommands, const UniformFrameData &p_frameData);
 	void processDrawing(const ComputeDispatchCommands &p_computeDispatchCommands, const UniformFrameData &p_frameData);
@@ -974,6 +976,23 @@ protected:
 			break;
 		}
 	}
+	inline void processCommand(const UnloadObjectType p_objectType, const int p_count, unsigned int *p_handles)
+	{
+		switch(p_objectType)
+		{
+			case UnloadObjectType_VAO:
+				glDeleteVertexArrays(p_count, p_handles);
+				break;
+			case UnloadObjectType_Buffer:
+				glDeleteBuffers(p_count, p_handles);
+				break;
+			case UnloadObjectType_Shader:
+				break;
+			case UnloadObjectType_Texture:
+				glDeleteTextures(p_count, p_handles);
+				break;
+		}
+	}
 
 	CurrentState m_rendererState;
 

+ 34 - 11
Praxis3D/Source/RendererFrontend.cpp

@@ -329,20 +329,37 @@ void RendererFrontend::renderFrame(SceneObjects &p_sceneObjects, const float p_d
 	// array might have been also added to objects-to-render arrays, so they need to be loaded first
 	for(decltype(p_sceneObjects.m_loadToVideoMemory.size()) i = 0, size = p_sceneObjects.m_loadToVideoMemory.size(); i < size; i++)
 	{
-		switch(p_sceneObjects.m_loadToVideoMemory[i].m_objectType)
+		switch(p_sceneObjects.m_loadToVideoMemory[i].getType())
 		{
 		case LoadableObjectsContainer::LoadableObjectType_Model:
-			queueForLoading(p_sceneObjects.m_loadToVideoMemory[i].m_loadableObject.m_model);
+			queueForLoading(p_sceneObjects.m_loadToVideoMemory[i].getModelHandle());
 			break;
 		case LoadableObjectsContainer::LoadableObjectType_Shader:
-			queueForLoading(*p_sceneObjects.m_loadToVideoMemory[i].m_loadableObject.m_shader);
+			queueForLoading(*p_sceneObjects.m_loadToVideoMemory[i].getShaderProgram());
 			break;
 		case LoadableObjectsContainer::LoadableObjectType_Texture:
-			queueForLoading(p_sceneObjects.m_loadToVideoMemory[i].m_loadableObject.m_texture);
+			queueForLoading(p_sceneObjects.m_loadToVideoMemory[i].getTextureHandle());
 			break;
 		}
 	}
 
+	// Release all objects from video memory that are in the unload-from-GPU queue
+	for(decltype(p_sceneObjects.m_unloadFromVideoMemory.size()) i = 0, size = p_sceneObjects.m_unloadFromVideoMemory.size(); i < size; i++)
+	{
+		switch(p_sceneObjects.m_unloadFromVideoMemory[i].getType())
+		{
+			case LoadableObjectsContainer::LoadableObjectType_Model:
+				queueForUnloading(p_sceneObjects.m_unloadFromVideoMemory[i].getModelHandle());
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Shader:
+				queueForUnloading(*p_sceneObjects.m_unloadFromVideoMemory[i].getShaderProgram());
+				break;
+			case LoadableObjectsContainer::LoadableObjectType_Texture:
+				queueForUnloading(p_sceneObjects.m_unloadFromVideoMemory[i].getTextureHandle());
+				break;
+		}
+	}
+
 	unsigned int numLoadedObjectsThisFrame = 0;
 
 	// Iterate over all objects to be rendered with geometry shader
@@ -357,21 +374,21 @@ void RendererFrontend::renderFrame(SceneObjects &p_sceneObjects, const float p_d
 
 			LoadableObjectsContainer &loadableObject = component.m_objectsToLoad.front();
 
-			switch(loadableObject.m_objectType)
+			switch(loadableObject.getType())
 			{
 			case LoadableObjectsContainer::LoadableObjectType_Model:
-				queueForLoading(loadableObject.m_loadableObject.m_model);
-				loadableObject.m_loadableObject.m_model.setLoadedToVideoMemory(true);
+				queueForLoading(loadableObject.getModelHandle());
+				loadableObject.getModelHandle().setLoadedToVideoMemory(true);
 				break;
 
 			case LoadableObjectsContainer::LoadableObjectType_Shader:
-				queueForLoading(*loadableObject.m_loadableObject.m_shader);
-				loadableObject.m_loadableObject.m_shader->setLoadedToVideoMemory(true);
+				queueForLoading(*loadableObject.getShaderProgram());
+				loadableObject.getShaderProgram()->setLoadedToVideoMemory(true);
 				break;
 
 			case LoadableObjectsContainer::LoadableObjectType_Texture:
-				queueForLoading(loadableObject.m_loadableObject.m_texture);
-				loadableObject.m_loadableObject.m_texture.setLoadedToVideoMemory(true);
+				queueForLoading(loadableObject.getTextureHandle());
+				loadableObject.getTextureHandle().setLoadedToVideoMemory(true);
 				break;
 			}
 
@@ -388,6 +405,12 @@ void RendererFrontend::renderFrame(SceneObjects &p_sceneObjects, const float p_d
 
 	// Clear the load-to-GPU queue, since everything in it has been processed
 	p_sceneObjects.m_loadToVideoMemory.clear();
+
+	// Handle object unloading
+	passUnloadCommandsToBackend();
+
+	// Clear the unload-from-GPU queue
+	p_sceneObjects.m_unloadFromVideoMemory.clear();
 	
 	// Calculate the view rotation matrix
 	const glm::quat orientation = glm::normalize(glm::toQuat(p_sceneObjects.m_cameraViewMatrix));

+ 30 - 0
Praxis3D/Source/RendererFrontend.h

@@ -231,6 +231,27 @@ protected:
 			p_shaderBuffer.m_data);
 	}
 
+	inline void queueForUnloading(ShaderLoader::ShaderProgram &p_shader)
+	{
+		m_unloadCommands.emplace_back(std::make_pair(UnloadObjectType::UnloadObjectType_Shader, p_shader.getShaderHandle()));
+	}
+	inline void queueForUnloading(ModelLoader::ModelHandle &p_model)
+	{
+		m_unloadCommands.emplace_back(std::make_pair(UnloadObjectType::UnloadObjectType_VAO, p_model.getHandle()));
+
+		for(unsigned int i = 0; i < ModelBuffer_NumAllTypes; i++)
+			m_unloadCommands.emplace_back(std::make_pair(UnloadObjectType::UnloadObjectType_Buffer, p_model.m_model->m_buffers[i]));
+
+	}
+	inline void queueForUnloading(TextureLoader2D::Texture2DHandle &p_texture)
+	{
+		m_unloadCommands.emplace_back(std::make_pair(UnloadObjectType::UnloadObjectType_Texture, p_texture.getHandle()));
+	}
+	inline void queueForUnloading(TextureLoaderCubemap::TextureCubemapHandle p_texture)
+	{
+		m_unloadCommands.emplace_back(std::make_pair(UnloadObjectType::UnloadObjectType_Texture, p_texture.getHandle()));
+	}
+
 	inline void queueForUpdate(ShaderBuffer &p_shaderBuffer)
 	{
 		m_bufferUpdateCommands.emplace_back(p_shaderBuffer.m_handle,
@@ -251,6 +272,14 @@ protected:
 		// Clear load commands, since they have already been passed to backend
 		m_loadCommands.clear();
 	}
+	inline void passUnloadCommandsToBackend()
+	{
+		// Pass the queued unload commands to the backend to be released from GPU memory
+		m_backend.processUnloading(m_unloadCommands);
+
+		// Clear unload commands, since they have already been passed to backend
+		m_unloadCommands.clear();
+	}
 	inline void passDrawCommandsToBackend()
 	{
 		// Pass the queued draw commands to the backend to be sent to GPU
@@ -321,6 +350,7 @@ protected:
 	// Renderer commands
 	RendererBackend::DrawCommands m_drawCommands;
 	RendererBackend::LoadCommands m_loadCommands;
+	RendererBackend::UnloadCommands m_unloadCommands;
 	RendererBackend::BufferUpdateCommands m_bufferUpdateCommands;
 	RendererBackend::ScreenSpaceDrawCommands m_screenSpaceDrawCommands;
 	RendererBackend::ComputeDispatchCommands m_computeDispatchCommands;

+ 36 - 20
Praxis3D/Source/RendererScene.cpp

@@ -379,23 +379,6 @@ void RendererScene::update(const float p_deltaTime)
 	//	|	  COMPONENT UPDATES		|
 	//	|___________________________|
 	//
-	/*auto modelView = entityRegistry.view<ModelComponent>();
-	for(auto entity : modelView)
-	{
-		auto &component = modelView.get<ModelComponent>(entity);
-
-		if(!component.isLoadedToVideoMemory())
-			component.performCheckIsLoadedToVideoMemory();
-	}
-
-	auto shaderView = entityRegistry.view<ShaderComponent>();
-	for(auto entity : shaderView)
-	{
-		auto &component = shaderView.get<ShaderComponent>(entity);
-
-		if(!component.isLoadedToVideoMemory())
-			component.performCheckIsLoadedToVideoMemory();
-	}*/
 
 	//	 ___________________________
 	//	|							|
@@ -743,7 +726,11 @@ void RendererScene::receiveData(const DataType p_dataType, void *p_data, const b
 	{
 		case DataType::DataType_CreateComponent:
 			{
+				auto *componentInfo = static_cast<ComponentsConstructionInfo *>(p_data);
 
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete componentInfo;
 			}
 			break;
 
@@ -817,7 +804,14 @@ void RendererScene::receiveData(const DataType p_dataType, void *p_data, const b
 			break;
 
 		case DataType::DataType_GUIPassFunctors:
-			m_renderTask->m_renderer.setGUIPassFunctorSequence(static_cast<FunctorSequence *>(p_data));
+			{
+				auto *functors = static_cast<FunctorSequence *>(p_data);
+				m_renderTask->m_renderer.setGUIPassFunctorSequence(functors);
+
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete functors;
+			}
 			break;
 
 		case DataType::DataType_RenderToTexture:
@@ -837,7 +831,7 @@ void RendererScene::receiveData(const DataType p_dataType, void *p_data, const b
 			}
 			break;
 
-		case DataType::DataType_Texture2D:
+		case DataType::DataType_LoadTexture2D:
 			{
 				TextureLoader2D::Texture2DHandle *textureHandle = static_cast<TextureLoader2D::Texture2DHandle *>(p_data);
 				if(textureHandle->isLoadedToMemory())
@@ -849,8 +843,30 @@ void RendererScene::receiveData(const DataType p_dataType, void *p_data, const b
 			}
 			break;
 
-		case DataType::DataType_Texture3D:
+		case DataType::DataType_UnloadTexture2D:
+			{
+				TextureLoader2D::Texture2DHandle *textureHandle = static_cast<TextureLoader2D::Texture2DHandle *>(p_data);
+				if(textureHandle->isLoadedToVideoMemory())
+					m_sceneObjects.m_unloadFromVideoMemory.emplace_back(*textureHandle);
+				
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete textureHandle;
+			}
+			break;
+
+		case DataType::DataType_LoadTexture3D:
+			{
+				TextureLoaderCubemap::TextureCubemapHandle *textureHandle = static_cast<TextureLoaderCubemap::TextureCubemapHandle *>(p_data);
+
+				// Delete the received data if it has been marked for deletion (ownership transfered upon receiving)
+				if(p_deleteAfterReceiving)
+					delete textureHandle;
+			}
+			break;
 
+		default:
+			assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
 			break;
 	}
 }

+ 3 - 0
Praxis3D/Source/RendererScene.h

@@ -97,6 +97,9 @@ struct SceneObjects
 
 	// Objects that need to be loaded to VRAM
 	std::vector<LoadableObjectsContainer> m_loadToVideoMemory;
+
+	// Objects that need to be removed from VRAM
+	std::vector<LoadableObjectsContainer> m_unloadFromVideoMemory;
 };
 
 class RendererScene : public SystemScene

+ 0 - 31
Praxis3D/Source/RigidBodyComponent.h

@@ -50,37 +50,6 @@ public:
 	}
 	~RigidBodyComponent()
 	{
-		switch(m_collisionShapeType)
-		{
-			case RigidBodyComponent::CollisionShapeType_Box:
-				if(m_collisionShape.m_boxShape != nullptr)
-					delete m_collisionShape.m_boxShape;
-				break;
-			case RigidBodyComponent::CollisionShapeType_Capsule:
-				if(m_collisionShape.m_capsuleShape != nullptr)
-					delete m_collisionShape.m_capsuleShape;
-				break;
-			case RigidBodyComponent::CollisionShapeType_Cone:
-				if(m_collisionShape.m_coneShape != nullptr)
-					delete m_collisionShape.m_coneShape;
-				break;
-			case RigidBodyComponent::CollisionShapeType_ConvexHull:
-				if(m_collisionShape.m_convexHullShape != nullptr)
-					delete m_collisionShape.m_convexHullShape;
-				break;
-			case RigidBodyComponent::CollisionShapeType_Cylinder:
-				if(m_collisionShape.m_cylinderShape != nullptr)
-					delete m_collisionShape.m_cylinderShape;
-				break;
-			case RigidBodyComponent::CollisionShapeType_Sphere:
-				if(m_collisionShape.m_sphereShape != nullptr)
-					delete m_collisionShape.m_sphereShape;
-				break;
-		}
-
-		if(m_rigidBody != nullptr)
-			delete m_rigidBody;
-
 		if(m_constructionInfo != nullptr)
 			delete m_constructionInfo;
 

+ 3 - 6
Praxis3D/Source/SceneLoader.cpp

@@ -649,18 +649,15 @@ void SceneLoader::importFromProperties(GraphicsComponentsConstructionInfo &p_con
 											}
 
 											// Get material alpha threshold value, if it is present
-											auto alphaThresholdProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::AlphaThreshold);
-											if(alphaThresholdProperty)
+											if(auto alphaThresholdProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::AlphaThreshold); alphaThresholdProperty)
 												newModelEntry.m_alphaThreshold[meshDataIndex] = alphaThresholdProperty.getFloat();
 
 											// Get emissive intensity, if it is present
-											auto emissiveIntensityProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::EmissiveIntensity);
-											if(emissiveIntensityProperty)
+											if(auto emissiveIntensityProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::EmissiveIntensity); emissiveIntensityProperty)
 												newModelEntry.m_emissiveIntensity[meshDataIndex] = emissiveIntensityProperty.getFloat();
 
 											// Get material height scale value, if it is present
-											auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale);
-											if(heightScaleProperty)
+											if(auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale); heightScaleProperty)
 												newModelEntry.m_heightScale[meshDataIndex] = heightScaleProperty.getFloat();
 
 											// Get material properties

+ 4 - 0
Praxis3D/Source/ScriptScene.cpp

@@ -290,6 +290,10 @@ void ScriptScene::receiveData(const DataType p_dataType, void *p_data, const boo
 	case DataType_EnableLuaScripting:
 		m_luaScriptsEnabled = static_cast<bool>(p_data);
 		break;
+
+	default:
+		assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
+		break;
 	}
 }
 

+ 53 - 45
Praxis3D/Source/TextureLoader.cpp

@@ -4,6 +4,7 @@
 #include "ErrorHandlerLocator.h"
 #include "Filesystem.h"
 #include "ModelLoader.h"
+#include "SceneLoader.h"
 #include "TaskManagerLocator.h"
 #include "TextureLoader.h"
 #include "Utilities.h"
@@ -11,58 +12,56 @@
 
 TextureLoader2D::TextureLoader2D()
 {
-	m_default2DTexture = new Texture2D(this, Config::textureVar().default_texture, m_objectPool.size(), 0);
-	m_defaultEmissive = new Texture2D(this, Config::textureVar().default_emissive_texture, m_objectPool.size(), 0);
-	m_defaultHeight = new Texture2D(this, Config::textureVar().default_height_texture, m_objectPool.size(), 0);
-	m_defaultNormal = new Texture2D(this, Config::textureVar().default_normal_texture, m_objectPool.size(), 0);
+	m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse] = new Texture2D(this, Config::textureVar().default_texture, m_objectPool.size(), 0);
+	m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive] = new Texture2D(this, Config::textureVar().default_emissive_texture, m_objectPool.size(), 0);
+	m_defaultTextures[DefaultTextureType::DefaultTextureType_Height] = new Texture2D(this, Config::textureVar().default_height_texture, m_objectPool.size(), 0);
+	m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal] = new Texture2D(this, Config::textureVar().default_normal_texture, m_objectPool.size(), 0);
+
+	for(unsigned int i = 0; i < DefaultTextureType::DefaultTextureType_NumOfTypes; i++)
+		m_defaultTextureHandles[i] = nullptr;
 }
 
 TextureLoader2D::~TextureLoader2D()
 {
+	for(unsigned int i = 0; i < DefaultTextureType::DefaultTextureType_NumOfTypes; i++)
+	{
+		delete m_defaultTextures[i];
+		delete m_defaultTextureHandles[i];
+	}
 }
 
 ErrorCode TextureLoader2D::init()
 {
 	// If the default texture filename changed upon loading the configuration
-	if(m_default2DTexture->m_filename != Config::textureVar().default_texture)
+	if(m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse]->m_filename != Config::textureVar().default_texture)
 	{
-		delete m_default2DTexture;
-		m_default2DTexture = new Texture2D(this, Config::textureVar().default_texture, m_objectPool.size(), 0);
+		delete m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse];
+		m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse] = new Texture2D(this, Config::textureVar().default_texture, m_objectPool.size(), 0);
 	}
-	if(m_defaultEmissive->m_filename != Config::textureVar().default_emissive_texture)
+	if(m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive]->m_filename != Config::textureVar().default_emissive_texture)
 	{
-		delete m_defaultEmissive;
-		m_defaultEmissive = new Texture2D(this, Config::textureVar().default_emissive_texture, m_objectPool.size(), 0);
+		delete m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive];
+		m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive] = new Texture2D(this, Config::textureVar().default_emissive_texture, m_objectPool.size(), 0);
 	}
-	if(m_defaultHeight->m_filename != Config::textureVar().default_height_texture)
+	if(m_defaultTextures[DefaultTextureType::DefaultTextureType_Height]->m_filename != Config::textureVar().default_height_texture)
 	{
-		delete m_defaultHeight;
-		m_defaultHeight = new Texture2D(this, Config::textureVar().default_height_texture, m_objectPool.size(), 0);
+		delete m_defaultTextures[DefaultTextureType::DefaultTextureType_Height];
+		m_defaultTextures[DefaultTextureType::DefaultTextureType_Height] = new Texture2D(this, Config::textureVar().default_height_texture, m_objectPool.size(), 0);
 	}
-	if(m_defaultNormal->m_filename != Config::textureVar().default_normal_texture)
+	if(m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal]->m_filename != Config::textureVar().default_normal_texture)
 	{
-		delete m_defaultNormal;
-		m_defaultNormal = new Texture2D(this, Config::textureVar().default_normal_texture, m_objectPool.size(), 0);
+		delete m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal];
+		m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal] = new Texture2D(this, Config::textureVar().default_normal_texture, m_objectPool.size(), 0);
 	}
 	
-	// Load default textures to memory and video memory
-	m_default2DTexture->loadToMemory();
-	//m_default2DTexture->loadToVideoMemory();
-
-	m_defaultEmissive->loadToMemory();
-	//m_defaultEmissive->loadToVideoMemory();
-
-	m_defaultHeight->loadToMemory();
-	//m_defaultHeight->loadToVideoMemory();
+	// Load default textures
+	for(unsigned int i = 0; i < DefaultTextureType::DefaultTextureType_NumOfTypes; i++)
+		if(const auto error = m_defaultTextures[i]->loadToMemory(); error != ErrorCode::Success)
+			ErrHandlerLoc::get().log(error, m_defaultTextures[i]->getFilename(), ErrorSource::Source_TextureLoader);
 
-	m_defaultNormal->loadToMemory();
-	//m_defaultNormal->loadToVideoMemory();
-
-	// Add default textures to the texture pool
-	m_objectPool.push_back(m_default2DTexture);
-	m_objectPool.push_back(m_defaultEmissive);
-	m_objectPool.push_back(m_defaultHeight);
-	m_objectPool.push_back(m_defaultNormal);
+	// Create texture handles for the default textures
+	for(unsigned int i = 0; i < DefaultTextureType::DefaultTextureType_NumOfTypes; i++)
+		m_defaultTextureHandles[i] = new Texture2DHandle(m_defaultTextures[i]);
 
 	return ErrorCode::Success;
 }
@@ -86,13 +85,13 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::load(const std::string &p_file
 		switch(p_materialType)
 		{
 		case MaterialType_Normal:
-			returnTexture = m_defaultNormal;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal];
 			break;
 		case MaterialType_Emissive:
-			returnTexture = m_defaultEmissive;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive];
 			break;
 		case MaterialType_Height:
-			returnTexture = m_defaultHeight;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Height];
 			break;
 		case MaterialType_Diffuse:
 		case MaterialType_Combined:
@@ -100,24 +99,24 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::load(const std::string &p_file
 		case MaterialType_Metalness:
 		case MaterialType_AmbientOcclusion:
 		default:
-			returnTexture = m_default2DTexture;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse];
 			break;
 		}
 	}
 	else
 	{
 		// Assign an appropriate default texture
-		unsigned int defaultTextureHandle = m_default2DTexture->m_handle;
+		unsigned int defaultTextureHandle = m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse]->m_handle;
 		switch(p_materialType)
 		{
 		case MaterialType_Normal:
-			defaultTextureHandle = m_defaultNormal->m_handle;
+			defaultTextureHandle = m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal]->m_handle;
 			break;
 		case MaterialType_Emissive:
-			defaultTextureHandle = m_defaultEmissive->m_handle;
+			defaultTextureHandle = m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive]->m_handle;
 			break;
 		case MaterialType_Height:
-			defaultTextureHandle = m_defaultHeight->m_handle;
+			defaultTextureHandle = m_defaultTextures[DefaultTextureType::DefaultTextureType_Height]->m_handle;
 			break;
 		}
 
@@ -165,7 +164,7 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::load(const std::string &p_file
 		if(!p_filename.empty())
 			ErrHandlerLoc::get().log(ErrorCode::Texture_not_found, ErrorSource::Source_TextureLoader, p_filename);
 
-		returnTexture = m_default2DTexture;
+		returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse];
 	}
 	else
 	{
@@ -205,7 +204,7 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::load(const std::string &p_file
 		if(!p_filename.empty())
 			ErrHandlerLoc::get().log(ErrorCode::Texture_not_found, ErrorSource::Source_TextureLoader, p_filename);
 
-		returnTexture = m_default2DTexture;
+		returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse];
 	}
 	else
 	{
@@ -218,7 +217,7 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::load(const std::string &p_file
 
 		// Texture wasn't loaded before, so create a new one
 		// Assign default handle (as a placeholder to be used before the texture is loaded from HDD)
-		returnTexture = new Texture2D(this, p_filename, m_objectPool.size(), m_default2DTexture->m_handle);
+		returnTexture = new Texture2D(this, p_filename, m_objectPool.size(), m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse]->m_handle);
 
 		// Set the loaded flag to true, because we have already provided the texture handle
 		returnTexture->setLoadedToVideoMemory(true);
@@ -249,7 +248,7 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::create(const std::string &p_na
 
 	// Texture wasn't loaded before, so create a new one
 	// Assign default handle (as a placeholder to be used before the texture is loaded from HDD)
-	returnTexture = new Texture2D(this, p_name, m_objectPool.size(), m_default2DTexture->m_handle);
+	returnTexture = new Texture2D(this, p_name, m_objectPool.size(), m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse]->m_handle);
 
 	returnTexture->m_textureWidth = p_width;
 	returnTexture->m_textureHeight = p_height;
@@ -268,6 +267,15 @@ TextureLoader2D::Texture2DHandle TextureLoader2D::create(const std::string &p_na
 	return Texture2DHandle(returnTexture);
 }
 
+void TextureLoader2D::unload(Texture2D &p_object, SceneLoader &p_sceneLoader)
+{
+	// Create new texture handle
+	Texture2DHandle *textureHandle = new Texture2DHandle(&p_object);
+
+	// Send a notification to graphics scene to unload the texture; set deleteAfterReceiving flag to true, to transfer the ownership of the texture handle pointer to the graphics scene (so it will be responsible for deleting it)
+	p_sceneLoader.getChangeController()->sendData(p_sceneLoader.getSystemScene(Systems::Graphics), DataType::DataType_UnloadTexture2D, (void*)textureHandle, true);
+}
+
 TextureLoaderCubemap::TextureLoaderCubemap()
 {
 	std::string defaultFilenames[CubemapFace_NumOfFaces];

+ 54 - 24
Praxis3D/Source/TextureLoader.h

@@ -240,6 +240,9 @@ public:
 		friend class TextureLoader2D;
 		friend class RendererFrontend;
 	public:
+		// Increment the reference counter when creating a handle
+		Texture2DHandle(const Texture2DHandle &p_textureHandle) : m_textureData(p_textureHandle.m_textureData) { m_textureData->incRefCounter(); }
+		Texture2DHandle(Texture2DHandle &&p_textureHandle) noexcept : m_textureData(p_textureHandle.m_textureData) { m_textureData->incRefCounter(); }
 		~Texture2DHandle() { m_textureData->decRefCounter(); }
 
 		// Loads data from HDD to RAM and restructures it to be used to fill buffers later
@@ -270,10 +273,17 @@ public:
 			}
 		}
 
-		// Assignment operator
+		// Copy assignment operator
 		Texture2DHandle &operator=(const Texture2DHandle &p_textureHandle)
 		{
-			m_textureData->decRefCounter();
+			m_textureData = p_textureHandle.m_textureData;
+			m_textureData->incRefCounter();
+			return *this;
+		}		
+		
+		// Move assignment operator
+		Texture2DHandle &operator=(Texture2DHandle &&p_textureHandle) noexcept
+		{
 			m_textureData = p_textureHandle.m_textureData;
 			m_textureData->incRefCounter();
 			return *this;
@@ -327,17 +337,17 @@ public:
 
 	Texture2DHandle getDefaultTexture(MaterialType p_materialType = MaterialType::MaterialType_Diffuse)
 	{
-		Texture2D *returnTexture = m_default2DTexture;
+		Texture2D *returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Diffuse];
 
 		switch(p_materialType)
 		{
 		case MaterialType_Diffuse:
 			break;
 		case MaterialType_Normal:
-			returnTexture = m_defaultNormal;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Normal];
 			break;
 		case MaterialType_Emissive:
-			returnTexture = m_defaultEmissive;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Emissive];
 			break;
 		case MaterialType_Combined:
 			break;
@@ -346,7 +356,7 @@ public:
 		case MaterialType_Metalness:
 			break;
 		case MaterialType_Height:
-			returnTexture = m_defaultHeight;
+			returnTexture = m_defaultTextures[DefaultTextureType::DefaultTextureType_Height];
 			break;
 		case MaterialType_AmbientOcclusion:
 			break;
@@ -360,19 +370,25 @@ public:
 	}
 
 	inline bool isTextureDefault(const Texture2DHandle &p_textureHandle) const 
-	{ 
-		return p_textureHandle.m_textureData == m_default2DTexture ||
-			p_textureHandle.m_textureData == m_defaultEmissive ||
-			p_textureHandle.m_textureData == m_defaultHeight ||
-			p_textureHandle.m_textureData == m_defaultNormal;
+	{
+		for(unsigned int i = 0; i < DefaultTextureType::DefaultTextureType_NumOfTypes; i++)
+			if(p_textureHandle.m_textureData == m_defaultTextures[i])
+				return true;
+
+		return false;
 	}
 
 protected:
-	// Default textures used in place of missing ones when loading textures
-	Texture2D *m_default2DTexture;
-	Texture2D *m_defaultEmissive;
-	Texture2D *m_defaultHeight;
-	Texture2D *m_defaultNormal;
+	enum DefaultTextureType : unsigned int
+	{
+		DefaultTextureType_Diffuse = 0,
+		DefaultTextureType_Emissive,
+		DefaultTextureType_Height,
+		DefaultTextureType_Normal,
+		DefaultTextureType_NumOfTypes
+	};
+
+	void unload(Texture2D &p_object, SceneLoader &p_sceneLoader);
 
 	// Returns a vector with all default 2D textures
 	// Meant to be called during initialization to load the 
@@ -382,17 +398,26 @@ protected:
 		std::vector<TextureLoader2D::Texture2DHandle> returnVector;
 
 		// Make sure to only return the textures that haven't been loaded
-		if(!m_default2DTexture->handleAssigned())
-			returnVector.push_back(m_default2DTexture);
-		if(!m_defaultEmissive->handleAssigned())
-			returnVector.push_back(m_defaultEmissive);
-		if(!m_defaultHeight->handleAssigned())
-			returnVector.push_back(m_defaultHeight);
-		if(!m_defaultNormal->handleAssigned())
-			returnVector.push_back(m_defaultNormal);
+		if(!m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Diffuse]->m_textureData->handleAssigned())
+			returnVector.push_back(*m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Diffuse]);
+
+		if(!m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Emissive]->m_textureData->handleAssigned())
+			returnVector.push_back(*m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Emissive]);
+
+		if(!m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Height]->m_textureData->handleAssigned())
+			returnVector.push_back(*m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Height]);
+
+		if(!m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Normal]->m_textureData->handleAssigned())
+			returnVector.push_back(*m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_Normal]);
 
 		return returnVector;
 	}
+
+	// Default textures used in place of missing ones when loading textures
+	Texture2D *m_defaultTextures[DefaultTextureType::DefaultTextureType_NumOfTypes];
+
+	// Handles for the default textures are held so that the reference counter for the default texture do not reach 0
+	Texture2DHandle *m_defaultTextureHandles[DefaultTextureType::DefaultTextureType_NumOfTypes];
 };
 
 class TextureCubemap : public LoaderBase<TextureLoaderCubemap, TextureCubemap>::UniqueObject
@@ -781,4 +806,9 @@ public:
 	TextureCubemapHandle load(const std::string (&p_filenames)[CubemapFace_NumOfFaces], bool p_startBackgroundLoading = true);
 
 	TextureCubemapHandle load(const std::string &p_filename, unsigned int p_textureHandle);
+
+protected:
+	void unload(TextureCubemap &p_object)
+	{
+	}
 };

+ 79 - 10
Praxis3D/Source/WorldScene.cpp

@@ -250,8 +250,8 @@ EntityID WorldScene::createEntity(const ComponentsConstructionInfo &p_constructi
 		// Add entity to the registry and assign its entity ID
 		newEntity = addEntity(p_constructionInfo.m_id);
 
-		// Log an error if the desired ID couldn't be assigned
-		if(p_constructionInfo.m_id != newEntity)
+		// Log an error if the desired ID couldn't be assigned, unless desired ID was 0
+		if(p_constructionInfo.m_id != 0 && p_constructionInfo.m_id != newEntity)
 			ErrHandlerLoc::get().log(ErrorCode::Duplicate_object_id, ErrorSource::Source_WorldScene, p_constructionInfo.m_name + " - Entity ID \'" + Utilities::toString(p_constructionInfo.m_id) + "\' is already taken. Replaced with: \'" + Utilities::toString(newEntity) + "\'");
 	}
 	else // Do not request a specific entity ID if the requested ID is null
@@ -465,14 +465,79 @@ void WorldScene::receiveData(const DataType p_dataType, void *p_data, const bool
 {
 	switch(p_dataType)
 	{
+		case DataType::DataType_CreateComponent:
+			{
+				// Get entity and component data
+				auto const *componentConstructionInfo = static_cast<ComponentsConstructionInfo *>(p_data);
+
+				auto addComponentError = addComponents(componentConstructionInfo->m_id, *componentConstructionInfo);
+
+				if(addComponentError == ErrorCode::Success)
+					ErrHandlerLoc::get().log(addComponentError, "EntityID: " + Utilities::toString(componentConstructionInfo->m_id), ErrorSource::Source_WorldScene);
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentConstructionInfo;
+			}
+			break;
+
 		case DataType::DataType_DeleteComponent:
 			{
-				// Get the world scene required for getting the entity registry and deleting components
-				WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+				// Get entity and component data
+				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
+
+				// Delete the component based on its type
+				switch(componentData->m_componentType)
+				{
+					case ComponentType::ComponentType_ObjectMaterialComponent:
+						{
+							// Check if the component exists
+							auto *component = m_entityRegistry.try_get<ObjectMaterialComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								removeComponent<ObjectMaterialComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+
+					case ComponentType::ComponentType_SpatialComponent:
+						{
+							// Check if the component exists
+							auto *component = m_entityRegistry.try_get<SpatialComponent>(componentData->m_entityID);
+							if(component != nullptr)
+							{
+								// Delete component
+								removeComponent<SpatialComponent>(componentData->m_entityID);
+							}
+						}
+						break;
+				}
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentData;
+			}
+			break;
+
+		case DataType::DataType_CreateEntity:
+			{
+				// Get entity and component data
+				auto const *componentConstructionInfo = static_cast<ComponentsConstructionInfo *>(p_data);
 
-				// Get the entity registry 
-				auto &entityRegistry = worldScene->getEntityRegistry();
+				auto entityID = createEntity(*componentConstructionInfo);
 
+				//if(addComponentError == ErrorCode::Success)
+				//	ErrHandlerLoc::get().log(addComponentError, "EntityID: " + Utilities::toString(componentConstructionInfo->m_id), ErrorSource::Source_WorldScene);
+
+				// Delete the sent data if the ownership of it was transfered
+				if(p_deleteAfterReceiving)
+					delete componentConstructionInfo;
+			}
+			break;
+
+		case DataType::DataType_DeleteEntity:
+			{
 				// Get entity and component data
 				auto const *componentData = static_cast<EntityAndComponent *>(p_data);
 
@@ -482,11 +547,11 @@ void WorldScene::receiveData(const DataType p_dataType, void *p_data, const bool
 					case ComponentType::ComponentType_ObjectMaterialComponent:
 						{
 							// Check if the component exists
-							auto *component = entityRegistry.try_get<ObjectMaterialComponent>(componentData->m_entityID);
+							auto *component = m_entityRegistry.try_get<ObjectMaterialComponent>(componentData->m_entityID);
 							if(component != nullptr)
 							{
 								// Delete component
-								worldScene->removeComponent<ObjectMaterialComponent>(componentData->m_entityID);
+								removeComponent<ObjectMaterialComponent>(componentData->m_entityID);
 							}
 						}
 						break;
@@ -494,11 +559,11 @@ void WorldScene::receiveData(const DataType p_dataType, void *p_data, const bool
 					case ComponentType::ComponentType_SpatialComponent:
 						{
 							// Check if the component exists
-							auto *component = entityRegistry.try_get<SpatialComponent>(componentData->m_entityID);
+							auto *component = m_entityRegistry.try_get<SpatialComponent>(componentData->m_entityID);
 							if(component != nullptr)
 							{
 								// Delete component
-								worldScene->removeComponent<SpatialComponent>(componentData->m_entityID);
+								removeComponent<SpatialComponent>(componentData->m_entityID);
 							}
 						}
 						break;
@@ -509,5 +574,9 @@ void WorldScene::receiveData(const DataType p_dataType, void *p_data, const bool
 					delete componentData;
 			}
 			break;
+
+		default:
+			assert(p_deleteAfterReceiving == true && "Memory leak - unhandled orphaned void data pointer in receiveData");
+			break;
 	}
 }

+ 47 - 2
Praxis3D/imgui.ini

@@ -4,7 +4,7 @@ Size=400,400
 Collapsed=0
 
 [Window][Dear ImGui Demo]
-Pos=49,544
+Pos=26,644
 Size=461,545
 Collapsed=1
 
@@ -128,7 +128,7 @@ Size=666,477
 Collapsed=0
 
 [Window][Dear ImGui Metrics/Debugger]
-Pos=560,105
+Pos=537,149
 Size=608,708
 Collapsed=0
 
@@ -212,6 +212,11 @@ Pos=145,151
 Size=855,618
 Collapsed=0
 
+[Window][##AddEntityPopup]
+Pos=720,419
+Size=449,275
+Collapsed=0
+
 [Table][0x9A4BDFDE,4]
 RefScale=13
 Column 0  Sort=0v
@@ -864,6 +869,46 @@ Column 0  Sort=0v
 RefScale=13
 Column 0  Sort=0v
 
+[Table][0x860DB733,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xB10C23B9,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x5849EA0F,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x479D9FEC,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xAED8565A,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x97126F26,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x7E57A690,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xA323D497,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0xBABB2B2C,4]
+RefScale=13
+Column 0  Sort=0v
+
+[Table][0x53FEE29A,4]
+RefScale=13
+Column 0  Sort=0v
+
 [Docking][Data]
 DockSpace         ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,69 Size=1920,1011 Split=X
   DockNode        ID=0x00000007 Parent=0x8B93E3BD SizeRef=1409,1011 Split=Y