From ec8ec5bbb498bb52a35bb7454c6f6ad559f8ed79 Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Wed, 29 Mar 2023 11:28:04 -0700
Subject: [PATCH] Working on Prefab Parser updates

---
 src/dawn/prefab/SceneItemPrefab.hpp           |   1 -
 .../physics/2d/CharacterController2D.hpp      |   2 +-
 src/dawnrose/CMakeLists.txt                   |   2 +-
 src/dawnrose/prefabs/PlayerPrefab_old.hpp     |  52 ----
 src/dawnrose/scenes/HelloWorldScene.hpp       |  17 +-
 src/dawnshared/util/string.hpp                |  63 +++++
 src/dawntools/prefabtool/CMakeLists.txt       |   3 +
 .../prefabtool/PrefabChildParser.hpp          |  10 +
 .../prefabtool/PrefabComponentParser.cpp      | 132 ++++++++++
 .../prefabtool/PrefabComponentParser.hpp      |  30 +++
 src/dawntools/prefabtool/PrefabParser.cpp     |  44 ++++
 src/dawntools/prefabtool/PrefabParser.hpp     |  28 +++
 src/dawntools/prefabtool/PrefabRegistry.cpp   | 115 +++++++++
 src/dawntools/prefabtool/PrefabRegistry.hpp   |  32 +++
 src/dawntools/prefabtool/PrefabTool.cpp       | 232 +-----------------
 src/dawntools/prefabtool/PrefabTool.hpp       |  63 +----
 16 files changed, 470 insertions(+), 356 deletions(-)
 delete mode 100644 src/dawnrose/prefabs/PlayerPrefab_old.hpp
 create mode 100644 src/dawntools/prefabtool/PrefabChildParser.hpp
 create mode 100644 src/dawntools/prefabtool/PrefabComponentParser.cpp
 create mode 100644 src/dawntools/prefabtool/PrefabComponentParser.hpp
 create mode 100644 src/dawntools/prefabtool/PrefabParser.cpp
 create mode 100644 src/dawntools/prefabtool/PrefabParser.hpp
 create mode 100644 src/dawntools/prefabtool/PrefabRegistry.cpp
 create mode 100644 src/dawntools/prefabtool/PrefabRegistry.hpp

