From: Nick Holland Date: Fri, 26 Jun 2015 14:51:24 +0000 (+0100) Subject: Node JS Addon for DALi + Improvements X-Git-Tag: dali_1.0.48~6^2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=9c6ea1aa6babb4e6e1c59187cad13b44e0639662 Node JS Addon for DALi + Improvements old: control = new dali.TextField(); new: control = new dali.Control('control-name'); old: log('my message \n'); new: console.log('my message'); // common.js style new: console.error('my error message') // added ability to require('dali') If we are running JavaScript in Node.JS it has to explicity call require('dali'); This is now supported in dali standalone as well to maintain code compatibility. Change-Id: I8f5f1828bb3e5df950f4fedf952013eb247906e5 --- diff --git a/node-addon/binding.gyp b/node-addon/binding.gyp new file mode 100644 index 0000000..d8f07f5 --- /dev/null +++ b/node-addon/binding.gyp @@ -0,0 +1,76 @@ +{ + "variables": { + 'DALI_JS_DIR%':'../plugins/dali-script-v8/src/' + }, + "targets": [ + { + 'target_name': 'dali', + 'include_dirs': ['<(DALI_JS_DIR)/', + '<(DALI_JS_DIR)/utils'], + 'sources': [ 'dali-addon.cpp', + 'javascript-application-options.cpp', + '<(DALI_JS_DIR)/utils/v8-utils.cpp', + '<(DALI_JS_DIR)/dali-wrapper.cpp', + '<(DALI_JS_DIR)/shared/base-wrapped-object.cpp', + '<(DALI_JS_DIR)/shared/object-template-helper.cpp', + '<(DALI_JS_DIR)/actors/actor-wrapper.cpp', + '<(DALI_JS_DIR)/actors/actor-api.cpp', + '<(DALI_JS_DIR)/actors/layer-api.cpp', + '<(DALI_JS_DIR)/actors/image-actor-api.cpp', + '<(DALI_JS_DIR)/actors/camera-actor-api.cpp', + '<(DALI_JS_DIR)/constants/constants-wrapper.cpp', + '<(DALI_JS_DIR)/animation/animation-api.cpp', + '<(DALI_JS_DIR)/animation/animation-wrapper.cpp', + '<(DALI_JS_DIR)/animation/constrainer-api.cpp', + '<(DALI_JS_DIR)/animation/linear-constrainer-wrapper.cpp', + '<(DALI_JS_DIR)/animation/path-api.cpp', + '<(DALI_JS_DIR)/animation/path-constrainer-wrapper.cpp', + '<(DALI_JS_DIR)/animation/path-wrapper.cpp', + '<(DALI_JS_DIR)/stage/stage-wrapper.cpp', + '<(DALI_JS_DIR)/events/event-object-generator.cpp', + '<(DALI_JS_DIR)/events/pan-gesture-detector-api.cpp', + '<(DALI_JS_DIR)/events/pan-gesture-detector-wrapper.cpp', + '<(DALI_JS_DIR)/stage/stage-api.cpp', + '<(DALI_JS_DIR)/shader-effects/shader-effect-api.cpp', + '<(DALI_JS_DIR)/shader-effects/shader-effect-wrapper.cpp', + '<(DALI_JS_DIR)/image/image-wrapper.cpp', + '<(DALI_JS_DIR)/image/image-api.cpp', + '<(DALI_JS_DIR)/image/buffer-image-api.cpp', + '<(DALI_JS_DIR)/image/native-image-api.cpp', + '<(DALI_JS_DIR)/image/frame-buffer-image-api.cpp', + '<(DALI_JS_DIR)/image/resource-image-api.cpp', + '<(DALI_JS_DIR)/image/nine-patch-image-api.cpp', + '<(DALI_JS_DIR)/object/handle-wrapper.cpp', + '<(DALI_JS_DIR)/object/property-value-wrapper.cpp', + '<(DALI_JS_DIR)/signals/signal-manager.cpp', + '<(DALI_JS_DIR)/render-tasks/render-task-list-api.cpp', + '<(DALI_JS_DIR)/render-tasks/render-task-list-wrapper.cpp', + '<(DALI_JS_DIR)/render-tasks/render-task-api.cpp', + '<(DALI_JS_DIR)/render-tasks/render-task-wrapper.cpp', + '<(DALI_JS_DIR)/toolkit/builder/builder-api.cpp', + '<(DALI_JS_DIR)/toolkit/builder/builder-wrapper.cpp', + '<(DALI_JS_DIR)/toolkit/focus-manager/keyboard-focus-manager-api.cpp', + '<(DALI_JS_DIR)/toolkit/focus-manager/keyboard-focus-manager-wrapper.cpp', + '<(DALI_JS_DIR)/signals/dali-any-javascript-converter.cpp', + '<(DALI_JS_DIR)/garbage-collector/garbage-collector.cpp', + '<(DALI_JS_DIR)/module-loader/module.cpp', + '<(DALI_JS_DIR)/module-loader/module-loader.cpp' + ], + 'cflags': [ + '-fPIC', + '-frtti', + ' +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include "javascript-application-options.h" + +using namespace Dali; + +namespace DaliNodeAddon +{ + +class DaliApplication +{ +public: + + DaliApplication() + : mInitialized( false), + mSingletonService(SingletonService::New()), + mAdaptor( NULL ) + { + } + + ~DaliApplication() + { + mSingletonService.UnregisterAll(); + delete mAdaptor; + mWindow.Reset(); + } + + bool Initialized() const + { + return mInitialized; + } + + void Initialize( ApplicationOptions options ) + { + if( mInitialized ) + { + return; + } + + // 1. Create the window ( adaptor requires a window) + const WindowOptions& window( options.window); + + mWindow = Window::New( window.positionSize, window.name, window.transparent ); + + // 2. create the adaptor + Adaptor* adaptor = &Adaptor::New( mWindow ); + + // 3. start the adaptor + adaptor->Start(); + + // Set the view modes + + if( options.stereo.viewMode > Dali::MONO ) + { + adaptor->SetStereoBase( options.stereo.stereoBase ); + adaptor->SetViewMode( options.stereo.viewMode ); + } + + // fire the scene create signal + adaptor->SceneCreated(); + + mInitialized = true; + } +private: + + bool mInitialized; + SingletonService mSingletonService; + Adaptor* mAdaptor; + Window mWindow; + +}; + +DaliApplication app; + +void CreateDali(const v8::FunctionCallbackInfo& args) +{ + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + + ApplicationOptions options; + + bool ok = GetApplicationOptions( args, options); + if (!ok ) + { + isolate->ThrowException( v8::Exception::TypeError( v8::String::NewFromUtf8(isolate, "Please check arguments passed to DALi require") ) ); + return; + } + + app.Initialize( options ); + + // the return value from calling the function require('dali.js)( options ) + // is the dali object + args.GetReturnValue().Set( V8Plugin::DaliWrapper::CreateWrapperForNodeJS( isolate ) ); +} + + +/** + * We make module.exports a function so that the developer can pass to + * parameters to DALi when it's 'required' + * E.g + * + * + * var window= { + * x:10, + * y:10, + * width:800, + * height: 600, + * transparent: false, + * name:'my-first-dali-app' + * }; + * + * var viewMode { + * 'stereoscopic-mode':'stereo-vertical', // mono, stereo-horizontal, stereo-vertical, stereo-interlaced, + * 'stereo-base': 65 // Distance in millimeters between left/right cameras typically between (50-70mm) + * }; + * + * var options= { + * 'window': window, + * 'view-mode': viewMode, + * 'style-sheet': 'my-theme.json' + * } + * + * var dali = require('dali.js')( options ) + * + * + */ +void ExportDaliModule(v8::Handle exports, v8::Handle module) +{ + NODE_SET_METHOD(module, "exports", CreateDali); +} + +} // namespace DaliNodeAddon + +NODE_MODULE(dali, DaliNodeAddon::ExportDaliModule) + + + + diff --git a/node-addon/javascript-application-options.cpp b/node-addon/javascript-application-options.cpp new file mode 100644 index 0000000..7811583 --- /dev/null +++ b/node-addon/javascript-application-options.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// HEADER +#include "javascript-application-options.h" + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include + +namespace DaliNodeAddon +{ + +namespace +{ + +struct StereoInfo +{ + const char* const name; + Dali::ViewMode mode; +}; +StereoInfo StereoModeTable[] = { + { "mono", Dali::MONO}, + { "stereo-horizontal", Dali::STEREO_HORIZONTAL }, + { "stereo-vertical", Dali::STEREO_VERTICAL }, + { "stereo-interlaced", Dali::STEREO_INTERLACED }, +}; + +const unsigned int numberViewModes = sizeof( StereoModeTable ) / sizeof( StereoModeTable[0] ); + +bool GetViewMode( const std::string& modeString, Dali::ViewMode& mode ) +{ + for( unsigned int i = 0; i < numberViewModes; ++i ) + { + const StereoInfo& info (StereoModeTable[i]); + + if( strcmp ( modeString.c_str() , info.name ) == 0 ) + { + mode = info.mode; + return true; + } + } + // mode not found + mode = Dali::MONO; + return false; +} + + +// Note we can't parse the enviroment options for window width / height because +// adaptor which holds the environment option class has not been created +// and we can't create it, until we have a window +bool ParseWindowOptions( v8::Isolate* isolate, const v8::Local& obj, WindowOptions& window ) +{ + v8::HandleScope scope(isolate); + + v8::Local xValue = obj->Get( v8::String::NewFromUtf8( isolate, "x" ) ); + v8::Local yValue = obj->Get( v8::String::NewFromUtf8( isolate, "y" ) ); + v8::Local widthValue = obj->Get( v8::String::NewFromUtf8( isolate, "width" ) ); + v8::Local heightValue = obj->Get( v8::String::NewFromUtf8( isolate, "height" ) ); + v8::Local nameValue = obj->Get( v8::String::NewFromUtf8( isolate, "name" ) ); + v8::Local transparencyValue = obj->Get( v8::String::NewFromUtf8( isolate, "transparent" ) ); + + // if x,y are optional + if( xValue->IsUint32() ) + { + window.positionSize.x = xValue->ToUint32()->Value(); + } + if( yValue->IsUint32() ) + { + window.positionSize.y = yValue->ToUint32()->Value(); + } + + // width and height are optional but will only accept them if they are both set + if( widthValue->IsUint32() && heightValue->IsUint32() ) + { + window.positionSize.width = widthValue->ToUint32()->Value(); + window.positionSize.height = heightValue->ToUint32()->Value(); + } + + // get the window name + if( nameValue->IsString() ) + { + window.name = Dali::V8Plugin::V8Utils::v8StringToStdString( nameValue ); + } + else + { + window.name ="DALi application"; + } + + if( transparencyValue->IsBoolean() ) + { + window.transparent = transparencyValue->ToBoolean()->Value(); + } + return true; +} + +bool ParseStereoScopicOptions( v8::Isolate* isolate, const v8::Local& stereoObject, StereoScopicOptions& options ) +{ + v8::HandleScope scope(isolate); + + v8::Local modeValue = stereoObject->Get( v8::String::NewFromUtf8( isolate, "stereoscopic-mode" ) ); + v8::Local stereoBaseValue = stereoObject->Get( v8::String::NewFromUtf8( isolate, "stereo-base" ) ); + + if( !modeValue->IsString() ) + { + return true; + } + + std::string mode = Dali::V8Plugin::V8Utils::v8StringToStdString( modeValue ); + bool ok = GetViewMode( mode, options.viewMode); + if( !ok ) + { + return false; + } + if( stereoBaseValue->IsNumber() ) + { + options.stereoBase = stereoBaseValue->ToNumber()->Value(); + } + + return true; +} + +} // unnamed namespace + +bool GetApplicationOptions(const v8::FunctionCallbackInfo& args, ApplicationOptions& options ) +{ + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + bool ok( false ); + + if( !args[ 0 ]->IsObject() ) + { + return false; + } + + v8::Local object = args[ 0 ]->ToObject(); + + // get the window settings + v8::Local windowValue= object->Get( v8::String::NewFromUtf8( isolate, "window" ) ); + if( windowValue->IsObject() ) + { + ok = ParseWindowOptions( isolate, windowValue->ToObject(), options.window ); + if( !ok ) + { + return false; // missing window size + } + } + + // get the stereoscopic settings + v8::Local stereoValue= object->Get( v8::String::NewFromUtf8( isolate, "view-mode" ) ); + if( stereoValue->IsObject() ) + { + ok = ParseStereoScopicOptions( isolate, stereoValue->ToObject(), options.stereo ); + if( !ok ) + { + return false; // incorrect stereoscopic mode + } + } + + // get the style sheet + v8::Local stylesheetValue= object->Get( v8::String::NewFromUtf8( isolate, "style-sheet" ) ); + if( stylesheetValue->IsString() ) + { + options.stylesheet = Dali::V8Plugin::V8Utils::v8StringToStdString( stylesheetValue ); + } + + return true; +} + +} // namespace DaliNodeAddon diff --git a/node-addon/javascript-application-options.h b/node-addon/javascript-application-options.h new file mode 100644 index 0000000..56c92fe --- /dev/null +++ b/node-addon/javascript-application-options.h @@ -0,0 +1,76 @@ +#ifndef __DALI_JAVASCRIPT_APPLICATION_OPTIONS_H__ +#define __DALI_JAVASCRIPT_APPLICATION_OPTIONS_H__ + +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +namespace DaliNodeAddon +{ + +struct WindowOptions +{ + WindowOptions() + : positionSize(0,0,0,0) + { + } + + std::string name; + Dali::PositionSize positionSize; + bool transparent:1; +}; + +struct StereoScopicOptions +{ + StereoScopicOptions() + : viewMode(Dali::MONO), + stereoBase(65.f) + { + } + + Dali::ViewMode viewMode; + float stereoBase; +}; + +struct ApplicationOptions +{ + WindowOptions window; + StereoScopicOptions stereo; + std::string stylesheet; +}; + +/** + * @brief parse the dali JavaScript application options + * The options are passed to dali when require is called. + * E.g. var dali.require('dali.js)( {options object} ) + * + * @param[in] args JavaScript arguments + * @param[out] options assigned Dali options + * @return true on success, false on failure + * + */ +bool GetApplicationOptions( const v8::FunctionCallbackInfo& args, ApplicationOptions& options ); + + +} // namespace DaliNodeAddon + + + +#endif // __DALI_JAVASCRIPT_APPLICATION_OPTIONS_H__ diff --git a/node-addon/package.json b/node-addon/package.json new file mode 100644 index 0000000..9314a16 --- /dev/null +++ b/node-addon/package.json @@ -0,0 +1,25 @@ +{ + "name": "dali", + "version": "0.0.0", + "description": "DALi 3D Engine addon", + "main": "index.js", + "gypfile": "true", + "scripts": { + "preinstall" : "./configure", + "install" : "make && make install", + "test" : "node test.js" + }, + "repository": { + "type": "git", + "url": "review.tizen.org:29418/platform/core/uifw/dali-core" + }, + "keywords": [ + "dali", + "3D", + "engine", + "OpenGL", + "user interface" + ], + "author": "Samsung", + "license": "http://www.apache.org/licenses/LICENSE-2.0" +} diff --git a/node-addon/test.js b/node-addon/test.js new file mode 100644 index 0000000..b3abe08 --- /dev/null +++ b/node-addon/test.js @@ -0,0 +1,149 @@ + var window= { + x:800, + y:500, + width:880, + height: 1020, + transparent: false, + name:'my-first-dali-app' + }; +var viewMode={ + 'stereoscopic-mode':'mono', // stereo-horizontal, stereo-vertical, stereo-interlaced, + 'stereo-base': 65 // Distance in millimeters between left/right cameras typically between (50-70mm) + }; + var options= { + 'window': window, + 'view-mode': viewMode, + } + +var dali = require('./build/Release/dali')( options ); +var netflixRoulette = require('netflix-roulette'); + + +var NUMBER_OF_IMAGES = 40; // for now use 16 ( demo files go up to 30) +var DEMO_IMAGES = []; // array to store Dali Images +var VIDEO_WALL_ACTORS = []; // array to store Image actors +var VIDEO_WALL_ROWS = 5; // use 3 rows for the video wall +var VIDEO_WALL_COLUMNS = 6; // use 12 columns for the video wall +var VIDEO_WALL_TOTAL_ITEMS = VIDEO_WALL_COLUMNS * VIDEO_WALL_ROWS; // total items +var VIDEO_WALL_ITEM_SIZE = 128; // width / height of a item in the video wall +var BORDER_SIZE = 5; +var VIDEO_WALL_ITEM_SIZE_NO_BORDER = VIDEO_WALL_ITEM_SIZE - BORDER_SIZE; +var VIDEO_WALL_WIDTH = VIDEO_WALL_COLUMNS * VIDEO_WALL_ITEM_SIZE; +var VIDEO_WALL_HEIGHT = VIDEO_WALL_ROWS * VIDEO_WALL_ITEM_SIZE; +var daliApp = {}; +var posters = []; + +var wallRootActor; // the root actor of the video wall + + +daliApp.loadNetflixImages = function() { + + if( NUMBER_OF_IMAGES >= VIDEO_WALL_TOTAL_ITEMS) + { + NUMBER_OF_IMAGES = VIDEO_WALL_TOTAL_ITEMS-1; + } + + for (index = 0; index < NUMBER_OF_IMAGES; ++index) { + + fileName = posters[ index % (posters.length-1) ]; + if ( fileName ) + { + DEMO_IMAGES[index] = new dali.ResourceImage( { url:fileName } ); + } + } +} + + +daliApp.createRootActor = function() { + wallRootActor = new dali.Actor(); + wallRootActor.parentOrigin = dali.CENTER; + wallRootActor.anchorPoint = dali.CENTER; + dali.stage.add(wallRootActor); + + var field = new dali.Control("TextField"); + field.parentOrigin = dali.CENTER; + field.anchorPoint = dali.CENTER; + + field.placeholderText = "DALi netflix netflix-roulette demo"; + dali.stage.add( field ); +} + + + +daliApp.getWallActorIndex = function(x, y) { + return x + y * VIDEO_WALL_COLUMNS; +} + +daliApp.createActors = function() { + daliApp.createRootActor(); + + var anim = new dali.Animation(1); + var animOptions = { + alpha: "linear", + delay: 0.0, // used to delay the start of the animation + duration: 1, // duration of the animation + }; + + for (y = 0; y < VIDEO_WALL_ROWS; ++y) { + for (x = 0; x < VIDEO_WALL_COLUMNS; ++x) { + + var actorIndex = daliApp.getWallActorIndex(x, y); + var imageActor = new dali.ImageActor(); + + // wrap image index between 0 and NUMBER_OF_IMAGES + var imageIndex = actorIndex % NUMBER_OF_IMAGES; + + imageActor.setImage(DEMO_IMAGES[imageIndex]); + + imageActor.parentOrigin = dali.CENTER; + imageActor.anchorPoint = dali.CENTER; + imageActor.size = [VIDEO_WALL_ITEM_SIZE_NO_BORDER, VIDEO_WALL_ITEM_SIZE_NO_BORDER, 1.0]; // start with zero size so it zooms up + + var xPosition = x * VIDEO_WALL_ITEM_SIZE; + // as the middle the wall is at zero (relative to wallRootActor), we need to subtract half the wall width. + // + add half item size because the item anchor point is the center of the wallRootActor. + xPosition = xPosition - (VIDEO_WALL_WIDTH / 2) + (VIDEO_WALL_ITEM_SIZE / 2); + + var yPosition = y * VIDEO_WALL_ITEM_SIZE; + yPosition = yPosition - (VIDEO_WALL_HEIGHT / 2) + (VIDEO_WALL_ITEM_SIZE / 2); + + imageActor.position = [0,0,0]; + + animOptions.delay+=0.25; + anim.animateTo( imageActor,"position",[xPosition, yPosition, 0.0],animOptions); + // store the actor + VIDEO_WALL_ACTORS[actorIndex] = imageActor; + + // Add to the video wall root actor. + wallRootActor.add(imageActor); + } + } + anim.play(); +} + +function Initialise() { + + daliApp.loadNetflixImages(); + + daliApp.createActors(); +} + +function actorLoaded( error, data ) +{ + for( i = 0; i < data.length; ++i ) + { + var entry = data[i]; + + if( entry.poster ) + { + posters.push(entry.poster); + //console.log(" entry = " + entry.poster ); + } + } + Initialise(); + +} + + +netflixRoulette.actor('nicolas', actorLoaded ); + diff --git a/plugins/dali-script-v8/src/actors/actor-wrapper.cpp b/plugins/dali-script-v8/src/actors/actor-wrapper.cpp index 704c2d1..7b9b158 100644 --- a/plugins/dali-script-v8/src/actors/actor-wrapper.cpp +++ b/plugins/dali-script-v8/src/actors/actor-wrapper.cpp @@ -39,7 +39,6 @@ v8::Persistent ActorWrapper::mActorTemplate; v8::Persistent ActorWrapper::mImageActorTemplate; v8::Persistent ActorWrapper::mCameraActorTemplate; v8::Persistent ActorWrapper::mLayerActorTemplate; -v8::Persistent ActorWrapper::mTextLabelTemplate; namespace { @@ -61,8 +60,7 @@ const ActorTemplate ActorTemplateLookup[]= { &ActorWrapper::mActorTemplate }, // ACTOR { &ActorWrapper::mImageActorTemplate }, // IMAGE_ACTOR { &ActorWrapper::mLayerActorTemplate }, // LAYER_ACTOR - { &ActorWrapper::mCameraActorTemplate}, // CAMERA_ACTOR - { &ActorWrapper::mTextLabelTemplate } + { &ActorWrapper::mCameraActorTemplate} // CAMERA_ACTOR }; /** @@ -89,6 +87,7 @@ struct ActorApiStruct /** * Lookup table to match a actor type with a constructor and supported API's. + * HandleWrapper::ActorType is used to index this table */ const ActorApiStruct ActorApiLookup[]= { @@ -96,8 +95,6 @@ const ActorApiStruct ActorApiLookup[]= {"ImageActor", ActorWrapper::IMAGE_ACTOR, ImageActorApi::New, ACTOR_API | IMAGE_ACTOR_API }, {"Layer", ActorWrapper::LAYER_ACTOR, LayerApi::New, ACTOR_API | LAYER_API }, {"CameraActor",ActorWrapper::CAMERA_ACTOR, CameraActorApi::New, ACTOR_API | CAMERA_ACTOR_API }, - {"TextLabel", ActorWrapper::TEXT_LABEL, TextLabelApi::New, ACTOR_API }, - }; const unsigned int ActorApiLookupCount = sizeof(ActorApiLookup)/sizeof(ActorApiLookup[0]); @@ -118,20 +115,8 @@ Actor CreateActor( const v8::FunctionCallbackInfo< v8::Value >& args, // if we don't currently support the actor type, then use type registry to create it if( actorType == ActorWrapper::UNKNOWN_ACTOR ) { - Dali::TypeInfo typeInfo = Dali::TypeRegistry::Get().GetTypeInfo( typeName ); - if( typeInfo ) // handle, check if it has a value - { - Dali::BaseHandle handle = typeInfo.CreateInstance(); - if( handle ) - { - actor = Actor::DownCast( handle ); - } - } - else - { - DALI_SCRIPT_EXCEPTION(args.GetIsolate(),"Unknown actor type"); + DALI_SCRIPT_EXCEPTION( args.GetIsolate(), "Unknown actor type" ); return Actor(); - } } else { @@ -440,6 +425,42 @@ void ActorWrapper::NewActor( const v8::FunctionCallbackInfo< v8::Value >& args) args.GetReturnValue().Set( localObject ); } +void ActorWrapper::NewControl( const v8::FunctionCallbackInfo< v8::Value >& args) +{ + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handleScope( isolate ); + + if( !args.IsConstructCall() ) + { + DALI_SCRIPT_EXCEPTION( isolate, "constructor called without 'new" ); + return; + } + + bool found( false ); + std::string controlName = V8Utils::GetStringParameter( PARAMETER_0, found, isolate, args ); + + if( !found ) + { + DALI_SCRIPT_EXCEPTION( isolate, "missing control name" ); + return; + } + Actor control; + Dali::TypeInfo typeInfo = Dali::TypeRegistry::Get().GetTypeInfo( controlName ); + if( typeInfo ) // handle, check if it has a value + { + Dali::BaseHandle handle = typeInfo.CreateInstance(); + if( handle ) + { + control = Actor::DownCast( handle ); + } + } + + v8::Local localObject = WrapActor( isolate, control, ACTOR ); + + args.GetReturnValue().Set( localObject ); +} + + /** * given an actor type name, e.g. ImageActor returns the type, e.g. ActorWrapper::IMAGE_ACTOR */ diff --git a/plugins/dali-script-v8/src/actors/actor-wrapper.h b/plugins/dali-script-v8/src/actors/actor-wrapper.h index 06b2e06..13cd811 100644 --- a/plugins/dali-script-v8/src/actors/actor-wrapper.h +++ b/plugins/dali-script-v8/src/actors/actor-wrapper.h @@ -41,17 +41,17 @@ class ActorWrapper : public HandleWrapper public: /** - * Actor type used an index + * Actor type used an index. + * These enums are used to index the ActorApiLookup table in actor-wrapper.cpp. + * Any changes made must be reflected in the ActorApiLookup otherwise it may segfault when creating an actor */ enum ActorType { UNKNOWN_ACTOR = -1, - ACTOR = 0, - IMAGE_ACTOR =1, - MESH_ACTOR =2, - LAYER_ACTOR =3, - CAMERA_ACTOR =4, - TEXT_LABEL =5 + ACTOR = 0, + IMAGE_ACTOR = 1, + LAYER_ACTOR = 2, + CAMERA_ACTOR = 3 }; /** @@ -77,6 +77,13 @@ public: static void NewActor( const v8::FunctionCallbackInfo< v8::Value >& args); /** + * @brief Creates a new Control wrapped inside a Javascript Object. + * @note: the control type is passed as a parameter e.g. 'TextField' + * @param[in] args v8 function call arguments interpreted + */ + static void NewControl( const v8::FunctionCallbackInfo< v8::Value >& args); + + /** * @brief Wraps an actor of a given type */ static v8::Handle WrapActor(v8::Isolate* isolate, Dali::Actor actor,ActorType actorType); @@ -89,10 +96,8 @@ public: // The Actor ObjectTemplates. static v8::Persistent mActorTemplate; static v8::Persistent mImageActorTemplate; - static v8::Persistent mMeshActorTemplate; static v8::Persistent mCameraActorTemplate; static v8::Persistent mLayerActorTemplate; - static v8::Persistent mTextLabelTemplate; /** * @return the wrapped actor diff --git a/plugins/dali-script-v8/src/dali-wrapper.cpp b/plugins/dali-script-v8/src/dali-wrapper.cpp index 93c85fc..c716f75 100644 --- a/plugins/dali-script-v8/src/dali-wrapper.cpp +++ b/plugins/dali-script-v8/src/dali-wrapper.cpp @@ -68,7 +68,7 @@ const ApiFunction ConstructorFunctionTable[]= { "MeshActor", ActorWrapper::NewActor }, { "CameraActor", ActorWrapper::NewActor }, { "Layer", ActorWrapper::NewActor }, - { "TextView", ActorWrapper::NewActor }, + { "Control", ActorWrapper::NewControl }, { "ResourceImage", ImageWrapper::NewImage }, { "BufferImage", ImageWrapper::NewImage }, { "NinePatchImage", ImageWrapper::NewImage }, @@ -99,8 +99,9 @@ Integration::Log::Filter* gLogExecuteFilter( Integration::Log::Filter::New(Debug bool DaliWrapper::mInstanceCreated = false; DaliWrapper* DaliWrapper::mWrapper = NULL; -DaliWrapper::DaliWrapper() -:mIsolate( NULL ) +DaliWrapper::DaliWrapper( RunMode runMode, v8::Isolate* isolate ) +:mIsolate( isolate ), + mRunMode(runMode) { } @@ -111,20 +112,60 @@ DaliWrapper::~DaliWrapper() DaliWrapper& DaliWrapper::Get() { - if(!mInstanceCreated) + if( !mInstanceCreated ) { - mWrapper = new DaliWrapper(); + mWrapper = new DaliWrapper( RUNNING_STANDALONE, NULL ); + mInstanceCreated = true; - if(mWrapper) - { - mWrapper->Initialize(); - } - } + mWrapper->InitializeStandAlone(); + } return *mWrapper; } +v8::Local DaliWrapper::CreateWrapperForNodeJS( v8::Isolate* isolate ) +{ + v8::EscapableHandleScope handleScope( isolate); + + mInstanceCreated = true; + + mWrapper = new DaliWrapper( RUNNING_IN_NODE_JS, isolate ); + + v8::Local dali = mWrapper->CreateDaliObject(); + + // As we running inside node, we already have an isolate and context + return handleScope.Escape( dali ); +} + +v8::Local DaliWrapper::CreateDaliObject() +{ + v8::EscapableHandleScope handleScope( mIsolate ); + + // Create dali object used for creating objects, and accessing constant values + // e.g. var x = new dali.Actor(), or var col = dali.COLOR_RED; + + v8::Local daliObjectTemplate = NewDaliObjectTemplate( mIsolate ); + + // add dali.staqe + v8::Local stageObject = StageWrapper::WrapStage( mIsolate, Stage::GetCurrent() ); + daliObjectTemplate->Set( v8::String::NewFromUtf8( mIsolate, "stage") , stageObject ); + + v8::Local keyboardObject = KeyboardFocusManagerWrapper::WrapKeyboardFocusManager( mIsolate,Toolkit::KeyboardFocusManager::Get() ); + daliObjectTemplate->Set( v8::String::NewFromUtf8( mIsolate, "keyboardFocusManager") , keyboardObject ); + + + //create an instance of the template + v8::Local daliObject = daliObjectTemplate->NewInstance(); + + ConstantsWrapper::AddDaliConstants( mIsolate, daliObject); + + daliObject->Set( v8::String::NewFromUtf8( mIsolate, "V8_VERSION") ,v8::String::NewFromUtf8( mIsolate, v8::V8::GetVersion() )); + + return handleScope.Escape( daliObject ); +} + + void DaliWrapper::SetFlagsFromString(const std::string &flags) { v8::V8::SetFlagsFromString(flags.c_str(), flags.size()); @@ -132,6 +173,12 @@ void DaliWrapper::SetFlagsFromString(const std::string &flags) void DaliWrapper::Shutdown() { + // if we're running inside node then we don't have ownership of the context + if( mRunMode == RUNNING_IN_NODE_JS ) + { + return; + } + DALI_LOG_WARNING("Destroying V8 DALi context\n"); if( !mContext.IsEmpty()) @@ -160,35 +207,33 @@ GarbageCollectorInterface& DaliWrapper::GetDaliGarbageCollector() return mGarbageCollector; } -void DaliWrapper::CreateContext( ) +void DaliWrapper::ApplyGlobalObjectsToContext( v8::Local context ) { v8::HandleScope handleScope( mIsolate ); - // Create a global JavaScript object so we can set built-in global functions, like Log. - v8::Handle global = v8::ObjectTemplate::New( mIsolate ); + // Add global objects ( functions/ values ) e.g. log function + // create a console.log and console.error functions + v8::Local consoleObjectTemplate = v8::ObjectTemplate::New( mIsolate ); + consoleObjectTemplate->Set( v8::String::NewFromUtf8( mIsolate, "log"), v8::FunctionTemplate::New( mIsolate, V8Utils::Log)); + consoleObjectTemplate->Set( v8::String::NewFromUtf8( mIsolate, "error"), v8::FunctionTemplate::New( mIsolate, V8Utils::LogError)); - // Add global objects ( functions/ values ) e.g. log function and V8_VERSION - global->Set( v8::String::NewFromUtf8( mIsolate, "log"), v8::FunctionTemplate::New( mIsolate, V8Utils::Log) ); - global->Set( v8::String::NewFromUtf8( mIsolate, "logError"), v8::FunctionTemplate::New( mIsolate, V8Utils::LogError) ); - global->Set( v8::String::NewFromUtf8( mIsolate, "require"), v8::FunctionTemplate::New( mIsolate, DaliWrapper::Require)); - global->Set( v8::String::NewFromUtf8( mIsolate, "V8_VERSION") ,v8::String::NewFromUtf8( mIsolate, v8::V8::GetVersion() )); + context->Global()->Set( v8::String::NewFromUtf8( mIsolate, "console"), consoleObjectTemplate->NewInstance() ); - // add the dali object to it, assume it won't be garbage collected until global is deleted - global->Set(v8::String::NewFromUtf8( mIsolate, DALI_API_NAME) , NewDaliObjectTemplate( mIsolate )); + // add require functionality + context->Global()->Set( v8::String::NewFromUtf8( mIsolate, "require"), v8::FunctionTemplate::New( mIsolate, DaliWrapper::Require)->GetFunction()); + // Create the Dali object + // @todo consider forcing developers to perform require('dali') if we want to avoid polluting the global namespace + v8::Local daliObject = CreateDaliObject(); - // create a new context. - // Isolate = isolated copy of the V8 including a heap manager, a garbage collector - // Only 1 thread can access a single Isolate at a given time. However, multiple Isolates can be run in parallel. - // Context = multiple contexts can exist in a given Isolate, and share data between contexts - v8::Handle context = v8::Context::New( mIsolate, NULL, global); + // allow developers to require('dali'); // this is to maintain compatibility with node.js where dali is not part of the global namespace + mModuleLoader.StorePreBuiltModule( mIsolate, daliObject, DALI_API_NAME ); - mGlobalObjectTemplate.Reset( mIsolate, global); + context->Global()->Set( v8::String::NewFromUtf8( mIsolate, DALI_API_NAME),daliObject ); - mContext.Reset( mIsolate, context); } -void DaliWrapper::Initialize() +void DaliWrapper::InitializeStandAlone() { if( !mIsolate ) { @@ -198,38 +243,35 @@ void DaliWrapper::Initialize() // default isolate removed from V8 version 3.27.1 and beyond. mIsolate = v8::Isolate::New(); + mIsolate->Enter(); v8::V8::SetFatalErrorHandler( FatalErrorCallback ); - } + // if context is null, create it and add dali object to the global object. if( mContext.IsEmpty()) { v8::HandleScope handleScope( mIsolate ); - CreateContext(); - v8::Local context = v8::Local::New(mIsolate, mContext); - - context->Enter(); - // Add the dali global object. Used for creating objects, and accessing constant values - // e.g. var x = new dali.ImageActor(), or var col = dali.COLOR_RED; + // create a new context. + // Isolate = isolated copy of the V8 including a heap manager, a garbage collector + // Only 1 thread can access a single Isolate at a given time. However, multiple Isolates can be run in parallel. + // Context = multiple contexts can exist in a given Isolate, and share data between contexts + v8::Local context = v8::Context::New( mIsolate ); - v8::Local daliObject = v8::Local::Cast( context->Global()->Get( v8::String::NewFromUtf8( mIsolate, DALI_API_NAME))); - - v8::Local stageObject = StageWrapper::WrapStage( mIsolate, Stage::GetCurrent() ); - daliObject->Set( v8::String::NewFromUtf8( mIsolate, "stage") , stageObject ); - - // keyboard focus manager is a singleton - v8::Local keyboardObject = KeyboardFocusManagerWrapper::WrapKeyboardFocusManager( mIsolate,Toolkit::KeyboardFocusManager::Get() ); - daliObject->Set( v8::String::NewFromUtf8( mIsolate, "keyboardFocusManager") , keyboardObject ); + context->Enter(); - ConstantsWrapper::AddDaliConstants( mIsolate, daliObject); + // Apply global objects like dali and console to the context + ApplyGlobalObjectsToContext(context); + mContext.Reset( mIsolate, context); } + DALI_LOG_INFO( gLogExecuteFilter, Debug::Verbose, "V8 Library %s loaded \n", v8::V8::GetVersion() ); } + v8::Handle DaliWrapper::NewDaliObjectTemplate( v8::Isolate* isolate ) { v8::EscapableHandleScope handleScope( isolate ); @@ -241,11 +283,11 @@ v8::Handle DaliWrapper::NewDaliObjectTemplate( v8::Isolate* objTemplate->Set( v8::String::NewFromUtf8( isolate, "BUILD"), v8::String::NewFromUtf8( isolate, "Dali binary built on:" __DATE__ ", at " __TIME__)); - +#ifdef DALI_DATA_READ_ONLY_DIR // add the data data directory, objTemplate->Set( v8::String::NewFromUtf8( isolate, "DALI_DATA_DIRECTORY"), - v8::String::NewFromUtf8( isolate, DALI_DATA_READ_ONLY_DIR)); - + v8::String::NewFromUtf8( isolate, DALI_DATA_READ_ONLY_DIR)); +#endif // add our constructor functions ObjectTemplateHelper::InstallFunctions( isolate, objTemplate, @@ -259,12 +301,10 @@ v8::Handle DaliWrapper::NewDaliObjectTemplate( v8::Isolate* void DaliWrapper::Require(const v8::FunctionCallbackInfo< v8::Value >& args) { DaliWrapper& wrapper( DaliWrapper::Get() ); - wrapper.mModuleLoader.Require( args, wrapper.mGlobalObjectTemplate ); + wrapper.mModuleLoader.Require( args ); } - - } // namespace V8Plugin } // namespace Dali diff --git a/plugins/dali-script-v8/src/dali-wrapper.h b/plugins/dali-script-v8/src/dali-wrapper.h index e576f4b..f1767e4 100644 --- a/plugins/dali-script-v8/src/dali-wrapper.h +++ b/plugins/dali-script-v8/src/dali-wrapper.h @@ -24,6 +24,7 @@ // INTERNAL INCLUDES #include + #include namespace Dali @@ -65,12 +66,25 @@ namespace V8Plugin */ class DALI_INTERNAL DaliWrapper { + public: /** - * Constructor + * @brief whether the wrapper is running in standalone using V8, + * or inside Node.JS using V8 */ - DaliWrapper(); + enum RunMode + { + RUNNING_STANDALONE, + RUNNING_IN_NODE_JS + }; + + /** + * @brief Constructor + * @param[in] runMode whether the wrapper is running standalone or inside Node.JS + * @param[in] isolate v8 isolate ( can be null if running standalone ) + */ + DaliWrapper( RunMode runMode, v8::Isolate* isolate ); /** * non virtual destructor, not intended as a base class @@ -85,6 +99,12 @@ public: static DaliWrapper& Get(); /** + * Intialize DaliWrapper for running inside NodeJS + */ + static v8::Local CreateWrapperForNodeJS( v8::Isolate* isolate); + + + /** * Set V8 engine configuration flags * * @param[in] flags Configruation flags (See v8 documentation) @@ -124,14 +144,21 @@ public: private: /** - * Create V8 context + * @brief Apply global objects like console.log and require() to the context */ - void CreateContext(); + void ApplyGlobalObjectsToContext( v8::Local context ); /** - * Initialize DaliWrapper + * @brief Initialize DaliWrapper for running standalone + * Creates a new isolate + * */ - void Initialize(); + void InitializeStandAlone(); + + /** + * @brief create dali namespace/object + */ + v8::Local CreateDaliObject(); /** * Create Dali ObjectTemplate @@ -150,9 +177,8 @@ private: GarbageCollector mGarbageCollector; ///< DALi garbage collector ModuleLoader mModuleLoader; ///< Module loader v8::Persistent mContext; ///< A sandboxed execution context with its own set of built-in objects and functions. - v8::Persistent mGlobalObjectTemplate; ///< Global object template for storing things like dali global object v8::Isolate* mIsolate; ///< represents an isolated instance of the V8 engine. - + RunMode mRunMode; }; diff --git a/plugins/dali-script-v8/src/module-loader/module-loader.cpp b/plugins/dali-script-v8/src/module-loader/module-loader.cpp index d475c90..12ad13c 100644 --- a/plugins/dali-script-v8/src/module-loader/module-loader.cpp +++ b/plugins/dali-script-v8/src/module-loader/module-loader.cpp @@ -29,7 +29,6 @@ namespace V8Plugin ModuleLoader::ModuleLoader() { - } ModuleLoader::~ModuleLoader() @@ -311,9 +310,7 @@ bool ModuleLoader::ExecuteScriptFromFile( v8::Isolate* isolate, * @for ModuleLoader * */ -void ModuleLoader::Require(const v8::FunctionCallbackInfo< v8::Value >& args, - - v8::Persistent& globalObjectTemplate ) +void ModuleLoader::Require(const v8::FunctionCallbackInfo< v8::Value >& args ) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope handleScope( isolate ); @@ -393,6 +390,12 @@ void ModuleLoader::Require(const v8::FunctionCallbackInfo< v8::Value >& args, args.GetReturnValue().Set( moduleExports ); } + +void ModuleLoader::StorePreBuiltModule( v8::Isolate* isolate, v8::Local& exportObject, const std::string& name ) +{ + StoreModule( "", name, name, isolate, exportObject ); +} + void ModuleLoader::StoreScriptInfo( const std::string& sourceFileName ) { V8Utils::GetFileDirectory( sourceFileName, mCurrentScriptPath); diff --git a/plugins/dali-script-v8/src/module-loader/module-loader.h b/plugins/dali-script-v8/src/module-loader/module-loader.h index 085f33f..001f83e 100644 --- a/plugins/dali-script-v8/src/module-loader/module-loader.h +++ b/plugins/dali-script-v8/src/module-loader/module-loader.h @@ -64,18 +64,21 @@ class ModuleLoader public: /** - * Constructor + * @brief Constructor + * @param[in] isolate v8 isolate + * @param[in] daliObject dali exports object, used when developer does require('dali'); */ ModuleLoader(); /** - * non virtual destructor, not intended as a base class + * @brief non virtual destructor, not intended as a base class */ ~ModuleLoader(); /** - * Execute a script from a file + * @brief Execute a script from a file + * @param[in] isolate v8 isolate * @param[in] fileName file name * @return true on success, false on failure * @@ -84,7 +87,8 @@ public: /** - * Execute a script + * @brief Execute a script + * @param[in] isolate v8 isolate * @param[in] sourceCode source code to run * @param[in] sourceFileName source file name * @return true on success, false on failure @@ -95,9 +99,21 @@ public: /** - * Implements JavaScript Require functionality + * @brief Implements JavaScript Require functionality + * @param[in] args arguments passed to require. The return value is set using args.GetReturnValue().Set( */ - void Require(const v8::FunctionCallbackInfo< v8::Value >& args, v8::Persistent& globalObjectTemplate ); + void Require( const v8::FunctionCallbackInfo< v8::Value >& args ); + + /** + * @brief + * Stores a pre compiled object as a module. + * Currently used for storing the Dali object, so the developer can + * perform var dali = require('dali'); + * @param[in] isolate v8 isolate + * @param[in] exportObject export object + * @param[in] name module name, used for the require('name') lookup + */ + void StorePreBuiltModule( v8::Isolate* isolate, v8::Local& exportObject, const std::string& name ); private: @@ -111,13 +127,13 @@ private: const std::string& sourceFileName ); /** - * Store information about the current script + * @brief Store information about the current script * @param[in] sourceFileName source file name */ void StoreScriptInfo( const std::string& sourceFileName ); /** - * Store module information + * @brief Store module information * @param[in] sourceFileName source file name * @return module object */ @@ -128,7 +144,7 @@ private: v8::Local& moduleExportsObject ); /** - * Find a module + * @brief Find a module * @param[in] moduleName module name * @return module */ diff --git a/plugins/dali-script-v8/src/utils/v8-utils.cpp b/plugins/dali-script-v8/src/utils/v8-utils.cpp index d9fbc7e..697b9dd 100644 --- a/plugins/dali-script-v8/src/utils/v8-utils.cpp +++ b/plugins/dali-script-v8/src/utils/v8-utils.cpp @@ -64,7 +64,7 @@ void Log(const v8::FunctionCallbackInfo< v8::Value >& args) std::cout << " "; } v8::String::Utf8Value utf8_value( args[i] ); - std::cout << *utf8_value; + std::cout << *utf8_value << "\n"; } } @@ -85,6 +85,7 @@ void LogError(const v8::FunctionCallbackInfo< v8::Value >& args) } v8::String::Utf8Value utf8_value( args[i] ); output += *utf8_value; + output +="\n"; } DALI_LOG_ERROR_NOFN( "JavaScript: %s",output.c_str() ); }