[ML][Pipeline] Change CustomFilter interface and implementation
[platform/core/api/webapi-plugins.git] / src / ml / js / ml_pipeline.js
index 5ace36b..f9a285f 100755 (executable)
@@ -720,54 +720,6 @@ var MachineLearningPipeline = function() {};
 MachineLearningPipeline.prototype.createPipeline = CreatePipeline;
 
 //Pipeline::registerCustomFilter() begin
-var CustomFilterOutput = function() {
-    validator_.isConstructorCall(this, CustomFilterOutput);
-
-    var args = validator_.validateArgs(arguments, [
-        {
-            name: 'status',
-            type: validator_.Types.LONG
-        },
-        {
-            name: 'data',
-            type: types_.PLATFORM_OBJECT,
-            values: TensorsData,
-            optional: true,
-            nullable: true
-        }
-    ]);
-
-    if (!args.has.data) {
-        args.data = null;
-    }
-
-    if (args.status > 0 && args.status !== 1) {
-        throw new WebAPIException(
-            WebAPIException.INVALID_VALUES_ERR,
-            'CustomFilterOutput.status === 1 is the only legal positive value'
-        );
-    }
-
-    if (args.status === 0 && args.data === null) {
-        throw new WebAPIException(
-            WebAPIException.INVALID_VALUES_ERR,
-            'CustomFilterOutput.data === null is illegal when ' +
-                'CustomFilterOutput.status === 0'
-        );
-    }
-
-    Object.defineProperties(this, {
-        status: {
-            enumerable: true,
-            value: args.status
-        },
-        data: {
-            enumerable: true,
-            value: args.data
-        }
-    });
-};
-
 var ValidRegisterCustomFilterExceptions = [
     'InvalidValuesError',
     'NotSupportedError',
@@ -776,6 +728,7 @@ var ValidRegisterCustomFilterExceptions = [
 ];
 
 var ValidCustomFilterOutputErrors = ['InvalidValuesError', 'AbortError'];
+
 MachineLearningPipeline.prototype.registerCustomFilter = function() {
     var args = validator_.validateArgs(arguments, [
         {
@@ -814,16 +767,22 @@ MachineLearningPipeline.prototype.registerCustomFilter = function() {
     /*
      * CustomFilter processing has 4 stages (the description below assumes
      * the typical scenario with no errors):
-     * 1. (C++) C++ callback is called by the native API with input data.
-     * The C++ callback clones the tensors data and associated info and
-     * sends it to JS.
-     * 2. (JS) customFilterWrapper is called with the input data from C++
-     * as one of its arguments. User-provided callback processes the data
-     * and the output is sent to C++ by a call of asynchronous function.
-     * 3. (C++) C++ callback is woken up and clones the output from user
-     * callback to native tensors data. It replies to JS with success/error.
-     * 4. (JS) If C++ responded with success, the operation stops.
+     * 1. (C++; non-main thread) C++ callback is called by the native API with input data.
+     * The C++ callback wraps native ml_tensors_data_h handles in TensorsData
+     * objects and sends them together with associated TensorsInfo to JS.
+     * 2. (JS; main thread) customFilterWrapper is called with the input data from C++
+     * as one of its arguments. User-provided callback processes the data.
+     * The input/output TensorsData that arrive to JS as CustomFilter arguments
+     * are unique in that they:
+     * - cannot be disposed, i.e. calling {input, output}.dispose() is no-op
+     * - input is immutable, i.e. calling input.setTensorRawData() is no-op
+     * output.setTensorRawData() modify the native nnstreamer object directly.
+     * 3. (C++; main thread) Sleeping callback thread is notified. If anything
+     * goes wrong, C++ function returns an error synchronously to stage 4.
+     * 4. (JS; main thread) If C++ returned a success, the operation stops.
      * Otherwise, the error callback provided by the user is called.
+     * 5. (C++; non-main thread) C++ callback is woken up and returns the status
+     * received from user to pipeline.
      */
     var customFilterWrapper = function(msg) {
         /*
@@ -834,7 +793,17 @@ MachineLearningPipeline.prototype.registerCustomFilter = function() {
             return;
         }
 
-        var inputData = new TensorsData(msg.tensorsDataId, msg.tensorsInfoId);
+        var inputData = new TensorsData(
+            msg.inputTensorsDataId,
+            msg.inputTensorsInfoId,
+            false
+        );
+        var outputData = new TensorsData(
+            msg.outputTensorsDataId,
+            msg.outputTensorsInfoId,
+            false
+        );
+
         /*
          * customFilterErrorInJs records errors caused by the CustomFilter callback
          * provided by the user.
@@ -842,75 +811,58 @@ MachineLearningPipeline.prototype.registerCustomFilter = function() {
         var customFilterErrorInJs = null;
         var jsResponse = {
             status: -1,
-            dataId: -1,
             name: nativeArgs.name,
             requestId: msg.requestId
         };
-        var output = null;
 
         try {
-            output = args.customFilter(inputData);
+            jsResponse.status = converter_.toLong(
+                args.customFilter(inputData, outputData)
+            );
         } catch (exception) {
+            var exceptionString =
+                typeof exception.toString === 'function'
+                    ? exception.toString()
+                    : JSON.stringify(exception);
             customFilterErrorInJs = new WebAPIException(
                 WebAPIException.ABORT_ERR,
-                'CustomFilter has thrown exception: ' + xwalk.JSON.stringify(exception)
+                'CustomFilter has thrown exception: ' + exceptionString
             );
         }
 
-        if (output instanceof CustomFilterOutput) {
-            jsResponse.status = output.status;
-            jsResponse.dataId = type_.isNullOrUndefined(output.data)
-                ? -1
-                : output.data._id;
-        } else if (customFilterErrorInJs === null) {
+        if (!customFilterErrorInJs && jsResponse.status > 0 && jsResponse.status !== 1) {
             customFilterErrorInJs = new WebAPIException(
-                WebAPIException.TYPE_MISMATCH_ERR,
-                'The value returned from CustomFilter is not a CustomFilterOutput object'
+                WebAPIException.INVALID_VALUES_ERR,
+                'The only legal positive value of status returned from CustomFilter is 1'
             );
+            jsResponse.status = -1;
         }
 
         /*
-         * Callback called in stage 4.
-         *
-         * It is used to process success/error messages that come from
-         * C++ (stage 3).
-         * It does not handle errors caused by the user-provided CustomFilter
-         * which we detect in JS.
+         * Entering stage 3.
          */
-        function filterOutputCallback(result) {
-            if (native_.isSuccess(result)) {
-                return;
-            }
-
-            var error = native_.getErrorObjectAndValidate(
-                result,
-                ValidCustomFilterOutputErrors,
-                AbortError
-            );
-
-            native_.callIfPossible(args.errorCallback, error);
-        }
+        var result = native_.callSync('MLPipelineManagerCustomFilterOutput', jsResponse);
 
         /*
-         * Entering stage 3.
+         * Stage 4.
          */
-        var result = native_.call(
-            'MLPipelineManagerCustomFilterOutput',
-            jsResponse,
-            filterOutputCallback
-        );
-
         if (customFilterErrorInJs) {
             /*
              * If we detect that user-provided CustomFilter callback caused
              * any errors in JS, the C++ layer gets the message to stop the
              * pipeline (status == -1) and does not reply to JS with errors.
-             * Thus, filterOutputCallback is not called and this is why we
-             * call the user-provided error callback from JS.
+             * Thus, "result" is a success we call the user-provided error
+             * callback here.
              */
             native_.callIfPossible(args.errorCallback, customFilterErrorInJs);
         } else if (native_.isFailure(result)) {
-            filterOutputCallback(result);
+            var error = native_.getErrorObjectAndValidate(
+                result,
+                ValidCustomFilterOutputErrors,
+                AbortError
+            );
+
+            native_.callIfPossible(args.errorCallback, error);
         }
     };