Feature/torrent tile source - #28
Open
acalcutt wants to merge 49 commits into
Open
Conversation
…base) The Vulkan bindings package packed only the Windows DLLs, so unlike the base MapLibreNative.Maui package it delivered no native library on iOS/macCatalyst - a transitive consumer (via the handlers) linked nothing. Bring it to parity: - Pack the iOS XCFramework (device + simulator) and macCatalyst .a into buildTransitive/native/, and prefer the XCFramework for local iOS builds. - Add buildTransitive/MapLibreNative.Maui.Vulkan.targets (mirrors the base package's targets) to re-add the Apple NativeReferences - with the Metal frameworks and -lsqlite3 -lz -lc++ linker flags - for transitive consumers. - release.yml: build the Vulkan iOS XCFramework in the pack-vulkan job before packing, matching the base pack job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CI pack-vulkan job (which runs on PRs) arranged the Apple slices but never built the iOS XCFramework, so with the new packaging the iOS native would be silently skipped (file-existence gated) - the parity change would go untested. Add the Create iOS XCFramework step (mirroring the base pack job) and a verification step that fails the build if the packed Vulkan .nupkg is missing the buildTransitive targets, the macCatalyst .a, or the iOS XCFramework. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for real Vulkan frontends. Adds, without changing existing GL/Metal behaviour (compiles on all backends): - mbgl_get_render_backend() -> "opengl"|"vulkan"|"metal" so the shared managed layer can pick the right surface handshake at runtime. - mbgl_frontend_create() (backend-agnostic) with mbgl_frontend_create_gl() kept as a thin alias. - mbgl_frontend_read_pixels() + PlatformFrontend::readPixels() (default no-op) for the offscreen (Vulkan Windows) read-back path. - platform_frontend_vulkan_common.hpp: VulkanFrontendT<Backend> implementing the PlatformFrontend/RendererFrontend interface once; each platform supplies only a surface-specific mbgl::vulkan::RendererBackend. Not yet wired in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the throwing stub with an ANativeWindow surface backend mirroring maplibre-native's android_vulkan_renderer_backend (VK_KHR_surface + VK_KHR_android_surface, swapchain presents into the TextureView surface, matching today's EGL path), driven through the shared VulkanFrontendT<Backend>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the throwing stub with an offscreen frontend that reuses mbgl::vulkan::HeadlessBackend (already compiled into mbgl-core on Windows Vulkan builds via platform/windows/windows.cmake): renders into a headless color texture and returns the pixels through mbgl_frontend_read_pixels(), which the managed layer blits into the WriteableBitmap. No HWND/window surface, so the airspace-free in-tree model of the WGL path is preserved. CMake adds platform/default/include for the headless header on Vulkan builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mbgl_frontend_read_pixels uses size_t but the header only pulled in <stdint.h>. Apple clang doesn't get size_t transitively there, so every target including the header (incl. the Metal iOS/macOS builds) failed with "unknown type name 'size_t'". Add <stddef.h>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al default) Wraps the existing Metal frontend in #if MLN_RENDER_BACKEND_METAL and adds a #elif MLN_RENDER_BACKEND_VULKAN path: renders Vulkan into a CAMetalLayer-backed UIView via VK_EXT_metal_surface (vk::MetalSurfaceCreateInfoEXT + createMetalSurfaceEXTUnique), driven through the shared VulkanFrontendT, and returns the view from getNativeView() like the MTKView. Apple keeps defaulting to Metal; the Vulkan path only compiles under -DMLN_WITH_VULKAN=ON. CMake selects link libs accordingly (Metal/MetalKit vs QuartzCore/UIKit; MoltenVK is linked by the consuming app since mln-cabi is a static archive on Apple). Not yet wired into CI — the native-apple-vulkan build job comes next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…package
Adds native-apple-vulkan.yml (mirrors native-apple.yml: iOS device/simulator +
macCatalyst) building mln-cabi with -DMLN_WITH_METAL=OFF -DMLN_WITH_VULKAN=ON.
Vulkan headers + VMA are vendored by maplibre-native, so only a Vulkan SDK
(MoltenVK) is installed to satisfy find_package(Vulkan) on the Darwin path;
mln-cabi is a static archive on Apple so MoltenVK links in the consuming app.
Artifacts: native-mln-{ios-arm64,iossimulator-arm64,maccatalyst}-vulkan.
Wires build-apple-vulkan into ci.yml + release.yml and repoints pack-vulkan to
consume the Vulkan Apple slices (was reusing the Metal build-apple output), so
the Vulkan package now ships a real Vulkan Apple binary. The base package still
uses the Metal build-apple.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bindings: add mbgl_get_render_backend, the backend-agnostic mbgl_frontend_create, and mbgl_frontend_read_pixels P/Invokes. MbglFrontend now creates via the generic entry point, exposes the static RenderBackend (OpenGL/Vulkan/Metal, queried once), and adds ReadPixels() for the offscreen path. - Windows MapImageView: branch on backend. OpenGL keeps the WGL context + glReadPixels; Vulkan skips WGL entirely, renders headless, and copies frames back via frontend.ReadPixels into the WriteableBitmap. The GL vertical flip (ScaleY = -1) is undone for Vulkan since the headless read-back is already top-down. Android/Apple need no change: they pass the ANativeWindow / consume getNativeView(), which the generic create routes to the Vulkan frontends. (WPF MlnMapImage still assumes GL — a Vulkan branch there is a follow-up.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds MauiSample-vulkan-win-x64 to the "Pack WPF NuGet + Windows samples" job output (ci.yml + release.yml). Because the managed layer is backend-agnostic, the Vulkan sample is the normal win-x64 MauiSample publish with the Vulkan-built mln-cabi.dll overlaid — at runtime MapImageView detects the "vulkan" backend and drives the offscreen Vulkan frontend. Downloads the native-mln-windows-x64-vulkan artifact (adds build-windows-vulkan to the job's needs) and zips the result alongside the existing Windows samples. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
humbletim/setup-vulkan-sdk cannot build MoltenVK (it errored with "unknown component: MoltenVK"). MoltenVK isn't needed at build time anyway: mln-cabi is a static archive using Vulkan's dynamic dispatch loader with vendored headers, so no Vulkan library links here (MoltenVK links later in the app). Install only Vulkan-Headers + Vulkan-Loader, which is enough to satisfy find_package(Vulkan) on the macCatalyst leg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maplibre-native's own mbgl/vulkan/*.cpp has latent -Wshorten-64-to-32 (size_t -> uint32_t) truncations that only surface under Apple's strict flags, since upstream never compiles the Vulkan backend for Apple (upload_pass.cpp:54 was the first of many). Set -DMLN_WITH_WERROR=OFF so these upstream warnings don't fail our build; our own code is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Windows samples - WPF MlnMapImage: branch on MbglFrontend.RenderBackend like MapImageView — OpenGL keeps the WGL context + glReadPixels; Vulkan skips WGL, renders headless, and copies frames back via frontend.ReadPixels into the WriteableBitmap (flip undone for the top-down read-back). - ConsoleExample: add a RunVulkan() headless render path (no WGL/Win32) selected when the loaded native reports the Vulkan backend. - ci.yml + release.yml: the Windows-samples job now overlays the Vulkan mln-cabi.dll onto all three win-x64 apps and zips ConsoleExample-vulkan / WpfExample-vulkan / MauiSample-vulkan alongside the existing samples, so the windows-samples bundle has a working Vulkan build of each renderer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-back The offscreen frontend rendered under BackendScope::Implicit and then read pixels back in a *separate* scope. Implicit never calls HeadlessBackend::activate() (which creates the backend impl and validates the Vulkan context), and reading in a second scope tears the just-rendered frame's resources down before readStillImage() copies getAcquiredImage() — reading/freeing invalid Vulkan memory and corrupting the heap (0xc0000374, crashing MauiSample/WpfExample on the Vulkan native). Mirror maplibre-native's HeadlessFrontend: render under the default (Explicit) scope and read the still image back inside the SAME scope, caching the RGBA frame so readPixels() just copies it out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on crash The offscreen Vulkan path still crashes with heap corruption (0xc0000374) and guessing hasn't pinned it, so instrument each stage of VulkanOffscreenFrontend (create / ctor / update / render steps / readStillImage / readPixels / dtor) to %TEMP%\mln_vulkan_diag.log, flushed per line so the last entry survives the crash and tells us exactly which step dies. Also ship mln-cabi.pdb in the Windows Vulkan artifact and alongside the overlaid sample DLL so the crash dump can be symbolicated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Temporary: use the custom maplibre-native (WifiDB fork, terrain-3d-color-relief) so the C ABI / MAUI apps can try the 3D terrain and color-relief features before they land upstream. Swaps the submodule URL to the fork and the pinned commit from v13.3.1 to the terrain branch tip. Revert to the upstream URL/commit once the terrain work is merged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Terrain is a style root property, so it was only reachable by loading a whole style JSON. Expose it directly for runtime on/off (like maplibre-gl-js TerrainControl): - C ABI: mbgl_style_set_terrain(source_id, exaggeration), mbgl_style_remove_terrain, mbgl_style_is_terrain_enabled, over Style::setTerrain/getTerrain. - Binding: NativeMethods P/Invokes and MbglStyle.SetTerrain/RemoveTerrain/ IsTerrainEnabled. - Control: MapLibreMap.SetTerrain/RemoveTerrain/ToggleTerrain/IsTerrainEnabled, through the controller interface and the Windows/Android/MaciOS controllers. ToggleTerrain reads the live style state so it stays correct across reloads. The raster-dem source is added the usual way (AddRasterDemSource) and may be shared with a hillshade layer; sources and layers already had add/remove, so only the terrain property was missing. Needs a native rebuild for the new exports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A "Terrain" tab whose style (OSM raster + hillshade over the Mapterhorn DEM, pitched over Innsbruck via the style-spec root camera) starts flat; a button calls MapLibreMap.ToggleTerrain to drape it over 3D terrain and back, updating its label from IsTerrainEnabled. The style is plain maplibre-style-spec with no terrain property - terrain is enabled through the API, not the JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MlnMapImage gets SetTerrain/RemoveTerrain/ToggleTerrain/IsTerrainEnabled over MbglStyle, matching the MAUI control. The WPF sample adds a "3D Terrain" preset style (OSM raster + hillshade over the Mapterhorn DEM, pitched over Innsbruck, plain style-spec with no terrain property) and a Toggle 3D Terrain button that enables/disables terrain through the API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework both samples so terrain is toggled on whatever style is loaded, using a configurable raster-dem source, instead of a dedicated terrain style: - MAUI Terrain page: a style picker + a terrain-source picker (preset Matterhorn or a custom tilejson/tiles URL). The toggle adds the picked raster-dem to the current style (once per style load) and calls ToggleTerrain. - WPF sample: an editable "Terrain source" combo (preset or custom URL); the Toggle 3D Terrain button adds that raster-dem to the current style and toggles. This mirrors how a consuming app would offer a terrain source setting, and lets terrain drape over any base style. Needs the native rebuilt with the terrain exports (a stale native was why enabling terrain crashed / showed only hillshade). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Terrain draping over a flat-coloured style is nearly invisible without relief shading, so the WPF and MAUI sample terrain toggles now add a hillshade layer from the DEM source when terrain is enabled and remove it when disabled. Surface AddHillshadeLayer on MlnMapImage (WPF) and MapLibreMap (MAUI), delegating to the existing style/controller binding. Add a headless --terraintest harness and MlnMapImage.SnapshotBitmap to the WPF sample that enable terrain over a style on the offscreen WGL backend and snapshot the frame, used to verify terrain draping renders on the Windows GL path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an on-map terrain toggle button across all four backends (Windows,
Android, iOS, WPF), matching the existing navigation and GPS controls.
Clicking it toggles terrain on TerrainControlSourceId — enable if off,
disable if on — mirroring maplibre-gl-js's TerrainControl. The raster-dem
source must already exist in the style; the control does not add sources
or hillshade (styles usually include hillshade themselves).
New properties on MapLibreMap and MlnMapImage:
ShowTerrainControl (default false), TerrainControlSourceId,
TerrainControlExaggeration, TerrainControlPosition.
The default source id is app-specific ("mln-terrain-dem") rather than a
generic name like "terrain" so it won't collide with a real style source.
The button stacks above the navigation control in its corner.
Plumbed through the handler mapper, options sink and per-platform builders
like the other Show* controls. Samples enable the control and add the DEM
(+ a hillshade layer, so the relief is visible) on style load so the
button has a source to toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # handlers/Windows/MapImageView.Windows.cs # wpf/MlnMapImage.cs
mbgl_get_render_backend and mbgl_get_last_error return pointers to memory the native library owns — a static string literal and a thread_local std::string buffer respectively. Marshalling their return as a `string` makes the source-generated P/Invoke free that pointer with FreeCoTaskMem, corrupting the heap (0xC0000374). mbgl_get_render_backend is the first native call at startup (static RenderBackend initializer), so every build — GL and Vulkan — crashed on load before showing a window. Return IntPtr and copy with Marshal.PtrToStringUTF8, which does not free. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picks up fix(terrain): consistent layer numbering between the drape's opaque and translucent passes. Without it, enabling terrain dropped every low-stack draped fill layer (landcover, parks, water) on styles with many symbol layers, leaving only hillshade, lines and labels on the terrain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x pitch) Advances the maplibre-native submodule from 12876dafbbbf to 4dacaf575f09, bringing the Vulkan 3D-terrain fixes verified on Adreno (S23 Ultra): stencil-attachment crash, drape clip-mask cast, blank-terrain clip-z, drape color/height alignment, vector fill/line drape (clip-mask clip-z), and the VkPipelineCache perf fix; plus raising the terrain samples' max pitch preference to 85. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in the Vulkan terrain symbol/label fixes plus terrain skirts, drape-target frustum culling, and un-ignored terrain render tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider hook already existed but was Android-only, because the file implementing it defines mbgl::HTTPFileSource — which a standalone NDK build needs, since nothing else supplies that symbol, but which cannot be compiled on Windows or Apple where maplibre-native links its own and a second definition would collide. Rather than replacing maplibre's HTTP stack at build time, this registers a FileSource for FileSourceType::Network at runtime through FileSourceManager. No build surgery, no symbol collision, and it is opt-in: the factory is installed only when a provider is actually set, so an application that never calls mbgl_set_http_provider keeps the network stack its platform already built, unchanged. Both routes now share one dispatchToProvider(), so the request table, cancellation and byte-range handling exist in exactly one place. The HTTPFileSource definition is guarded to __ANDROID__; everything else compiles everywhere. The header's provider declarations were also inside the Android guard and are now outside it, leaving only the ANativeWindow helpers, which really are Android-only. File renamed http_file_source_android.cpp -> http_provider.cpp to match. Why this matters beyond tidiness: mbgl passes the byte range on the Resource, and every PMTiles read is ranged, so a host can now serve tile reads from somewhere other than a web server — a BitTorrent swarm, an embedded database, an encrypted bundle — on desktop as well as Android, with maplibre unaware of the difference. Verified by compiling the translation unit against the real include paths from both build trees: clean under MSVC for Windows, and clean under the Android NDK clang apart from a pre-existing unused-parameter warning on http_status that only appears under the stricter mbgl-core flags borrowed for the check (mln-cabi does not build with -Werror). Not yet exercised: no C# side registers a provider on Windows yet, so this changes nothing at runtime until one does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Following up the previous commit: installing a FileSourceType::Network factory whenever a provider is set was a regression on the one platform that already worked. maplibre's default Network factory returns an OnlineFileSource, which wraps HTTPFileSource and adds retry with backoff, rate-limit handling and request queueing. On Android the provider is already reached through mbgl::HTTPFileSource, i.e. underneath OnlineFileSource, so today only the transport is delegated and all of that is retained. Registering the factory there would have replaced OnlineFileSource outright and thrown it away. The factory is now installed only off Android, where slotting in beneath OnlineFileSource is impossible without colliding with the platform's own HTTPFileSource. The header documents the resulting asymmetry: a host registering a provider on those platforms owns its own retry and backoff, because mbgl will not retry on its behalf. Re-verified: clean under MSVC, and clean under Android NDK clang apart from the pre-existing unused-parameter warning on http_status. Also adds the changelog entry for both commits. This includes the already-uncommitted release-note reordering that was in the working tree, which was not mine but is committed here as requested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answers the retry question raised by the previous commit by removing the need for one, rather than pushing a retry policy onto every host. Replacing OnlineFileSource means inheriting its job: retry with backoff, rate-limit handling, queueing. That is a lot to owe when the actual goal is serving two or three archive URLs from somewhere other than a web server. So ProviderFileSource now wraps an OnlineFileSource and consults a list of URL prefixes the host has claimed: claimed requests go to the host, everything else is delegated and behaves exactly as if no provider had been registered. Claiming nothing preserves the previous all-or-nothing behaviour for a host that really does want the whole stack. Prefix matching rather than a callback into the host on purpose — this runs for every resource the map fetches, and a reverse P/Invoke per tile would be a poor trade for matching a handful of known URLs. The fallback is held as unique_ptr<FileSource> rather than unique_ptr<OnlineFileSource>, since OnlineFileSource declares its overrides private and they are only reachable through the base. Every FileSource method is forwarded, not just request(), so pause/resume, properties, resource transforms and cache-only support keep working for the delegated path. Verified: clean under MSVC, and clean under Android NDK clang apart from the pre-existing unused-parameter warning on http_status. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First half of the torrent-aware client: the archive reader, which is what lets a device resolve a tile to a byte range itself. There is nothing tile-specific in a swarm — it holds one file — so both ends need to know how to read tiles out of it, and this is that knowledge on the device side. A separate package rather than part of MapLibreNative.Maui. MonoTorrent and its transitive dependencies would otherwise land in every app that draws a map, and embedding a BitTorrent client is a decision an app should make deliberately. Written against an IByteRangeSource rather than against a swarm, so the same reader works over a torrent, a local file or HTTP ranges, and is testable without a network. Two entry points the C header already declared were never bound: mbgl_http_provider_claim_prefix and _clear_claims. They are what let this serve a few URLs from the swarm while everything else keeps maplibre's own network stack, retry and backoff intact. The tests are cross-implementation on purpose. Tile ids are checked against 1421 cases generated by the reference JavaScript, and archives are read from fixtures that same library's format produced — plain, fully gzipped, and gzipped tiles with plain directories. A self-consistent test would happily agree with a wrong Hilbert curve and misread every archive in existence. One assertion is worth keeping: neighbouring tiles are a median of one apart in the file in *both* directions. Row-major would score one horizontally and a full row vertically. That isotropy is why a single torrent piece tends to carry a whole neighbourhood, which is the property this design rests on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The layer between the archive reader and the swarm: turns "give me bytes 4096 to 8192" into "fetch piece 217". Ported from pmtiles-torrent, whose TypeScript is the reference for the behaviour here. Three things it does that a naive implementation does not: Concurrent callers of the same piece share one fetch. A map asks for a screenful at once and neighbouring tiles land in the same piece, so without this the swarm sees one request per tile on screen. Cancellation is reference counted. An abandoned read stops waiting immediately, but the fetch is only cancelled once every waiter has gone. Forwarding a caller's token straight through would let one abandoned tile kill a piece another tile still needs — and a panning map abandons requests constantly, so that is the common case, not an edge one. Pieces covering a range are fetched together rather than in sequence. Serialising them was the largest latency cost in the original: a range across three pieces paid three round-trips. Caught a real bug while testing the multi-file case. The fetch clipped a piece to the file with Math.Max(0, start) but the reassembly used the unclipped start, so every byte came back shifted by the file offset — plausible-looking wrong data rather than a failure. Both now go through one PieceFileRange helper, which is the only way they cannot drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The contract between pmtiles-swarm and this plugin. It describes the archive, not tiles — there is nothing tile-specific in a swarm, which holds one file that both ends know how to read tiles out of. Degrades rather than fails. A TileJSON with no torrent member parses fine and reports null, because an ordinary tile server is not an error; a block with no infohash is treated as absent, since half a descriptor is worse than none; and unexpected types fall back to "no acceleration" rather than breaking the map. Fixtures are generated by pmtiles-swarm's own TileJSON builder, so a change to either side that breaks the contract fails here. A hand-written fixture would only prove this parser agrees with itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reads go through MonoTorrent's streaming provider, which promotes the pieces around the read position instead of waiting for the normal picker. That is the difference between a tile arriving in seconds and arriving whenever a sequential download reaches it. The provider's stream is not safe for concurrent use, so reads are serialised. That gives up the parallel piece fetches the layer above would otherwise get — an acceptable trade on a client, where the limit is one slow swarm rather than many fast pieces. Verified against a live 71.93 GiB OpenMapTiles torrent: metadata 0.8s (from a .torrent; a magnet costs minutes) header 4.5s tile 0/0/0 34.8s cold — a whole 16 MiB piece must arrive tile 1/0/0 0.0s already inside a fetched piece fetched 32 MiB to serve 204 KB That second tile is the design working: Hilbert ordering puts map neighbours next to each other in the file, so the read amplification is the prefetch rather than waste. The measurement also showed a default of mine was wrong. Sizing the cache as pieces x piece length gives 128 MiB against the 16 MiB pieces these torrents use, which is not a thing to allocate on a phone. Piece count now sizes the cache relative to the piece length under a 64 MiB ceiling. The swarm test lives here but skips unless PMTILES_TORRENT_TEST_ID names a torrent, so nothing large is committed and CI never depends on public peers being reachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last piece. Point TorrentTileProvider.AttachAsync at a pmtiles-swarm tiles.json: if it carries a torrent block the archive is joined and its tiles come from swarm pieces, and if it does not, nothing changes. The style needs no special syntax either way, which is the point — one URL works for both kinds of client. Only the archive's own tile URLs are claimed, so every other resource the map fetches keeps maplibre's network stack with its retry and rate-limit handling. HTTP is never abandoned: it answers while the swarm connects, and remains the fallback for anything the swarm cannot produce within SwarmTimeout. Six seconds by default, because a cold 16 MiB piece can take thirty and a map that stalls that long reads as broken, whereas the piece will be there by the next pan. A missing tile is answered as 204 rather than passed to HTTP: a sparse archive genuinely has no tile there, and asking HTTP the same question only adds latency to a known answer. Also fixes something the cross-platform provider work missed. Those commits took the native side to every platform, but NativeMethods.cs still had the whole provider block inside #if ANDROID, so nothing could reach it anywhere else — the C# half of the same guard bug fixed in the C header. The guard now covers only the ANativeWindow helpers, which really are Android-only. The URL parser lives in src/ rather than platform/ so it is testable: it runs for every resource the map requests on Android, where the provider sits beneath OnlineFileSource and sees all traffic. Its job is mostly to say no, and the tests are mostly things that must not be claimed — sprites, fonts, style documents, and tile URLs belonging to other servers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same bug as pmtiles-swarm had, and mine in both places: every missing tile answered 204. MapLibre only falls back to a parent tile when the child 404s, so a sparse raster-dem rendered as holes wherever the data was never built. Now decided by the archive's own tile type — raster 404, vector 204 — with a Sparse option for anything that wants the opposite. PMTiles cannot say whether raster data is a DEM, so raster defaults to the answer a DEM needs. Verified against a 698 GiB sparse webp planet: header in 19.2s, first tile 3.3s, neighbour 0.0s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pmtiles-swarm publishes an archived source as one string with both halves of the story in it: https://host/latest/<category>/tiles.json#torrent=<url>&magnet=<magnet> A fragment is never sent in a request, so that is an ordinary TileJSON URL to MapLibre and to everything else that reads the style; only something looking for it sees more. AttachAsync now takes that form. The document still wins. Its torrent block carries the infohash, the size, the web seeds and the mutable identity, none of which fit in a URL, so it is asked for first and the fragment is what remains when it cannot be had -- a server that is down, or a source that never published a block. That is the case the fragment exists for, so an unreadable document falls back rather than failing. A fragment names handles, not an identity, so the infohash is recovered from the magnet, or from the metainfo the .torrent URL points at -- which is written into the cache as it is read, so the join does not ask for it twice. Unlike the browser this does not require torrent=. WebTorrent has no DHT, so a magnet alone leaves it nothing to ask for the metadata a magnet omits; MonoTorrent has both a DHT and BEP 9, so a magnet on its own is a route this client can take and refusing it would discard one. Also ports pmtiles-torrent 0.4.6's tail prefetch, which landed after this port was written: one piece of the archive's end, hinted at normal priority as soon as the geometry is known, to cover the gap before a header can point anywhere. Inert with the bundled engine -- MonoTorrentEngine.Hint is a deliberate no-op because the streaming provider owns piece priority -- and marked as such. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch builds against a fork of maplibre-native (terrain-3d-color-relief)
and carries the torrent tile source, so what it produces is not the stable line
at a newer version -- it is a different build of the same API. Shipping it as a
prerelease under the stable IDs would make a fork native installable by
accident, so the whole family is renamed instead:
MapLibreNative.Maui -> MapLibreNative.Maui.Experimental
MapLibreNative.Maui.WPF -> MapLibreNative.Maui.Experimental.WPF
MapLibreNative.Maui.Vulkan -> MapLibreNative.Maui.Experimental.Vulkan
MapLibreNative.Maui.Handlers -> MapLibreNative.Maui.Experimental.Handlers
MapLibreNative.Maui.Torrent -> MapLibreNative.Maui.Experimental.Torrent
Only the package identity moves. AssemblyName and RootNamespace stay, so
consuming code needs no changes, and the two lines are mutually exclusive by
construction since both carry an assembly called MapLibreNative.Maui.
The rename lives in Directory.Build.targets behind -p:Experimental=true, which
as a global property flows across ProjectReference -- so a dependent packs with
a dependency on MapLibreNative.Maui.Experimental rather than on the stable
package. Verified by packing WPF and reading the nuspec back.
Versioned from VERSION.experimental at 5.0.0-experimental.1: a prerelease of the
next major, so 4.x stays free for minors and patches, and the version string
carries its own prerelease segment so the tag cannot collide with a stable v4.x.
Rather than duplicating five hundred lines of release.yml, the existing workflow
takes a `line` dispatch input. A push to main is always stable; the input only
exists on a manual dispatch. The torrent package is packed only on the
experimental line, because the stable native carries neither the host HTTP
provider nor URL claiming.
Sample apps are skipped on the experimental line: they reference the packages by
their stable IDs from a local feed, which under the renamed line holds nothing
they ask for. Making them work means giving those csproj files a conditional
reference, which is not done here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The experimental 3D-terrain work we were carrying on the WifiDB fork has landed upstream (#4190, plus #4389 as 81d19a3bd2a0), so the submodule now points at maplibre/maplibre-native feature/terrain-3d instead of the fork. That branch also carries main's two renames: #4487 moved the C++ namespace mbgl -> mln, and #4511 moved the mbgl/ header directories to mln/. Applied both to native/ (includes, mln:: qualifiers, namespace blocks, and the platform/default/src stub paths in CMakeLists). The mbgl_* C ABI export names and the mbgl-core / mbgl-vendor-* CMake target names are unchanged upstream, so they stay as they are. Verified with a Windows OpenGL build of mln-cabi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings the upstream retarget over. The three conflicts (http_provider.cpp, platform_frontend_android.cpp, platform_frontend_windows.cpp) are this branch's Vulkan/HTTP-provider additions against the mbgl -> mln rename, so each resolves as this branch's content with the rename re-applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.