maplibre_native 0.8.6

Rust bindings to the MapLibre Native map rendering engine
Documentation
#include "rust_file_source.h"
#include "maplibre_native/src/bridge.rs.h"

#include <mbgl/actor/scheduler.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/storage/file_source_manager.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/client_options.hpp>

#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>

namespace mln {
namespace bridge {

namespace {

mbgl::Response buildResponse(const RawResponse& r) {
    mbgl::Response response;
    if (r.has_error) {
        response.error = std::make_unique<mbgl::Response::Error>(
            r.error_reason, std::string(r.error_message));
    }
    response.noContent = r.no_content;
    if (r.has_data) {
        response.data = std::make_shared<std::string>(
            reinterpret_cast<const char*>(r.data.data()), r.data.size());
    }
    if (r.has_expires) {
        response.expires = mbgl::Timestamp(mbgl::Seconds(r.modified_epoch_s));
    }
    if (r.has_expires) {
        response.expires = mbgl::Timestamp(mbgl::Seconds(r.expires_epoch_s));
    }
    if (r.has_etag) {
        response.etag = std::string(r.etag);
    }
    return response;
}

RawResponse toRustResponse(const mbgl::Response& response) {
    RawResponse out{};
    out.error_reason = ErrorReason::Success;
    if (response.error) {
        out.has_error = true;
        out.error_reason = response.error->reason;
        out.error_message = rust::String::lossy(response.error->message);
        if (response.error->retryAfter) {
            out.has_retry_after = true;
            out.retry_after_epoch_s = response.error->retryAfter->time_since_epoch().count();
        }
    }
    out.no_content = response.noContent;
    out.not_modified = response.notModified;
    out.must_revalidate = response.mustRevalidate;
    if (response.data) {
        out.has_data = true;
        out.data.reserve(response.data->size());
        for (unsigned char byte : *response.data) {
            out.data.push_back(byte);
        }
    }
    if (response.modified) {
        out.has_modified = true;
        out.modified_epoch_s = response.modified->time_since_epoch().count();
    }
    if (response.expires) {
        out.has_expires = true;
        out.expires_epoch_s = response.expires->time_since_epoch().count();
    }
    if (response.etag) {
        out.has_etag = true;
        out.etag = rust::String::lossy(*response.etag);
    }
    return out;
}

RawResourceRequest toRustResourceRequest(const mbgl::Resource& resource, bool include_prior_data) {
    RawResourceRequest out{};
    out.url = rust::String::lossy(resource.url);
    out.kind = resource.kind;
    out.loading_methods = static_cast<std::uint8_t>(resource.loadingMethod);
    out.is_volatile = resource.storagePolicy == mbgl::Resource::StoragePolicy::Volatile;
    out.is_low_priority = resource.priority == mbgl::Resource::Priority::Low;
    out.is_offline = resource.usage == mbgl::Resource::Usage::Offline;

    if (resource.tileData) {
        out.has_tile = true;
        out.tile_url_template = rust::String::lossy(resource.tileData->urlTemplate);
        out.tile_pixel_ratio = resource.tileData->pixelRatio;
        out.tile_x = resource.tileData->x;
        out.tile_y = resource.tileData->y;
        out.tile_z = resource.tileData->z;
    }

    if (resource.dataRange) {
        out.has_data_range = true;
        out.data_range_start = resource.dataRange->first;
        out.data_range_end = resource.dataRange->second;
    }
    if (resource.priorModified) {
        out.has_prior_modified = true;
        out.prior_modified_epoch_s = resource.priorModified->time_since_epoch().count();
    }
    if (resource.priorExpires) {
        out.has_prior_expires = true;
        out.prior_expires_epoch_s = resource.priorExpires->time_since_epoch().count();
    }
    if (resource.priorEtag) {
        out.has_prior_etag = true;
        out.prior_etag = rust::String::lossy(*resource.priorEtag);
    }
    if (include_prior_data && resource.priorData) {
        out.has_prior_data = true;
        out.prior_data.reserve(resource.priorData->size());
        for (unsigned char byte : *resource.priorData) {
            out.prior_data.push_back(byte);
        }
    }
    out.minimum_update_interval_ms =
        std::chrono::duration_cast<mbgl::Milliseconds>(resource.minimumUpdateInterval).count();

    return out;
}

void completeState(const std::shared_ptr<RequestState>& state, mbgl::Response response) {
    if (state->cancelled.load()) {
        return;
    }

    {
        std::scoped_lock lock(state->response_mutex);
        state->response.emplace(std::move(response));
    }
    state->dispatch();
}

void completeForwardState(const std::shared_ptr<ForwardState>& state) {
    if (state->cb) {
        state->cb();
    }
}

class RustAsyncRequest final : public mbgl::AsyncRequest {
public:
    RustAsyncRequest(rust::Box<RequestHandleFfi> handle, std::shared_ptr<RequestState> state)
        : handle_(std::move(handle)), state_(std::move(state)) {}