diff --git a/src/dawn/prefab/SceneItemPrefab.hpp b/src/dawn/prefab/SceneItemPrefab.hpp
index 3b3b6c04..6fc24048 100644
--- a/src/dawn/prefab/SceneItemPrefab.hpp
+++ b/src/dawn/prefab/SceneItemPrefab.hpp
@@ -35,7 +35,6 @@ namespace Dawn {
       SceneItemPrefab(Scene *scene, sceneitemid_t id) :
         SceneItem(scene, id)
       {
-
       }
 
       /**
diff --git a/src/dawn/scene/components/physics/2d/CharacterController2D.hpp b/src/dawn/scene/components/physics/2d/CharacterController2D.hpp
index 7b9e9a06..04d5bbd4 100644
--- a/src/dawn/scene/components/physics/2d/CharacterController2D.hpp
+++ b/src/dawn/scene/components/physics/2d/CharacterController2D.hpp
@@ -13,7 +13,7 @@ namespace Dawn {
       
     public:
       // @optional
-      glm::vec2 velocity;
+      glm::vec2 velocity = glm::vec2(0, 0);
       // @optional
       float_t friction = 12.0f;
       
diff --git a/src/dawnrose/CMakeLists.txt b/src/dawnrose/CMakeLists.txt
index cb218c1f..a103937e 100644
--- a/src/dawnrose/CMakeLists.txt
+++ b/src/dawnrose/CMakeLists.txt
@@ -19,4 +19,4 @@ add_subdirectory(scene)
 
 # Assets
 set(ROSE_ASSETS_DIR ${DAWN_ASSETS_DIR}/games/rose)
-tool_prefab(${ROSE_ASSETS_DIR}/prefabs/PlayerPrefab.xml)
\ No newline at end of file
+tool_prefab(${ROSE_ASSETS_DIR}/prefabs/Player.xml)
\ No newline at end of file
diff --git a/src/dawnrose/prefabs/PlayerPrefab_old.hpp b/src/dawnrose/prefabs/PlayerPrefab_old.hpp
deleted file mode 100644
index 088d1f71..00000000
--- a/src/dawnrose/prefabs/PlayerPrefab_old.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2023 Dominic Masters
-// 
-// This software is released under the MIT License.
-// https://opensource.org/licenses/MIT
-
-#pragma once
-#include "prefab/SceneItemPrefab.hpp"
-#include "scene/components/display/MeshHost.hpp"
-#include "scene/components/display/MeshRenderer.hpp"
-#include "scene/components/display/material/SimpleTexturedMaterial.hpp"
-#include "scene/components/physics/2d/CharacterController2D.hpp"
-#include "scene/components/physics/2d/BoxCollider.hpp"
-#include "scene/components/PlayerController.hpp"
-#include "display/mesh/CubeMesh.hpp"
-#include "display/mesh/CapsuleMesh.hpp"
-
-namespace Dawn {
-  class PlayerPrefab : public SceneItemPrefab<PlayerPrefab>, public StateOwner {
-    public:
-      static std::vector<Asset*> prefabAssets(AssetManager *man) {
-        return {};
-      }
-
-      PlayerController *player;
-
-      PlayerPrefab(Scene *s, sceneitemid_t i) : SceneItemPrefab(s, i) {}
-
-      void prefabInit(AssetManager *man) override {
-        auto characterController = this->addComponent<CharacterController2D>();
-        auto hitbox = this->addComponent<BoxCollider>();
-        auto meshHost = this->addComponent<MeshHost>();
-        auto meshRenderer = this->addComponent<MeshRenderer>();
-        auto material = this->addComponent<SimpleTexturedMaterial>();
-        
-        CapsuleMesh::create(&meshHost->mesh, 0.5f, 1.5f);
-
-        auto nose = scene->createSceneItem();
-        auto noseMeshHost = nose->addComponent<MeshHost>();
-        auto noseMeshRenderer = nose->addComponent<MeshRenderer>();
-        auto noseMaterial = nose->addComponent<SimpleTexturedMaterial>();
-        noseMeshHost->mesh.createBuffers(CUBE_VERTICE_COUNT, CUBE_INDICE_COUNT);
-        glm::vec3 noseSize = glm::vec3(0.25f, 0.25f, 0.25f);
-        CubeMesh::buffer(&noseMeshHost->mesh, -(noseSize * 0.5f), noseSize, 0, 0);
-        noseMaterial->color = COLOR_RED;
-        nose->transform.setParent(&this->transform);
-        nose->transform.setLocalPosition(glm::vec3(0, 0, 0.5f));
-
-
-        player = this->addComponent<PlayerController>();
-      }
-  };
-}
\ No newline at end of file
diff --git a/src/dawnrose/scenes/HelloWorldScene.hpp b/src/dawnrose/scenes/HelloWorldScene.hpp
index 690a7665..c4a4383c 100644
--- a/src/dawnrose/scenes/HelloWorldScene.hpp
+++ b/src/dawnrose/scenes/HelloWorldScene.hpp
@@ -6,8 +6,11 @@
 #pragma once
 #include "scene/Scene.hpp"
 #include "scene/components/GameCamera.hpp"
-#include "prefabs/PlayerPrefab.hpp"
+#include "prefabs/Player.hpp"
+#include "prefabs/SpinningCube.hpp"
 #include "prefabs/ui/debug/FPSLabel.hpp"
+#include "display/mesh/CapsuleMesh.hpp"
+#include "display/mesh/CubeMesh.hpp"
 
 namespace Dawn {
   class HelloWorldScene : public Scene {
@@ -16,16 +19,14 @@ namespace Dawn {
       UICanvas *canvas;
 
       void stage() override {
-        auto player = PlayerPrefab::create(this);
+
+        auto player = Player::create(this);
+        CapsuleMesh::create(&player->meshHost->mesh, 0.5f, 1.5f);
+        player->meshHost->mesh.createBuffers(CUBE_VERTICE_COUNT, CUBE_INDICE_COUNT);
+        CubeMesh::buffer(&player->meshHost->mesh, glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3(1, 1, 1), 0, 0);
 
         canvas = UICanvas::create(this);
 
-        auto labelItem = FPSLabel::create(this);
-        labelItem->transform.setParent(canvas->transform);
-        labelItem->label->alignX = UI_COMPONENT_ALIGN_END;
-        labelItem->label->alignment = glm::vec4(0, 0, 0, 0);
-        labelItem->label->fontSize = 16;
-
         auto wallBox = this->createSceneItem()->addComponent<BoxCollider>();
         wallBox->min = glm::vec2(-4, -3);
         wallBox->max = glm::vec2(-3, 3);
diff --git a/src/dawnshared/util/string.hpp b/src/dawnshared/util/string.hpp
index 99f26ada..9894927b 100644
--- a/src/dawnshared/util/string.hpp
+++ b/src/dawnshared/util/string.hpp
@@ -32,4 +32,67 @@ static inline char * stringFindNext(
   }
 
   return NULL;
+}
+
+/**
+ * Splits a string into a vector of strings, using a delimiter.
+ * 
+ * @param s String to split.
+ * @param delim Delimiter to split by.
+ * @return Vector of strings.
+ */
+static inline std::vector<std::string> stringSplit(
+  const std::string &s,
+  const std::string delim
+) {
+  size_t posStart = 0, posEnd, delimLength = delim.length();
+  std::string token;
+  std::vector<std::string> res;
+
+  while((posEnd = s.find(delim, posStart)) != std::string::npos) {
+    token = s.substr(posStart, posEnd - posStart);
+    posStart = posEnd + delimLength;
+    res.push_back (token);
+  }
+
+  res.push_back(s.substr(posStart));
+  return res;
+}
+
+/**
+ * Trims the whitespace from the left side of a string.
+ * 
+ * @param i Input string to trim.
+ * @return Trimmed string.
+ */
+static inline std::string stringLTrim(const std::string &i) {
+  std::string s = i;
+  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
+    return !std::isspace(ch);
+  }));
+  return s;
+}
+
+/**
+ * Trims the whitespace from the right side of a string.
+ * 
+ * @param i Input string to trim.
+ * @return Trimmed string.
+ */
+static inline std::string stringRTrim(const std::string &i) {
+  std::string s = i;
+  s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
+    return !std::isspace(ch);
+  }).base(), s.end());
+  return s;
+}
+
+/**
+ * Trims the whitespace from both sides of a string.
+ * 
+ * @param s Input string to trim.
+ * @return Trimmed string.
+ */
+static inline std::string stringTrim(const std::string &s) {
+  return stringLTrim(stringRTrim(s));
 }
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/CMakeLists.txt b/src/dawntools/prefabtool/CMakeLists.txt
index ee025b7d..b51ece00 100644
--- a/src/dawntools/prefabtool/CMakeLists.txt
+++ b/src/dawntools/prefabtool/CMakeLists.txt
@@ -13,6 +13,9 @@ target_sources(prefabtool
     ${DAWN_SHARED_SOURCES}
     ${DAWN_TOOL_SOURCES}
     PrefabTool.cpp
+    PrefabParser.cpp
+    PrefabRegistry.cpp
+    PrefabComponentParser.cpp
 )
 
 # Includes
