[ML][Pipeline] Add Pipeline and PipelineManager 25/250425/19
authorPawel Wasowski <p.wasowski2@samsung.com>
Thu, 24 Dec 2020 10:36:26 +0000 (11:36 +0100)
committerPawel Wasowski <p.wasowski2@samsung.com>
Mon, 18 Jan 2021 09:00:10 +0000 (09:00 +0000)
ACR: TWDAPI-274

[Verification] createPipeline() tested in Chrome
DevTools with the snippet below works fine

var pipeline = tizen.ml.pipeline.createPipeline(
                 'videoteststrc ! tizenwlsink',
                  function(state) {
                    console.log(state);
                  })
//a moment later:
// READY
// PAUSED

tizen.ml.createPipeline()
// WebAPIException: InvalidValuesError

Change-Id: I68beaebf7e248b61bba9dc06ce6124c187c45fae
Signed-off-by: Pawel Wasowski <p.wasowski2@samsung.com>
src/ml/js/ml_common.js
src/ml/js/ml_pipeline.js
src/ml/ml_instance.cc
src/ml/ml_instance.h
src/ml/ml_pipeline.cc
src/ml/ml_pipeline.h
src/ml/ml_pipeline_manager.cc
src/ml/ml_pipeline_manager.h

index 21819da..c8bc981 100755 (executable)
@@ -17,6 +17,9 @@
 var privUtils_ = xwalk.utils;
 var validator_ = privUtils_.validator;
 var types_ = validator_.Types;
+var native_ = new xwalk.utils.NativeManager(extension);
+
+var AbortError = new WebAPIException('AbortError', 'An unknown error occurred');
 
 // TensorRawData
 
index fdd1a2d..f50b97a 100755 (executable)
  *    limitations under the License.
  */
 
-//PipelineManager::createPipeline() begin
+var kPipelineStateChangeListenerNamePrefix = 'MLPipelineStateChangeListener';
 
+//PipelineManager::createPipeline() begin
+var nextPipelineId = 1;
+function NextPipelineId() {
+    return nextPipelineId++;
+}
+
+var ValidPipelineManagerCreatePipelineExceptions = [
+    'InvalidValuesError',
+    'TypeMismatchError',
+    'NotSupportedError',
+    'SecurityError',
+    'AbortError'
+];
+
+var CreatePipeline = function() {
+    privUtils_.log('Entered PipelineManager.createPipeline()');
+    var args = validator_.validateArgs(arguments, [
+        {
+            name: 'definition',
+            type: validator_.Types.STRING
+        },
+        {
+            name: 'listener',
+            type: validator_.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        }
+    ]);
+
+    if (!args.has.definition) {
+        throw new WebAPIException(
+            WebAPIException.INVALID_VALUES_ERR,
+            'Invalid parameter: pipeline definition is mandatory'
+        );
+    }
+
+    var pipeline = new Pipeline(NextPipelineId());
+    var nativeArgs = {
+        id: pipeline._id,
+        definition: args.definition
+    };
+
+    if (args.listener) {
+        nativeArgs.listenerName = kPipelineStateChangeListenerNamePrefix + pipeline._id;
+        var stateChangeListener = function(stateObject) {
+            args.listener(stateObject.state);
+        };
+        native_.addListener(nativeArgs.listenerName, stateChangeListener);
+    }
+
+    var result = native_.callSync('MLPipelineManagerCreatePipeline', nativeArgs);
+
+    if (native_.isFailure(result)) {
+        if (nativeArgs.listenerName) {
+            native_.removeListener(nativeArgs.listenerName);
+        }
+        throw native_.getErrorObjectAndValidate(
+            result,
+            ValidPipelineManagerCreatePipelineExceptions,
+            AbortError
+        );
+    }
+
+    return pipeline;
+};
 //PipelineManager::createPipeline() end
 
 //Pipeline::state begin
-
+var ValidPipelineStateExceptions = ['NotSupportedError', 'AbortError'];
+var Pipeline = function(id) {
+    Object.defineProperties(this, {
+        state: {
+            enumerable: true,
+            get: function() {
+                var result = native_.callSync('MLPipelineGetState', {
+                    id: id
+                });
+                if (native_.isFailure(result)) {
+                    throw native_.getErrorObjectAndValidate(
+                        result,
+                        ValidPipelineStateExceptions,
+                        AbortError
+                    );
+                }
+
+                return result.state;
+            }
+        },
+        _id: {
+            value: id
+        }
+    });
+};
 //Pipeline::state end
 
 //Pipeline::start() begin
 //Valve::setOpen() begin
 
 //Valve::setOpen() end
-
 var MachineLearningPipeline = function() {};
 