    ~RustAsyncRequest() override {
        state_->cancelled.store(true);
        handle_->cancel();
    }

private:
    rust::Box<RequestHandleFfi> handle_;
    std::shared_ptr<RequestState> state_;
};

class RustFileSource final : public mbgl::FileSource {
public:
    RustFileSource(std::shared_ptr<rust::Box<BoxedFileSource>> source,
                   mbgl::ResourceOptions resourceOpts,
                   mbgl::ClientOptions clientOpts)
        : source_(std::move(source)),
          resourceOpts_(std::move(resourceOpts)),
          clientOpts_(std::move(clientOpts)) {}

    std::unique_ptr<mbgl::AsyncRequest> request(const mbgl::Resource& resource,
                                                Callback cb) override {
        auto state = std::make_shared<RequestState>();
        state->cb = std::move(cb);

        // FileSource callbacks must run on the thread that issued request().
        // Capture the scheduler's weak binding while that scheduler is known to
        // be alive. A late completion becomes a no-op after scheduler teardown.
        std::weak_ptr<RequestState> weakState = state;
        state->dispatch = mbgl::Scheduler::GetCurrent()->bindOnce([weakState]() mutable {
            auto state = weakState.lock();
            if (!state || state->cancelled.load()) {
                return;
            }

            std::optional<mbgl::Response> response;
            {
                std::scoped_lock lock(state->response_mutex);
                response = std::move(state->response);
                state->response.reset();
            }
            if (response && !state->cancelled.load()) {
                state->cb(std::move(*response));
            }
        });

        auto request = toRustResourceRequest(resource, true);
        rust::Box<RequestHandleFfi> handle = (**source_).request(request, state);
        return std::make_unique<RustAsyncRequest>(std::move(handle), std::move(state));
    }

    bool canRequest(const mbgl::Resource& resource) const override {
        auto request = toRustResourceRequest(resource, false);
        return (**source_).can_request(request);
    }

    void forward(const mbgl::Resource& resource,
                 const mbgl::Response& response,
                 std::function<void()> callback) override {
        auto state = std::make_shared<ForwardState>();
        if (callback) {
            state->cb = mbgl::Scheduler::GetCurrent()->bindOnce(std::move(callback));
        }

        auto request = toRustResourceRequest(resource, true);
        (**source_).forward(request, toRustResponse(response), std::move(state));
    }

    void setResourceOptions(mbgl::ResourceOptions options) override {
        resourceOpts_ = std::move(options);
    }
    mbgl::ResourceOptions getResourceOptions() override {
        return resourceOpts_.clone();
    }

    void setClientOptions(mbgl::ClientOptions options) override {
        clientOpts_ = std::move(options);
    }
    mbgl::ClientOptions getClientOptions() override {
        return clientOpts_.clone();
    }

private:
    std::shared_ptr<rust::Box<BoxedFileSource>> source_;
    mbgl::ResourceOptions resourceOpts_;
    mbgl::ClientOptions clientOpts_;
};

} // namespace

void responder_complete(std::shared_ptr<RequestState> state, const RawResponse& response) {
    completeState(state, buildResponse(response));
}

void responder_cancel(std::shared_ptr<RequestState> state) {
    state->cancelled.store(true);
}

void forward_complete(std::shared_ptr<ForwardState> state) {
    completeForwardState(state);
}

void register_rust_file_source(FileSourceType source_type, rust::Box<BoxedFileSource> source) {
    auto shared_source = std::make_shared<rust::Box<BoxedFileSource>>(std::move(source));
    mbgl::FileSourceManager::get()->registerFileSourceFactory(
        source_type,
        [shared_source](const mbgl::ResourceOptions& ro, const mbgl::ClientOptions& co)
            -> std::unique_ptr<mbgl::FileSource> {
            return std::make_unique<RustFileSource>(shared_source, ro.clone(), co.clone());
        });
}

} // namespace bridge
} // namespace mln