diff --git a/bench/src/benchStyleContext.cpp b/bench/src/benchStyleContext.cpp index 1dd57c59d2..8fbc58bd1b 100644 --- a/bench/src/benchStyleContext.cpp +++ b/bench/src/benchStyleContext.cpp @@ -7,6 +7,7 @@ #include "mockPlatform.h" #include "log.h" #include "data/tileSource.h" +#include "scene/filters.h" #include "scene/importer.h" #include "scene/scene.h" #include "scene/dataLayer.h" @@ -19,14 +20,54 @@ #include +#define NUM_ITERATIONS 0 + +#if NUM_ITERATIONS +#define ITERATIONS ->Iterations(NUM_ITERATIONS) +#else +#define ITERATIONS +#endif + #define RUN(FIXTURE, NAME) \ BENCHMARK_DEFINE_F(FIXTURE, NAME)(benchmark::State& st) { while (st.KeepRunning()) { run(); } } \ - BENCHMARK_REGISTER_F(FIXTURE, NAME); //->Iterations(10000); + BENCHMARK_REGISTER_F(FIXTURE, NAME) ITERATIONS; using namespace Tangram; -//const char scene_file[] = "bubble-wrap-style.zip"; -const char scene_file[] = "res/scene.yaml"; +template +class GetPropertyFixtureFixture : public benchmark::Fixture { +public: + Context ctx; + Feature feature; + void SetUp(const ::benchmark::State& state) override { + JavaScriptScope jsScope(ctx); + ctx.setGlobalValue("language", jsScope.newString("en")); + feature.props.set("name:en", "Ozymandias"); + feature.props.set("title", "King of Kings"); + feature.props.set("number", 17); + ctx.setCurrentFeature(&feature); + ctx.setFunction(0, "function () { return (language && feature['name:' + language]) || title; }"); + ctx.setFunction(1, "function () { return feature.order || feature.number; }"); + } + __attribute__ ((noinline)) void run() { + StyleParam::Value value; + benchmark::DoNotOptimize(value); + JavaScriptScope jsScope(ctx); + value = jsScope.getFunctionResult(0).toString(); + value = (float)jsScope.getFunctionResult(1).toDouble(); + } +}; + +#ifdef TANGRAM_USE_JSCORE +using JSCoreGetPropertyFixture = GetPropertyFixtureFixture; +RUN(JSCoreGetPropertyFixture, JSCoreGetPropertyBench) +#endif + +using DuktapeGetPropertyFixture = GetPropertyFixtureFixture; +RUN(DuktapeGetPropertyFixture, DuktapeGetPropertyBench) + +const char scene_file[] = "bubble-wrap-style.zip"; +//const char scene_file[] = "res/scene.yaml"; const char tile_file[] = "res/tile.mvt"; std::shared_ptr scene; @@ -72,95 +113,91 @@ void globalSetup() { } } +__attribute__ ((noinline)) +void filter(StyleContext& ctx, const SceneLayer& layer, const Feature& feature) { + StyleParam::Value styleValue; + benchmark::DoNotOptimize(styleValue); + + if (layer.filter().eval(feature, ctx)) { + for (auto& r : layer.rules()) { + for (auto& sp : r.parameters) { + if (sp.function >= 0) { + //evalCnt++; + ctx.evalStyle(sp.function, sp.key, styleValue); + } + } + } + for (const auto& sublayer : layer.sublayers()) { + filter(ctx, sublayer, feature); + } + } +} -template -class JSGetPropertyFixture : public benchmark::Fixture { -public: - Context ctx; - Feature feature; +void applyStyling(StyleContext& ctx) { + for (const auto& datalayer : scene->layers()) { + for (const auto& collection : tileData->layers) { + if (!collection.name.empty()) { + const auto& dlc = datalayer.collections(); + bool layerContainsCollection = + std::find(dlc.begin(), dlc.end(), collection.name) != dlc.end(); + + if (!layerContainsCollection) { continue; } + } + + for (const auto& feature : collection.features) { + ctx.setFeature(feature); + filter(ctx, datalayer, feature); + } + } + } +} + +template +struct JSTileStyleFnFixture : public benchmark::Fixture { + std::unique_ptr ctx; void SetUp(const ::benchmark::State& state) override { - JavaScriptScope jsScope(ctx); - ctx.setGlobalValue("language", jsScope.newString("en")); - feature.props.set("name:en", "Ozymandias"); - feature.props.set("title", "King of Kings"); - feature.props.set("number", 17); - ctx.setCurrentFeature(&feature); - ctx.setFunction(0, "function () { return (language && feature['name:' + language]) || title; }"); - ctx.setFunction(1, "function () { return feature.order || feature.number; }"); + globalSetup(); + ctx.reset(new StyleContext(jsCore)); + ctx->initScene(*scene); + ctx->setFilterKey(Filter::Key::zoom, 10); } __attribute__ ((noinline)) void run() { - StyleParam::Value value; - benchmark::DoNotOptimize(value); - JavaScriptScope jsScope(ctx); - value = jsScope.getFunctionResult(0).toString(); - value = (float)jsScope.getFunctionResult(1).toDouble(); - } + applyStyling(*ctx); + } }; #ifdef TANGRAM_USE_JSCORE -using JSCoreGetPropertyFixture = JSGetPropertyFixture; -RUN(JSCoreGetPropertyFixture, JSCoreGetPropertyBench) -#else -using DuktapeGetPropertyFixture = JSGetPropertyFixture; -RUN(DuktapeGetPropertyFixture, DuktapeGetPropertyBench) +using JSCoreTileStyleFnFixture = JSTileStyleFnFixture<1>; +RUN(JSCoreTileStyleFnFixture, JSCoreTileStyleFnBench) #endif +using DuktapeTileStyleFnFixture = JSTileStyleFnFixture<0>; +RUN(DuktapeTileStyleFnFixture, DuktapeTileStyleFnBench) -struct JSTileStyleFnFixture : public benchmark::Fixture { - StyleContext ctx; - Feature feature; - uint32_t numFunctions = 0; - uint32_t evalCnt = 0; +template +struct JSTileStyleFnReplayFixture : public benchmark::Fixture { + std::unique_ptr ctx; void SetUp(const ::benchmark::State& state) override { globalSetup(); - ctx.initFunctions(*scene); - ctx.setKeywordZoom(10); - } - void TearDown(const ::benchmark::State& state) override { - LOG(">>> %d", evalCnt); + ctx.reset(new StyleContext(jsCore, true)); + ctx->initScene(*scene); + ctx->setFilterKey(Filter::Key::zoom, 10); + applyStyling(*ctx); + ctx->impl->recorderLog(); } __attribute__ ((noinline)) void run() { - StyleParam::Value styleValue; - benchmark::DoNotOptimize(styleValue); - - for (const auto& datalayer : scene->layers()) { - for (const auto& collection : tileData->layers) { - if (!collection.name.empty()) { - const auto& dlc = datalayer.collections(); - bool layerContainsCollection = - std::find(dlc.begin(), dlc.end(), collection.name) != dlc.end(); - - if (!layerContainsCollection) { continue; } - } - - for (const auto& feat : collection.features) { - ctx.setFeature(feat); - - std::function filter; - filter = [&](const auto& layer) { - if (layer.filter().eval(feature, ctx)) { - for (auto& r : layer.rules()) { - for (auto& sp : r.parameters) { - if (sp.function >= 0) { - evalCnt++; - ctx.evalStyle(sp.function, sp.key, styleValue); - } - } - } - - for (const auto& sublayer : layer.sublayers()) { - filter(sublayer); - } - } - }; - filter(datalayer); - } - } - } + ctx->impl->replayFilters(); + ctx->impl->replayStyles(); } }; -RUN(JSTileStyleFnFixture, TileStyleFnBench); +#ifdef TANGRAM_USE_JSCORE +using JSCoreTileStyleFnReplayFixture = JSTileStyleFnReplayFixture<1>; +RUN(JSCoreTileStyleFnReplayFixture, JSCoreTileStyleFnReplayBench) +#endif +using DuktapeTileStyleFnReplayFixture = JSTileStyleFnReplayFixture<0>; +RUN(DuktapeTileStyleFnReplayFixture, DuktapeTileStyleFnReplayBench) + class DirectGetPropertyFixture : public benchmark::Fixture { public: @@ -182,6 +219,6 @@ BENCHMARK_DEFINE_F(DirectGetPropertyFixture, DirectGetPropertyBench)(benchmark:: } } } -BENCHMARK_REGISTER_F(DirectGetPropertyFixture, DirectGetPropertyBench); +BENCHMARK_REGISTER_F(DirectGetPropertyFixture, DirectGetPropertyBench) ITERATIONS; BENCHMARK_MAIN(); diff --git a/bench/src/benchTileBuilder.cpp b/bench/src/benchTileBuilder.cpp index 8b641bd6a0..fa4f46a1d5 100644 --- a/bench/src/benchTileBuilder.cpp +++ b/bench/src/benchTileBuilder.cpp @@ -18,26 +18,34 @@ #include #include +#define NUM_ITERATIONS 0 + +#if NUM_ITERATIONS +#define ITERATIONS ->Iterations(NUM_ITERATIONS) +#else +#define ITERATIONS +#endif #define RUN(FIXTURE, NAME) \ BENCHMARK_DEFINE_F(FIXTURE, NAME)(benchmark::State& st) { while (st.KeepRunning()) { run(); } } \ - BENCHMARK_REGISTER_F(FIXTURE, NAME); //->Iterations(1) + BENCHMARK_REGISTER_F(FIXTURE, NAME)ITERATIONS; using namespace Tangram; -//const char scene_file[] = "bubble-wrap-style.zip"; -const char scene_file[] = "res/scene.yaml"; +const char scene_file[] = "bubble-wrap-style.zip"; +//const char scene_file[] = "res/scene.yaml"; const char tile_file[] = "res/tile.mvt"; std::shared_ptr scene; std::shared_ptr source; std::shared_ptr tileData; +std::shared_ptr platform; void globalSetup() { - static std::atomic initialized{false}; - if (initialized.exchange(true)) { return; } + static bool initialized = false; + if (initialized) { return; } - std::shared_ptr platform = std::make_shared(); + platform = std::make_shared(); Url sceneUrl(scene_file); platform->putMockUrlContents(sceneUrl, MockPlatform::getBytesFromFile(scene_file)); @@ -74,15 +82,19 @@ void globalSetup() { LOGE("Invalid tile file '%s'", tile_file); exit(-1); } + initialized = true; } +template class TileBuilderFixture : public benchmark::Fixture { public: std::unique_ptr tileBuilder; + StyleContext* styleContext; std::shared_ptr result; void SetUp(const ::benchmark::State& state) override { globalSetup(); - tileBuilder = std::make_unique(scene, new StyleContext()); + styleContext = new StyleContext(jscontext); + tileBuilder = std::make_unique(scene, styleContext); } void TearDown(const ::benchmark::State& state) override { result.reset(); @@ -90,11 +102,15 @@ class TileBuilderFixture : public benchmark::Fixture { __attribute__ ((noinline)) void run() { result = tileBuilder->build({0,0,10,10}, *tileData, *source); + styleContext->clear(); } }; -RUN(TileBuilderFixture, TileBuilderBench); +using DUKTileBuilderFixture = TileBuilderFixture<0>; +using JSCTileBuilderFixture = TileBuilderFixture<1>; +RUN(DUKTileBuilderFixture, DUKTileBuilderBench) +RUN(JSCTileBuilderFixture, JSCTileBuilderBench) BENCHMARK_MAIN(); diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f5cda004d7..373ae3ca21 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -138,18 +138,18 @@ target_link_libraries(tangram-core # Add JavaScript implementation. if(TANGRAM_JSCORE_ENABLED) - target_sources(tangram-core PRIVATE src/js/JSCoreContext.cpp) if (TANGRAM_USE_JSCORE_STATIC) - target_link_libraries(tangram-core PRIVATE jscore-static) + # target_link_libraries(tangram-core PRIVATE jscore-static) + pkg_check_modules(JSCORE REQUIRED IMPORTED_TARGET javascriptcoregtk-4.0) + target_link_libraries(tangram-core PRIVATE PkgConfig::JSCORE) else() target_link_libraries(tangram-core PRIVATE "-framework JavaScriptCore") endif() target_compile_definitions(tangram-core PRIVATE TANGRAM_USE_JSCORE=1) -else() - target_sources(tangram-core PRIVATE src/js/DuktapeContext.cpp) - target_link_libraries(tangram-core PRIVATE duktape) endif() +target_link_libraries(tangram-core PRIVATE duktape) + # Add MBTiles implementation. if(TANGRAM_MBTILES_DATASOURCE) target_sources(tangram-core PRIVATE src/data/mbtilesDataSource.cpp) diff --git a/core/deps/duktape/CMakeLists.txt b/core/deps/duktape/CMakeLists.txt index fa4abdb77b..63ce2d09a0 100644 --- a/core/deps/duktape/CMakeLists.txt +++ b/core/deps/duktape/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(duktape duktape.c) +add_library(duktape duktape.c extstr.c) target_compile_options(duktape PRIVATE -fstrict-aliasing @@ -6,4 +6,6 @@ target_compile_options(duktape PRIVATE -std=c99 -Wall) -target_include_directories(duktape PUBLIC .) +target_include_directories(duktape + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/core/deps/duktape/duk_config.h b/core/deps/duktape/duk_config.h index 141414abea..76b63aeec1 100644 --- a/core/deps/duktape/duk_config.h +++ b/core/deps/duktape/duk_config.h @@ -59,6 +59,28 @@ #if !defined(DUK_CONFIG_H_INCLUDED) #define DUK_CONFIG_H_INCLUDED +#if defined(__cplusplus) +extern "C" { +#endif + +typedef const char* (*extstr_intern_check_fn)(void* _udata, void* _str, unsigned long _blen); +typedef void (*extstr_free_fn)(void* _udata, const void* _extdata); + +//const duk_uint8_t *str +const char* duk_extstr_intern_check(void* _udata, void* _str, unsigned long _blen); +void duk_extstr_free(void* _udata, const void* _extdata); +void duk_extstr_set_handler(extstr_intern_check_fn _check, extstr_free_fn _free); + +#if defined(__cplusplus) +/* end 'extern "C"' wrapper */ +} +#endif + +#define DUK_USE_EXTSTR_INTERN_CHECK duk_extstr_intern_check +#define DUK_USE_EXTSTR_FREE duk_extstr_free +//#define DUK_USE_VOLUNTARY_GC +#undef DUK_USE_VOLUNTARY_GC + /* * Intermediate helper defines */ @@ -2854,8 +2876,8 @@ typedef struct duk_hthread duk_context; #define DUK_USE_EXEC_REGCONST_OPTIMIZE #undef DUK_USE_EXEC_TIMEOUT_CHECK #undef DUK_USE_EXPLICIT_NULL_INIT -#undef DUK_USE_EXTSTR_FREE -#undef DUK_USE_EXTSTR_INTERN_CHECK +//#undef DUK_USE_EXTSTR_FREE +//#undef DUK_USE_EXTSTR_INTERN_CHECK #undef DUK_USE_FASTINT #define DUK_USE_FAST_REFCOUNT_DEFAULT #undef DUK_USE_FATAL_HANDLER @@ -2888,7 +2910,7 @@ typedef struct duk_hthread duk_context; #define DUK_USE_HOBJECT_HASH_PROP_LIMIT 8 #define DUK_USE_HSTRING_ARRIDX #define DUK_USE_HSTRING_CLEN -#undef DUK_USE_HSTRING_EXTDATA +#define DUK_USE_HSTRING_EXTDATA #define DUK_USE_HSTRING_LAZY_CLEN #define DUK_USE_HTML_COMMENTS #define DUK_USE_IDCHAR_FASTPATH @@ -2975,7 +2997,6 @@ typedef struct duk_hthread duk_context; #undef DUK_USE_VALSTACK_UNSAFE #define DUK_USE_VERBOSE_ERRORS #define DUK_USE_VERBOSE_EXECUTOR_ERRORS -#define DUK_USE_VOLUNTARY_GC #define DUK_USE_ZERO_BUFFER_DATA /* diff --git a/core/deps/duktape/extstr.c b/core/deps/duktape/extstr.c new file mode 100644 index 0000000000..11e575e695 --- /dev/null +++ b/core/deps/duktape/extstr.c @@ -0,0 +1,21 @@ +#include "duktape.h" + +static extstr_intern_check_fn extstr_intern_check_func = NULL; +static extstr_free_fn extstr_free_func= NULL; + +const char* duk_extstr_intern_check(void* _udata, void* _str, unsigned long _blen) { + if (extstr_intern_check_func) { + return extstr_intern_check_func(_udata, _str, _blen); + } + return NULL; +} + +void duk_extstr_free(void* _udata, const void * _extdata) { + if (extstr_free_func) { extstr_free_func(_udata, _extdata); } + +} +void duk_extstr_set_handler(extstr_intern_check_fn _check, extstr_free_fn _free) { + extstr_intern_check_func = _check; + extstr_free_func = _free; +} + diff --git a/core/include/tangram/data/tileSource.h b/core/include/tangram/data/tileSource.h index 93ede416bc..6904cbefb9 100644 --- a/core/include/tangram/data/tileSource.h +++ b/core/include/tangram/data/tileSource.h @@ -142,6 +142,12 @@ class TileSource : public std::enable_shared_from_this { void setFormat(Format format) { m_format = format; } + struct PropertyFilter { + std::vector drop; + std::vector keep; + }; + void setPropertyFilter(PropertyFilter&& filter) { m_propertyFilter = std::move(filter); } + protected: void createSubTasks(std::shared_ptr _task); @@ -167,6 +173,8 @@ class TileSource : public std::enable_shared_from_this { std::vector> m_rasterSources; std::unique_ptr m_sources; + + PropertyFilter m_propertyFilter; }; } diff --git a/core/include/tangram/log.h b/core/include/tangram/log.h index be7c148234..ca8af4b914 100644 --- a/core/include/tangram/log.h +++ b/core/include/tangram/log.h @@ -66,3 +66,52 @@ do { Tangram::logMsg("ERROR %s:%d: " fmt "\n", __FILENAME__, __LINE__, ## __VA_A #define LOG(fmt, ...) #define LOGN(fmt, ...) #endif + +#include +#include + +extern std::chrono::time_point tangram_log_time_start, tangram_log_time_last; +extern std::mutex tangram_log_time_mutex; + +#ifdef TIME_LOGGING +#define LOGTIME(fmt, ...) do { \ + int l = strlen( __FILENAME__); \ + Tangram::logMsg("TIME %-18.*s " fmt "\n", \ + l > 4 ? l-4 : l, __FILENAME__, ##__VA_ARGS__); } while (0) + +// Overall timing init/reset +#define LOGTOInit() do { \ + std::lock_guard lock(tangram_log_time_mutex); \ + tangram_log_time_last = tangram_log_time_start = std::chrono::system_clock::now(); } while(0) + +// Overall timing +#define LOGTO(fmt, ...) do { \ + std::lock_guard lock(tangram_log_time_mutex); \ + std::chrono::time_point now = std::chrono::system_clock::now(); \ + std::chrono::duration t1 = now - tangram_log_time_start; \ + std::chrono::duration t2 = now - tangram_log_time_last; \ + tangram_log_time_last = now; \ + LOGTIME("%7.2f %7.2f " fmt, t1.count()*1000.f, t2.count()*1000.f, ## __VA_ARGS__); } while(0) + +// Local timing init +#define LOGTInit(fmt, ...) \ + std::chrono::time_point _time_last, _time_start; \ + std::chrono::time_point now = std::chrono::system_clock::now(); \ + std::chrono::duration t0 = now - tangram_log_time_start; \ + _time_start = _time_last = now; \ + LOGTIME("%7.2f " fmt, t0.count()*1000.f, ## __VA_ARGS__) + +// Local timing +#define LOGT(fmt, ...) do { \ + std::chrono::time_point now = std::chrono::system_clock::now(); \ + std::chrono::duration t0 = now - tangram_log_time_start; \ + std::chrono::duration t1 = now - _time_start; \ + std::chrono::duration t2 = now - _time_last; \ + _time_last = now; \ + LOGTIME("%7.2f %7.2f %7.2f " fmt, t0.count()*1000.f, t1.count()*1000.f, t2.count()*1000.f, ## __VA_ARGS__); } while(0) +#else +#define LOGTOInit(...) +#define LOGTO(...) +#define LOGTInit(...) +#define LOGT(...) +#endif diff --git a/core/include/tangram/platform.h b/core/include/tangram/platform.h index 354ebac785..163b59060e 100644 --- a/core/include/tangram/platform.h +++ b/core/include/tangram/platform.h @@ -61,6 +61,7 @@ class Platform { Platform(); virtual ~Platform(); + virtual void shutdown() = 0; // Request that a new frame be rendered by the windowing system virtual void requestRender() const = 0; diff --git a/core/src/data/formats/mvt.cpp b/core/src/data/formats/mvt.cpp index 09c95a48a5..5657215517 100644 --- a/core/src/data/formats/mvt.cpp +++ b/core/src/data/formats/mvt.cpp @@ -117,12 +117,14 @@ Feature Mvt::getFeature(ParserContext& _ctx, protobuf::message _featureIn) { } auto valueKey = tagsMsg.varint(); - + // Check if the property should be dropped + if (_ctx.keys[tagKey].empty()) { + continue; + } if( _ctx.values.size() <= valueKey ) { LOGE("accessing out of bound values"); return feature; } - _ctx.featureTags[tagKey] = valueKey; } break; @@ -215,7 +217,8 @@ Feature Mvt::getFeature(ParserContext& _ctx, protobuf::message _featureIn) { return feature; } -Layer Mvt::getLayer(ParserContext& _ctx, protobuf::message _layerIn) { +Layer Mvt::getLayer(ParserContext& _ctx, protobuf::message _layerIn, + const TileSource::PropertyFilter& filter) { Layer layer(""); @@ -245,7 +248,29 @@ Layer Mvt::getLayer(ParserContext& _ctx, protobuf::message _layerIn) { continue; } case LAYER_KEY: { - _ctx.keys.push_back(_layerIn.string()); + std::string key = _layerIn.string(); + // Check whether the key must be kept + if (std::find(std::begin(filter.keep), std::end(filter.keep), key) == std::end(filter.keep)) { + // Check whether the key should be dropped + if (std::find_if(std::begin(filter.drop), std::end(filter.drop), + [&](auto& k) { + if (k.back() == '*' && key.length() >= k.length()-1) { + int n = std::strncmp(key.c_str(), k.c_str(), k.length()-1); + //LOG("check %s / %d / %d", k.c_str(), n, k.length()-1); + return n == 0; + } else { + return key == k; + }}) != std::end(filter.drop)) { + + LOG("drop key: %s", key.c_str()); + key = ""; + //} else { + //LOG("keep key: %s", key.c_str()); + } + //} else { + //LOG("keep key: %s", key.c_str()); + } + _ctx.keys.emplace_back(std::move(key)); break; } case LAYER_VALUE: { @@ -321,7 +346,8 @@ Layer Mvt::getLayer(ParserContext& _ctx, protobuf::message _layerIn) { return layer; } -std::shared_ptr Mvt::parseTile(const TileTask& _task, int32_t _sourceId) { +std::shared_ptr Mvt::parseTile(const TileTask& _task, int32_t _sourceId, + const TileSource::PropertyFilter& filter) { auto tileData = std::make_shared(); @@ -333,7 +359,7 @@ std::shared_ptr Mvt::parseTile(const TileTask& _task, int32_t _sourceI try { while(item.next()) { if(item.tag == LAYER) { - tileData->layers.push_back(getLayer(ctx, item.getMessage())); + tileData->layers.push_back(getLayer(ctx, item.getMessage(), filter)); } else { item.skip(); } diff --git a/core/src/data/formats/mvt.h b/core/src/data/formats/mvt.h index 54b19db10f..d3d11b2ac8 100644 --- a/core/src/data/formats/mvt.h +++ b/core/src/data/formats/mvt.h @@ -1,6 +1,7 @@ #pragma once #include "data/tileData.h" +#include "data/tileSource.h" #include "pbf/pbf.hpp" #include "util/variant.h" @@ -16,41 +17,43 @@ class MapProjection; namespace Mvt { - struct Geometry { - std::vector coordinates; - std::vector sizes; - }; +struct Geometry { + std::vector coordinates; + std::vector sizes; +}; - struct ParserContext { - ParserContext(int32_t _sourceId) : sourceId(_sourceId){} +struct ParserContext { + explicit ParserContext(int32_t _sourceId) : sourceId(_sourceId){} - int32_t sourceId; - std::vector keys; - std::vector values; - std::vector featureMsgs; - Geometry geometry; - // Map Key ID -> Tag values - std::vector featureTags; - // Key IDs sorted by Property key ordering - std::vector orderedKeys; + int32_t sourceId; + std::vector keys; + std::vector values; + std::vector featureMsgs; + Geometry geometry; + // Map Key ID -> Tag values + std::vector featureTags; + // Key IDs sorted by Property key ordering + std::vector orderedKeys; - int tileExtent = 0; - int winding = 0; - }; + int tileExtent = 0; + int winding = 0; +}; - enum GeomCmd { - moveTo = 1, - lineTo = 2, - closePath = 7 - }; +enum GeomCmd { + moveTo = 1, + lineTo = 2, + closePath = 7 +}; - Geometry getGeometry(ParserContext& _ctx, protobuf::message _geomIn); +Geometry getGeometry(ParserContext& _ctx, protobuf::message _geomIn); - Feature getFeature(ParserContext& _ctx, protobuf::message _featureIn); +Feature getFeature(ParserContext& _ctx, protobuf::message _featureIn); - Layer getLayer(ParserContext& _ctx, protobuf::message _layerIn); +Layer getLayer(ParserContext& _ctx, protobuf::message _layerIn, + const TileSource::PropertyFilter& filter); - std::shared_ptr parseTile(const TileTask& _task, int32_t _sourceId); +std::shared_ptr parseTile(const TileTask& _task, int32_t _sourceId, + const TileSource::PropertyFilter& filter); } // namespace Mvt diff --git a/core/src/data/networkDataSource.cpp b/core/src/data/networkDataSource.cpp index ffeab7213d..c01c3bd2b0 100644 --- a/core/src/data/networkDataSource.cpp +++ b/core/src/data/networkDataSource.cpp @@ -58,13 +58,15 @@ bool NetworkDataSource::loadTileData(std::shared_ptr task, TileTaskCb m_urlSubdomainIndex = (m_urlSubdomainIndex + 1) % m_urlSubdomains.size(); } - UrlCallback onRequestFinish = [callback, task, url](UrlResponse&& response) { - + LOGTInit(">>> %s", task->tileId().toString().c_str()); + UrlCallback onRequestFinish = [=](UrlResponse&& response) mutable { auto source = task->source(); if (!source) { LOGW("URL Callback for deleted TileSource '%s'", url.string().c_str()); return; } + LOGT("<<< %s -- canceled:%d", task->tileId().toString().c_str(), task->isCanceled()); + if (task->isCanceled()) { return; } diff --git a/core/src/data/tileSource.cpp b/core/src/data/tileSource.cpp index 6b7afde6c3..f4f0e7b5b2 100644 --- a/core/src/data/tileSource.cpp +++ b/core/src/data/tileSource.cpp @@ -106,7 +106,7 @@ std::shared_ptr TileSource::parse(const TileTask& _task) const { switch (m_format) { case Format::TopoJson: return TopoJson::parseTile(_task, m_id); case Format::GeoJson: return GeoJson::parseTile(_task, m_id); - case Format::Mvt: return Mvt::parseTile(_task, m_id); + case Format::Mvt: return Mvt::parseTile(_task, m_id, m_propertyFilter); } assert(false); return nullptr; diff --git a/core/src/js/DuktapeContext.cpp b/core/src/js/DuktapeContext.cpp deleted file mode 100644 index ccfc6c6114..0000000000 --- a/core/src/js/DuktapeContext.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// -// Created by Matt Blair on 11/9/18. -// -#include "DuktapeContext.h" - -#include "log.h" -#include "data/tileData.h" -#include "util/variant.h" - -#include "duktape/duktape.h" -#include "glm/vec2.hpp" - -namespace Tangram { - -const static char INSTANCE_ID[] = "\xff""\xff""obj"; -const static char FUNC_ID[] = "\xff""\xff""fns"; - -DuktapeContext::DuktapeContext() { - // Create duktape heap with default allocation functions and custom fatal error handler. - _ctx = duk_create_heap(nullptr, nullptr, nullptr, nullptr, fatalErrorHandler); - - //// Create global geometry constants - // TODO make immutable - duk_push_number(_ctx, GeometryType::points); - duk_put_global_string(_ctx, "point"); - - duk_push_number(_ctx, GeometryType::lines); - duk_put_global_string(_ctx, "line"); - - duk_push_number(_ctx, GeometryType::polygons); - duk_put_global_string(_ctx, "polygon"); - - //// Create global 'feature' object - // Get Proxy constructor - // -> [cons] - duk_eval_string(_ctx, "Proxy"); - - // Add feature object - // -> [cons, { __obj: this }] - duk_idx_t featureObj = duk_push_object(_ctx); - duk_push_pointer(_ctx, this); - duk_put_prop_string(_ctx, featureObj, INSTANCE_ID); - - // Add handler object - // -> [cons, {...}, { get: func, has: func }] - duk_idx_t handlerObj = duk_push_object(_ctx); - // Add 'get' property to handler - duk_push_c_function(_ctx, jsGetProperty, 3 /*nargs*/); - duk_put_prop_string(_ctx, handlerObj, "get"); - // Add 'has' property to handler - duk_push_c_function(_ctx, jsHasProperty, 2 /*nargs*/); - duk_put_prop_string(_ctx, handlerObj, "has"); - - // Call proxy constructor - // [cons, feature, handler ] -> [obj|error] - if (duk_pnew(_ctx, 2) == 0) { - // put feature proxy object in global scope - if (!duk_put_global_string(_ctx, "feature")) { - LOGE("Initialization failed"); - } - } else { - LOGE("Failure: %s", duk_safe_to_string(_ctx, -1)); - duk_pop(_ctx); - } - - // Set up 'fns' array. - duk_push_array(_ctx); - if (!duk_put_global_string(_ctx, FUNC_ID)) { - LOGE("'fns' object not set"); - } -} - -DuktapeContext::~DuktapeContext() { - duk_destroy_heap(_ctx); -} - -void DuktapeContext::setGlobalValue(const std::string& name, DuktapeValue value) { - value.ensureExistsOnStackTop(); - duk_put_global_lstring(_ctx, name.data(), name.length()); -} - -void DuktapeContext::setCurrentFeature(const Feature* feature) { - _feature = feature; -} - -bool DuktapeContext::setFunction(JSFunctionIndex index, const std::string& source) { - // Get all functions (array) in context - if (!duk_get_global_string(_ctx, FUNC_ID)) { - LOGE("AddFunction - functions array not initialized"); - duk_pop(_ctx); // pop [undefined] sitting at stack top - return false; - } - - duk_push_string(_ctx, source.c_str()); - duk_push_string(_ctx, ""); - - if (duk_pcompile(_ctx, DUK_COMPILE_FUNCTION) == 0) { - duk_put_prop_index(_ctx, -2, index); - } else { - LOGW("Compile failed: %s\n%s\n---", - duk_safe_to_string(_ctx, -1), - source.c_str()); - duk_pop(_ctx); - return false; - } - - // Pop the functions array off the stack - duk_pop(_ctx); - - return true; -} - -bool DuktapeContext::evaluateBooleanFunction(uint32_t index) { - if (!evaluateFunction(index)) { - return false; - } - - // Evaluate the "truthiness" of the function result at the top of the stack. - bool result = duk_to_boolean(_ctx, -1) != 0; - - // pop result - duk_pop(_ctx); - - return result; -} - -DuktapeValue DuktapeContext::getFunctionResult(uint32_t index) { - if (!evaluateFunction(index)) { - return DuktapeValue(); - } - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newNull() { - duk_push_null(_ctx); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newBoolean(bool value) { - duk_push_boolean(_ctx, static_cast(value)); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newNumber(double value) { - duk_push_number(_ctx, value); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newString(const std::string& value) { - duk_push_lstring(_ctx, value.data(), value.length()); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newArray() { - duk_push_array(_ctx); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newObject() { - duk_push_object(_ctx); - return getStackTopValue(); -} - -DuktapeValue DuktapeContext::newFunction(const std::string& value) { - if (duk_pcompile_lstring(_ctx, DUK_COMPILE_FUNCTION, value.data(), value.length()) != 0) { - auto error = duk_safe_to_string(_ctx, -1); - LOGW("Compile failed in global function: %s\n%s\n---", error, value.c_str()); - duk_pop(_ctx); // Pop error. - return DuktapeValue(); - } - return getStackTopValue(); -} - -JSScopeMarker DuktapeContext::getScopeMarker() { - return duk_get_top(_ctx); -} - -void DuktapeContext::resetToScopeMarker(JSScopeMarker marker) { - duk_set_top(_ctx, marker); -} - -// Implements Proxy handler.has(target_object, key) -int DuktapeContext::jsHasProperty(duk_context *_ctx) { - - duk_get_prop_string(_ctx, 0, INSTANCE_ID); - auto context = static_cast(duk_to_pointer(_ctx, -1)); - if (!context || !context->_feature) { - LOGE("Error: no context set %p %p", context, context ? context->_feature : nullptr); - duk_pop(_ctx); - return 0; - } - - const char* key = duk_require_string(_ctx, 1); - auto result = static_cast(context->_feature->props.contains(key)); - duk_push_boolean(_ctx, result); - - return 1; -} - -// Implements Proxy handler.get(target_object, key) -int DuktapeContext::jsGetProperty(duk_context *_ctx) { - - // Get the JavaScriptContext instance from JS Feature object (first parameter). - duk_get_prop_string(_ctx, 0, INSTANCE_ID); - auto context = static_cast(duk_to_pointer(_ctx, -1)); - if (!context || !context->_feature) { - LOGE("Error: no context set %p %p", context, context ? context->_feature : nullptr); - duk_pop(_ctx); - return 0; - } - - // Get the property name (second parameter) - const char* key = duk_require_string(_ctx, 1); - - auto it = context->_feature->props.get(key); - if (it.is()) { - duk_push_string(_ctx, it.get().c_str()); - } else if (it.is()) { - duk_push_number(_ctx, it.get()); - } else { - duk_push_undefined(_ctx); - } - // FIXME: Distinguish Booleans here as well - - return 1; -} - -void DuktapeContext::fatalErrorHandler(void*, const char* message) { - LOGE("Fatal Error in DuktapeJavaScriptContext: %s", message); - abort(); -} - -bool DuktapeContext::evaluateFunction(uint32_t index) { - // Get all functions (array) in context - if (!duk_get_global_string(_ctx, FUNC_ID)) { - LOGE("EvalFilterFn - functions array not initialized"); - duk_pop(_ctx); // pop [undefined] sitting at stack top - return false; - } - - // Get function at index `id` from functions array, put it at stack top - if (!duk_get_prop_index(_ctx, -1, index)) { - LOGE("EvalFilterFn - function %d not set", index); - duk_pop(_ctx); // pop "undefined" sitting at stack top - duk_pop(_ctx); // pop functions (array) now sitting at stack top - return false; - } - - // pop fns array - duk_remove(_ctx, -2); - - // call popped function (sitting at stack top), evaluated value is put on stack top - if (duk_pcall(_ctx, 0) != 0) { - LOGE("EvalFilterFn: %s", duk_safe_to_string(_ctx, -1)); - duk_pop(_ctx); - return false; - } - - return true; -} - -} // namespace Tangram diff --git a/core/src/js/DuktapeContext.h b/core/src/js/DuktapeContext.h deleted file mode 100644 index 43127574cb..0000000000 --- a/core/src/js/DuktapeContext.h +++ /dev/null @@ -1,179 +0,0 @@ -// -// Created by Matt Blair on 11/13/18. -// - -#pragma once - -#include "js/JavaScriptFwd.h" -#include "duktape/duktape.h" - -#include - -namespace Tangram { - -class DuktapeValue { - -public: - - DuktapeValue() = default; - - DuktapeValue(duk_context* ctx, duk_idx_t index) : _ctx(ctx), _index(index) {} - - DuktapeValue(DuktapeValue&& other) noexcept : _ctx(other._ctx), _index(other._index) { - other._ctx = nullptr; - } - - ~DuktapeValue() = default; - - DuktapeValue& operator=(const DuktapeValue& other) = delete; - - DuktapeValue& operator=(DuktapeValue&& other) noexcept { - _ctx = other._ctx; - _index = other._index; - other._ctx = nullptr; - return *this; - } - - operator bool() const { - return _ctx != nullptr; - } - - bool isUndefined() { - return duk_is_undefined(_ctx, _index) != 0; - } - - bool isNull() { - return duk_is_null(_ctx, _index) != 0; - } - - bool isBoolean() { - return duk_is_boolean(_ctx, _index) != 0; - } - - bool isNumber() { - return duk_is_number(_ctx, _index) != 0; - } - - bool isString() { - return duk_is_string(_ctx, _index) != 0; - } - - bool isArray() { - return duk_is_array(_ctx, _index) != 0; - } - - bool isObject() { - return duk_is_object(_ctx, _index) != 0; - } - - bool toBool() { - return duk_to_boolean(_ctx, _index) != 0; - } - - int toInt() { - return duk_to_int(_ctx, _index); - } - - double toDouble() { - return duk_to_number(_ctx, _index); - } - - std::string toString() { - return std::string(duk_to_string(_ctx, _index)); - } - - size_t getLength() { - return duk_get_length(_ctx, _index); - } - - DuktapeValue getValueAtIndex(size_t index) { - duk_get_prop_index(_ctx, _index, static_cast(index)); - return DuktapeValue(_ctx, duk_normalize_index(_ctx, -1)); - } - - DuktapeValue getValueForProperty(const std::string& name) { - duk_get_prop_lstring(_ctx, _index, name.data(), name.length()); - return DuktapeValue(_ctx, duk_normalize_index(_ctx, -1)); - } - - void setValueAtIndex(size_t index, DuktapeValue value) { - value.ensureExistsOnStackTop(); - duk_put_prop_index(_ctx, _index, static_cast(index)); - } - - void setValueForProperty(const std::string& name, DuktapeValue value) { - value.ensureExistsOnStackTop(); - duk_put_prop_lstring(_ctx, _index, name.data(), name.length()); - } - - void ensureExistsOnStackTop() { - auto dukTopIndex = duk_get_top_index(_ctx); - if (_index != dukTopIndex) { - duk_require_stack_top(_ctx, dukTopIndex + 1); - duk_dup(_ctx, _index); - } - } - - auto getStackIndex() { - return _index; - } - -private: - - duk_context* _ctx = nullptr; - - duk_idx_t _index = 0; -}; - -class DuktapeContext { - -public: - - DuktapeContext(); - - ~DuktapeContext(); - - void setGlobalValue(const std::string& name, DuktapeValue value); - - void setCurrentFeature(const Feature* feature); - - bool setFunction(JSFunctionIndex index, const std::string& source); - - bool evaluateBooleanFunction(JSFunctionIndex index); - -protected: - DuktapeValue newNull(); - - DuktapeValue newBoolean(bool value); - DuktapeValue newNumber(double value); - DuktapeValue newString(const std::string& value); - DuktapeValue newArray(); - DuktapeValue newObject(); - DuktapeValue newFunction(const std::string& value); - DuktapeValue getFunctionResult(JSFunctionIndex index); - - JSScopeMarker getScopeMarker(); - void resetToScopeMarker(JSScopeMarker marker); - -private: - - // Used for proxy object. - static int jsGetProperty(duk_context *_ctx); - static int jsHasProperty(duk_context *_ctx); - - static void fatalErrorHandler(void* userData, const char* message); - - bool evaluateFunction(uint32_t index); - - DuktapeValue getStackTopValue() { - return DuktapeValue(_ctx, duk_normalize_index(_ctx, -1)); - } - - duk_context* _ctx = nullptr; - - const Feature* _feature = nullptr; - - friend JavaScriptScope; -}; - -} // namespace Tangram diff --git a/core/src/js/JSCoreContext.cpp b/core/src/js/JSCoreContext.cpp deleted file mode 100644 index b2004aae18..0000000000 --- a/core/src/js/JSCoreContext.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// -// Created by Matt Blair on 11/15/18. -// - -#include "JSCoreContext.h" -#include "log.h" -#include "data/tileData.h" -#include "util/variant.h" -#include - -namespace Tangram { - -JSCoreContext::JSCoreContext() : _strings(256) { - - _group = JSContextGroupCreate(); - _context = JSGlobalContextCreateInGroup(_group, nullptr); - - // Create the global 'feature' object. - JSClassDefinition jsFeatureClassDefinition = kJSClassDefinitionEmpty; - // Empirically, feature property access is slightly faster when only 'getProperty' is provided. - //jsFeatureClassDefinition.hasProperty = jsHasPropertyCallback; - jsFeatureClassDefinition.getProperty = jsGetPropertyCallback; - JSClassRef jsFeatureClass = JSClassCreate(&jsFeatureClassDefinition); - JSObjectRef jsFeatureObject = JSObjectMake(_context, jsFeatureClass, this); - JSClassRelease(jsFeatureClass); - JSObjectRef jsGlobalObject = JSContextGetGlobalObject(_context); - JSStringRef jsFeatureName = JSStringCreateWithUTF8CString("feature"); - JSObjectSetProperty(_context, jsGlobalObject, jsFeatureName, jsFeatureObject, kJSPropertyAttributeReadOnly, nullptr); - JSStringRelease(jsFeatureName); - - // Create geometry constants. - std::array, 3> geometryConstants{ - {{"point", GeometryType::points}, {"line", GeometryType::lines}, {"polygon", GeometryType::polygons}} - }; - for (const auto& pair : geometryConstants) { - JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(pair.first); - JSValueRef jsPropertyValue = JSValueMakeNumber(_context, pair.second); - JSObjectSetProperty(_context, jsGlobalObject, jsPropertyName, jsPropertyValue, kJSPropertyAttributeReadOnly, nullptr); - JSStringRelease(jsPropertyName); - } -} - -JSCoreContext::~JSCoreContext() { - JSGlobalContextRelease(_context); - JSContextGroupRelease(_group); -} - -void JSCoreContext::setGlobalValue(const std::string& name, JSCoreValue value) { - JSObjectRef jsGlobalObject = JSContextGetGlobalObject(_context); - JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); - JSValueRef jsValueForProperty = value.getValueRef(); - JSObjectSetProperty(_context, jsGlobalObject, jsPropertyName, jsValueForProperty, kJSPropertyAttributeReadOnly, nullptr); - JSStringRelease(jsPropertyName); -} - -void JSCoreContext::setCurrentFeature(const Feature* feature) { - _feature = feature; -} - -bool JSCoreContext::setFunction(JSFunctionIndex index, const std::string& source) { - JSObjectRef jsFunctionObject = compileFunction(source); - if (!jsFunctionObject) { - return false; - } - if (index >= _functions.size()) { - _functions.resize(index + 1); - } - auto& functionEntry = _functions[index]; - if (functionEntry != nullptr) { - JSValueUnprotect(_context, functionEntry); - } - JSValueProtect(_context, jsFunctionObject); - functionEntry = jsFunctionObject; - return true; -} - -bool JSCoreContext::evaluateBooleanFunction(JSFunctionIndex index) { - auto resultValue = getFunctionResult(index); - if (resultValue) { - return resultValue.toBool(); - } - return false; -} - -JSCoreValue JSCoreContext::newNull() { - JSValueRef jsValue = JSValueMakeNull(_context); - return JSCoreValue(_context, jsValue); -} - -JSCoreValue JSCoreContext::newBoolean(bool value) { - JSValueRef jsValue = JSValueMakeBoolean(_context, value); - return JSCoreValue(_context, jsValue); -} - -JSCoreValue JSCoreContext::newNumber(double value) { - JSValueRef jsValue = JSValueMakeNumber(_context, value); - return JSCoreValue(_context, jsValue); -} - -JSCoreValue JSCoreContext::newString(const std::string& value) { - JSStringRef jsString = JSStringCreateWithUTF8CString(value.c_str()); - JSValueRef jsValue = JSValueMakeString(_context, jsString); - return JSCoreValue(_context, jsValue); -} - -JSCoreValue JSCoreContext::newArray() { - JSObjectRef jsObject = JSObjectMakeArray(_context, 0, nullptr, nullptr); - return JSCoreValue(_context, jsObject); -} - -JSCoreValue JSCoreContext::newObject() { - JSObjectRef jsObject = JSObjectMake(_context, nullptr, nullptr); - return JSCoreValue(_context, jsObject); -} - -JSCoreValue JSCoreContext::newFunction(const std::string& value) { - JSObjectRef jsFunctionObject = compileFunction(value); - if (jsFunctionObject == nullptr) { - return JSCoreValue(); - } - return JSCoreValue(_context, jsFunctionObject); -} - -JSCoreValue JSCoreContext::getFunctionResult(JSFunctionIndex index) { - if (index > _functions.size()) { - return JSCoreValue(); - } - JSObjectRef jsFunctionObject = _functions[index]; - JSValueRef jsException = nullptr; - JSValueRef jsResultValue = JSObjectCallAsFunction(_context, jsFunctionObject, nullptr, 0, nullptr, &jsException); - if (jsException != nullptr) { - char buffer[128]; - JSStringRef jsExceptionString = JSValueToStringCopy(_context, jsException, nullptr); - JSStringGetUTF8CString(jsExceptionString, buffer, sizeof(buffer)); - LOGE("Error evaluating JavaScript function - %s", buffer); - JSStringRelease(jsExceptionString); - return JSCoreValue(); - } - return JSCoreValue(_context, jsResultValue); -} - -JSScopeMarker JSCoreContext::getScopeMarker() { - // Not needed for JSCore implementation. - return 0; -} - -void JSCoreContext::resetToScopeMarker(JSScopeMarker) { - // Not needed for JSCore implementation. -} - -JSObjectRef JSCoreContext::compileFunction(const std::string& source) { - std::string expression("(" + source + ")"); - JSStringRef jsFunctionSource = JSStringCreateWithUTF8CString(expression.c_str()); - JSValueRef jsException = nullptr; - JSValueRef jsFunction = JSEvaluateScript(_context, jsFunctionSource, nullptr, nullptr, 0, &jsException); - JSStringRelease(jsFunctionSource); - if (jsException) { - char buffer[128]; - JSStringRef jsExceptionString = JSValueToStringCopy(_context, jsException, nullptr); - JSStringGetUTF8CString(jsExceptionString, buffer, sizeof(buffer)); - LOGE("Error compiling JavaScript function - %s", buffer); - JSStringRelease(jsExceptionString); - } - return JSValueToObject(_context, jsFunction, nullptr); -} - -bool JSCoreContext::jsHasPropertyCallback(JSContextRef, JSObjectRef object, JSStringRef property) { - auto jsCoreContext = reinterpret_cast(JSObjectGetPrivate(object)); - if (!jsCoreContext) { - return false; - } - auto feature = jsCoreContext->_feature; - if (!feature) { - return false; - } - char nameBuffer[128]; // This should be enough for all the names we use - could make it dynamically-sized if needed. - JSStringGetUTF8CString(property, nameBuffer, sizeof(nameBuffer)); - return feature->props.contains(nameBuffer); -} - -JSValueRef JSCoreContext::jsGetPropertyCallback(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef*) { - auto jsCoreContext = reinterpret_cast(JSObjectGetPrivate(object)); - if (!jsCoreContext) { - return nullptr; - } - auto feature = jsCoreContext->_feature; - if (!feature) { - return nullptr; - } - JSValueRef jsValue = nullptr; - char nameBuffer[128]; // This should be enough for all the names we use - could make it dynamically-sized if needed. - JSStringGetUTF8CString(property, nameBuffer, sizeof(nameBuffer)); - auto it = feature->props.get(nameBuffer); - if (it.is()) { - jsValue = jsCoreContext->_strings.get(context, it.get()); - } else if (it.is()) { - jsValue = JSValueMakeNumber(context, it.get()); - } - return jsValue; -} - -} // namespace Tangram diff --git a/core/src/js/JSCoreContext.h b/core/src/js/JSCoreContext.h deleted file mode 100644 index c36abf7452..0000000000 --- a/core/src/js/JSCoreContext.h +++ /dev/null @@ -1,241 +0,0 @@ -// -// Created by Matt Blair on 11/15/18. -// -#pragma once -#include "js/JavaScriptFwd.h" -#include -#include -#include -#include -#include - -namespace Tangram { - -class JSCoreValue { - -public: - - JSCoreValue() = default; - - JSCoreValue(JSContextRef ctx, JSValueRef value) : _ctx(ctx), _value(value) { - JSValueProtect(_ctx, _value); - } - - JSCoreValue(JSCoreValue&& other) noexcept : _ctx(other._ctx), _value(other._value) { - other._ctx = nullptr; - other._value = nullptr; - } - - ~JSCoreValue() { - if (_ctx) { - JSValueUnprotect(_ctx, _value); - } - } - - JSCoreValue& operator=(const JSCoreValue& other) = delete; - - JSCoreValue& operator=(JSCoreValue&& other) noexcept { - _ctx = other._ctx; - _value = other._value; - other._ctx = nullptr; - other._value = nullptr; - return *this; - } - - operator bool() const { return _ctx != nullptr; } - - bool isUndefined() { - return JSValueIsUndefined(_ctx, _value); - } - - bool isNull() { - return JSValueIsNull(_ctx, _value); - } - - bool isBoolean() { - return JSValueIsBoolean(_ctx, _value); - } - - bool isNumber() { - return JSValueIsNumber(_ctx, _value); - } - - bool isString() { - return JSValueIsString(_ctx, _value); - } - - bool isArray() { - return JSValueIsArray(_ctx, _value); - } - - bool isObject() { - return JSValueIsObject(_ctx, _value); - } - - bool toBool() { - return JSValueToBoolean(_ctx, _value); - } - - int toInt() { - return static_cast(JSValueToNumber(_ctx, _value, nullptr)); - } - - double toDouble() { - return JSValueToNumber(_ctx, _value, nullptr); - } - - std::string toString() { - JSStringRef jsString = JSValueToStringCopy(_ctx, _value, nullptr); - std::string result(JSStringGetMaximumUTF8CStringSize(jsString), '\0'); - size_t bytesWrittenIncludingNull = JSStringGetUTF8CString(jsString, &result[0], result.capacity()); - result.resize(bytesWrittenIncludingNull - 1); - JSStringRelease(jsString); - return result; - } - - size_t getLength() { - JSStringRef jsLengthProperty = JSStringCreateWithUTF8CString("length"); - JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); - JSValueRef jsLengthValue = JSObjectGetProperty(_ctx, jsObject, jsLengthProperty, nullptr); - JSStringRelease(jsLengthProperty); - return static_cast(JSValueToNumber(_ctx, jsLengthValue, nullptr)); - } - - JSCoreValue getValueAtIndex(size_t index) { - JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); - JSValueRef jsValue = JSObjectGetPropertyAtIndex(_ctx, jsObject, static_cast(index), nullptr); - return JSCoreValue(_ctx, jsValue); - } - - JSCoreValue getValueForProperty(const std::string& name) { - JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); - JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); - JSValueRef jsPropertyValue = JSObjectGetProperty(_ctx, jsObject, jsPropertyName, nullptr); - JSStringRelease(jsPropertyName); - return JSCoreValue(_ctx, jsPropertyValue); - } - - void setValueAtIndex(size_t index, JSCoreValue value) { - JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); - JSObjectSetPropertyAtIndex(_ctx, jsObject, static_cast(index), value.getValueRef(), nullptr); - } - - void setValueForProperty(const std::string& name, JSCoreValue value) { - JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); - JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); - JSObjectSetProperty(_ctx, jsObject, jsPropertyName, value.getValueRef(), kJSPropertyAttributeNone, nullptr); - JSStringRelease(jsPropertyName); - } - - JSValueRef getValueRef() { return _value; } - -private: - - JSContextRef _ctx = nullptr; - JSValueRef _value = nullptr; -}; - -class JSCoreStringCache { - -public: - - explicit JSCoreStringCache(size_t capacity) : _capacity(capacity) { - // Must reserve the maximum number of keys we'll need so no rehashing occurs, otherwise we'll invalidate the - // iterators in CacheEntry. - _map.reserve(capacity + 1); - }; - - JSValueRef get(JSContextRef context, const std::string& key) { - bool inserted; - CacheMap::iterator mapIt; - std::tie(mapIt, inserted) = _map.emplace(key, CacheList::iterator{}); - if (inserted) { - // New entry - create JS string and add to front of list. - JSStringRef jsString = JSStringCreateWithUTF8CString(key.c_str()); - JSValueRef jsValue = JSValueMakeString(context, jsString); - JSValueProtect(context, jsValue); - JSStringRelease(jsString); - _list.emplace_front(CacheEntry{mapIt, jsValue}); - mapIt->second = _list.begin(); - } else { - // Existing entry - move to front of list. - auto listIt = mapIt->second; - if (listIt != _list.begin()) { - _list.splice(_list.begin(), _list, listIt); - } - } - while (_list.size() > _capacity) { - auto& leastRecentEntry = _list.back(); - JSValueUnprotect(context, leastRecentEntry.jsValue); - _map.erase(leastRecentEntry.mapIt); - _list.pop_back(); - } - return mapIt->second->jsValue; - } - -private: - - struct CacheEntry; - using CacheList = std::list; - using CacheMap = std::unordered_map; - - struct CacheEntry { - CacheMap::iterator mapIt; - JSValueRef jsValue; - }; - - CacheMap _map; - CacheList _list; - size_t _capacity; -}; - -class JSCoreContext { - -public: - - JSCoreContext(); - - ~JSCoreContext(); - - void setGlobalValue(const std::string& name, JSCoreValue value); - - void setCurrentFeature(const Feature* feature); - - bool setFunction(JSFunctionIndex index, const std::string& source); - - bool evaluateBooleanFunction(JSFunctionIndex index); - -protected: - - JSCoreValue newNull(); - JSCoreValue newBoolean(bool value); - JSCoreValue newNumber(double value); - JSCoreValue newString(const std::string& value); - JSCoreValue newArray(); - JSCoreValue newObject(); - JSCoreValue newFunction(const std::string& value); - JSCoreValue getFunctionResult(JSFunctionIndex index); - - JSScopeMarker getScopeMarker(); - void resetToScopeMarker(JSScopeMarker marker); - -private: - - static bool jsHasPropertyCallback(JSContextRef context, JSObjectRef object, JSStringRef property); - static JSValueRef jsGetPropertyCallback(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef* exception); - - JSObjectRef compileFunction(const std::string& source); - - std::vector _functions; - - JSContextGroupRef _group; - JSGlobalContextRef _context; - - JSCoreStringCache _strings; - - const Feature* _feature; - - friend JavaScriptScope; -}; - -} // namespace Tangram diff --git a/core/src/js/JavaScript.h b/core/src/js/JavaScript.h index 023e335b8f..710f4fc567 100644 --- a/core/src/js/JavaScript.h +++ b/core/src/js/JavaScript.h @@ -4,13 +4,11 @@ #pragma once #include #include -#include "js/JavaScriptFwd.h" #if TANGRAM_USE_JSCORE -#include "js/JSCoreContext.h" -#else -#include "js/DuktapeContext.h" +#include "js/jsCoreContext.h" #endif +#include "js/duktapeContext.h" namespace Tangram { diff --git a/core/src/js/JavaScriptFwd.h b/core/src/js/JavaScriptFwd.h deleted file mode 100644 index 512b032638..0000000000 --- a/core/src/js/JavaScriptFwd.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by Matt Blair on 2018-12-03. -// -#pragma once - -#include - -namespace Tangram { - -#if TANGRAM_USE_JSCORE -class JSCoreValue; -class JSCoreContext; -using JSValue = JSCoreValue; -using JSContext = JSCoreContext; -#else -class DuktapeValue; -class DuktapeContext; -using JSValue = DuktapeValue; -using JSContext = DuktapeContext; -#endif - -struct Feature; - -using JSScopeMarker = int32_t; -using JSFunctionIndex = uint32_t; - -template class JavaScriptScope; - -using JSScope = JavaScriptScope; - -} // namespace Tangram diff --git a/core/src/js/duktapeContext.h b/core/src/js/duktapeContext.h new file mode 100644 index 0000000000..4b9b63d1b8 --- /dev/null +++ b/core/src/js/duktapeContext.h @@ -0,0 +1,832 @@ +#pragma once + +#include "scene/styleContext.h" +#include "duktape/duktape.h" +#include "scene/scene.h" +#include "log.h" +#include + +#if 0 +#define DUMP() do { \ + bool calling = _calling; \ + _dumping = true; \ + duk_push_context_dump(_ctx); \ + LOG("%s", duk_to_string(_ctx, -1)); \ + duk_pop(_ctx); \ + _calling = calling; \ + _dumping = false; \ + } while(0) +#define DUMPCTX(context) do { \ + bool calling = context._calling; \ + context._dumping = true; \ + duk_push_context_dump(context._ctx); \ + LOG("%s", duk_to_string(context._ctx, -1)); \ + duk_pop(context._ctx); \ + context._calling = calling; \ + context._dumping = false; \ + } while(0) +#define DBG(...) LOG(__VA_ARGS__) +#else +#define DUMP() +#define DBG(...) +#define DUMPCTX(...) +#endif +#define DBGCACHE(...) +//#define DBGCACHE(...) LOG(__VA_ARGS__) + +namespace Tangram { + +using JSScopeMarker = int32_t; +using JSFunctionIndex = uint32_t; + +namespace Duktape { + +const static char FUNC_ID[] = "\xff""\xff""fns"; +const static char GLOBAL_ID[] = "\xff""\xff""glb"; +const static char FEATURE_ID[] = "\xff""\xff""fet"; + +struct Context; + +struct Value { + + duk_context* _ctx = nullptr; + duk_idx_t _index = 0; + + Value(duk_context* ctx, duk_idx_t index) : _ctx(ctx), _index(index) {} + + Value(Value&& _other) : _ctx(_other._ctx), _index(_other._index) { + _other._ctx = nullptr; + } + + Value& operator=(Value&& _other) { + this->_ctx = _other._ctx; + this->_index = _other._index; + _other._ctx = nullptr; + return *this; + } + + operator bool() const { return bool(_ctx ); } + + bool isUndefined() { + return duk_is_undefined(_ctx, _index) != 0; + } + + bool isNull() { + return duk_is_null(_ctx, _index) != 0; + } + + bool isBoolean() { + return duk_is_boolean(_ctx, _index) != 0; + } + + bool isNumber() { + return duk_is_number(_ctx, _index) != 0; + } + + bool isString() { + return duk_is_string(_ctx, _index) != 0; + } + + bool isArray() { + return duk_is_array(_ctx, _index) != 0; + } + + bool isObject() { + return duk_is_object(_ctx, _index) != 0; + } + + bool toBool() { + return duk_to_boolean(_ctx, _index) != 0; + } + + int toInt() { + return duk_to_int(_ctx, _index); + } + + double toDouble() { + return duk_to_number(_ctx, _index); + } + + std::string toString() { + std::string result; + size_t len; + // NB this requires the value to be a string. Rename to getString()? + if (const char* data = duk_get_lstring(_ctx, _index, &len)) { + if (len > 0) { result = std::string(data, len); } + } + return result; + } + + size_t getLength() { + return duk_get_length(_ctx, _index); + } + + Value getValueAtIndex(size_t index) { + duk_get_prop_index(_ctx, _index, static_cast(index)); + return {_ctx, duk_normalize_index(_ctx, -1)}; + } + + Value getValueForProperty(const std::string& name) { + duk_get_prop_lstring(_ctx, _index, name.data(), name.length()); + return {_ctx, duk_normalize_index(_ctx, -1)}; + } + + void setValueAtIndex(size_t index, Value value) { + value.ensureExistsOnStackTop(); + duk_put_prop_index(_ctx, _index, static_cast(index)); + } + + void setValueForProperty(const std::string& name, Value value) { + value.ensureExistsOnStackTop(); + duk_put_prop_lstring(_ctx, _index, name.data(), name.length()); + + } + + void ensureExistsOnStackTop() { + auto dukTopIndex = duk_get_top_index(_ctx); + if (_index != dukTopIndex) { + duk_require_stack_top(_ctx, dukTopIndex + 1); + duk_dup(_ctx, _index); + } + } +}; + +struct Function { + JSFunctionIndex idx = 0; + void* ptr = nullptr; + struct { + bool feature = false; + bool zoom = false; + bool geom = false; + bool global = false; + } context; + std::string source; +}; + +static Context& getContext(duk_context* _ctx) { + duk_memory_functions funcs; + duk_get_memory_functions(_ctx, &funcs); + assert(funcs.udata != nullptr); + return *reinterpret_cast(funcs.udata); +} + +struct Context { + using Value = Duktape::Value; + + duk_context* _ctx = nullptr; + const Feature* _feature = nullptr; + void* _featurePtr = nullptr; + void* _objectPtr = nullptr; + void* _functionsPtr = nullptr; + void* _globalPtr = nullptr; + void* _stringStashPtr = nullptr; + + std::vector m_functions; + std::array m_filterKeys {}; + + ~Context() { + duk_destroy_heap(_ctx); + LOG("gets:%d reused:%d - %f%% / extstr:%d freed:%d\n", + _fetchCnt+_reuseCnt, _reuseCnt, + float(_reuseCnt) / float(_fetchCnt + _reuseCnt) * 100, + _allocCnt, _freeCnt); + + _stringCache8.dump(); + _stringCache32.dump(); + _stringCache64.dump(); + } + + Context() { + + static std::atomic initialized{false}; + if (!initialized.exchange(true)) { + duk_extstr_set_handler(jsExtstrInternCheck, jsExtstrFree); + } + + // Create duktape heap with default allocation functions and + // custom fatal error handler. + _ctx = duk_create_heap(nullptr, nullptr, nullptr, + this, fatalErrorHandler); + + //// Create global geometry constants + duk_push_number(_ctx, GeometryType::points); + duk_put_global_string(_ctx, "point"); + duk_push_number(_ctx, GeometryType::lines); + duk_put_global_string(_ctx, "line"); + duk_push_number(_ctx, GeometryType::polygons); + duk_put_global_string(_ctx, "polygon"); + + // Set up 'fns' array. + duk_idx_t functionsObj = duk_push_array(_ctx); + _functionsPtr = duk_get_heapptr(_ctx, functionsObj); + if (!_functionsPtr) { + LOGE("'Function object not set"); + return; + } + if(!duk_put_global_string(_ctx, FUNC_ID)) { + LOGE("'Function object not set 2"); + return; + } + + // Create 'feature' object and store in global + // Feature object + duk_push_object(_ctx); + _objectPtr = duk_require_heapptr(_ctx, -1); + + // Handler object + duk_idx_t handlerObj = duk_push_object(_ctx); + // Add 'get' property to handler + duk_push_c_function(_ctx, jsGetProperty, 3); + duk_put_prop_string(_ctx, handlerObj, "get"); + // Add 'has' property to handler + duk_push_c_function(_ctx, jsHasProperty, 2); + duk_put_prop_string(_ctx, handlerObj, "has"); + // [{get:func,has:func}] + + duk_push_proxy(_ctx, 0); + duk_freeze(_ctx, -1); + + _featurePtr = duk_get_heapptr(_ctx, -1); + // Stash 'feature' proxy object + if (!duk_put_global_string(_ctx, FEATURE_ID)) { + LOGE("Initialization failed"); + duk_pop(_ctx); + } + + { + duk_push_heap_stash(_ctx); + duk_push_array(_ctx); + _stringStashPtr = duk_get_heapptr(_ctx, -1); + duk_put_prop_string(_ctx, -2, "stringstash"); + duk_pop(_ctx); // pop stash + } + + DUMP(); + DBG("<<<<<<<<<"); + } + + void addStringProxy() { + // duk_push_object(_ctx); + // //_objectPtr = duk_require_heapptr(_ctx, -1); + // // Handler object + // duk_idx_t handlerObj = duk_push_object(_ctx); + // // Add 'get' property to handler + // duk_push_c_function(_ctx, dukStringProxyLength, 0); + // duk_put_prop_string(_ctx, handlerObj, "length"); + // // Add 'has' property to handler + // duk_push_c_function(_ctx, dukStringProxyValueOf, 0); + // duk_put_prop_string(_ctx, handlerObj, "valueOf"); + // // [{get:func,has:func}] + // duk_push_proxy(_ctx, 0); + // _featurePtr = duk_get_heapptr(_ctx, -1); + } + + void setGlobalValue(const std::string& name, Value value) { + DBG(">>>>> GLOBAL >>>>>"); + //DUMP(); + value.ensureExistsOnStackTop(); + + if (name == "global") { + _globalPtr = duk_get_heapptr(_ctx, value._index); + if (!_globalPtr) { LOGE("Global object invalid!"); } + + // Freeze object to not allow modifications + duk_freeze(_ctx, -1); + //duk_push_proxy(duk_context *ctx, duk_uint_t proxy_flags) + // Stash global object + duk_put_global_string(_ctx, GLOBAL_ID); + } else { + duk_put_global_lstring(_ctx, name.data(), name.length()); + } + DBG("<<<<< GLOBAL <<<<<"); + } + + void setCurrentFeature(const Feature* feature) { + // if (_was_called) LOG("[%p] Feature", _feature); + // _was_called = false; + + _feature = feature; + _lastFeature = nullptr; + _propertyCacheUse = 0; + + //m_stringCache.clear(); + } + + void setFilterKey(Filter::Key _key, int _val) { + m_filterKeys[uint8_t(_key)] = _val; + } + + Value getStackTopValue() { + return {_ctx, duk_normalize_index(_ctx, -1)}; + } + + JSScopeMarker getScopeMarker() { + return duk_get_top(_ctx); + } + + void resetToScopeMarker(JSScopeMarker marker) { + duk_set_top(_ctx, marker); + } + + bool evaluateBooleanFunction(JSFunctionIndex index) { + if (!evaluateFunction(index)) { return false; } + // Evaluate the "truthiness" of the function result at the top + // of the stack. + bool result = duk_to_boolean(_ctx, -1) != 0; + + // pop result + duk_pop(_ctx); + return result; + } + + Value getFunctionResult(JSFunctionIndex index) { + if (!evaluateFunction(index)) { + return {nullptr, 0}; + } + return getStackTopValue(); + } + + bool setFunction(JSFunctionIndex index, const std::string& source) { + DBG(">>>> FUNCTION >>>> %d", index); + + if (m_functions.size() == index) { + m_functions.emplace_back(); + } else { + m_functions.resize(index+1); + } + + if (!duk_get_global_string(_ctx, FUNC_ID)) { + LOGE("AddFunction - functions array not initialized"); + DUMP(); + duk_pop(_ctx); // pop [undefined] sitting at stack top + return false; + } + auto& function = m_functions.back(); + + bool append = false; + std::string args; + // TODO use proper regex + +//#define BIND_GLOBAL_TO_FUNCTION +#ifndef BIND_GLOBAL_TO_FUNCTION + size_t hasGlobal = source.find("global"); + if (hasGlobal != std::string::npos) { + args += "global"; + append = true; + function.context.global = true; + } +#endif + + + size_t hasFeature = source.find("feature"); + if (hasFeature != std::string::npos) { + if (append) { args += ","; } + args += "feature"; + append = true; + function.context.feature = true; + } + size_t hasZoom = source.find("$zoom"); + if (hasZoom != std::string::npos) { + if (append) { args += ","; } + args += "$zoom"; + append = true; + function.context.zoom = true; + } + size_t hasGeom = source.find("$geometry"); + if (hasGeom != std::string::npos) { + if (append) { args += ","; } + args += "$geometry"; + function.context.geom = true; + } + + size_t beg = source.find('(')+1; + function.source = source; + function.source.insert(beg, args); + + +#ifdef BIND_GLOBAL_TO_FUNCTION + size_t hasGlobal = source.find("global"); + if (hasGlobal != std::string::npos) { + function.source = "function(global) { return " + function.source + " }"; + function.context.global = true; + } +#endif + + if (duk_pcompile_lstring(_ctx, DUK_COMPILE_FUNCTION, + function.source.c_str(), function.source.length()) == 0) { + +#ifdef BIND_GLOBAL_TO_FUNCTION + if (function.context.global) { + duk_push_heapptr(_ctx, _globalPtr); + + if (duk_pcall(_ctx, 1) != 0) { + LOG("ERRRRRRRRRRRRR"); + } + } +#endif + + function.ptr = duk_get_heapptr(_ctx, -1); + + // Store function in global.functions to make sure it will not be + // garbage collected. + duk_put_prop_index(_ctx, -2, index); + + } else { + LOGW("Compile failed: %s\n%s\n---", + duk_safe_to_string(_ctx, -1), function.source.c_str()); + // Pop error + duk_pop(_ctx); + // Pop array + duk_pop(_ctx); + return false; + } + // Pop the functions array off the stack + duk_pop(_ctx); + DBG("<<<< FUNCTION <<<<"); + return true; + } + + bool evaluateFunction(JSFunctionIndex index) { + //DBG(">>>>> EVAL >>>>> %d", index); + + if (m_functions.size() <= index) { + LOGE("Functions array not initialized. index:%d size:%d", + index, m_functions.size()); + return false; + } + if (m_functions[index].ptr == nullptr) { + LOGE("Function not set. index:%d size:%d", index, m_functions.size()); + return false; + } + + auto& function = m_functions[index]; + duk_push_heapptr(_ctx, function.ptr); + int args = 0; +#ifndef BIND_GLOBAL_TO_FUNCTION + if (function.context.global) { + args++; + duk_push_heapptr(_ctx, _globalPtr); + } +#endif + if (function.context.feature) { + args++; + duk_push_heapptr(_ctx, _featurePtr); + } + if (function.context.zoom) { + args++; + duk_push_number(_ctx, m_filterKeys[uint8_t(Filter::Key::zoom)]); + } + if (function.context.geom) { + args++; + duk_push_number(_ctx, m_filterKeys[uint8_t(Filter::Key::geometry)]); + } + + _calling = true; + _was_called = true; + + DBG(">>>>> Calling %d", index); + DUMP(); + + // DUMP(); + if (duk_pcall(_ctx, args) != 0) { + _calling = false; + + LOG("Error: %s, function:%d feature:%p\n%s", duk_safe_to_string(_ctx, -1), + index, _feature, function.source.c_str()); + + // Pop error + duk_pop(_ctx); + return false; + } + _calling = false; + // -- why not return value here? + //DBG("<<<<< EVAL <<<<<"); + return true; + } + + Value newNull() { + duk_push_null(_ctx); + return getStackTopValue(); + } + + Value newBoolean(bool value) { + duk_push_boolean(_ctx, static_cast(value)); + return getStackTopValue(); + } + + Value newNumber(double value) { + duk_push_number(_ctx, value); + return getStackTopValue(); + } + + Value newString(const std::string& value) { + duk_push_lstring(_ctx, value.data(), value.length()); + return getStackTopValue(); + } + + Value newArray() { + duk_push_array(_ctx); + return getStackTopValue(); + } + + Value newObject() { + duk_push_object(_ctx); + return getStackTopValue(); + } + + Value newFunction(const std::string& value) { + if (duk_pcompile_lstring(_ctx, DUK_COMPILE_FUNCTION, + value.data(), value.length()) != 0) { + + auto error = duk_safe_to_string(_ctx, -1); + LOGW("Compile failed in global function: %s\n%s\n---", error, value.c_str()); + duk_pop(_ctx); // Pop error. + return {nullptr, 0}; + } + return getStackTopValue(); + } +private: + + // Cache Feature property indexes + const Feature* _lastFeature = nullptr; + + // Map strings to duk heapPtr + //std::vector> _stringCache {}; + + using prop_key = const char*; + using prop_val = const Tangram::Value*; + using heap_ptr = void*; + + + struct cache_entry { + prop_key key = nullptr; + prop_val val = &NOT_A_VALUE; + heap_ptr ptr = nullptr; + }; + std::array _propertyCache {}; + + uint32_t _propertyCacheUse = 0; + int _reuseCnt = 0; + int _fetchCnt = 0; + int _allocCnt = 0; + int _freeCnt = 0; + + bool _calling = false; + bool _was_called = false; + bool _dumping = false; + + const char* _lastPushed = nullptr; + + //std::set _stringCache; + std::string _buffer; + + template + struct StringCache { + std::array lengths{}; + std::array, ENTRIES> strings{}; + std::array refs{}; + int usage = 0; + + void free(Context& context, const void* ptr) { + const char* str = (const char*)(ptr); + const char* arr = (const char*)(&strings); + const ptrdiff_t diff = str - arr; + + if ((diff > 0) && (diff < sizeof(strings))) { + context._freeCnt++; + + int pos = diff / ptrdiff_t(SIZE); + refs[pos]--; + + DBGCACHE("[%d] freed %.*s", refs[pos], lengths[pos], &strings[pos][stroffset]); + } + } + + const char* add(Context& context, void* ptr, size_t length) { + const char* str = (const char*)ptr; + int slot = -1; + for (int i = 0; i < usage; i++) { + DBGCACHE("[%d/%d]", i, usage); + assert(int64_t(ENTRIES) - int64_t(i) > 0); + + if (length <= lengths[0] && std::memcmp(&strings[i][0], str, length) == 0) { + refs[i]++; + context._allocCnt++; + DBGCACHE("[%d] found '%.*s'", refs[i], length, &strings[i][0]); + return &strings[i][0]; + } + if (slot < 0 && refs[i] == 0) { + slot = i; + } + } + if (slot == -1) { + if (usage < ENTRIES) { + slot = usage++; + } else { + LOG("cache full: %.*s len:%d - usage:%d, slot:%d", length, str, length, usage, slot); + return nullptr; + } + } + + std::memcpy(&strings[slot][0], str, length); + lengths[slot] = uint8_t(length); + + refs[slot]++; + context._allocCnt++; + + DBGCACHE("[%d] added: '%.*s'", refs[slot], length, str); + + return &strings[slot][0]; + } + void dump() { + DBGCACHE("CACHE usage %d", usage); + for (size_t i = 0; i < usage; i++) { + DBGCACHE("[%d] refs:%d '%.*s'", i, refs[i], strings[i][0], &strings[i][0]); + } + } + }; + + StringCache<8, 128> _stringCache8; + StringCache<32, 64> _stringCache32; + StringCache<64, 16> _stringCache64; + + static const char* jsExtstrInternCheck(void* udata, void* str, unsigned long blen) { + if (blen < 2) { + // TODO duk should have it's own char cache. Interning these is somewhat stupid overhead! + // LOG("[%p] >>>>> %.*s - %d / %d", str, blen, str, blen, (*static_cast(str)) & 0x000000ff); + return nullptr; + } + if (blen > 64) { + //LOG("[%p] >>>>> not my business %.*s - %d", str, blen, str, blen); + return nullptr; + } + + Context& context = *reinterpret_cast(udata); + if (context._calling) { + //DBG("[%p / %p] >>>>> check %.*s - %d", str, context._lastPushed, blen, str, blen); + + if (context._lastPushed == str) { + // We hold it - safe! + DBGCACHE("[%p] >>>>> found %.*s - %d", str, blen, str, blen); + return context._lastPushed; + } + + // todo could one stash the heapptr in this call? + if (blen <= 8) { + return context._stringCache8.add(context, str, blen); + } + if (blen <= 64) { + return context._stringCache32.add(context, str, blen); + } + return context._stringCache64.add(context, str, blen); + } + return nullptr; + } + + static void jsExtstrFree(void* udata, const void *extdata) { + Context& context = *reinterpret_cast(udata); + context._stringCache8.free(context, extdata); + context._stringCache32.free(context, extdata); + context._stringCache64.free(context, extdata); + } + + static int getProperty(Context& context) { + // Get the requested object key + const char* key = duk_require_string(context._ctx, 1); + + auto& cache = context._propertyCache; + size_t use = context._propertyCacheUse; + + DBG("get prop %d %s", use, key); + + for (auto i = 0; i < use; i++) { + DBG("entry cached"); + if (cache[i].key == key) { + context._reuseCnt++; + return i; + } + } + + context._fetchCnt++; + + if (use == cache.size()) { + LOG("overflowing cache!"); + use = context._propertyCacheUse = 0; + } + + //if (use < context->_propertyCache.size()) { + DBG("caching"); + + auto& entry= cache[use]; + entry.key = key; + entry.val = &(context._feature->props.get(key)); + entry.ptr = nullptr; + + ++context._propertyCacheUse; + //} + return use; + } + + // Implements Proxy handler.has(target_object, key) + static int jsHasProperty(duk_context *_ctx) { + auto& context = getContext(_ctx); + if (context._feature) { + + int id = getProperty(context); + auto& entry = context._propertyCache[id]; + + bool hasProp = !(entry.val->is()); + + duk_push_boolean(_ctx, static_cast(hasProp)); + return 1; + } + return 0; + } + + // Implements Proxy handler.get(target_object, key) + static int jsGetProperty(duk_context *_ctx) { + DBG("jsGetProperty"); + + auto& context = getContext(_ctx); + if (context._dumping) { return 0; } + + DUMPCTX(context); + + if (!context._feature) { return 0; } + +#ifdef STASH_PROPERTIES + if (context._feature == context._lastFeature) { + duk_push_heap_stash(_ctx); + + // duk_idx_t featureIdx = duk_push_heapptr(_ctx, context._objectPtr); + duk_dup(_ctx, 1); + + if (duk_get_prop(_ctx, -2)) { + DBG("stashed"); + return 1; + } else { + duk_pop(_ctx); // pop undefined + } + duk_pop(_ctx); // pop stash + } + context._lastFeature = context._feature; +#endif + //DUMP(); + + // Get the property name (second parameter) + int id = getProperty(context); + auto& entry = context._propertyCache[id]; + + if (entry.val->is()) { + const auto& str = entry.val->get(); + if (entry.ptr) { + DBG("push pointer %p - %s", entry.ptr, str.c_str()); + duk_push_heapptr(_ctx, entry.ptr); + } else { + context._lastPushed = str.c_str(); + +#ifdef STASH_PROPERTIES + duk_push_heap_stash(_ctx); +#else + //duk_idx_t featureIdx = duk_push_heapptr(_ctx, context._featurePtr); + duk_idx_t stringStash = duk_push_heapptr(_ctx, context._stringStashPtr); +#endif + DBG("push string %p %s -> %p", str.c_str(), str.c_str(), entry.ptr); + + // TODO check if we get an interned ref! + duk_push_lstring(_ctx, str.c_str(), str.length()); + entry.ptr = duk_get_heapptr(_ctx, -1); + + DUMPCTX(context); + + // Duplicate to stash string + duk_dup_top(_ctx); + +#ifdef STASH_PROPERTIES + duk_put_prop_heapptr(_ctx, -3, duk_get_heapptr(_ctx, 1)); +#else + //duk_put_prop_heapptr(_ctx, featureIdx, duk_get_heapptr(_ctx, 1)); + duk_put_prop_index(_ctx, stringStash, id); +#endif + + DUMPCTX(context); + } + } else if (entry.val->is()) { + duk_push_number(_ctx, entry.val->get()); + } else { + duk_push_undefined(_ctx); + } + //DUMP(); + return 1; + } + + static void fatalErrorHandler(void* udata, const char* message) { + //Context* context = reinterpret_cast(udata); + + LOGE("Fatal Error in DuktapeJavaScriptContext: %s", message); + abort(); + } +}; + +} // namespace Duktape +} // namespace Tangram diff --git a/core/src/js/jsCoreContext.h b/core/src/js/jsCoreContext.h new file mode 100644 index 0000000000..4535943640 --- /dev/null +++ b/core/src/js/jsCoreContext.h @@ -0,0 +1,341 @@ +#pragma once + +#include +#include "log.h" +#include "scene/filters.h" +#include "scene/scene.h" + +#include + +namespace Tangram { + +using JSScopeMarker = int32_t; +using JSFunctionIndex = uint32_t; + +namespace JSCore { + +struct Value { + + JSContextRef _ctx; + JSValueRef _value; + + Value(JSContextRef ctx, JSValueRef value) + : _ctx(ctx), _value(value) { + JSValueProtect(_ctx, _value); + } + + Value() : _ctx(nullptr), _value(nullptr) {} + + Value(Value&& _other) : _ctx(_other._ctx), _value(_other._value) { + _other._ctx = nullptr; + } + + ~Value() { + if (_ctx) { JSValueUnprotect(_ctx, _value); } + } + + Value& operator=(Value&& _other) { + this->_ctx = _other._ctx; + this->_value = _other._value; + _other._ctx = nullptr; + return *this; + } + + operator bool() const { return bool(_ctx); } + + bool isUndefined() { + return JSValueIsUndefined(_ctx, _value); + } + + bool isNull() { + return JSValueIsNull(_ctx, _value); + } + + bool isBoolean() { + return JSValueIsBoolean(_ctx, _value); + } + + bool isNumber() { + return JSValueIsNumber(_ctx, _value); + } + + bool isString() { + return JSValueIsString(_ctx, _value); + } + + bool isArray() { + return JSValueIsArray(_ctx, _value); + } + + bool isObject() { + return JSValueIsObject(_ctx, _value); + } + + bool toBool() { + return JSValueToBoolean(_ctx, _value); + } + + int toInt() { + return static_cast(JSValueToNumber(_ctx, _value, nullptr)); + } + + double toDouble() { + return JSValueToNumber(_ctx, _value, nullptr); + } + + std::string toString() { + JSStringRef jsString = JSValueToStringCopy(_ctx, _value, nullptr); + std::string result(JSStringGetMaximumUTF8CStringSize(jsString), '\0'); + size_t bytesWrittenIncludingNull = JSStringGetUTF8CString(jsString, &result[0], result.capacity()); + result.resize(bytesWrittenIncludingNull - 1); + JSStringRelease(jsString); + return result; + } + + size_t getLength() { + JSStringRef jsLengthProperty = JSStringCreateWithUTF8CString("length"); + JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); + JSValueRef jsLengthValue = JSObjectGetProperty(_ctx, jsObject, jsLengthProperty, nullptr); + return static_cast(JSValueToNumber(_ctx, jsLengthValue, nullptr)); + } + + Value getValueAtIndex(size_t index) { + JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); + JSValueRef jsValue = JSObjectGetPropertyAtIndex(_ctx, jsObject, static_cast(index), nullptr); + return {_ctx, jsValue}; + } + + Value getValueForProperty(const std::string& name) { + JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); + JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); + JSValueRef jsPropertyValue = JSObjectGetProperty(_ctx, jsObject, jsPropertyName, nullptr); + return {_ctx, jsPropertyValue}; + } + + void setValueAtIndex(size_t index, Value value) { + JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); + JSValueRef jsValueForIndex = value.getValueRef(); + JSObjectSetPropertyAtIndex(_ctx, jsObject, static_cast(index), jsValueForIndex, nullptr); + } + + void setValueForProperty(const std::string& name, Value value) { + JSObjectRef jsObject = JSValueToObject(_ctx, _value, nullptr); + JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); + JSValueRef jsValueForProperty = value.getValueRef(); + JSObjectSetProperty(_ctx, jsObject, jsPropertyName, jsValueForProperty, + kJSPropertyAttributeNone, nullptr); + } + + JSValueRef getValueRef() { return _value; } +}; + + +struct Context { + using Value = JSCore::Value; + + const Feature* _feature; + std::vector _functions; + JSGlobalContextRef _context; + + Context() : _context(JSGlobalContextCreate(nullptr)) { + // Create the global 'feature' object. + JSClassDefinition jsFeatureClassDefinition = kJSClassDefinitionEmpty; + jsFeatureClassDefinition.hasProperty = &jsHasPropertyCallback; + jsFeatureClassDefinition.getProperty = &jsGetPropertyCallback; + JSClassRef jsFeatureClass = JSClassCreate(&jsFeatureClassDefinition); + JSObjectRef jsFeatureObject = JSObjectMake(_context, jsFeatureClass, this); + JSClassRelease(jsFeatureClass); + JSObjectRef jsGlobalObject = JSContextGetGlobalObject(_context); + JSStringRef jsFeatureName = JSStringCreateWithUTF8CString("feature"); + JSObjectSetProperty(_context, jsGlobalObject, jsFeatureName, jsFeatureObject, + kJSPropertyAttributeNone, nullptr); + JSStringRelease(jsFeatureName); + + // Create geometry constants. + std::array, 3> geometryConstants{ + {{"point", GeometryType::points}, + {"line", GeometryType::lines}, + {"polygon", GeometryType::polygons}} + }; + for (const auto& pair : geometryConstants) { + JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(pair.first); + JSValueRef jsPropertyValue = JSValueMakeNumber(_context, pair.second); + JSObjectSetProperty(_context, jsGlobalObject, jsPropertyName, jsPropertyValue, + kJSPropertyAttributeReadOnly, nullptr); + JSStringRelease(jsPropertyName); + } + } + + ~Context() { + JSGlobalContextRelease(_context); + } + + void setGlobalValue(const std::string& name, Value value) { + JSObjectRef jsGlobalObject = JSContextGetGlobalObject(_context); + JSStringRef jsPropertyName = JSStringCreateWithUTF8CString(name.c_str()); + JSValueRef jsValueForProperty = value.getValueRef(); + JSObjectSetProperty(_context, jsGlobalObject, jsPropertyName, jsValueForProperty, + kJSPropertyAttributeNone, nullptr); + JSStringRelease(jsPropertyName); + } + + void setCurrentFeature(const Feature* feature) { + _feature = feature; + } + + void setFilterKey(Filter::Key _key, int _val) { + setGlobalValue(Filter::keyName(_key), newNumber(_val)); + } + + bool setFunction(JSFunctionIndex index, const std::string& fn) { + JSObjectRef jsFunctionObject = compileFunction(fn); + if (!jsFunctionObject) { + return false; + } + if (index >= _functions.size()) { + _functions.resize(index + 1); + } + auto& functionEntry = _functions[index]; + if (functionEntry != nullptr) { + JSValueUnprotect(_context, functionEntry); + } + JSValueProtect(_context, jsFunctionObject); + functionEntry = jsFunctionObject; + return true; + } + + bool evaluateBooleanFunction(JSFunctionIndex index) { + auto resultValue = getFunctionResult(index); + if (resultValue) { + return resultValue.toBool(); + } + return false; + } + + Value newNull() { + JSValueRef jsValue = JSValueMakeNull(_context); + return {_context, jsValue}; + } + + Value newBoolean(bool value) { + JSValueRef jsValue = JSValueMakeBoolean(_context, value); + return {_context, jsValue}; + } + + Value newNumber(double value) { + JSValueRef jsValue = JSValueMakeNumber(_context, value); + return {_context, jsValue}; + } + + Value newString(const std::string& value) { + JSStringRef jsString = JSStringCreateWithUTF8CString(value.c_str()); + JSValueRef jsValue = JSValueMakeString(_context, jsString); + return {_context, jsValue}; + } + + Value newArray() { + JSObjectRef jsObject = JSObjectMakeArray(_context, 0, nullptr, nullptr); + return {_context, jsObject}; + } + + Value newObject() { + JSObjectRef jsObject = JSObjectMake(_context, nullptr, nullptr); + return {_context, jsObject}; + } + + Value newFunction(const std::string& value) { + JSObjectRef jsFunctionObject = compileFunction(value); + if (jsFunctionObject == nullptr) { + return {}; + } + return {_context, jsFunctionObject}; + } + + Value getFunctionResult(JSFunctionIndex index) { + if (index > _functions.size()) { + return {}; + } + JSObjectRef jsFunctionObject = _functions[index]; + JSValueRef jsException = nullptr; + JSValueRef jsResultValue = JSObjectCallAsFunction(_context, jsFunctionObject, nullptr, + 0, nullptr, &jsException); + if (jsException != nullptr) { + char buffer[128]; + JSStringRef jsExceptionString = JSValueToStringCopy(_context, jsException, nullptr); + JSStringGetUTF8CString(jsExceptionString, buffer, sizeof(buffer)); + LOGE("Error evaluating JavaScript function - %s", buffer); + JSStringRelease(jsExceptionString); + return {}; + } + return {_context, jsResultValue}; + } + + JSScopeMarker getScopeMarker() { + // Not needed for JSCore implementation. + return 0; + } + + void resetToScopeMarker(JSScopeMarker) { + // Not needed for JSCore implementation. + } + + JSObjectRef compileFunction(const std::string& fn) { + std::string expression("(" + fn + ")"); + JSStringRef jsFunctionSource = JSStringCreateWithUTF8CString(expression.c_str()); + JSValueRef jsException = nullptr; + JSValueRef jsFunction = JSEvaluateScript(_context, jsFunctionSource, nullptr, + nullptr, 0, &jsException); + JSStringRelease(jsFunctionSource); + if (jsException) { + char buffer[128]; + JSStringRef jsExceptionString = JSValueToStringCopy(_context, jsException, nullptr); + JSStringGetUTF8CString(jsExceptionString, buffer, sizeof(buffer)); + LOGE("Error compiling JavaScript function - %s", buffer); + JSStringRelease(jsExceptionString); + } + return JSValueToObject(_context, jsFunction, nullptr); + } + + static bool jsHasPropertyCallback(JSContextRef, JSObjectRef object, JSStringRef property) { + auto jsCoreContext = reinterpret_cast(JSObjectGetPrivate(object)); + if (!jsCoreContext) { + return false; + } + auto feature = jsCoreContext->_feature; + if (!feature) { + return false; + } + // This should be enough for all the names we use - could make it dynamically-sized if needed. + char nameBuffer[128]; + JSStringGetUTF8CString(property, nameBuffer, sizeof(nameBuffer)); + return feature->props.contains(nameBuffer); + } + + static JSValueRef jsGetPropertyCallback(JSContextRef context, JSObjectRef object, + JSStringRef property, JSValueRef*) { + auto jsCoreContext = reinterpret_cast(JSObjectGetPrivate(object)); + if (!jsCoreContext) { + return nullptr; + } + auto feature = jsCoreContext->_feature; + if (!feature) { + return nullptr; + } + JSValueRef jsValue = nullptr; + // This should be enough for all the names we use - could make it dynamically-sized if needed. + char nameBuffer[128]; + JSStringGetUTF8CString(property, nameBuffer, sizeof(nameBuffer)); + auto it = feature->props.get(nameBuffer); + if (it.is()) { + JSStringRef jsString = JSStringCreateWithUTF8CString(it.get().c_str()); + jsValue = JSValueMakeString(context, jsString); + JSStringRelease(jsString); + } else if (it.is()) { + jsValue = JSValueMakeNumber(context, it.get()); + } + return jsValue; + } +}; + +} // namespace JSCore +} // namespace Tangram diff --git a/core/src/map.cpp b/core/src/map.cpp index d387842b74..5ec42d05cf 100644 --- a/core/src/map.cpp +++ b/core/src/map.cpp @@ -184,6 +184,7 @@ void Map::Impl::setScene(std::shared_ptr& _scene) { SceneID Map::loadScene(std::shared_ptr scene, const std::vector& _sceneUpdates) { + LOGTOInit(); { std::unique_lock lock(impl->sceneMutex); @@ -250,6 +251,7 @@ SceneID Map::loadSceneYamlAsync(const std::string& _yaml, const std::string& _re SceneID Map::loadSceneAsync(std::shared_ptr nextScene, const std::vector& _sceneUpdates) { + LOGTOInit(); impl->sceneLoadBegin(); runAsyncTask([nextScene, _sceneUpdates, this](){ @@ -392,6 +394,8 @@ bool Map::update(float _dt) { // Wait until font and texture resources are fully loaded if (impl->scene->pendingFonts > 0 || impl->scene->pendingTextures > 0) { platform->requestRender(); + LOGTO("Waiting for Scene fonts:%d textures:%d", + impl->scene->pendingFonts.load(), impl->scene->pendingTextures.load()); return false; } @@ -467,6 +471,10 @@ bool Map::update(float _dt) { platform->requestRender(); } + LOGTO("View complete:%d vc:%d tl:%d tc:%d easing:%d label:%d maker:%d ", + viewComplete, viewChanged, tilesLoading, tilesChanged, + impl->isCameraEasing, labelsNeedUpdate, markersNeedUpdate); + return viewComplete; } diff --git a/core/src/marker/markerManager.cpp b/core/src/marker/markerManager.cpp index fa92ecbf0c..4b2f3e1f18 100644 --- a/core/src/marker/markerManager.cpp +++ b/core/src/marker/markerManager.cpp @@ -24,8 +24,8 @@ void MarkerManager::setScene(std::shared_ptr scene) { m_scene = scene; - m_styleContext = std::make_unique(); - m_styleContext->initFunctions(*scene); + m_styleContext = std::make_unique(false); + m_styleContext->initScene(*scene); // Initialize StyleBuilders. m_styleBuilders.clear(); @@ -402,7 +402,7 @@ bool MarkerManager::buildStyling(Marker& marker) { } // Compile any new JS functions used for styling. for (auto i = jsFnIndex; i < sceneJsFnList.size(); ++i) { - m_styleContext->addFunction(sceneJsFnList[i]); + m_styleContext->addFunction(sceneJsFnList.functions[i]); } marker.setDrawRuleData(std::make_unique("", 0, std::move(params))); @@ -433,7 +433,7 @@ bool MarkerManager::buildMesh(Marker& marker, int zoom) { // Apply defaul draw rules defined for this style styler->style().applyDefaultDrawRules(*rule); - m_styleContext->setKeywordZoom(zoom); + m_styleContext->setFilterKey(Filter::Key::zoom, zoom); bool valid = marker.evaluateRuleForContext(*m_styleContext); diff --git a/core/src/platform.cpp b/core/src/platform.cpp index a0b84d3f01..fb9d6cbd78 100644 --- a/core/src/platform.cpp +++ b/core/src/platform.cpp @@ -4,6 +4,9 @@ #include #include +std::chrono::time_point tangram_log_time_start, tangram_log_time_last; +std::mutex tangram_log_time_mutex; + namespace Tangram { Platform::Platform() : m_continuousRendering(false) {} diff --git a/core/src/scene/drawRule.cpp b/core/src/scene/drawRule.cpp index 7cca54cef8..2f56723e15 100644 --- a/core/src/scene/drawRule.cpp +++ b/core/src/scene/drawRule.cpp @@ -191,7 +191,7 @@ bool DrawRuleMergeSet::evaluateRuleForContext(DrawRule& rule, StyleContext& ctx) m_evaluated[i] = *param; param = &m_evaluated[i]; - Stops::eval(*param->stops, param->key, ctx.getKeywordZoom(), m_evaluated[i].value); + Stops::eval(*param->stops, param->key, ctx.getZoomLevel(), m_evaluated[i].value); } } diff --git a/core/src/scene/filters.cpp b/core/src/scene/filters.cpp index f476d9fdf0..d0faede1cf 100644 --- a/core/src/scene/filters.cpp +++ b/core/src/scene/filters.cpp @@ -1,13 +1,28 @@ #include "scene/filters.h" #include "data/tileData.h" +#include "log.h" #include "platform.h" #include "scene/styleContext.h" +#include "scene/scene.h" +#include "util/floatFormatter.h" +#include "util/yamlUtil.h" + +#include "yaml-cpp/yaml.h" #include +#define LOGNode(fmt, node, ...) LOGW(fmt ":\n'%s'\n", ## __VA_ARGS__, YAML::Dump(node).c_str()) + namespace Tangram { +const std::string Filter::key_geom{"$geometry"}; +const std::string Filter::key_zoom{"$zoom"}; +const std::string Filter::key_other{""}; + +const std::vector Filter::geometryStrings = { "", "point", "line", "polygon" }; + + void Filter::print(int _indent) const { switch (data.which()) { @@ -38,53 +53,60 @@ void Filter::print(int _indent) const { logMsg("%*s existence - key:%s\n", _indent, "", f.key.c_str()); break; } - case Data::type::value: { - auto& f = data.get(); - if (f.values[0].is()) { - logMsg("%*s equality set - keyword:%d key:%s val:%s\n", _indent, "", - f.keyword != FilterKeyword::undefined, - f.key.c_str(), - f.values[0].get().c_str()); - } - if (f.values[0].is()) { - logMsg("%*s equality - keyword:%d key:%s val:%f\n", _indent, "", - f.keyword != FilterKeyword::undefined, - f.key.c_str(), - f.values[0].get()); + case Data::type::value: { + auto& f = data.get(); + for (auto& val : data.get().values) { + logMsg("%*s equality set - key:%s val:%s\n", _indent, "", + f.key.c_str(), val.c_str()); } break; } - case Data::type::value: { - auto& f = data.get(); - if (f.value.is()) { - logMsg("%*s equality - keyword:%d key:%s val:%s\n", _indent, "", - f.keyword != FilterKeyword::undefined, - f.key.c_str(), - f.value.get().c_str()); - } - if (f.value.is()) { - logMsg("%*s equality - keyword:%d key:%s val:%f\n", _indent, "", - f.keyword != FilterKeyword::undefined, - f.key.c_str(), - f.value.get()); + case Data::type::value: { + auto& f = data.get(); + for (auto& val : data.get().values) { + logMsg("%*s equality set - key:%f val:%f\n", _indent, "", + f.key.c_str(), val); } break; } + case Data::type::value: { + auto& f = data.get(); + logMsg("%*s equality - key:%s val:%s\n", _indent, "", + f.key.c_str(), f.value.c_str()); + break; + } + case Data::type::value: { + auto& f = data.get(); + logMsg("%*s equality - key:%s val:%f\n", _indent, "", + f.key.c_str(), f.value); + break; + } case Data::type::value: { auto& f = data.get(); - logMsg("%*s range - keyword:%d key:%s min:%f max:%f\n", _indent, "", - f.keyword != FilterKeyword::undefined, + logMsg("%*s range - key:%s min:%f max:%f\n", _indent, "", f.key.c_str(), f.min, f.max); return; } + case Data::type::value: { + auto& f = data.get(); + logMsg("%*s range - key:%s min:%d max:%d\n", _indent, "", + keyName(f.key).c_str(), f.min, f.max); + return; + } + case Data::type::value: { + auto& f = data.get(); + logMsg("%*s equality - key:%s val:%d\n", _indent, "", + keyName(f.key).c_str(), f.value); + break; + } case Data::type::value: { logMsg("%*s function\n", _indent, ""); break; } default: + logMsg("missing!\n"); break; } - } @@ -95,32 +117,46 @@ int Filter::filterCost() const { switch (data.which()) { case Data::type::value: for (auto& f : operands()) { sum += f.filterCost(); } - return sum; + return sum * operands().size(); case Data::type::value: for (auto& f : operands()) { sum += f.filterCost(); } - return sum; + return sum * operands().size(); case Data::type::value: for (auto& f : operands()) { sum += f.filterCost(); } - return sum; + return sum * operands().size(); - case Data::type::value: - // Equality and Range are more specific for increasing - // the chance to fail early check them before Existence - return 20; + case Data::type::value: + return 5 * data.get().values.size(); + + case Data::type::value: + return 5 * data.get().values.size(); - case Data::type::value: - return data.get().keyword == FilterKeyword::undefined ? 10 : 1; + case Data::type::value: + return 10; - case Data::type::value: - return data.get().keyword == FilterKeyword::undefined ? 10 : 1; + case Data::type::value: + return 10; case Data::type::value: - return data.get().keyword == FilterKeyword::undefined ? 10 : 1; + return 10; + + case Data::type::value: + return 10; + + case Data::type::value: + return 5; + + case Data::type::value: + return 1; + + case Data::type::value: + return 1; case Data::type::value: // Most expensive filter should be checked last + // - unless it is most specific, e.g. global false.. return 1000; } return 0; @@ -134,19 +170,37 @@ const std::string& Filter::key() const { case Data::type::value: return data.get().key; - case Data::type::value: - return data.get().key; + // case Data::type::value: + // return keyName(data.get().key); + + case Data::type::value: + return data.get().key; - case Data::type::value: - return data.get().key; + case Data::type::value: + return data.get().key; + + case Data::type::value: + return keyName(data.get().key); + + case Data::type::value: + return data.get().key; + + case Data::type::value: + return data.get().key; case Data::type::value: return data.get().key; + case Data::type::value: + return data.get().key; + + case Data::type::value: + return key_zoom; + default: break; } - return empty; + return key_other; } const std::vector& Filter::operands() const { @@ -186,7 +240,7 @@ bool Filter::isOperator() const { return false; } -int compareSetFilter(const Filter& a, const Filter& b) { +int Filter::compareSetFilter(const Filter& a, const Filter& b) { auto& oa = a.operands(); auto& ob = b.operands(); @@ -203,131 +257,98 @@ int compareSetFilter(const Filter& a, const Filter& b) { return rb.min - ra.min; } } - return 0; } - void Filter::sort(std::vector& _filters) { - std::sort(_filters.begin(), _filters.end(), - [](Filter& a, Filter& b) { - - // Sort simple filters by eval cost - int ma = a.filterCost(); - int mb = b.filterCost(); - - if (!a.isOperator() && !b.isOperator()) { - int diff = ma - mb; - if (diff != 0) { - return diff < 0; - } - - // just for consistent ordering - // (and using > to prefer $zoom over $geom) - return a.key() > b.key(); - } - - // When one is a simple Filter and the other is a operaor - // or both are operators prefer the one with the cheaper - // filter(s). - if (ma != mb) { - return ma < mb; - } - - return compareSetFilter(a, b) < 0; - }); -} + std::sort(_filters.begin(), _filters.end(), [](Filter& a, Filter& b) { + // Sort simple filters by eval cost + int ma = a.filterCost(); + int mb = b.filterCost(); + + if (!a.isOperator() && !b.isOperator()) { + int diff = ma - mb; + if (diff != 0) { + return diff < 0; + } + // just for consistent ordering + // (and using > to prefer $zoom over $geom) + return a.key() > b.key(); + } -struct string_matcher { - using result_type = bool; - const std::string& str; + // When one is a simple Filter and the other is a operaor + // or both are operators prefer the one with the cheaper + // filter(s). + if (ma != mb) { + return ma < mb; + } - template - bool operator()(T v) const { return false; } - bool operator()(const std::string& v) const { - return str == v; - } -}; + return compareSetFilter(a, b) < 0; + }); +} -struct number_matcher { +struct Filter::matcher { using result_type = bool; - double num; - template - bool operator()(T v) const { return false; } - bool operator()(const double& v) const { - if (num == v) { return true; } - return std::fabs(num - v) <= std::numeric_limits::epsilon(); + matcher(const Feature& feat, StyleContext& ctx) : + props(feat.props), ctx(ctx) {} + + const Properties& props; + StyleContext& ctx; + + bool eval(const Filter::Data& data) const { + return Filter::Data::visit(data, *this); } -}; -struct match_equal_set { - using result_type = bool; - const std::vector& values; + bool operator() (const Filter::EqualityKey& f) const { + return f.value == ctx.getFilterKey(f.key); + } - template - bool operator()(T) const { return false; } + bool operator() (const Filter::EqualityString& f) const { + auto& propValue = props.get(f.key); - bool operator()(const double& num) const { - number_matcher m{num}; - for (const auto& v : values) { - if (Value::visit(v, m)) { - return true; - } + if (propValue.is()) { + auto& str = propValue.get(); + return str == f.value; } return false; } - bool operator()(const std::string& str) const { - string_matcher m{str}; - for (const auto& v : values) { - if (Value::visit(v, m)) { - return true; - } + bool operator() (const Filter::EqualityNumber& f) const { + auto& propValue = props.get(f.key); + if (propValue.is()) { + double num = propValue.get(); + if (num == f.value) { return true; } + return std::fabs(num - f.value) <= std::numeric_limits::epsilon(); } return false; } -}; -struct match_equal { - using result_type = bool; - const Value& value; + bool operator() (const Filter::RangeKeyZoom& f) const { + float num = ctx.getZoomLevel(); + return num >= f.min && num < f.max; + } - template - bool operator()(T) const { return false; } + bool operator() (const Filter::Range& f) const { + auto& propValue = props.get(f.key); + if (!propValue.is()) return false; - bool operator()(const double& num) const { - return Value::visit(value, number_matcher{num}); - } - bool operator()(const std::string& str) const { - return Value::visit(value, string_matcher{str}); + double num = propValue.get(); + return num >= f.min && num < f.max; } -}; -struct match_range { - const Filter::Range& f; - double scale; + bool operator() (const Filter::RangeArea& f) const { + auto& propValue = props.get(f.key); + if (!propValue.is()) return false; + + float scale = ctx.getPixelAreaScale(); - bool operator() (const double& num) const { + double num = propValue.get(); return num >= f.min * scale && num < f.max * scale; } - bool operator() (const std::string&) const { return false; } - bool operator() (const none_type&) const { return false; } -}; - -struct matcher { - using result_type = bool; - - matcher(const Feature& feat, StyleContext& ctx) : - props(feat.props), ctx(ctx) {} - const Properties& props; - StyleContext& ctx; - bool eval(const Filter::Data& data) const { - return Filter::Data::visit(data, *this); - } bool operator() (const Filter::OperatorAny& f) const { for (const auto& filt : f.operands) { @@ -335,45 +356,55 @@ struct matcher { } return false; } + bool operator() (const Filter::OperatorAll& f) const { for (const auto& filt : f.operands) { if (!eval(filt.data)) { return false; } } return true; } + bool operator() (const Filter::OperatorNone& f) const { for (const auto& filt : f.operands) { if (eval(filt.data)) { return false; } } return true; } + bool operator() (const Filter::Existence& f) const { return f.exists == props.contains(f.key); } - bool operator() (const Filter::EqualitySet& f) const { - auto& value = (f.keyword == FilterKeyword::undefined) - ? props.get(f.key) - : ctx.getKeyword(f.keyword); - return Value::visit(value, match_equal_set{f.values}); + bool operator() (const Filter::EqualityNumberSet& f) const { + auto& propValue = props.get(f.key); + if (!propValue.is()) { + return false; + } + double num = propValue.get(); + for (auto& value : f.values) { + if (num == value || std::fabs(num - value) <= std::numeric_limits::epsilon()) { + return true; + } + } + return false; } - bool operator() (const Filter::Equality& f) const { - auto& value = (f.keyword == FilterKeyword::undefined) - ? props.get(f.key) - : ctx.getKeyword(f.keyword); - return Value::visit(value, match_equal{f.value}); - } - bool operator() (const Filter::Range& f) const { - auto scale = (f.hasPixelArea) ? ctx.getPixelAreaScale() : 1.f; - auto& value = (f.keyword == FilterKeyword::undefined) - ? props.get(f.key) - : ctx.getKeyword(f.keyword); - return Value::visit(value, match_range{f, scale}); + bool operator() (const Filter::EqualityStringSet& f) const { + auto& propValue = props.get(f.key); + if (!propValue.is()) { + return false; + } + auto& str = propValue.get(); + for (auto& value : f.values) { + if (str == value) { return true;} + } + return false; } + bool operator() (const Filter::Function& f) const { return ctx.evalFilter(f.id); } + bool operator() (const none_type& f) const { return true; } @@ -383,4 +414,358 @@ bool Filter::eval(const Feature& feat, StyleContext& ctx) const { return Data::visit(data, matcher(feat, ctx)); } + + + +Filter Filter::getEqualityStringFilter(const std::string& k, const std::vector& vals) { + auto key = keyType(k); + if (key == Key::other) { + if (vals.size() == 1) { + return Filter{ EqualityString { k, vals[0] }}; + } else { + return Filter{ EqualityStringSet{ k, vals }}; + } + } + auto checkGeomType = [](const std::string& val, int& out) { + for (int i = 1; i < geometryStrings.size(); i++) { + if (val == geometryStrings[i]) { + out = i; + return true; + } + } + return false; + }; + + if (key == Filter::Key::geometry) { + std::vector geoms; + for (auto& val : vals) { + int geomType; + if (checkGeomType(val, geomType)) { + geoms.emplace_back(Filter::EqualityKey{ Key::geometry, geomType }); + } else { + //LOG(invalid); + } + } + if (geoms.empty()) { + // LOG(invalid) + return {}; + } + if (geoms.size() == 1) { + return std::move(geoms[0]); + } + return Filter{ OperatorAny{ std::move(geoms) }}; + } + // LOG(invalid) + return {}; +} + +Filter Filter::getEqualityNumberFilter(const std::string& k, const std::vector& vals) { + auto key = keyType(k); + if (key == Key::other) { + if (vals.size() == 1) { + return Filter{ EqualityNumber { k, vals[0] }}; + } else { + return Filter{ EqualityNumberSet { k, vals }}; + } + } + if (key == Key::zoom) { + // TODO EqualityKeySet + + // TODO could also LOG negative zooms... + if (vals.size() == 1) { + return Filter{ EqualityKey{ Key::zoom, int(vals[0]) } }; + } + std::vector zooms; + + for (auto& val : vals) { + int ival = int(val); + if (val > std::numeric_limits::max()) { + ival = std::numeric_limits::max(); + } + if (ival < 0) { ival = 0; } + zooms.emplace_back(Filter::EqualityKey{ Key::zoom, ival}); + } + return Filter{ OperatorAny{ std::move(zooms) }}; + } + return {}; +} + +Filter Filter::getRangeFilter(const std::string& k, float min, float max, bool sqArea) { + auto key = keyType(k); + if (key == Key::other) { + if (sqArea) { + return Filter{ RangeArea{ k, min, max }}; + } else { + return Filter{ Range{ k, min, max }}; + } + } + if (key == Key::zoom) { + int imax = int(max); + if (imax < 0) { imax = std::numeric_limits::max(); } + int imin = int(min); + if (imin < 0) { imin = 0; } + return Filter{ RangeKeyZoom{ key, imin, imax }}; + } + return {}; +} + +Filter Filter::generateFilter(const YAML::Node& _filter, SceneFunctions& _fns) { + + switch (_filter.Type()) { + case YAML::NodeType::Scalar: { + + const std::string& val = _filter.Scalar(); + if (val.compare(0, 8, "function") == 0) { + return Filter{Function{uint32_t(_fns.add(val))}}; + } + LOGNode("invalid", _filter); + return {}; + } + case YAML::NodeType::Sequence: { + return generateAnyFilter(_filter, _fns); + } + case YAML::NodeType::Map: { + std::vector filters; + for (const auto& filtItr : _filter) { + const std::string& key = filtItr.first.Scalar(); + const YAML::Node& node = _filter[key]; + Filter&& f = {}; + if (key == "none") { + f = generateNoneFilter(node, _fns); + } else if (key == "not") { + f = generateNoneFilter(node, _fns); + } else if (key == "any") { + f = generateAnyFilter(node, _fns); + } else if (key == "all") { + f = generateAllFilter(node, _fns); + } else { + f = generatePredicate(node, key); + } + if (f.isValid()) { filters.push_back(std::move(f)); } + } + if (!filters.empty()) { + if (filters.size() == 1) { + return std::move(filters.front()); + } + sort(filters); + return Filter{OperatorAll{std::move(filters)}}; + } + } + default: + break; + } + LOGNode("invalid", _filter); + return {}; + +} + +Filter Filter::generatePredicate(const YAML::Node& _value, std::string _key) { + + switch (_value.Type()) { + case YAML::NodeType::Scalar: { + if (_value.Tag() == "tag:yaml.org,2002:str") { + // Node was explicitly tagged with '!!str' or the canonical tag + // 'tag:yaml.org,2002:str' yaml-cpp normalizes the tag value to the + // canonical form + return getEqualityStringFilter(_key, { _value.Scalar() }); + } + double number; + if (YamlUtil::getDouble(_value, number, false)) { + return getEqualityNumberFilter(_key, { number }); + } + bool existence; + if (YamlUtil::getBool(_value, existence)) { + return Filter{Existence{_key, existence}}; + } + return getEqualityStringFilter(_key, { _value.Scalar() }); + } + case YAML::NodeType::Sequence: { + std::vector strings; + std::vector numbers; + + for (const auto& valItr : _value) { + double number; + // Don't allow mixing type - does not make sense + if (YamlUtil::getDouble(valItr, number, false)) { + if (strings.empty()) { + numbers.emplace_back(number); + } else { + LOGNode("invalid, key:%s", _value, _key.c_str()); + } + } else { + if (numbers.empty()) { + strings.push_back(valItr.Scalar()); + } else { + LOGNode("invalid, key:%s", _value, _key.c_str()); + } + } + } + if (!strings.empty()) { + return getEqualityStringFilter(_key, std::move(strings)); + } + if (!numbers.empty()) { + return getEqualityNumberFilter(_key, std::move(numbers)); + } + break; + } + case YAML::NodeType::Map: { + double minVal = -std::numeric_limits::infinity(); + double maxVal = std::numeric_limits::infinity(); + bool hasMinPixelArea = false; + bool hasMaxPixelArea = false; + bool hasMin = false; + bool hasMax = false; + + for (const auto& n : _value) { + if (n.first.Scalar() == "min") { + hasMin = getFilterRangeValue(n.second, minVal, hasMinPixelArea); + continue; + } else if (n.first.Scalar() == "max") { + hasMax = getFilterRangeValue(n.second, maxVal, hasMaxPixelArea); + continue; + } + LOGNode("invalid, key:%s", _value, _key.c_str()); + return {}; + } + + if (hasMin && hasMax && hasMinPixelArea != hasMaxPixelArea) { + LOGNode("invalid, key:%s", _value, _key.c_str()); + return {}; + } + return getRangeFilter(_key, minVal, maxVal, hasMinPixelArea); + } + default: + break; + } + + LOGNode("invalid, key:%s", _value, _key.c_str()); + return {}; +} + +bool Filter::getFilterRangeValue(const YAML::Node& node, double& val, bool& hasPixelArea) { + if (!YamlUtil::getDouble(node, val, false)) { + auto strVal = node.Scalar(); + auto n = strVal.find("px2"); + if (n == std::string::npos) { return false; } + try { + val = ff::stof(std::string(strVal, 0, n)); + hasPixelArea = true; + } catch (std::invalid_argument) { return false; } + } + return true; +} + +Filter Filter::generateAnyFilter(const YAML::Node& _filter, SceneFunctions& _fns) { + + if (!_filter.IsSequence()) { + LOGNode("invalid any filter", _filter); + return {}; + } + std::vector filters; + + for (const auto& filt : _filter) { + if (Filter f = generateFilter(filt, _fns)) { + if (f.data.is()) { + // flatten filter + for (auto& ff : f.data.get().operands) { + filters.push_back(std::move(ff)); + } + } else { + filters.push_back(std::move(f)); + } + } else { + LOGNode("invalid any filter", _filter); + continue; + } + } + + if (filters.empty()) { + LOGNode("invalid any filter", _filter); + return {}; + } + if (filters.size() == 1) { + LOGNode("useless any filter", _filter); + return std::move(filters.front()); + } + sort(filters); + return Filter{OperatorAny{std::move(filters)}}; +} + +Filter Filter::generateAllFilter(const YAML::Node& _filter, SceneFunctions& _fns) { + + if (!_filter.IsSequence()) { + LOGNode("invalid all filter", _filter); + return {}; + } + + std::vector filters; + + for (const auto& filt : _filter) { + if (Filter f = generateFilter(filt, _fns)) { + if (f.data.is()) { + // flatten filter + for (auto& ff : f.data.get().operands) { + filters.push_back(std::move(ff)); + } + } else { + filters.push_back(std::move(f)); + } + } else { + LOGNode("invalid all filter", _filter); + } + } + if (filters.empty()) { + LOGNode("invalid all filter", _filter); + return {}; + } + if (filters.size() == 1) { + LOGNode("useless all filter", _filter); + return std::move(filters.front()); + } + sort(filters); + return Filter{OperatorAll{std::move(filters)}}; +} + +Filter Filter::generateNoneFilter(const YAML::Node& _filter, SceneFunctions& _fns) { + + if (_filter.IsMap() || _filter.IsScalar()) { + // 'not' case + if (Filter&& f = generateFilter(_filter, _fns)) { + return Filter{OperatorNone{{std::move(f)}}}; + } + LOGNode("invalid 'none' filter", _filter); + return {}; + } + if (!_filter.IsSequence()) { + LOGNode("invalid 'none' filter", _filter); + return {}; + } + std::vector filters; + for (const auto& filt : _filter) { + if (Filter&& f = generateFilter(filt, _fns)) { + if (f.data.is()) { + // flatten filter + for (auto& ff : f.data.get().operands) { + filters.push_back(std::move(ff)); + } + } else { + filters.push_back(std::move(f)); + } + + } else { + LOGNode("invalid 'none' filter", _filter); + } + } + if (filters.empty()) { + LOGNode("invalid 'none' filter", _filter); + return {}; + } + if (filters.size() == 1) { + LOGNode("useless 'none' filter", _filter); + return std::move(filters.front()); + } + sort(filters); + return Filter{OperatorNone{std::move(filters)}}; +} + } diff --git a/core/src/scene/filters.h b/core/src/scene/filters.h index 4bcf50199f..14dce15e9f 100644 --- a/core/src/scene/filters.h +++ b/core/src/scene/filters.h @@ -5,18 +5,35 @@ #include #include +namespace YAML { +class Node; +} + namespace Tangram { class StyleContext; +struct SceneFunctions; struct Feature; -enum class FilterKeyword : uint8_t { - undefined, - zoom, - geometry, -}; - struct Filter { + static const std::string key_geom; + static const std::string key_zoom; + static const std::string key_other; + static const std::vector geometryStrings; + + enum class Key : uint8_t { + other, + zoom, + geometry, + }; + + enum class Geometry : uint8_t { + unknown, + point, + line, + polygon, + }; + struct OperatorAll { std::vector operands; }; @@ -26,23 +43,44 @@ struct Filter { struct OperatorNone { std::vector operands; }; - - struct EqualitySet { + struct EqualityKey { + Key key; + int value; + }; + struct EqualityString { + std::string key; + std::string value; + }; + struct EqualityNumber { + std::string key; + double value; + }; + // struct EqualityKeySet { + // Key key; + // std::vector values; + // }; + struct EqualityStringSet { std::string key; - std::vector values; - FilterKeyword keyword; + std::vector values; }; - struct Equality { + struct EqualityNumberSet { std::string key; - Value value; - FilterKeyword keyword; + std::vector values; }; struct Range { std::string key; float min; float max; - FilterKeyword keyword; - bool hasPixelArea; + }; + struct RangeArea { + std::string key; + float min; + float max; + }; + struct RangeKeyZoom { + Key key; + int min; + int max; }; struct Existence { std::string key; @@ -51,63 +89,28 @@ struct Filter { struct Function { uint32_t id; }; - using Data = variant; - Data data; - Filter() : data(none_type{}) {} - Filter(Data _data) : data(std::move(_data)) {} bool eval(const Feature& feat, StyleContext& ctx) const; - // Create an 'any', 'all', or 'none' filter - inline static Filter MatchAny(std::vector filters) { - sort(filters); - return { OperatorAny{ std::move(filters) }}; - } - inline static Filter MatchAll(std::vector filters) { - sort(filters); - return { OperatorAll{ std::move(filters) }}; - } - inline static Filter MatchNone(std::vector filters) { - sort(filters); - return { OperatorNone{ std::move(filters) }}; - } - // Create an 'equality' filter - inline static Filter MatchEquality(const std::string& k, const std::vector& vals) { - if (vals.size() == 1) { - return { Equality{ k, vals[0], keywordType(k) }}; - } else { - return { EqualitySet{ k, vals, keywordType(k) }}; + static Key keyType(const std::string& _key) { + if (_key == key_geom) { + return Key::geometry; + } else if (_key == key_zoom) { + return Key::zoom; } + return Key::other; } - // Create a 'range' filter - inline static Filter MatchRange(const std::string& k, float min, float max, bool sqA) { - return { Range{ k, min, max, keywordType(k), sqA }}; - } - // Create an 'existence' filter - inline static Filter MatchExistence(const std::string& k, bool ex) { - return { Existence{ k, ex }}; - } - // Create an 'function' filter with reference to Scene function id - inline static Filter MatchFunction(uint32_t id) { - return { Function{ id }}; - } - - static FilterKeyword keywordType(const std::string& _key) { - if (_key == "$geometry") { - return FilterKeyword::geometry; - } else if (_key == "$zoom") { - return FilterKeyword::zoom; + static const std::string& keyName(Key _key) { + switch(_key) { + case Key::geometry: + return key_geom; + case Key::zoom: + return key_zoom; + default: + break; } - return FilterKeyword::undefined; + return key_other; } /* Public for testing */ @@ -120,5 +123,54 @@ struct Filter { bool isValid() const { return !data.is(); } operator bool() const { return isValid(); } + + using Data = + variant; + Data data; + explicit Filter(Data&& _data) : data(std::move(_data)) {} + static int compareSetFilter(const Filter& a, const Filter& b); + struct matcher; + +public: + Filter() : data(none_type{}) {} + + // Create an 'equality' filter + static Filter getEqualityStringFilter(const std::string& k, const std::vector& vals); + static Filter getEqualityNumberFilter(const std::string& k, const std::vector& vals); + + // Create a 'range' filter + static Filter getRangeFilter(const std::string& k, float min, float max, bool sqArea); + + // Create an 'existence' filter + static Filter getExistenceFilter(const std::string& k, bool ex) { + return Filter{ Existence{ k, ex }}; + } + // Create an 'function' filter with reference to Scene function id + static Filter getFunctionFilter(uint32_t id) { + return Filter{ Function{ id }}; + } + + static Filter generateFilter(const YAML::Node& _filter, SceneFunctions& _fns); + static Filter generatePredicate(const YAML::Node& _value, std::string _key); + static Filter generateAnyFilter(const YAML::Node& _filter, SceneFunctions& _fns); + static Filter generateAllFilter(const YAML::Node& _filter, SceneFunctions& _fns); + static Filter generateNoneFilter(const YAML::Node& _filter, SceneFunctions& _fns); + static bool getFilterRangeValue(const YAML::Node& node, double& val, bool& hasPixelArea); + }; } diff --git a/core/src/scene/scene.cpp b/core/src/scene/scene.cpp index 29b042d3a8..5315845285 100644 --- a/core/src/scene/scene.cpp +++ b/core/src/scene/scene.cpp @@ -128,11 +128,15 @@ int Scene::getIdForName(const std::string& _name) const { } int Scene::addJsFunction(const std::string& _function) { - for (size_t i = 0; i < m_jsFunctions.size(); i++) { - if (m_jsFunctions[i] == _function) { return i; } + return m_jsFunctions.add(_function); +} + +int SceneFunctions::add(const std::string& _function) { + for (size_t i = 0; i < functions.size(); i++) { + if (functions[i] == _function) { return i; } } - m_jsFunctions.push_back(_function); - return m_jsFunctions.size()-1; + functions.push_back(_function); + return functions.size()-1; } const Light* Scene::findLight(const std::string &_name) const { diff --git a/core/src/scene/scene.h b/core/src/scene/scene.h index 9285818718..242b04e5f3 100644 --- a/core/src/scene/scene.h +++ b/core/src/scene/scene.h @@ -37,6 +37,12 @@ class ZipArchive; // Delimiter used in sceneloader for style params and layer-sublayer naming const std::string DELIMITER = ":"; +struct SceneFunctions { + int add(const std::string&); + size_t size() const { return functions.size(); } + std::vector functions; +}; + /* Singleton container of