1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3  * Copyright 2019 Pelican Mapping
4  * http://osgearth.org
5  *
6  * osgEarth is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 #include "DuktapeEngine"
20 #include "JSGeometry"
21 #include <osgEarth/JsonUtils>
22 #include <osgEarth/StringUtils>
23 #include <osgEarthFeatures/GeometryUtils>
24 #include <sstream>
25 
26 #undef  LC
27 #define LC "[duktape] "
28 
29 // defining this will setup and tear down a complete duktape heap/context
30 // for each and every invocation. Good for testing memory usage until we
31 // complete the feature set.
32 //#define MAXIMUM_ISOLATION
33 
34 using namespace osgEarth;
35 using namespace osgEarth::Features;
36 using namespace osgEarth::Drivers::Duktape;
37 
38 //............................................................................
39 
40 namespace
41 {
42     // generic logging function.
log(duk_context * ctx)43     static duk_ret_t log( duk_context *ctx ) {
44         duk_idx_t i, n;
45 
46         std::string msg;
47         for( i = 0, n = duk_get_top( ctx ); i < n; i++ ) {
48             if( i > 0 ) {
49                 msg += " ";
50             }
51             msg += duk_safe_to_string( ctx, i );
52         }
53         OE_WARN << LC << msg << std::endl;
54         return 0;
55     }
56 
oe_duk_save_feature(duk_context * ctx)57     static duk_ret_t oe_duk_save_feature(duk_context* ctx)
58     {
59         // stack: [ptr]
60 
61         // pull the feature ptr from argument #0
62         Feature* feature = reinterpret_cast<Feature*>(duk_require_pointer(ctx, 0));
63 
64         // Fetch the feature data:
65         duk_push_global_object(ctx);
66         // [ptr, global]
67 
68         if ( !duk_get_prop_string(ctx, -1, "feature") || !duk_is_object(ctx, -1))
69             return 0;
70 
71          // [ptr, global, feature]
72 
73         if ( duk_get_prop_string(ctx, -1, "properties") && duk_is_object(ctx, -1) )
74         {
75             // [ptr, global, feature, props]
76             duk_enum(ctx, -1, 0);
77 
78             // [ptr, global, feature, props, enum]
79             while( duk_next(ctx, -1, 1/*get_value=true*/) )
80             {
81                 std::string key( duk_get_string(ctx, -2) );
82                 if (duk_is_string(ctx, -1))
83                 {
84                     feature->set( key, std::string(duk_get_string(ctx, -1)) );
85                 }
86                 else if (duk_is_number(ctx, -1))
87                 {
88                     feature->set( key, (double)duk_get_number(ctx, -1) );
89                 }
90                 else if (duk_is_boolean(ctx, -1))
91                 {
92                     feature->set( key, duk_get_boolean(ctx, -1) );
93                 }
94                 else if( duk_is_null_or_undefined( ctx, -1 ) )
95                 {
96                     feature->setNull( key );
97                 }
98                  duk_pop_2(ctx);
99             }
100 
101             duk_pop_2(ctx);
102             // [ptr, global, feature]
103         }
104         else
105         {   // [ptr, global, feature, undefined]
106             duk_pop(ctx);
107             // [ptr, global, feature]
108         }
109 
110         // save the geometry, if set:
111         if ( duk_get_prop_string(ctx, -1, "geometry") )
112         {
113             if (duk_is_object(ctx, -1))
114             {
115                 // [ptr, global, feature, geometry]
116                 std::string json( duk_json_encode(ctx, -1) ); // [ptr, global, feature, json]
117                 Geometry* newGeom = GeometryUtils::geometryFromGeoJSON(json);
118                 if ( newGeom )
119                 {
120                     feature->setGeometry( newGeom );
121                 }
122                 duk_pop(ctx); // [ptr, global, feature]
123             }
124             else
125             {
126                 feature->setGeometry(0L);
127             }
128         }
129         else
130         {
131             // [ptr, global, feature, undefined]
132         }
133 
134         // [ptr, global, feature]
135         duk_pop_2(ctx);     // [ptr] (as we found it)
136         return 0;           // no return values.
137     }
138 }
139 
140 //............................................................................
141 
142 namespace
143 {
144     // Create a "feature" object in the global namespace.
setFeature(duk_context * ctx,Feature const * feature,bool complete)145     void setFeature(duk_context* ctx, Feature const* feature, bool complete)
146     {
147         duk_push_global_object(ctx);                             // [global]
148 
149         // Complete profile: properties, geometry, and API bindings.
150         if ( complete )
151         {
152             std::string geojson = feature->getGeoJSON();
153             duk_push_string(ctx, geojson.c_str());               // [global, json]
154             duk_json_decode(ctx, -1);                            // [global, feature]
155             duk_push_pointer(ctx, (void*)feature);               // [global, feature, ptr]
156             duk_put_prop_string(ctx, -2, "__ptr");               // [global, feature]
157             duk_put_prop_string(ctx, -2, "feature");             // [global]
158 
159             // add the save() function and the "attributes" alias.
160             duk_eval_string_noresult(ctx,
161                 "feature.save = function() {"
162                 "    oe_duk_save_feature(this.__ptr);"
163                 "} ");
164 
165             duk_eval_string_noresult(ctx,
166                 "Object.defineProperty(feature, 'attributes', {get:function() {return feature.properties;}});");
167 
168             GeometryAPI::bindToFeature(ctx);
169         }
170 
171         // Minimal profile: ID and properties only. MUCH faster!
172         else
173         {
174             duk_idx_t feature_i = duk_push_object(ctx);
175             {
176                 duk_push_int(ctx, feature->getFID());
177                 duk_put_prop_string(ctx, feature_i, "id");
178 
179                 duk_idx_t props_i = duk_push_object(ctx);
180                 {
181                     const AttributeTable& attrs = feature->getAttrs();
182                     for(AttributeTable::const_iterator a = attrs.begin(); a != attrs.end(); ++a)
183                     {
184                         AttributeType type = a->second.first;
185                         switch(type) {
186                         case ATTRTYPE_DOUBLE: duk_push_number (ctx, a->second.getDouble()); break;
187                         case ATTRTYPE_INT:    duk_push_int    (ctx, a->second.getInt()); break;
188                         case ATTRTYPE_BOOL:   duk_push_boolean(ctx, a->second.getBool()); break;
189                         case ATTRTYPE_STRING:
190                         default:              duk_push_string (ctx, a->second.getString().c_str()); break;
191                         }
192                         duk_put_prop_string(ctx, props_i, a->first.c_str());
193                     }
194                 }
195                 duk_put_prop_string(ctx, feature_i, "properties");
196             }
197             duk_put_prop_string(ctx, -2, "feature");
198         }
199 
200         duk_pop(ctx);
201     }
202 
203 }
204 
205 //............................................................................
206 
Context()207 DuktapeEngine::Context::Context()
208 {
209     _ctx = 0L;
210 }
211 
212 void
initialize(const ScriptEngineOptions & options,bool complete)213 DuktapeEngine::Context::initialize(const ScriptEngineOptions& options, bool complete)
214 {
215     if ( _ctx == 0L )
216     {
217         // new heap + context.
218         _ctx = duk_create_heap_default();
219 
220         // if there is a static script, evaluate it first. This will register
221         // any functions or objects with the EcmaScript global object.
222         if ( options.script().isSet() )
223         {
224             bool ok = (duk_peval_string(_ctx, options.script()->getCode().c_str()) == 0); // [ "result" ]
225             if ( !ok )
226             {
227                 const char* err = duk_safe_to_string(_ctx, -1);
228                 OE_WARN << LC << err << std::endl;
229             }
230             duk_pop(_ctx); // []
231         }
232 
233         duk_push_global_object( _ctx );
234 
235         // Add global log function.
236         duk_push_c_function( _ctx, log, DUK_VARARGS ); // [global, function]
237         duk_put_prop_string( _ctx, -2, "log" );        // [global]
238 
239         if ( complete )
240         {
241             // feature.save() callback
242             duk_push_c_function(_ctx, oe_duk_save_feature, 1/*numargs*/); // [global, function]
243             duk_put_prop_string(_ctx, -2, "oe_duk_save_feature");         // [global]
244 
245             GeometryAPI::install(_ctx);
246         }
247 
248         duk_pop(_ctx); // []
249     }
250 }
251 
~Context()252 DuktapeEngine::Context::~Context()
253 {
254     if ( _ctx )
255     {
256         duk_destroy_heap(_ctx);
257         _ctx = 0L;
258     }
259 }
260 
261 //............................................................................
262 
DuktapeEngine(const ScriptEngineOptions & options)263 DuktapeEngine::DuktapeEngine(const ScriptEngineOptions& options) :
264 ScriptEngine( options ),
265 _options    ( options )
266 {
267     //nop
268 }
269 
~DuktapeEngine()270 DuktapeEngine::~DuktapeEngine()
271 {
272     //nop
273 }
274 
275 ScriptResult
run(const std::string & code,Feature const * feature,FilterContext const * context)276 DuktapeEngine::run(const std::string&   code,
277                    Feature const*       feature,
278                    FilterContext const* context)
279 {
280     if (code.empty())
281         return ScriptResult(EMPTY_STRING, false, "Script is empty.");
282 
283     bool complete = (getProfile() == "full");
284 
285 #ifdef MAXIMUM_ISOLATION
286     // brand new context every time
287     Context c;
288     c.initialize( _options, complete );
289     duk_context* ctx = c._ctx;
290 #else
291     // cache the Context on a per-thread basis
292     Context& c = _contexts.get();
293     c.initialize( _options, complete );
294     duk_context* ctx = c._ctx;
295 #endif
296 
297 	if ( feature && feature != c._feature.get() )
298     {
299 		// encode the feature in the global object and push a native pointer:
300 		setFeature(ctx, feature, complete);
301 	}
302 
303     // remember the feature so we don't re-create it if not necessary
304     c._feature = feature;
305 
306     // run the script. On error, the top of stack will hold the error
307     // message instead of the return value.
308     std::string resultString;
309 
310     bool ok = (duk_peval_string(ctx, code.c_str()) == 0); // [ "result" ]
311     const char* resultVal = duk_to_string(ctx, -1);
312     if ( resultVal )
313         resultString = resultVal;
314 
315     if ( !ok )
316     {
317         OE_DEBUG << LC << "Error: source =" << std::endl << code << std::endl;
318     }
319 
320     // pop the return value:
321     duk_pop(ctx); // []
322 
323     return ok ?
324         ScriptResult(resultString, true) :
325         ScriptResult("", false, resultString);
326 }
327