[android] Convert GeoJSON features to tiles in background

Composing tiles from the GeoJSON features is an expensive
operation that might block UI thread on updating the `GeoJsonSource`
with the new data.

This change moves tile composing to the background thread and thus
unblocks the UI thread.
This commit is contained in:
Mikhail Pozdnyakov
2019-10-30 12:04:01 +02:00
parent 8c498cd36c
commit 4afb4438eb
4 changed files with 54 additions and 49 deletions

View File

@@ -60,6 +60,7 @@ public:
void setGeoJSONData(std::shared_ptr<GeoJSONData>);
optional<std::string> getURL() const;
const GeoJSONOptions& getOptions() const;
class Impl;
const Impl& impl() const;

View File

@@ -44,18 +44,16 @@ namespace android {
}
GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, const jni::String& sourceId, const jni::Object<>& options)
: Source(env, std::make_unique<mbgl::style::GeoJSONSource>(
jni::Make<std::string>(env, sourceId),
convertGeoJSONOptions(env, options)))
, converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground())) {
}
: Source(env,
std::make_unique<mbgl::style::GeoJSONSource>(jni::Make<std::string>(env, sourceId),
convertGeoJSONOptions(env, options))),
converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground(),
source.as<style::GeoJSONSource>()->getOptions())) {}
GeoJSONSource::GeoJSONSource(jni::JNIEnv& env,
mbgl::style::Source& coreSource,
AndroidRendererFrontend& frontend)
: Source(env, coreSource, createJavaPeer(env), frontend)
, converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground())) {
}
GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend)
: Source(env, coreSource, createJavaPeer(env), frontend),
converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground(),
source.as<style::GeoJSONSource>()->getOptions())) {}
GeoJSONSource::~GeoJSONSource() = default;
@@ -63,7 +61,7 @@ namespace android {
std::shared_ptr<std::string> json = std::make_shared<std::string>(jni::Make<std::string>(env, jString));
Update::Converter converterFn = [this, json](ActorRef<Callback> _callback) {
Update::Converter converterFn = [this, json](ActorRef<GeoJSONDataCallback> _callback) {
converter->self().invoke(&FeatureConverter::convertJson, json, _callback);
};
@@ -84,11 +82,11 @@ namespace android {
void GeoJSONSource::setURL(jni::JNIEnv& env, const jni::String& url) {
// Update the core source
source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setURL(jni::Make<std::string>(env, url));
source.as<style::GeoJSONSource>()->setURL(jni::Make<std::string>(env, url));
}
jni::Local<jni::String> GeoJSONSource::getURL(jni::JNIEnv& env) {
optional<std::string> url = source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::getURL();
optional<std::string> url = source.as<style::GeoJSONSource>()->getURL();
return url ? jni::Make<jni::String>(env, *url) : jni::Local<jni::String>();
}
@@ -166,7 +164,7 @@ namespace android {
auto global = jni::NewGlobal<jni::EnvAttachingDeleter>(env, jObject);
auto object = std::make_shared<decltype(global)>(std::move(global));
Update::Converter converterFn = [this, object](ActorRef<Callback> _callback) {
Update::Converter converterFn = [this, object](ActorRef<GeoJSONDataCallback> _callback) {
converter->self().invoke(&FeatureConverter::convertObject<JNIType>, object, _callback);
};
@@ -175,25 +173,23 @@ namespace android {
void GeoJSONSource::setAsync(Update::Converter converterFn) {
awaitingUpdate = std::make_unique<Update>(
std::move(converterFn),
std::make_unique<Actor<Callback>>(
*Scheduler::GetCurrent(),
[this](GeoJSON geoJSON) {
// conversion from Java features to core ones finished
android::UniqueEnv _env = android::AttachEnv();
std::move(converterFn),
std::make_unique<Actor<GeoJSONDataCallback>>(
*Scheduler::GetCurrent(), [this](std::shared_ptr<style::GeoJSONData> geoJSONData) {
// conversion from Java features to core ones finished
android::UniqueEnv _env = android::AttachEnv();
// Update the core source
source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setGeoJSON(geoJSON);
// Update the core source
source.as<mbgl::style::GeoJSONSource>()->setGeoJSONData(std::move(geoJSONData));
// if there is an awaiting update, execute it, otherwise, release resources
if (awaitingUpdate) {
update = std::move(awaitingUpdate);
update->converterFn(update->callback->self());
} else {
update.reset();
}
})
);
// if there is an awaiting update, execute it, otherwise, release resources
if (awaitingUpdate) {
update = std::move(awaitingUpdate);
update->converterFn(update->callback->self());
} else {
update.reset();
}
}));
// If another update is running, wait
if (update) {
@@ -230,12 +226,10 @@ namespace android {
);
}
void FeatureConverter::convertJson(std::shared_ptr<std::string> json,
ActorRef<Callback> callback) {
void FeatureConverter::convertJson(std::shared_ptr<std::string> json, ActorRef<GeoJSONDataCallback> callback) {
using namespace mbgl::style::conversion;
android::UniqueEnv _env = android::AttachEnv();
// Convert the jni object
Error error;
optional<GeoJSON> converted = parseGeoJSON(*json, error);
@@ -243,23 +237,23 @@ namespace android {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message);
return;
}
callback.invoke(&Callback::operator(), *converted);
callback.invoke(&GeoJSONDataCallback::operator(), style::GeoJSONData::create(*converted, options));
}
template <class JNIType>
void FeatureConverter::convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>> jObject, ActorRef<Callback> callback) {
void FeatureConverter::convertObject(
std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>> jObject,
ActorRef<GeoJSONDataCallback> callback) {
using namespace mbgl::android::geojson;
android::UniqueEnv _env = android::AttachEnv();
// Convert the jni object
auto geometry = JNIType::convert(*_env, *jObject);
callback.invoke(&Callback::operator(), GeoJSON(geometry));
callback.invoke(&GeoJSONDataCallback::operator(), style::GeoJSONData::create(geometry, options));
}
Update::Update(Converter _converterFn, std::unique_ptr<Actor<Callback>> _callback)
: converterFn(std::move(_converterFn))
, callback(std::move(_callback)) {}
Update::Update(Converter _converterFn, std::unique_ptr<Actor<GeoJSONDataCallback>> _callback)
: converterFn(std::move(_converterFn)), callback(std::move(_callback)) {}
} // namespace android
} // namespace mbgl

View File

@@ -11,22 +11,28 @@
namespace mbgl {
namespace android {
using Callback = std::function<void (GeoJSON)>;
using GeoJSONDataCallback = std::function<void(std::shared_ptr<style::GeoJSONData>)>;
struct FeatureConverter {
void convertJson(std::shared_ptr<std::string>, ActorRef<Callback>);
class FeatureConverter {
public:
explicit FeatureConverter(style::GeoJSONOptions options_) : options(std::move(options_)) {}
void convertJson(std::shared_ptr<std::string>, ActorRef<GeoJSONDataCallback>);
template <class JNIType>
void convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>>, ActorRef<Callback>);
void convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>>,
ActorRef<GeoJSONDataCallback>);
private:
style::GeoJSONOptions options;
};
struct Update {
using Converter = std::function<void (ActorRef<Callback>)>;
using Converter = std::function<void(ActorRef<GeoJSONDataCallback>)>;
Converter converterFn;
std::unique_ptr<Actor<Callback>> callback;
std::unique_ptr<Actor<GeoJSONDataCallback>> callback;
Update(Converter, std::unique_ptr<Actor<Callback>>);
Update(Converter, std::unique_ptr<Actor<GeoJSONDataCallback>>);
};
class GeoJSONSource : public Source {

View File

@@ -45,6 +45,10 @@ optional<std::string> GeoJSONSource::getURL() const {
return url;
}
const GeoJSONOptions& GeoJSONSource::getOptions() const {
return impl().getOptions();
}
void GeoJSONSource::loadDescription(FileSource& fileSource) {
if (!url) {
loaded = true;