+MachineLearningPipeline.prototype.createPipeline = CreatePipeline;
+
 // ML Pipeline API
index 5706267..5d2f242 100644 (file)
@@ -24,21 +24,22 @@ namespace ml {
 
 using namespace common;
 
-MlInstance::MlInstance() {
+MlInstance::MlInstance() : pipeline_manager_{this} {
   ScopeLogger();
   using namespace std::placeholders;
 
 #define REGISTER_METHOD(M) RegisterSyncHandler(#M, std::bind(&MlInstance::M, this, _1, _2))
 
-// Common ML API begin
+  // Common ML API begin
 
-// Common ML API end
+  // Common ML API end
 
-// Single API begin
+  // Single API begin
 
-// Single API end
+  // Single API end
 
-// Pipeline API begin
+  // Pipeline API begin
+  REGISTER_METHOD(MLPipelineManagerCreatePipeline);
 
 // Pipeline API end
 
@@ -58,8 +59,57 @@ MlInstance::~MlInstance() {
 // Single API end
 
 // Pipeline API begin
+
+namespace {
+
+const std::string kId = "id";
+const std::string kDefinition = "definition";
+const std::string kPipelineStateChangeListenerName = "listenerName";
+
+}  //  namespace
+
 // PipelineManager::createPipeline() begin
+namespace {
+
+bool CreatePipelineArgumentsAreInvalid(const picojson::value& args) {
+  ScopeLogger();
 
+  auto arguments_valid = args.get(kId).is<double>();
+  arguments_valid &= args.get(kDefinition).is<std::string>();
+  arguments_valid &= (args.get(kPipelineStateChangeListenerName).is<std::string>() ||
+                      args.get(kPipelineStateChangeListenerName).is<picojson::null>());
+  LoggerD("CreatePipeline arguments are %s", arguments_valid ? "valid" : "invalid");
+
+  return !arguments_valid;
+}
+
+};  // namespace
+
+void MlInstance::MLPipelineManagerCreatePipeline(const picojson::value& args,
+                                                 picojson::object& out) {
+  ScopeLogger("args: %s", args.serialize().c_str());
+
+  if (CreatePipelineArgumentsAreInvalid(args)) {
+    ReportError(PlatformResult{ErrorCode::ABORT_ERR, "Could not create pipeline"}, &out);
+    return;
+  }
+
+  auto id = static_cast<int>(args.get(kId).get<double>());
+  auto definition = args.get(kDefinition).get<std::string>();
+  auto state_change_listener_name =
+      args.get(kPipelineStateChangeListenerName).is<std::string>()
+          ? args.get(kPipelineStateChangeListenerName).get<std::string>()
+          : "";
+
+  auto ret = pipeline_manager_.CreatePipeline(id, definition, state_change_listener_name);
+
+  if (!ret) {
+    ReportError(ret, &out);
+    return;
+  }
+
+  ReportSuccess(out);
+}
 // PipelineManager::createPipeline() end
 
 // Pipeline::state begin
@@ -133,6 +183,7 @@ MlInstance::~MlInstance() {
 // Valve::setOpen() begin
 
 // Valve::setOpen() end
+
 // Pipeline API end
 
 }  // namespace ml
index e7a1998..622bcc8 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "common/extension.h"
 
+#include "ml/ml_pipeline_manager.h"
 #include "nnstreamer/nnstreamer-single.h"
 #include "nnstreamer/nnstreamer.h"
 
@@ -40,9 +41,10 @@ class MlInstance : public common::ParsedInstance {
   // Single API end
 
   // Pipeline API begin
+  PipelineManager pipeline_manager_;
 
   // PipelineManager::createPipeline() begin
-
+  void MLPipelineManagerCreatePipeline(const picojson::value& args, picojson::object& out);
   // PipelineManager::createPipeline() end
 
   // Pipeline::state begin
@@ -116,7 +118,6 @@ class MlInstance : public common::ParsedInstance {
   // Valve::setOpen() begin
 
   // Valve::setOpen() end
-
   // Pipeline API end
 };
 
index 60a0108..caa63a5 100644 (file)
  *    See the License for the specific language governing permissions and
  *    limitations under the License.
  */
+#include <tizen.h>
+
+#include "common/logger.h"
+#include "common/picojson.h"
 #include "ml_pipeline.h"
+#include "ml_utils.h"
+
+using common::PlatformResult;
+using common::ErrorCode;
+
+namespace {
+
+const std::string kListenerId = "listenerId";
+const std::string kState = "state";
+
+std::string StateToString(ml_pipeline_state_e state) {
+  ScopeLogger("state: [%d]", state);
+
+  std::string state_str;
+  switch (state) {
+    case ML_PIPELINE_STATE_UNKNOWN:
+      state_str = "UNKNOWN";
+      break;
+    case ML_PIPELINE_STATE_NULL:
+      state_str = "NULL";
+      break;
+    case ML_PIPELINE_STATE_READY:
+      state_str = "READY";
+      break;
+    case ML_PIPELINE_STATE_PAUSED:
+      state_str = "PAUSED";
+      break;
+    case ML_PIPELINE_STATE_PLAYING:
+      state_str = "PLAYING";
+      break;
+    default:
+      LoggerE("Illegal ml_pipeline_state_e value: [%d]", state);
+      state_str = "UNKNOWN";
+  }
+
+  LoggerD("state_str: [%s]", state_str.c_str());
+  return state_str;
+}
+
+}  // namespace
+
+namespace extension {
+namespace ml {
+
+Pipeline::Pipeline(int id, const std::string& state_change_listener_name,
+                   common::Instance* instance_ptr)
+    : id_{id},
+      pipeline_{nullptr},  // this will be set to a proper pointer in CreatePipeline()
+      state_change_listener_name_{state_change_listener_name},
+      instance_ptr_{instance_ptr} {
+  ScopeLogger("id: [%d], state_change_listener_name: [%s]", id, state_change_listener_name.c_str());
+}
 
 // PipelineManager::createPipeline() begin
-
+PlatformResult Pipeline::CreatePipeline(int id, const std::string& definition,
+                                        const std::string& state_change_listener_name,
+                                        common::Instance* instance_ptr,
+                                        std::unique_ptr<Pipeline>* out) {
+  ScopeLogger("id: [%d], definition: [%s], state_change_listener_name: [%s]", id,
+              definition.c_str(), state_change_listener_name.c_str());
+
+  /* We need to create the Pipeline object before setting its pipeline_ member,
+   * because Pipeline is the user data for the listener registered by
+   * ml_pipeline_construct().
+   */
+  std::unique_ptr<Pipeline> pipeline_ptr{
+      new (std::nothrow) Pipeline{id, state_change_listener_name, instance_ptr}};
+  if (!pipeline_ptr) {
+    return LogAndCreateResult(ErrorCode::ABORT_ERR, "An unknown occurred.",
+                              ("Could not allocate memory for Pipeline"));
+  }
+
+  int ret = ML_ERROR_UNKNOWN;
+  if (state_change_listener_name == "") {
+    ret = ml_pipeline_construct(definition.c_str(), nullptr, nullptr, &pipeline_ptr->pipeline_);
+  } else {
+    ret = ml_pipeline_construct(definition.c_str(), PipelineStateChangeListener,
+                                static_cast<void*>(pipeline_ptr.get()), &pipeline_ptr->pipeline_);
+  }
+
+  if (ML_ERROR_NONE != ret) {
+    LoggerE("ml_pipeline_construct() failed: [%d] (%s)", ret, get_error_message(ret));
+    return util::ToPlatformResult(ret, "Could not create a pipeline");
+  }
+  LoggerD("ml_pipeline_construct() succeeded");
+
+  *out = std::move(pipeline_ptr);
+  return PlatformResult{};
+}
 // PipelineManager::createPipeline() end
 
+Pipeline::~Pipeline() {
+  ScopeLogger("Destroying pipeline: [%d]", id_);
+
+  auto ret = ml_pipeline_destroy(pipeline_);
+  if (ML_ERROR_NONE != ret) {
+    LoggerE("ml_pipeline_destroy() failed: [%d] (%s)", ret, get_error_message(ret));
+  }
+  LoggerD("ml_pipeline_destroy() succeeded");
+}
+
+void Pipeline::PipelineStateChangeListener(ml_pipeline_state_e state, void* user_data) {
+  ScopeLogger("state: [%s]", StateToString(state).c_str());
+
+  Pipeline* pipeline = static_cast<Pipeline*>(user_data);
+
+  picojson::value response{picojson::object{}};
+  response.get<picojson::object>()[kListenerId] =
+      picojson::value{pipeline->state_change_listener_name_};
+  response.get<picojson::object>()[kState] = picojson::value{StateToString(state)};
+
+  common::Instance::PostMessage(pipeline->instance_ptr_, response);
+}
+
 // Pipeline::state begin
 
 // Pipeline::state end
 
 // Valve::setOpen() begin
 
-// Valve::setOpen() end
\ No newline at end of file
+// Valve::setOpen() end
+
+}  // namespace extension
+}  // namespace ml
index 029269e..b5834f4 100644 (file)
 #ifndef ML_ML_PIPELINE_H_
 #define ML_ML_PIPELINE_H_
 
+#include <map>
+#include <string>
+
+#include <nnstreamer/nnstreamer.h>
+
+#include "common/extension.h"
 #include "common/picojson.h"
 #include "common/platform_result.h"
 
+using common::PlatformResult;
+
 namespace extension {
 namespace ml {
 
 class Pipeline {
  public:
   // PipelineManager::createPipeline() begin
-
+  /*
+   * We use a static function to create Pipeline objects, instead of
+   * a typical constructor, to be able to return an error without
+   * throwing a C++ exception.
+   */
+  static PlatformResult CreatePipeline(int id, const std::string& definition,
+                                       const std::string& state_change_listener_name,
+                                       common::Instance* instance_ptr,
+                                       std::unique_ptr<Pipeline>* out);
   // PipelineManager::createPipeline() end
 
+  Pipeline() = delete;
+  Pipeline(const Pipeline&) = delete;
+  Pipeline& operator=(const Pipeline&) = delete;
+
+  ~Pipeline();
+
   // Pipeline::state begin
 
   // Pipeline::state end
@@ -101,6 +123,14 @@ class Pipeline {
 
   // Valve::setOpen() end
  private:
+  Pipeline(int id, const std::string& state_change_listener_name, common::Instance* instance_ptr);
+
+  const int id_;
+  ml_pipeline_h pipeline_;
+  const std::string state_change_listener_name_;
+  common::Instance* instance_ptr_;
+
+  static void PipelineStateChangeListener(ml_pipeline_state_e state, void* user_data);
 };
 
 }  // namespace ml
index b33a771..1a21516 100644 (file)
  */
 
 #include "ml_pipeline_manager.h"
+#include "common/tools.h"
 
-// PipelineManager::createPipeline() begin
+using common::PlatformResult;
+using common::ErrorCode;
+using common::tools::ReportError;
+using common::tools::ReportSuccess;
+
+namespace extension {
+namespace ml {
+
+PipelineManager::PipelineManager(common::Instance* instance_ptr) : instance_ptr_{instance_ptr} {
+  ScopeLogger();
+}
 
+PipelineManager::~PipelineManager() {
+  ScopeLogger();
+}
+
+// PipelineManager::createPipeline() begin
+PlatformResult PipelineManager::CreatePipeline(int id, const std::string& definition,
+                                               const std::string& state_change_listener_name) {
+  ScopeLogger("id: [%d], definition: [%s], state_change_listener_name: [%s]", id,
+              definition.c_str(), state_change_listener_name.c_str());
+
+  if (pipelines_.count(id)) {
+    LoggerD("The pipeline already exists: [%d]", id);
+    return PlatformResult{ErrorCode::ABORT_ERR, "Could not create pipeline"};
+  }
+
+  std::unique_ptr<Pipeline> pipeline_ptr;
+  auto ret = Pipeline::CreatePipeline(id, definition, state_change_listener_name, instance_ptr_,
+                                      &pipeline_ptr);
+  if (!ret) {
+    return ret;
+  }
+
+  pipelines_.insert({id, std::move(pipeline_ptr)});
+
+  return PlatformResult{};
+}
 // PipelineManager::createPipeline() end
 
 // Pipeline::state begin
 
 // Valve::setOpen() begin
 
-// Valve::setOpen() end
\ No newline at end of file
+// Valve::setOpen() end
+
+}  // namespace ml
+}  // namespace extension
index 68891e3..1ecf79a 100644 (file)
 #ifndef ML_ML_PIPELINE_MANAGER_H_
 #define ML_ML_PIPELINE_MANAGER_H_
 
+#include "common/extension.h"
 #include "common/picojson.h"
 #include "common/platform_result.h"
 
+#include "ml_pipeline.h"
+
+using common::PlatformResult;
+
 namespace extension {
 namespace ml {
 
 class PipelineManager {
  public:
-  // PipelineManager::createPipeline() begin
+  PipelineManager(common::Instance* instance_ptr);
 
+  ~PipelineManager();
+
+  PipelineManager() = delete;
+  PipelineManager(const PipelineManager&) = delete;
+  PipelineManager& operator=(const PipelineManager&) = delete;
+
+  // PipelineManager::createPipeline() begin
+  PlatformResult CreatePipeline(int id, const std::string& definition,
+                                const std::string& state_change_listener_name);
   // PipelineManager::createPipeline() end
 
   // Pipeline::state begin
@@ -101,6 +115,8 @@ class PipelineManager {
 
   // Valve::setOpen() end
  private:
+  common::Instance* instance_ptr_;
+  std::map<int, std::unique_ptr<Pipeline>> pipelines_;
 };
 
 }  // namespace ml