diff --git a/src/dawntools/prefabtool/PrefabChildParser.hpp b/src/dawntools/prefabtool/PrefabChildParser.hpp
new file mode 100644
index 00000000..c346c8ba
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabChildParser.hpp
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "PrefabParser.hpp"
+
+namespace Dawn {
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabComponentParser.cpp b/src/dawntools/prefabtool/PrefabComponentParser.cpp
new file mode 100644
index 00000000..657b5108
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabComponentParser.cpp
@@ -0,0 +1,132 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "PrefabComponentParser.hpp"
+
+using namespace Dawn;
+
+std::vector<std::string> PrefabComponentParser::getRequiredAttributes() {
+  return { };
+}
+
+std::map<std::string, std::string> PrefabComponentParser::getOptionalAttributes() {
+  return { { "ref", "" } };
+}
+
+int32_t PrefabComponentParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct PrefabComponent *out,
+  std::string *error
+) {
+  // Check if values contains key "ref" and has a string of size more than 0
+  if(values.find("ref") != values.end() && values["ref"].size() > 0) {
+    out->ref = values["ref"];
+  }
+
+  // Get the ruleset.
+  auto ruleset = out->registry->getRuleset(node->node);
+  if(ruleset.name.size() == 0) {
+    *error =  "Unknown prefab node type: " + node->node;
+    return 1;
+  }
+
+  out->include = ruleset.include;
+  out->type = node->node;
+
+  // Define parser types here.
+  auto rawParser = [&](std::string v){
+    return v;
+  };
+
+  auto stringParser = [&](std::string v){
+    return "\"" + v + "\"";
+  };
+
+  auto floatParser = [&](std::string v){
+    v = stringTrim(v);
+
+    // Make sure number contains decimal
+    if(v.find(".") == std::string::npos) {
+      v += ".0";
+    }
+    // Make sure number contains a number before the decimal
+    if(v.find(".") == 0) {
+      v = "0" + v;
+    }
+    // Make sure ends with f
+    if(v.find("f") == std::string::npos) {
+      v += "f";
+    }
+    return v;
+  };
+
+  auto vec2Parser = [&](std::string v) {
+    // Split string by comma into two strings that we pass into float
+    auto split = stringSplit(v, ",");
+    if(split.size() != 2) {
+      *error = "Invalid vec2 value: " + v;
+      return std::string("");
+    }
+    return std::string(
+      "glm::vec2(" + floatParser(split[0]) + ", " + floatParser(split[1]) + ")"
+    );
+  };
+
+
+  auto vec3Parser = [&](std::string v) {
+    // Split string by comma into two strings that we pass into float
+    auto split = stringSplit(v, ",");
+    if(split.size() != 3) {
+      *error = "Invalid vec3 value: " + v;
+      return std::string("");
+    }
+    return std::string(
+      "glm::vec3(" + floatParser(split[0]) + ", " + floatParser(split[1]) + ", " + floatParser(split[2]) + ")"
+    );
+  };
+
+  auto colorParser = rawParser;
+
+  // Iterate all the optional attributes and store within the out if present
+  auto itOptional = ruleset.optional.begin();
+  while(itOptional != ruleset.optional.end()) {
+    // Determine the parser to use
+    auto name = itOptional->first;
+    auto type = itOptional->second;
+
+    std::function<std::string(std::string)> parser = rawParser;
+    if(type.find("string") != std::string::npos) {
+      parser = stringParser;
+    } else if(type.find("float") != std::string::npos) {
+      parser = floatParser;
+    } else if(type.find("Color") != std::string::npos) {
+      parser = colorParser;
+    } else if(type.find("vec2") != std::string::npos) {
+      parser = vec2Parser;
+    } else if(type.find("vec3") != std::string::npos) {
+      parser = vec3Parser;
+    } else if(type.find("*") == (type.size() - 1)) {
+      type = type.substr(0, type.size() - 1);
+      parser = rawParser;
+    } else if(name[0] == '*') {
+      name = name.substr(1, name.size());
+      parser = rawParser;
+    } else {
+      *error = "Unkown parser for type: " + type + "::" + name;
+      return 1;
+    }
+
+    if(node->attributes.find(name) != node->attributes.end()) {
+      auto raw = node->attributes[name];
+      out->values[name] = parser(raw);
+      if(error->size() != 0) return 1;
+    }
+    ++itOptional;
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabComponentParser.hpp b/src/dawntools/prefabtool/PrefabComponentParser.hpp
new file mode 100644
index 00000000..e4f06020
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabComponentParser.hpp
@@ -0,0 +1,30 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "PrefabRegistry.hpp"
+
+namespace Dawn {
+  struct PrefabComponent {
+    struct PrefabRegistry *registry;
+
+    std::string include;
+    std::string type;
+    std::map<std::string, std::string> values;
+    std::string ref;
+  };
+
+  class PrefabComponentParser : public XmlParser<struct PrefabComponent> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct PrefabComponent *out,
+        std::string *error
+      ) override;
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabParser.cpp b/src/dawntools/prefabtool/PrefabParser.cpp
new file mode 100644
index 00000000..c659edb1
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabParser.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "PrefabParser.hpp"
+
+using namespace Dawn;
+
+std::vector<std::string> PrefabParser::getRequiredAttributes() {
+  return { "name", "type" };
+}
+
+std::map<std::string, std::string> PrefabParser::getOptionalAttributes() {
+  return { };
+}
+
+int32_t PrefabParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct Prefab *out,
+  std::string *error
+) {
+  out->name = values["name"];
+  out->type = values["type"];
+
+  auto itChildren = node->children.begin();
+  while(itChildren != node->children.end()) {
+    // Parse child nodes, they may be components or not
+    auto c = *itChildren;
+    if(c->node == "child") {
+      std::cout << "Child detected" << std::endl;
+    } else {
+      struct PrefabComponent component;
+      component.registry = out->registry;
+      auto ret = (PrefabComponentParser()).parse(*itChildren, &component, error);
+      if(ret != 0) return ret;
+      out->components.push_back(component);
+    }
+    ++itChildren;
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabParser.hpp b/src/dawntools/prefabtool/PrefabParser.hpp
new file mode 100644
index 00000000..e39cecc5
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabParser.hpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "PrefabComponentParser.hpp"
+
+namespace Dawn {
+  struct Prefab {
+    struct PrefabRegistry *registry;
+    std::string name;
+    std::string type;
+    std::vector<struct PrefabComponent> components;
+  };
+  
+  class PrefabParser : public XmlParser<struct Prefab> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct Prefab *out,
+        std::string *error
+      ) override;
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabRegistry.cpp b/src/dawntools/prefabtool/PrefabRegistry.cpp
new file mode 100644
index 00000000..950410f1
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabRegistry.cpp
@@ -0,0 +1,115 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "PrefabRegistry.hpp"
+
+using namespace Dawn;
+
+struct PrefabComponentParserRuleset PrefabRegistry::getRuleset(std::string type) {
+  std::vector<std::string> pathsToScan;
+  pathsToScan.push_back(this->sources);
+
+  auto it = pathsToScan.begin();
+  while(it != pathsToScan.end()) {
+    auto str = *it;
+    pathsToScan.erase(it);
+
+    Directory dir = Directory(str);
+    auto children = dir.readDirectory();
+    
+    auto itChildren = children.begin();
+    while(itChildren != children.end()) {
+      if(itChildren->second == DIRECTORY_CHILD_TYPE_DIRECTORY) {
+        pathsToScan.push_back(str + "/" + itChildren->first);
+        ++itChildren;
+        continue;
+      }
+        
+      if(itChildren->first != type+".hpp") {
+        ++itChildren;
+        continue;
+      }
+
+      // Load ruleset
+      std::string data;
+      File file(str + "/" + itChildren->first);
+      if(!file.readString(&data)) {
+        std::cout << "Failed to read ruleset file!" << std::endl;
+        return { .name = "" };
+      }
+
+      // Begin scanning contentsr
+      // std::string include = this->sources;
+      auto toRemove = File::normalizeSlashes(this->sources + FILE_PATH_SEP);
+      auto includePath = file.filename.substr(toRemove.size(), file.filename.size() - toRemove.size());
+
+      // Now locate the first subdir since we don't want to include the root path (e.g. dawn, dawnrose, etc)
+      auto firstSlash = includePath.find(FILE_PATH_SEP);
+      if(firstSlash != std::string::npos) {
+        includePath = includePath.substr(firstSlash + 1, includePath.size() - firstSlash - 1);
+      }
+      
+      struct PrefabComponentParserRuleset ruleset;
+      // Replace all file seps with slashes
+      size_t pos = 0;
+      while ((pos = includePath.find(FILE_PATH_SEP, pos)) != std::string::npos) {
+        includePath.replace(pos, 1, "/");
+        pos += 1;
+      }
+      ruleset.include = includePath;
+      ruleset.name = type;
+
+      // Find each instance of "@optional" when it's used within a comment
+      // e.g. // @optional or /* @optional */ in the string data.
+      std::regex regex("^\\s*(?:\\/\\/|\\/\\*){1}\\s*\\@optional\\s*(?:\\*\\/)?\\s*$", std::regex_constants::ECMAScript);
+      std::sregex_iterator it(data.begin(), data.end(), regex);
+      std::sregex_iterator end;
+      while(it != end) {
+        // Find the next ";"
+        auto endPos = data.find(";", it->position() + it->length());
+        if(endPos == std::string::npos) {
+          std::cout << "Failed to find end of line for optional attribute!" << std::endl;
+          return { .name = "" };
+        }
+
+        // Go backwards see if there's an equals sign after the match but before endPos
+        auto equalsPos = data.rfind("=", endPos);
+
+        // If there's an equals sign, we search backwards from there,
+        // otherwise we search backwards from the ;
+        auto varStart = it->position() + it->length() + 1;
+        size_t lastSearchPos = (
+          (equalsPos == std::string::npos || equalsPos <= varStart) ?
+          endPos :
+          equalsPos
+        );
+        
+        // Now we have our string
+        auto varLength = lastSearchPos - varStart;
+        auto variableString = data.substr(varStart, varLength);
+
+        // Now (should) be able to extract the type;
+        std::regex regex2("^\\s*(?:[\\S]+<)?([\\w*:_\\s]+)(?:[\\S]+)? (\\**[\\w]+)\\s*$", std::regex_constants::ECMAScript);
+        std::smatch match;
+        if(!std::regex_search(variableString, match, regex2)) {
+          std::cout << "Failed to extract type and name from variable string! " << variableString << std::endl;
+          return { .name = "" };
+        }
+
+        // Now we have our type and name
+        auto type = match[1].str();
+        auto name = match[2].str();
+        ruleset.optional[name] = type;
+        ++it;
+      }
+      return ruleset;
+      ++itChildren;
+    }
+    
+    it = pathsToScan.begin();
+  };
+
+  return { .name = "" };
+}
diff --git a/src/dawntools/prefabtool/PrefabRegistry.hpp b/src/dawntools/prefabtool/PrefabRegistry.hpp
new file mode 100644
index 00000000..1e606a18
--- /dev/null
+++ b/src/dawntools/prefabtool/PrefabRegistry.hpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "util/DawnTool.hpp"
+#include "util/Directory.hpp"
+#include "util/XmlParser.hpp"
+#include "util/CodeGen.hpp"
+#include "util/string.hpp"
+#include <regex>
+
+namespace Dawn {
+  struct PrefabComponentParserRuleset {
+    std::string name;
+    std::string include;
+    std::map<std::string, std::string> optional;
+  };
+
+  struct PrefabRegistry {
+    std::string sources;
+    
+    /**
+     * Gets a prefab component ruleset for a specific scene item component type.
+     * 
+     * @param type Scene Item Component Type to get ruleset for.
+     * @return The found ruleset, or an empty ruleset if not found.
+     */
+    struct PrefabComponentParserRuleset getRuleset(std::string type);
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/prefabtool/PrefabTool.cpp b/src/dawntools/prefabtool/PrefabTool.cpp
index 769f90af..c6ad8e8a 100644
--- a/src/dawntools/prefabtool/PrefabTool.cpp
+++ b/src/dawntools/prefabtool/PrefabTool.cpp
@@ -7,109 +7,6 @@
 
 using namespace Dawn;
 
-
-struct PrefabComponentParserRuleset PrefabRegistry::getRuleset(std::string type) {
-  std::vector<std::string> pathsToScan;
-  pathsToScan.push_back(this->sources);
-
-  auto it = pathsToScan.begin();
-  while(it != pathsToScan.end()) {
-    auto str = *it;
-    pathsToScan.erase(it);
-
-    Directory dir = Directory(str);
-    auto children = dir.readDirectory();
-    
-    auto itChildren = children.begin();
-    while(itChildren != children.end()) {
-      if(itChildren->second == DIRECTORY_CHILD_TYPE_DIRECTORY) {
-        pathsToScan.push_back(str + "/" + itChildren->first);
-      } else {
-        if(itChildren->first == type+".hpp") {
-          // Load ruleset
-          std::string data;
-          File file(str + "/" + itChildren->first);
-          if(!file.readString(&data)) {
-            std::cout << "Failed to read ruleset file!" << file.filename << std::endl;
-          } else {
-            // Begin scanning contentsr
-            // std::string include = this->sources;
-            auto toRemove = File::normalizeSlashes(this->sources + FILE_PATH_SEP);
-            auto includePath = file.filename.substr(toRemove.size(), file.filename.size() - toRemove.size());
-
-            // Now locate the first subdir since we don't want to include the root path (e.g. dawn, dawnrose, etc)
-            auto firstSlash = includePath.find(FILE_PATH_SEP);
-            if(firstSlash != std::string::npos) {
-              includePath = includePath.substr(firstSlash + 1, includePath.size() - firstSlash - 1);
-            }
-            
-            struct PrefabComponentParserRuleset ruleset;
-            // Replace all file seps with slashes
-            size_t pos = 0;
-            while ((pos = includePath.find(FILE_PATH_SEP, pos)) != std::string::npos) {
-              includePath.replace(pos, 1, "/");
-              pos += 1;
-            }
-            ruleset.include = includePath;
-            ruleset.name = type;
-
-            // Find each instance of "@optional" when it's used within a comment
-            // e.g. // @optional or /* @optional */ in the string data.
-            std::regex regex("^(\\s)*(\\/\\/|\\/\\*\\s*)(\\@optional)(\\s*\\*\\/)?(\\s)$", std::regex_constants::ECMAScript);
-            std::sregex_iterator it(data.begin(), data.end(), regex);
-            std::sregex_iterator end;
-            while(it != end) {
-              // Find the next ";"
-              auto endPos = data.find(";", it->position() + it->length());
-              if(endPos == std::string::npos) {
-                std::cout << "Failed to find end of line for optional attribute!" << std::endl;
-                return { .name = "" };
-              }
-
-              // Go backwards see if there's an equals sign after the match but before endPos
-              auto equalsPos = data.rfind("=", endPos);
-
-              // If there's an equals sign, we search backwards from there,
-              // otherwise we search backwards from the ;
-              auto varStart = it->position() + it->length() + 1;
-              size_t lastSearchPos = (
-                (equalsPos == std::string::npos || equalsPos <= varStart) ?
-                endPos :
-                equalsPos
-              );
-              
-              // Now we have our string
-              auto varLength = lastSearchPos - varStart;
-              auto variableString = data.substr(varStart, varLength);
-
-              // Now (should) be able to extract the type;
-              std::regex regex2("^\\s*(?:[\\S]+<)?([\\w*:_\\s]+)(?:[\\S]+)? (\\**[\\w]+)\\s*$", std::regex_constants::ECMAScript);
-              std::smatch match;
-              if(!std::regex_search(variableString, match, regex2)) {
-                std::cout << "Failed to extract type and name from variable string! " << variableString << std::endl;
-                return { .name = "" };
-              }
-
-              // Now we have our type and name
-              auto type = match[1].str();
-              auto name = match[2].str();
-              ruleset.optional[name] = type;
-              ++it;
-            }
-            return ruleset;
-          }
-        }
-      }
-      ++itChildren;
-    }
-    it = pathsToScan.begin();
-  };
-
-  return { .name = "" };
-}
-
-//
-
 std::vector<std::string> PrefabTool::getRequiredFlags() {
   return { "input", "output", "sources" };
 }
@@ -173,133 +70,6 @@ int32_t PrefabTool::start() {
 
 //
 
-std::vector<std::string> PrefabParser::getRequiredAttributes() {
-  return { "name", "type" };
-}
-
-std::map<std::string, std::string> PrefabParser::getOptionalAttributes() {
-  return { };
-}
-
-int32_t PrefabParser::onParse(
-  Xml *node,
-  std::map<std::string, std::string> values,
-  struct Prefab *out,
-  std::string *error
-) {
-  out->name = values["name"];
-  out->type = values["type"];
-
-  auto itChildren = node->children.begin();
-  while(itChildren != node->children.end()) {
-    // Parse children as components
-    struct PrefabComponent component;
-    component.registry = out->registry;
-
-    auto ret = (PrefabComponentParser()).parse(*itChildren, &component, error);
-    if(ret != 0) return ret;
-    out->components.push_back(component);
-    ++itChildren;
-  }
-
-  return 0;
-}
-
-//
-
-std::vector<std::string> PrefabComponentParser::getRequiredAttributes() {
-  return { };
-}
-
-std::map<std::string, std::string> PrefabComponentParser::getOptionalAttributes() {
-  return { { "ref", "" } };
-}
-
-int32_t PrefabComponentParser::onParse(
-  Xml *node,
-  std::map<std::string, std::string> values,
-  struct PrefabComponent *out,
-  std::string *error
-) {
-  // Check if values contains key "ref" and has a string of size more than 0
-  if(values.find("ref") != values.end() && values["ref"].size() > 0) {
-    out->ref = values["ref"];
-  }
-
-  // Get the ruleset.
-  auto ruleset = out->registry->getRuleset(node->node);
-  if(ruleset.name.size() == 0) {
-    *error =  "Unknown prefab node type: " + node->node;
-    return 1;
-  }
-
-  out->include = ruleset.include;
-  out->type = node->node;
-
-  // Define parser types here.
-  auto rawParser = [&](std::string v){
-    return v;
-  };
-
-  auto stringParser = [&](std::string v){
-    return "\"" + v + "\"";
-  };
-
-  auto floatParser = [&](std::string v){
-    // Make sure number contains decimal
-    if(v.find(".") == std::string::npos) {
-      v += ".0";
-    }
-    // Make sure number contains a number before the decimal
-    if(v.find(".") == 0) {
-      v = "0" + v;
-    }
-    // Make sure ends with f
-    if(v.find("f") == std::string::npos) {
-      v += "f";
-    }
-    return v;
-  };
-
-  auto colorParser = rawParser;
-
-  // Iterate all the optional attributes and store within the out if present
-  auto itOptional = ruleset.optional.begin();
-  while(itOptional != ruleset.optional.end()) {
-    // Determine the parser to use
-    auto name = itOptional->first;
-    auto type = itOptional->second;
-
-    std::function<std::string(std::string)> parser = rawParser;
-    if(type.find("string") != std::string::npos) {
-      parser = stringParser;
-    } else if(type.find("float") != std::string::npos) {
-      parser = floatParser;
-    } else if(type.find("Color") != std::string::npos) {
-      parser = colorParser;
-    } else if(type.find("*") == (type.size() - 1)) {
-      type = type.substr(0, type.size() - 1);
-      parser = rawParser;
-    } else if(name[0] == '*') {
-      name = name.substr(1, name.size());
-      parser = rawParser;
-    } else {
-      *error = "Unkown parser for type: " + type + "::" + name;
-      return 1;
-    }
-
-    if(node->attributes.find(name) != node->attributes.end()) {
-      auto raw = node->attributes[name];
-      out->values[name] = parser(raw);
-    }
-    ++itOptional;
-  }
-
-  return 0;
-}
-
-//
-
 void PrefabGen::generate(
   std::vector<std::string> *out,
   struct Prefab *info,
@@ -335,7 +105,7 @@ void PrefabGen::generate(
     if(c.ref.size() > 0) {
       init = false;
       name = c.ref;
-      line(&classInfo.publicProperties, c.type + " *" + c.ref + ";", "");
+      line(&classInfo.publicProperties, c.type + " *" + c.ref + " = nullptr;", "");
     }
 
     // Initialize
diff --git a/src/dawntools/prefabtool/PrefabTool.hpp b/src/dawntools/prefabtool/PrefabTool.hpp
index 7b44b1e2..66975056 100644
--- a/src/dawntools/prefabtool/PrefabTool.hpp
+++ b/src/dawntools/prefabtool/PrefabTool.hpp
@@ -4,46 +4,9 @@
 // https://opensource.org/licenses/MIT
 
 #pragma once
-#include "util/DawnTool.hpp"
-#include "util/Directory.hpp"
-#include "util/XmlParser.hpp"
-#include "util/CodeGen.hpp"
-#include <regex>
+#include "PrefabParser.hpp"
 
 namespace Dawn {
-  enum PrefabcComponentParserRulesetType {
-    STRIN
-  };
-
-  struct PrefabComponentParserRuleset {
-    std::string name;
-    std::string include;
-    std::map<std::string, std::string> optional;
-  };
-
-  struct PrefabRegistry {
-    std::string sources;
-    
-    struct PrefabComponentParserRuleset getRuleset(std::string type);
-  };
-
-  struct PrefabComponent {
-    struct PrefabRegistry *registry;
-
-    std::string include;
-    std::string type;
-    std::map<std::string, std::string> values;
-    std::string ref;
-  };
-
-  struct Prefab {
-    struct PrefabRegistry *registry;
-
-    std::string name;
-    std::string type;
-    std::vector<struct PrefabComponent> components;
-  };
-
   class PrefabTool : public DawnTool {
     protected:
       std::vector<std::string> getRequiredFlags() override;
@@ -53,30 +16,6 @@ namespace Dawn {
       int32_t start();
   };
 
-  class PrefabParser : public XmlParser<struct Prefab> {
-    protected:
-      std::vector<std::string> getRequiredAttributes() override;
-      std::map<std::string, std::string> getOptionalAttributes() override;
-      int32_t onParse(
-        Xml *node,
-        std::map<std::string, std::string> values,
-        struct Prefab *out,
-        std::string *error
-      ) override;
-  };
-
-  class PrefabComponentParser : public XmlParser<struct PrefabComponent> {
-    protected:
-      std::vector<std::string> getRequiredAttributes() override;
-      std::map<std::string, std::string> getOptionalAttributes() override;
-      int32_t onParse(
-        Xml *node,
-        std::map<std::string, std::string> values,
-        struct PrefabComponent *out,
-        std::string *error
-      ) override;
-  };
-
   class PrefabGen : public CodeGen {
     public:
       static void generate(