From 88ac2fc59f82290cd3c4c0b66433a3b5b8a906dd Mon Sep 17 00:00:00 2001
From: Jochen Vothknecht <jochen3120@gmail.com>
Date: Fri, 24 Jun 2022 08:53:23 +0200
Subject: [PATCH] ST: Service starting order automation

---
 .../SiliconTorch/Service/CyanStripe.cpp       |   2 +-
 CLC-qthing/SiliconTorch/Service/FxCyanF.cpp   |  12 +-
 CLC-qthing/SiliconTorch/Service/FxPublish.cpp |   2 +-
 CLC-qthing/SiliconTorch/Service/Service.cpp   |  70 ++++++++--
 CLC-qthing/SiliconTorch/Service/Service.hpp   |  30 ++++-
 .../SiliconTorch/Service/ServiceManager.cpp   | 123 +++++++++++++++++-
 .../SiliconTorch/Service/ServiceManager.hpp   |   6 +-
 7 files changed, 218 insertions(+), 27 deletions(-)

diff --git a/CLC-qthing/SiliconTorch/Service/CyanStripe.cpp b/CLC-qthing/SiliconTorch/Service/CyanStripe.cpp
index c499c0e..62ae821 100644
--- a/CLC-qthing/SiliconTorch/Service/CyanStripe.cpp
+++ b/CLC-qthing/SiliconTorch/Service/CyanStripe.cpp
@@ -54,7 +54,7 @@ namespace SiliconTorch {
     }
 
     void CyanStripe::start() {
-      ESP_LOGW(getName().c_str(), "Starting service[ %s ] with order[ %d ]  *WHOOP* *WHOOP*", getName().c_str(), getStartOrder());
+      ESP_LOGW(getName().c_str(), "Starting service[ %s ]  *WHOOP* *WHOOP*", getName().c_str());
     }
 
 
diff --git a/CLC-qthing/SiliconTorch/Service/FxCyanF.cpp b/CLC-qthing/SiliconTorch/Service/FxCyanF.cpp
index e85e890..e4fd215 100644
--- a/CLC-qthing/SiliconTorch/Service/FxCyanF.cpp
+++ b/CLC-qthing/SiliconTorch/Service/FxCyanF.cpp
@@ -26,6 +26,8 @@ namespace SiliconTorch {
     void FxCyanF::init() {
       setName("FxCyanF");
       setNameSpace("fxCyan");  // TODO: "FxCyanF"
+
+      addWantedService("CyanBus");
     }
 
 
@@ -44,8 +46,10 @@ namespace SiliconTorch {
       u32 frq = SiliconTorch::FxCyanF::DefaultFrequency;
       u8  res = SiliconTorch::FxCyanF::DefaultResolution;
 
-      frq = NVSExplorer::NVSExplorer::instance().getUnsignedInt(getNameSpace(), "frequency", frq);
-      res = NVSExplorer::NVSExplorer::instance().getUnsignedInt(getNameSpace(), "resolution", res);
+      NVSExplorer::NVSExplorer& nvs = NVSExplorer::NVSExplorer::instance();
+
+      frq = nvs.getUnsignedInt(getNameSpace(), "frequency", frq);
+      res = nvs.getUnsignedInt(getNameSpace(), "resolution", res);
 
       fxCyan->setFrqRes(frq, res);
 
@@ -55,10 +59,10 @@ namespace SiliconTorch {
         char buffer[32];
         snprintf(buffer, 32, "ch%d_gpio", ch);
 
-        u8 gpio = NVSExplorer::NVSExplorer::instance().getUnsignedInt(getNameSpace(), buffer, 0xFF) & 0xFF;
+        u8 gpio = nvs.getUnsignedInt(getNameSpace(), buffer, 0xFF) & 0xFF;
 
         snprintf(buffer, 32, "ch%d_pwm", ch);
-        f32 pwm = NVSExplorer::NVSExplorer::instance().getFloat(getNameSpace(), buffer);
+        f32 pwm = nvs.getFloat(getNameSpace(), buffer);
 
         // NaN == "no value stored" or any other reading error
         if (std::isnan(pwm)) pwm = 0.0f;
diff --git a/CLC-qthing/SiliconTorch/Service/FxPublish.cpp b/CLC-qthing/SiliconTorch/Service/FxPublish.cpp
index 69abef7..ad2820f 100644
--- a/CLC-qthing/SiliconTorch/Service/FxPublish.cpp
+++ b/CLC-qthing/SiliconTorch/Service/FxPublish.cpp
@@ -41,7 +41,7 @@ namespace SiliconTorch {
     }
 
     void FxPublish::start() {
-      ESP_LOGW(getName().c_str(), "Starting service[ %s ] with order[ %d ]  *WHOOP* *WHOOP*", getName().c_str(), getStartOrder());
+      ESP_LOGW(getName().c_str(), "Starting service[ %s ]  *WHOOP* *WHOOP*", getName().c_str());
     }
 
 
diff --git a/CLC-qthing/SiliconTorch/Service/Service.cpp b/CLC-qthing/SiliconTorch/Service/Service.cpp
index d42151a..6da3240 100644
--- a/CLC-qthing/SiliconTorch/Service/Service.cpp
+++ b/CLC-qthing/SiliconTorch/Service/Service.cpp
@@ -16,7 +16,7 @@
 // #include <qthing>
 
 
-const char* TAG = "ServiceManager";
+static const char* TAG = "ServiceManager";
 
 
 namespace SiliconTorch {
@@ -28,25 +28,32 @@ namespace SiliconTorch {
 
     Service::Service() {
       // u8 state = 0;  // disabled -> 0   enabled -> 1
-      // state = NVSExplorer::NVSExplorer::instance().getUnsignedInt(nameSpace(), NVSStateKey, state);
+      // state = nvs.getUnsignedInt(nameSpace(), NVSStateKey, state);
 
       // if (state >= (u8)Invalid) state = (u8)Disabled;
       // nvsState = (NVSState)state;
     }
 
+    void Service::preInit(ServiceLookup lookup) {
+      serviceLookup = lookup;
+    }
+
     void Service::postInit() {
 
       attrsLocked = true;
 
       u8 state = 0;  // disabled -> 0   enabled -> 1
-      state = NVSExplorer::NVSExplorer::instance().getUnsignedInt(getNameSpace(), NVSStateKey, state);
+      state = SiliconTorch::NVSExplorer::NVSExplorer::instance().getUnsignedInt(getNameSpace(), NVSStateKey, state);
 
       if (state >= (u8)Invalid) state = (u8)Disabled;
       nvsState = (NVSState)state;
+
+      ESP_LOGW(TAG, "Service[ %s ] may not start due to errorneous initialization", getName().c_str());
+
     }
 
     bool Service::isEnabled() const {
-      return nvsState == Enabled;
+      return nvsState == Enabled && !hasErrors;
     }
 
     void Service::setEnabled(bool enabled) {
@@ -86,15 +93,62 @@ namespace SiliconTorch {
       }
     }
 
-    void Service::setStartOrder(u16 startOrder) {
+
+    void Service::addWantedService(const str& name) {
+      if (!attrsLocked) {
+        Service* service = otherByName(name);
+
+        if (service != NULL)
+          wantedServices.insert(service);
+      } else {
+        // TODO: report error somehow?
+      }
+    }
+
+    void Service::addRequiredService(const str& name) {
       if (!attrsLocked) {
-        this->startOrder = startOrder;
+        Service* service = otherByName(name);
+
+        if (service != NULL) {
+          requiredServices.insert(service);
+        } else {
+          hasErrors = true;
+          ESP_LOGW(TAG, "Service[ %s ] requires unknown otherService[ %s ]", getName().c_str(), name.c_str());
+        }
       } else {
         // TODO: report error somehow?
       }
     }
 
 
+    ServiceSet Service::getWantedServices() {
+      ServiceSet out;
+
+      // TODO: better method for set copy?!
+
+      for (const auto& service : wantedServices)
+        out.insert(service);
+
+      return out;
+    }
+
+    ServiceSet Service::getRequiredServices() {
+      ServiceSet out;
+
+      // TODO: better method for set copy?!
+
+      for (const auto& service : requiredServices)
+        out.insert(service);
+
+      return out;
+    }
+
+
+    Service* Service::otherByName(const str& name) {
+      return serviceLookup(name);
+    }
+
+
     const str& Service::getName() const {
       return name;
     }
@@ -103,9 +157,5 @@ namespace SiliconTorch {
       return nameSpace;
     }
 
-    u16 Service::getStartOrder() const {
-      return startOrder;
-    }
-
   }
 }
diff --git a/CLC-qthing/SiliconTorch/Service/Service.hpp b/CLC-qthing/SiliconTorch/Service/Service.hpp
index 0f7de7d..aaf4f8b 100644
--- a/CLC-qthing/SiliconTorch/Service/Service.hpp
+++ b/CLC-qthing/SiliconTorch/Service/Service.hpp
@@ -1,9 +1,10 @@
 #pragma once
 
 // C++ system level
+#include <set>
 // #include <cstring>     // memset, strncmp
 // #include <cstdlib>     // TODO: is this for memcpy?
-// #include <functional>
+#include <functional>
 
 // ESP32 specific
 #include "esp_log.h"
@@ -27,6 +28,11 @@ namespace SiliconTorch {
       Invalid
     };
 
+    class Service;
+
+    using ServiceSet    = std::set<Service*>;
+    using ServiceLookup = std::function<Service*(const str&)>;
+
 
     class Service {
       public:
@@ -45,29 +51,43 @@ namespace SiliconTorch {
         const str& getName() const;
         const str& getNameSpace() const;
 
-        u16 getStartOrder() const;
-
         bool isEnabled() const;
         void setEnabled(bool enabled);
 
+        Service* otherByName(const str& name);
+
         // internal stuff, only to be called by ServiceManager!
+        void preInit(ServiceLookup lookup);
         void postInit();
 
+        ServiceSet getWantedServices();
+        ServiceSet getRequiredServices();
+
       protected:
 
         void setName(const str& name);
         void setNameSpace(const str& nameSpace);
-        void setStartOrder(u16 startOrder);
+
+        // Service will be started after the wanted one (if wanted is disabled this limitation is waived)    // TODO: (waived / raised / lowered?)
+        void addWantedService(const str& name);
+
+        // Service will only be started after the required one
+        void addRequiredService(const str& name);
 
       private:
 
         NVSState nvsState = Disabled;
 
+        bool hasErrors = false;  // Set if an error is detected. Prevents the service from being started
+
         bool attrsLocked = false;
         str name = "";
         str nameSpace = "";
 
-        u16 startOrder = 1337;
+        ServiceSet wantedServices;
+        ServiceSet requiredServices;
+
+        ServiceLookup serviceLookup = [](const str& ignored) { return (Service*)NULL; };
 
     };
 
diff --git a/CLC-qthing/SiliconTorch/Service/ServiceManager.cpp b/CLC-qthing/SiliconTorch/Service/ServiceManager.cpp
index a15fa1f..8c7a92f 100644
--- a/CLC-qthing/SiliconTorch/Service/ServiceManager.cpp
+++ b/CLC-qthing/SiliconTorch/Service/ServiceManager.cpp
@@ -14,6 +14,7 @@
 
 // project specific
 #include <Types.hpp>
+#include <SpiderLib/Util.hpp>  // TODO: remove after startAlgo()-debuggin!!
 // #include "SiliconTorch/NVSExplorer.hpp"
 
 // qthing stuff
@@ -37,6 +38,18 @@ namespace SiliconTorch {
 
       registrationLocked = true;
 
+
+      auto sers = startAlgo();
+
+      ESP_LOGI(TAG, "=====   Services starting order   =====");
+
+      for (const auto& s : sers) ESP_LOGI(TAG, "Service: %s", s->getName().c_str());
+
+      ESP_LOGI(TAG, "=======================================");
+
+      return;
+
+
       printServiceTable();
 
       ESP_LOGI(TAG, "Starting services…");
@@ -44,15 +57,102 @@ namespace SiliconTorch {
       ESP_LOGI(TAG, "Starting services#[ %d ] done.", started);
     }
 
+
+
+    std::vector<Service*> ServiceManager::startAlgo() {
+      
+      std::vector<Service*> startOrder;  // our result
+
+
+      std::map<Service*, ServiceSet> services;
+
+      // prepare our data structures
+      for (const auto& [name, service] : serviceMap) {
+
+        if (service->isEnabled()) {
+
+          bool reqsOK = true;  // TODO: requirements_"zufriedengestellt" …?  (naming)
+
+          ServiceSet deps;
+          ServiceSet wanted   = service->getWantedServices();
+          ServiceSet required = service->getRequiredServices();
+
+          // copy wanted services if enabled
+          for (const auto& sw : wanted)
+            if (sw->isEnabled()) deps.insert(sw);
+
+          // copy required services
+          for (const auto& sr : required)
+            if (sr->isEnabled()) deps.insert(sr);
+            else reqsOK = false;
+
+          if (reqsOK)
+            services[service] = deps;
+        }
+
+      }
+
+
+      // Debugging stuff: print it!
+
+      ESP_LOGI(TAG, "=====      Service structure      =====");
+
+      for (const auto& [service, deps] : services) {
+
+        std::set<std::string> depsnames;
+        for (const auto& d : deps) depsnames.insert(d->getName());
+
+        std::string sdeps = SpiderLib::Util::join(depsnames, "  ");
+
+        ESP_LOGI(TAG, "Service[ %s ] has deps{  %s  }", service->getName().c_str(), sdeps.c_str());
+      }
+
+      ESP_LOGI(TAG, "=======================================");
+
+      // end debugging stuff
+
+
+
+      bool dslr = true;  // dslr = "did something last round" -> used for cycle detection
+
+      while (dslr) {
+        bool serviceCopied = false;
+
+        auto it = services.begin();
+        while (it != services.end()) {
+
+          if (it->second.size() == 0) {
+            startOrder.push_back(it->first);
+            serviceCopied = true;
+            it = services.erase(it);
+          } else {
+            it++;
+          }
+
+        }
+
+
+        for (const auto& starting : startOrder)
+          for (auto& [ignored, deps] : services)
+            deps.erase(starting);
+
+
+        dslr = serviceCopied;
+      }
+
+      return startOrder;
+    }
+
+
     std::vector<Service*> ServiceManager::getServicesInStartOrder() const {
       std::vector<Service*> services;
 
       for (const auto& [_, service] : serviceMap)
         services.push_back(service);
 
-      std::sort(services.begin(), services.end(), [](const Service* x, const Service* y) {
-        return x->getStartOrder() < y->getStartOrder();
-      });
+      // std::sort(services.begin(), services.end(), [](const Service* x, const Service* y) {
+      //   return x->getStartOrder() < y->getStartOrder();
+      // });
 
       return services;
     }
@@ -129,7 +229,7 @@ namespace SiliconTorch {
         ESP_LOGI(TAG, "│ %s %s│ % 5d %s│%s%s%s│",
           service->getName().c_str(),
           strmul(" ", nameColSize - 2 - nameSize).c_str(),
-          service->getStartOrder(),
+          1337,                                                   // service->getStartOrder(),
           strmul(" ", startOrderColSize - 2 - 5).c_str(),
           strmul(" ", (enabledColSize - 2) / 2).c_str(),
           service->isEnabled() ? "✅" : "❌",
@@ -167,10 +267,23 @@ namespace SiliconTorch {
     }
 
 
+    Service* ServiceManager::byName(const str& name) {
+      if (serviceMap.count(name) > 0)
+        return serviceMap.at(name);
+      else
+        return NULL;
+    }
+
+
     void ServiceManager::registerService(Service* s) {
       if (!registrationLocked) {
+
+        auto serviceLookup = [&](const str& name) {
+          return byName(name);          
+        };
+
         s->init();
-        s->postInit();
+        s->postInit();//serviceLookup);
 
         serviceMap[s->getName()] = s;
 
diff --git a/CLC-qthing/SiliconTorch/Service/ServiceManager.hpp b/CLC-qthing/SiliconTorch/Service/ServiceManager.hpp
index 32a7dd0..ed43416 100644
--- a/CLC-qthing/SiliconTorch/Service/ServiceManager.hpp
+++ b/CLC-qthing/SiliconTorch/Service/ServiceManager.hpp
@@ -25,7 +25,8 @@ namespace SiliconTorch {
     extern const str NVSStateKey;
 
 
-    using ServiceMap = std::map<str, Service*>;
+    using ServiceSet = std::set<Service*>;
+    using ServiceMap = std::map<str, SiliconTorch::ServiceManager::Service*>;
 
 
     class ServiceManager {
@@ -43,9 +44,12 @@ namespace SiliconTorch {
 
         std::vector<Service*> getServicesInStartOrder() const;
 
+        Service* byName(const str& name);
 
         void registerService(Service* s);
 
+        std::vector<Service*> startAlgo();
+
       private:
 
         bool registrationLocked = false;
-- 
GitLab