Android snapshotter#9748
Conversation
b80f8fe to
e99d6c5
Compare
|
@tobrun Could you help out defining the initial API on the Android side here? |
e99d6c5 to
a12ed5a
Compare
|
For a initial implementation I believe changing the camera would be most usefull?
This leaves us with a choice, we either expose the same models as the SDK (eg. CameraPosition and LatLngBounds) or we keep the api in line what core exposes? A second iteration could bring support for Layer/Sources? Sidenote: I'm not aware of any requirements around items as showing the mapbox logo? |
|
Have you thought about implementing this as a service?
|
|
A good benchmark for this feature would be to have a grid or a list which loads map images on the fly. |
85fdee6 to
6c73a9a
Compare
|
Added:
I like the idea of a Service or threadpool somehow. Maybe separate from this PR though as it could just re-use these building blocks?
I assumed we need to add the logo somehow. cc @zugaldia |
6c73a9a to
b5dc09c
Compare
Yes. We should include the logo in a way that both a regular map and a snapshot render in exactly the same manner. That way it's easier to set up transitions between the two that flow naturally. |
b5dc09c to
9e8310c
Compare
|
Is there a reason we can't reuse // .hpp
class SnapshotCallback {
public:
void operator()(std::exception_ptr, PremultipliedImage);
};
class MapSnapshotter {
public:
MapSnapshotter(const std::string& styleURL,
const Size&,
const float pixelRatio,
const CameraOptions&);
void snapshot(ActorRef<SnapshotCallback>);
private:
class Impl;
Thread<Impl> impl;
};
// .cpp
MapSnapshotter::MapSnapshotter(...)
: impl(...) {
}
MapSnapshotter::snapshot(ActorRef<SnapshotCallback> callback) {
impl.invoke(&Impl::snapshot, callback);
}
class Impl {
public:
Impl(...);
void snapshot(ActorRef<SnapshotCallback>);
private:
HeadlessFrontend frontend;
Map map;
}
Impl::Impl(...)
: frontend(...)
, map(...) {
map.setStyle(...);
map.setCamera(...);
}
Impl::snapshot(ActorRef<SnapshotCallback> callback) {
map.renderStill([callback, &] (std::exception_ptr error) {
callback.invoke(&SnapshotCallback::operator(), error, frontend.readStillImage());
});
}In other words: put everything -- frontend, map, backend, renderer -- on the background snapshot thread, and invoke the callback when done. No thread pausing/resuming needed. |
9e8310c to
14eba10
Compare
14eba10 to
413c660
Compare
413c660 to
77a646f
Compare
|
@jfirebaugh Thanks, that makes it indeed a lot simpler. I've added a implementation for SnapshotCallback that takes a lambda so platforms don't have to implement it. Besides some fixes, I also took the opportunity to enable destruction of the Java object through regular garbage collection. By making This makes the API much more natural on the Java side and there is no need for explicit destruction other than to abort prematurely (now called cancel). The only pre-requisite now for this PR is #9816 as cd8eb13 prevents the still image generation from rendering. |
There was a problem hiding this comment.
You can use
here.But it seems like we should propagate errors to the caller, via an onSnapshotError method or something, no?
There was a problem hiding this comment.
Thanks. I will add an additional callback. I refrained from doing so initially as there is not much you can do in these situations.
There was a problem hiding this comment.
You might be able to do using SnapshotCallback = std::function<void (std::exception_ptr, PremultipliedImage)> and avoid the wrapper class.
There was a problem hiding this comment.
I left the wrapper class as it makes clear on which thread the callback is going to happen when requiring an ActorRef in MapSnapshotter::snapshot. If we require only the lambda, we would still internally use an actor/mailbox to ensure the callback happens on the original thread right?
There was a problem hiding this comment.
I mean keep ActorRef<SnapshotCallback> as it is, but have SnapshotCallback just be a std::function typedef. I believe that's possible with your change in 68f470f.
There was a problem hiding this comment.
Yes, you're totally right. Very nice!
77a646f to
b1dae4a
Compare
b1dae4a to
69050dd
Compare
| if (region) { | ||
| mbgl::EdgeInsets insets = { 0, 0, 0, 0 }; | ||
| std::vector<LatLng> latLngs = { region->southwest(), region->northeast() }; | ||
| map.jumpTo(map.cameraForLatLngs(latLngs, insets)); |
There was a problem hiding this comment.
In case we have a region set, can we avoid calling the first jumpTo?
There was a problem hiding this comment.
Besides the coordinate, this also sets the pitch, etc. Since the map doesn't do anything significant before the render is started in Still mode I didn't see it as significant. (the onUpdate does not trigger an actual update yet).
| @@ -61,8 +61,6 @@ class Thread : public Scheduler { | |||
| } | |||
|
|
|||
| ~Thread() override { | |||
| MBGL_VERIFY_THREAD(tid); | |||
There was a problem hiding this comment.
Curious, what prompted this removal?
There was a problem hiding this comment.
This is c607c03. Enables the thread to be destructed from other threads (eg, the Finalizer thread on Android). Otherwise the client side api is quite awkward as the Java object needs to be explicitly "destructed" on the creating thread.
|
|
||
| private: | ||
| class Impl; | ||
| std::unique_ptr<util::Thread<Impl>> impl; |
There was a problem hiding this comment.
Given that this is in a unique_ptr anyway, can we instead forward declare util::Thread with
namespace util {
template <typename T> class Thread;
} // namespace util
instead of exposing it publicly and including it here?
| : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, scheduler, styleURL, size, pixelRatio, cameraOptions, region, programCacheDir)) { | ||
| } | ||
|
|
||
| MapSnapshotter::~MapSnapshotter() = default; |
There was a problem hiding this comment.
This destructor synchronously destroys the thread. If a render is in progress, this could potentially take a few dozen or even hundred milliseconds. We could introduce a similar system like the tile parsing obsolete atomic bool that we check after every layer and abort rendering in case we try to abort the rendering.
There was a problem hiding this comment.
Could be worth looking into. This doesn't add too much overhead in the "regular" case, it does add a check every frame for every layer in Still and Continuous mode.
Initial implementation for client side static maps (#9340).
This implementation uses a object on the main thread and manages an internal thread for rendering. #9340 mentions the ability to render on arbitrary threads, but I think this brings more complexity than it's worth as the result has to be forwarded to the main thread by the users. Instead, this API offers a simple callback that is called when the image is ready and handles the cross-thread communication internally.
Todo: