Skip to content

Stop the styleReady flip-flop; load marker icons through loadImage's callback - #17

Merged
wf9a5m75 merged 2 commits into
developfrom
fix/clear-followups
Nov 8, 2026
Merged

wf9a5m75 merged 2 commits into
developfrom
fix/clear-followups

Conversation

@wf9a5m75

@wf9a5m75 wf9a5m75 commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

なぜ

何も操作していない地図でも、styleReady が約 0.3 秒ごとに true / false を往復し続けていました。

  • 再同期のたびに、全ソースへの setData と、マーカーレイヤーへの moveLayer が走っていました。
  • moveLayer は位置が変わらなくても style を変更扱いにするので、毎回 styledata が出ます。そのとき直前の setData でソースが読み込み中だと isStyleLoaded() が false になり、styleReady が false に落ちます。
  • 続く idle で true に戻って再同期し、また同じ書き込みをする、の繰り返しでした。6 種類の再同期がどれも書き込んでいたので、1 つ止めただけでは止まりませんでした。

無駄な再描画に加えて、styleReady が false の間に来た削除が待たされる(#16 の GroundImage の件)原因にもなっていました。

何を変えたか

  • bringMarkerLayersToFront: マーカーレイヤーがすでに最上段にあるとき、または前回自分で並べたときから順序が変わっていないときは動かしません。
  • setSourceFeatures(新設): 前回と同じ地物オブジェクトが同じ順で、同じソースへ書かれる場合は setData を飛ばします。デザイン変更で style を差し替えるとソースのオブジェクトが新しくなるので、その場合は必ず書き直します。

マーカーアイコンの読み込み(2 つ目の commit)

Mappls のページでは、マーカーがあるたびに Mappls SDK の中から「Uncaught (in promise) TypeError: e is not a function」が出ていました(marker のサンプルで 21 回、polygon で 2 回、info-bubble-rich で 3 回)。

  • Mappls の地図は mapbox-gl v1 系で、loadImage(url, callback) は何も返さずコールバックで結果を返します。
  • マーカーのレンダラはこれを maplibre の Promise 形式として await していたので undefined を受け取り、普通の Image での読み込みに切り替えていました(アイコンを 2 回取りに行っていた)。さらに SDK が自分の読み込みを終えたとき、渡されていないコールバックを呼んで例外になっていました。
  • SDK はその版の仕様どおりで、こちらの呼び方の誤りでした。loadImageThroughMap がコールバックを渡す形に直しました。将来の SDK が Promise を返す場合も受けられます。

直した後は 3 つのページとも 0 回で、地図とオーバーレイは従来どおり出ています。

検証

  • 単体テスト ResyncWritesNothing.test.mjs を足しました。直す前に落ち、直した後に通ることを確かめています。MarkerIconLoad.test.mjs(SDK と同じくコールバックだけの loadImage で確かめる)も足しました。 既存のテストもすべて通っています。

  • Playwright で、何も操作しない状態の 10 秒間の往復回数と再描画回数を数えました。

    往復回数 再描画
    直す前 maplibre 52 / mapbox 44 / mappls 47 / maptiler 50 / tomtom 44 / longdo 26 86〜129 回
    直した後 6 本とも 0 0 回

    送り直した後とデザイン変更の後も往復は 0 回でした。デザイン変更の後にオーバーレイが戻ることも、レイヤー一覧とスクリーンショットで確かめています。

  • api-surface は ok です(private メンバーの追加だけ)。

関連 PR(fix/clear-followups の一式)

それぞれ独立していて、マージの順番の制約はありません(web の変更は js-sdk-core の新しい版を必要としません)。

styleReady flipped between true and false about every 0.3 s on a map
that was doing nothing (26-52 flips in 10 s on the clearOverlays test
page, with the map re-rendering ~100 times in that window). The loop:
a styledata arriving while a GeoJSON source is still loading sets
styleReady to false, the idle that follows sets it back and resyncs
every overlay, and each resync called setData on every source and
moveLayer on the marker layers. moveLayer marks the style changed even
when the layer stays where it was, so another styledata came while the
just-rewritten sources were loading, and so on. Stopping any single
controller's resync did not end it, because they all wrote.

bringMarkerLayersToFront now moves nothing when the marker layers are
already on top (or the order is still the one it last produced), and
the layers write through setSourceFeatures, which skips setData when
the features are the same objects as the last write to that same
source. Renderers build a new feature for every change, so identity
means "unchanged"; a style swap creates new source objects, so a lost
source is always written again and design changes still restore the
overlays.

ResyncWritesNothing.test.mjs pins both, plus "a resync with nothing to
do touches nothing" and "after a style swap it rebuilds".
Every Mappls page with a marker logged "Uncaught (in promise) TypeError:
e is not a function" from inside the Mappls SDK (21 times on the marker
sample, 2 on the polygon sample). The Mappls map is on the mapbox-gl v1
line, where loadImage(url, callback) returns nothing and answers
through the callback. The renderer awaited it in maplibre's promise
form, so it got undefined, fell back to a plain Image (fetching each
icon twice), and the SDK called the missing callback when its own fetch
finished.

The SDK is behaving as documented for its version; the call was wrong.
loadImageThroughMap passes a callback and still honours a promise if a
later SDK returns one.

MarkerIconLoad.test.mjs uses a callback-only loadImage like the SDK's.

The API snapshot gains the private member.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant