From c963ab328c273c164d20b09818dcdf4570875b19 Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Thu, 4 Aug 2022 12:56:30 +0100
Subject: [PATCH] add hexboard-style inheritance to prototypes

---
 demo/data/rollball.yml                | 25 ++++----
 fggl/entity/loader/loader.cpp         |  5 +-
 include/fggl/entity/loader/loader.hpp | 83 ++++++++++++++++++---------
 include/fggl/entity/loader/spec.hpp   |  9 ++-
 4 files changed, 78 insertions(+), 44 deletions(-)

diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml
index ea445d5..bdf74de 100644
--- a/demo/data/rollball.yml
+++ b/demo/data/rollball.yml
@@ -1,6 +1,14 @@
 ---
 prefabs:
+  - name: "environment"
+    components:
+      gfx::material:
+        ambient: [0.0215, 0.1754, 0.0215]
+        diffuse: [1, 1, 1]
+        specular: [0.0633, 0.727811, 0.633]
+        shininess: 16
   - name: "wallX"
+    parent: "environment"
     components:
       Transform:
       StaticMesh:
@@ -8,11 +16,6 @@ prefabs:
         shape:
           type: box
           scale: [1.0, 5.0, 41]
-      gfx::material:
-        ambient: [0.0215, 0.1754, 0.0215]
-        diffuse: [1, 1, 1]
-        specular: [0.0633, 0.727811, 0.633]
-        shininess: 16
       phys::Body:
         type: static
         shape:
@@ -20,6 +23,7 @@ prefabs:
           extents: [0.5, 2.5, 20.5]
   # Wall Z shorter to avoid z-fighting
   - name: "wallZ"
+    parent: "environment"
     components:
       Transform:
       StaticMesh:
@@ -27,17 +31,13 @@ prefabs:
         shape:
           type: box
           scale: [39, 5, 1]
-      gfx::material:
-        ambient: [0.0215, 0.1754, 0.0215]
-        diffuse: [1, 1, 1]
-        specular: [0.0633, 0.727811, 0.633]
-        shininess: 16
       phys::Body:
         type: static
         shape:
           type: box
           extents: [ 19.5, 2.5, 0.5 ]
   - name: "floor"
+    parent: "environment"
     components:
       Transform:
       StaticMesh:
@@ -45,11 +45,6 @@ prefabs:
         shape:
           type: box # we don't (currently) support planes...
           scale: [39, 0.5, 39]
-      gfx::material:
-        ambient: [0.0215, 0.1754, 0.0215]
-        diffuse: [1, 1, 1]
-        specular: [0.0633, 0.727811, 0.633]
-        shininess: 16
       phys::Body:
         type: static
         shape:
diff --git a/fggl/entity/loader/loader.cpp b/fggl/entity/loader/loader.cpp
index fb29fa8..362e1cc 100644
--- a/fggl/entity/loader/loader.cpp
+++ b/fggl/entity/loader/loader.cpp
@@ -28,6 +28,7 @@ namespace fggl::entity {
 		auto nodes = YAML::LoadAllFromFile( filePath->c_str() );
 		for (const auto& node : nodes) {
 			auto prefabs = node["prefabs"];
+
 			for ( const auto& prefab : prefabs ) {
 				auto name = prefab["name"].as<fggl::util::GUID>();
 
@@ -35,8 +36,10 @@ namespace fggl::entity {
 					debug::info("found prefab: {}", fggl::util::guidToString(name) );
 				#endif
 
-				// setup the components
+				// set up the components
 				EntitySpec entity{};
+				entity.parent = prefab["parent"].as<fggl::util::GUID>(NO_PARENT);
+
 				for (const auto& compEntry : prefab["components"]) {
 					auto compId = compEntry.first.as<fggl::util::GUID>();
 
diff --git a/include/fggl/entity/loader/loader.hpp b/include/fggl/entity/loader/loader.hpp
index 8fc47fa..598ee62 100644
--- a/include/fggl/entity/loader/loader.hpp
+++ b/include/fggl/entity/loader/loader.hpp
@@ -33,12 +33,8 @@ namespace fggl::entity {
 
 	constexpr auto PROTOTYPE_ASSET = assets::AssetType::make("entity_prototype");
 	using FactoryFunc = std::function<void(const ComponentSpec& config, EntityManager&, const EntityID&)>;
-
 	using CustomiseFunc = std::function<void(EntityManager&, const EntityID&)>;
 
-	using ComponentID = util::GUID;
-	using EntityType = util::GUID;
-
 	struct FactoryInfo {
 		FactoryFunc factory;
 		CustomiseFunc finalise = nullptr;
@@ -50,36 +46,21 @@ namespace fggl::entity {
 
 			EntityID create(const EntityType& spec, EntityManager& manager, const CustomiseFunc& customise = nullptr) {
 				try {
-					auto &prototype = m_prototypes.at(spec);
-
 					std::vector<CustomiseFunc> finishers;
 
-					// invoke each component factory as required
-					auto entity = manager.create();
-					for ( auto& component : prototype.ordering ) {
-						try {
-							auto& data = prototype.components.at(component);
-
-							auto& info = m_factories.at(component);
-							info.factory(data, manager, entity);
-
-							if ( info.finalise != nullptr ) {
-								finishers.push_back(info.finalise);
-							}
-						} catch (std::out_of_range& ex) {
-							#ifndef NDEBUG
-								debug::log(debug::Level::error, "EntityFactory: Unknown component factory type '{}'", fggl::util::guidToString(component));
-							#endif
-							manager.destroy(entity);
-							return fggl::entity::INVALID;
-						}
+					// set up the components for the entity
+					auto entity = setupComponents(spec, manager, finishers);
+					if ( entity == entity::INVALID ) {
+						debug::error("Error attempting to create entity with type {}", std::to_string(spec.get()) );
+						return entity::INVALID;
 					}
 
+					// allow the caller to perform any setup needed
 					if (customise != nullptr) {
 						customise(manager, entity);
 					}
 
-					// finalise the entities
+					// finally, we run any cleanup/init setups required by the component factories
 					for( auto& finisher : finishers ) {
 						finisher(manager, entity);
 					}
@@ -110,6 +91,56 @@ namespace fggl::entity {
 		private:
 			std::map<ComponentID, FactoryInfo> m_factories;
 			std::map<EntityType, EntitySpec> m_prototypes;
+
+			entity::EntityID setupComponents(EntityType entityType, EntityManager& manager, std::vector<CustomiseFunc>& finishers) {
+				auto entity = manager.create();
+				std::vector<ComponentID> loadedComps;
+
+				auto currentType = entityType;
+				while ( currentType != NO_PARENT ) {
+					auto& entitySpec = m_prototypes.at(currentType);
+
+					for ( auto& component : entitySpec.ordering ) {
+						// skip comps loaded by children
+						if (std::find(loadedComps.begin(), loadedComps.end(), component) != loadedComps.end()) {
+							continue;
+						}
+
+						try {
+							auto& data = getComponent(entitySpec, component);
+							loadedComps.push_back(component);
+
+							auto& info = m_factories.at(component);
+							info.factory(data, manager, entity);
+
+							if ( info.finalise != nullptr ) {
+								finishers.push_back(info.finalise);
+							}
+						} catch (std::out_of_range& ex) {
+							#ifndef NDEBUG
+								debug::log(debug::Level::error, "EntityFactory: Unknown component factory type '{}'", fggl::util::guidToString(component));
+							#endif
+							manager.destroy(entity);
+							return entity::INVALID;
+						}
+					}
+					currentType = entitySpec.parent;
+				}
+
+				return entity;
+			}
+
+			ComponentSpec& getComponent(EntitySpec& prototype, util::GUID compToken) {
+				auto compItr = prototype.components.find(compToken);
+				if ( compItr != prototype.components.end() ) {
+					return compItr->second;
+				}
+
+				if ( prototype.parent == NO_PARENT ) {
+					throw std::out_of_range("EntityFactory: no such component!");
+				}
+				return getComponent(m_prototypes.at(prototype.parent), compToken);
+			}
 	};
 
 	assets::AssetRefRaw load_prototype(EntityFactory* factory, const assets::AssetGUID& guid, assets::AssetData data);
diff --git a/include/fggl/entity/loader/spec.hpp b/include/fggl/entity/loader/spec.hpp
index c9ef555..b5f50ee 100644
--- a/include/fggl/entity/loader/spec.hpp
+++ b/include/fggl/entity/loader/spec.hpp
@@ -26,6 +26,10 @@
 
 namespace fggl::entity {
 
+	using ComponentID = util::GUID;
+	using EntityType = util::GUID;
+	constexpr EntityType NO_PARENT = util::make_guid("FGGL_NULL_PARENT");
+
 	struct ComponentSpec {
 
 		template<typename T>
@@ -46,8 +50,9 @@ namespace fggl::entity {
 	};
 
 	struct EntitySpec {
-		std::vector<util::GUID> ordering;
-		std::map<util::GUID, ComponentSpec> components;
+		EntityType parent = NO_PARENT;
+		std::vector<ComponentID> ordering;
+		std::map<ComponentID, ComponentSpec> components;
 	};
 
 } // namespace fggl::entity
-- 
GitLab