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