Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions Sources/MapConductorCore/MapCollectionItems.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,63 @@ import UIKit
/// ビューを作らないので、`MapViewContentBuilder` の中でそのまま使える。
public struct Markers: MapOverlayItemProtocol {
private let states: [MarkerState]
private let progress: AddProgressRequest?

public init(_ states: [MarkerState]) {
/// `onAddStart`, `onAddProgress` and `onAddEnd` say how far the states this
/// list adds are with reaching the map; see ``AddProgressCallbacks``. Leave
/// `fileID`, `line` and `column` to their defaults: they tell this
/// collection apart from the others on the map.
public init(
_ states: [MarkerState],
onAddStart: ((_ total: Int) -> Void)? = nil,
onAddProgress: ((_ added: Int, _ total: Int) -> Void)? = nil,
onAddEnd: ((_ total: Int) -> Void)? = nil,
fileID: StaticString = #fileID,
line: UInt = #line,
column: UInt = #column
) {
self.states = states
let callbacks = AddProgressCallbacks(onAddStart: onAddStart, onAddProgress: onAddProgress, onAddEnd: onAddEnd)
progress = callbacks.isEmpty
? nil
: AddProgressRequest(kind: "markers", states: states, callbacks: callbacks, fileID: fileID, line: line, column: column)
}

public func append(to content: inout MapViewContent) {
let items = states.map { Marker(state: $0) }
if content.markers.isEmpty { content.markers = items } else { content.markers.append(contentsOf: items) }
if let progress { content.addProgressRequests.append(progress) }
}
}

public struct Circles: MapOverlayItemProtocol {
private let states: [CircleState]
private let progress: AddProgressRequest?

public init(_ states: [CircleState]) {
/// `onAddStart`, `onAddProgress` and `onAddEnd` say how far the states this
/// list adds are with reaching the map; see ``AddProgressCallbacks``. Leave
/// `fileID`, `line` and `column` to their defaults: they tell this
/// collection apart from the others on the map.
public init(
_ states: [CircleState],
onAddStart: ((_ total: Int) -> Void)? = nil,
onAddProgress: ((_ added: Int, _ total: Int) -> Void)? = nil,
onAddEnd: ((_ total: Int) -> Void)? = nil,
fileID: StaticString = #fileID,
line: UInt = #line,
column: UInt = #column
) {
self.states = states
let callbacks = AddProgressCallbacks(onAddStart: onAddStart, onAddProgress: onAddProgress, onAddEnd: onAddEnd)
progress = callbacks.isEmpty
? nil
: AddProgressRequest(kind: "circles", states: states, callbacks: callbacks, fileID: fileID, line: line, column: column)
}

public func append(to content: inout MapViewContent) {
let items = states.map { Circle(state: $0) }
if content.circles.isEmpty { content.circles = items } else { content.circles.append(contentsOf: items) }
if let progress { content.addProgressRequests.append(progress) }
}
}

Expand All @@ -54,14 +90,32 @@ public struct CircleTiling: MapOverlayItemProtocol {

public struct Polylines: MapOverlayItemProtocol {
private let states: [PolylineState]
private let progress: AddProgressRequest?

public init(_ states: [PolylineState]) {
/// `onAddStart`, `onAddProgress` and `onAddEnd` say how far the states this
/// list adds are with reaching the map; see ``AddProgressCallbacks``. Leave
/// `fileID`, `line` and `column` to their defaults: they tell this
/// collection apart from the others on the map.
public init(
_ states: [PolylineState],
onAddStart: ((_ total: Int) -> Void)? = nil,
onAddProgress: ((_ added: Int, _ total: Int) -> Void)? = nil,
onAddEnd: ((_ total: Int) -> Void)? = nil,
fileID: StaticString = #fileID,
line: UInt = #line,
column: UInt = #column
) {
self.states = states
let callbacks = AddProgressCallbacks(onAddStart: onAddStart, onAddProgress: onAddProgress, onAddEnd: onAddEnd)
progress = callbacks.isEmpty
? nil
: AddProgressRequest(kind: "polylines", states: states, callbacks: callbacks, fileID: fileID, line: line, column: column)
}

public func append(to content: inout MapViewContent) {
let items = states.map { Polyline(state: $0) }
if content.polylines.isEmpty { content.polylines = items } else { content.polylines.append(contentsOf: items) }
if let progress { content.addProgressRequests.append(progress) }
}
}

Expand All @@ -88,14 +142,32 @@ public struct PolylineTiling: MapOverlayItemProtocol {

public struct Polygons: MapOverlayItemProtocol {
private let states: [PolygonState]
private let progress: AddProgressRequest?

public init(_ states: [PolygonState]) {
/// `onAddStart`, `onAddProgress` and `onAddEnd` say how far the states this
/// list adds are with reaching the map; see ``AddProgressCallbacks``. Leave
/// `fileID`, `line` and `column` to their defaults: they tell this
/// collection apart from the others on the map.
public init(
_ states: [PolygonState],
onAddStart: ((_ total: Int) -> Void)? = nil,
onAddProgress: ((_ added: Int, _ total: Int) -> Void)? = nil,
onAddEnd: ((_ total: Int) -> Void)? = nil,
fileID: StaticString = #fileID,
line: UInt = #line,
column: UInt = #column
) {
self.states = states
let callbacks = AddProgressCallbacks(onAddStart: onAddStart, onAddProgress: onAddProgress, onAddEnd: onAddEnd)
progress = callbacks.isEmpty
? nil
: AddProgressRequest(kind: "polygons", states: states, callbacks: callbacks, fileID: fileID, line: line, column: column)
}

public func append(to content: inout MapViewContent) {
let items = states.map { Polygon(state: $0) }
if content.polygons.isEmpty { content.polygons = items } else { content.polygons.append(contentsOf: items) }
if let progress { content.addProgressRequests.append(progress) }
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/MapConductorCore/MapViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public struct MapViewBase<MapContent: View, TopContent: View>: View {
private let content: MapViewContent
private let mapContent: () -> MapContent
private let topContent: () -> TopContent
@StateObject private var addProgress = AddProgressHost()

public init(
attributionRules: [AttributionRule],
Expand All @@ -65,6 +66,9 @@ public struct MapViewBase<MapContent: View, TopContent: View>: View {
}

public var body: some View {
// Before the map below is handed the content, so that the collections
// know their lists ahead of the first arrival.
let _ = addProgress.sync(content.addProgressRequests)
ZStack {
mapContent()
ForEach(0..<content.views.count, id: \.self) { index in
Expand Down
3 changes: 3 additions & 0 deletions Sources/MapConductorCore/MapViewContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public struct MapViewContent {
/// to provide a direct polygon sync function, ensuring those polygons are committed before
/// marker animations start rather than through SwiftUI's deferred recomposition path.
public var polygonSyncHandlers: [any PolygonSyncHandler] = []
/// The collections that asked for ``AddProgressCallbacks``; ``MapViewBase`` keeps their count.
var addProgressRequests: [AddProgressRequest] = []

public init() {}

Expand All @@ -88,6 +90,7 @@ public struct MapViewContent {
Self.append(other.rasterLayers, to: &rasterLayers)
Self.append(other.views, to: &views)
polygonSyncHandlers.append(contentsOf: other.polygonSyncHandlers)
addProgressRequests.append(contentsOf: other.addProgressRequests)
// An explicit setting wins over an unset one, whether it enables tiling or not;
// between two explicit settings the later one wins.
if let tiling = other.explicitMarkerTilingOptions {
Expand Down
17 changes: 13 additions & 4 deletions Sources/MapConductorCore/marker/StrategyMarkerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,23 +110,30 @@ where Strategy.ActualMarker == ActualMarker, Renderer.ActualMarker == ActualMark
}

public func add(data: [MarkerState]) async {
await semaphore.withPermit { await addLocked(data: data) }
let landed = await semaphore.withPermit { await addLocked(data: data) }
await OverlayAddedTracker.notify(landed)
}

/// ``add(data:)`` の中身。**呼び出し側が permit を持っていること。**
/// ``onCameraChanged(mapCameraPosition:)`` が保留分を流すときに、
/// permit を持ったまま呼ぶ(``AsyncSemaphore`` は再入できないので
/// `add(data:)` をそのまま呼ぶとデッドロックする)。
private func addLocked(data: [MarkerState]) async {
///
/// Returns the states that reached the map, for their `onAdded`: the strategy
/// keeps every marker it shows (or clusters) in its manager, the same as
/// js-sdk-core's and android-sdk's `StrategyMarkerController`.
private func addLocked(data: [MarkerState]) async -> [MarkerState] {
guard let bounds = mapCameraPosition?.visibleRegion?.bounds ?? lastKnownBounds else {
pendingStates = data
return
return []
}
let fresh = data.filter { !markerManager.hasEntity($0.id) }
_ = await strategy.onAdd(
data: data,
viewport: bounds,
renderer: renderer
)
return fresh.filter { markerManager.hasEntity($0.id) }
}

public func update(state: MarkerState) async {
Expand Down Expand Up @@ -206,6 +213,7 @@ where Strategy.ActualMarker == ActualMarker, Renderer.ActualMarker == ActualMark
/// ではなくなる)。終了時の見た目は変わらない。
public func onCameraChanged(mapCameraPosition: MapCameraPosition) async {
await latestCamera.store(mapCameraPosition)
var landed: [MarkerState] = []
await semaphore.withPermit {
if let camera = await latestCamera.take() {
self.mapCameraPosition = camera
Expand All @@ -229,9 +237,10 @@ where Strategy.ActualMarker == ActualMarker, Renderer.ActualMarker == ActualMark
// 流したいのであって、どのタスクが流すかは問わない。
if let pending = pendingStates {
pendingStates = nil
await addLocked(data: pending)
landed = await addLocked(data: pending)
}
}
await OverlayAddedTracker.notify(landed)
}

public func destroy() {
Expand Down
Loading
Loading