Skip to content

Commit bb91c63

Browse files
Streamline timers
1 parent 30ac3e2 commit bb91c63

File tree

6 files changed

+117
-32
lines changed

6 files changed

+117
-32
lines changed

Alidade.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCMaps/Legacy/Views/CartographyOrnamentMap.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//
77

88
import AsyncAlgorithms
9-
import Combine
109
import CubiomesKit
1110
import MCMap
1211
import SwiftUI
@@ -51,7 +50,7 @@ struct CartographyOrnamentMap: View {
5150
@State private var integrationData = [IntegrationServiceType: any CartographyIntegrationServiceData]()
5251
@State private var integrationFetchState = IntegrationFetchState.initial
5352

54-
private let bmapTimer: Publishers.Autoconnect<Timer.TimerPublisher>
53+
private let clock: CartographyClock
5554

5655
private var integrationAnnotations: [any MinecraftMapBuilderContent] {
5756
var annotations = [any MinecraftMapBuilderContent]()
@@ -70,9 +69,11 @@ struct CartographyOrnamentMap: View {
7069
self._file = file
7170
self._viewModel = viewModel
7271

73-
self.bmapTimer = Timer
74-
.publish(every: file.wrappedValue.integrations.bluemap.refreshRate, on: .main, in: .common)
75-
.autoconnect()
72+
self.clock = CartographyClock()
73+
if file.wrappedValue.integrations.bluemap.enabled {
74+
clock.setupTimer(for: .bluemap, with: file.wrappedValue.integrations.bluemap.refreshRate)
75+
}
76+
clock.restartTimers()
7677
}
7778

7879
var body: some View {
@@ -123,18 +124,18 @@ struct CartographyOrnamentMap: View {
123124
}
124125
}
125126
}
126-
.onReceive(bmapTimer) { _ in
127+
.onReceive(clock.bluemap) { _ in
127128
Task { await updateIntegrationData() }
128129
}
129130
.onDisappear {
130-
bmapTimer.upstream.connect().cancel()
131+
clock.cancelTimers()
131132
}
132133
} ornaments: {
133134
Ornament(alignment: Constants.locationBadgePlacement) {
134135
VStack(alignment: .trailing) {
135136
#if os(iOS)
136-
LocationBadge(location: centerCoordinate)
137-
.environment(\.contentTransitionAddsDrawingGroup, true)
137+
LocationBadge(location: centerCoordinate)
138+
.environment(\.contentTransitionAddsDrawingGroup, true)
138139
HStack {
139140
Menu {
140141
Toggle(isOn: $viewModel.renderNaturalColors) {
@@ -154,12 +155,12 @@ struct CartographyOrnamentMap: View {
154155
LocalTips.dimensionPicker.invalidate(reason: .actionPerformed)
155156
}
156157
#else
157-
HStack(spacing: 0) {
158-
IntegrationFetchStateView(state: integrationFetchState)
159-
.padding(8)
160-
LocationBadge(location: centerCoordinate)
161-
.environment(\.contentTransitionAddsDrawingGroup, true)
162-
}
158+
HStack(spacing: 0) {
159+
IntegrationFetchStateView(state: integrationFetchState)
160+
.padding(8)
161+
LocationBadge(location: centerCoordinate)
162+
.environment(\.contentTransitionAddsDrawingGroup, true)
163+
}
163164
#endif
164165
}
165166
}

MCMaps/Red Window/Views/Map/RedWindowMapView.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//
77

88
import AlidadeUI
9-
import Combine
109
import CubiomesKit
1110
import MCMap
1211
import SwiftUI
@@ -52,12 +51,15 @@ struct RedWindowMapView: View {
5251
return annotations
5352
}
5453

55-
private let bmapTimer: Publishers.Autoconnect<Timer.TimerPublisher>
54+
private let clock = CartographyClock()
5655

5756
init(file: Binding<CartographyMapFile>) {
5857
self._file = file
5958
let bmap = file.wrappedValue.integrations.bluemap
60-
self.bmapTimer = Timer.publish(every: bmap.refreshRate, tolerance: 0.5, on: .main, in: .common).autoconnect()
59+
if bmap.enabled {
60+
clock.setupTimer(for: .bluemap, with: bmap.refreshRate)
61+
}
62+
clock.restartTimers()
6163
}
6264

6365
var body: some View {
@@ -91,10 +93,9 @@ struct RedWindowMapView: View {
9193
await updateIntegrationData()
9294
}
9395
.onDisappear {
94-
bmapTimer.upstream.connect().cancel()
95-
logger.debug("⏰ Timer has been stopped.")
96+
clock.cancelTimers()
9697
}
97-
.onReceive(bmapTimer) { _ in
98+
.onReceive(clock.bluemap) { _ in
9899
Task { await updateIntegrationData() }
99100
}
100101
.onChange(of: env.currentDimension, initial: false) { _, _ in

MCMaps/Shared/Models/Bluemap/BluemapResults.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ extension BluemapResults: CartographyIntegrationServiceData {
6161
if let markers {
6262
for (markerGroup, group) in markers {
6363
if markerGroup == "death-markers", configuration.displayOptions.contains(.deathMarkers) {
64-
let deathMarkers = group.markers.values.map { annotation in
64+
let deathMarkers = group.markers.map { (id, annotation) in
6565
Marker(
6666
location: CGPoint(x: annotation.position.x, y: annotation.position.z),
6767
title: annotation.label,
@@ -74,7 +74,7 @@ extension BluemapResults: CartographyIntegrationServiceData {
7474
continue
7575
}
7676

77-
let mapMarkers = group.markers.values.map { annotation in
77+
let mapMarkers = group.markers.map { (id, annotation) in
7878
Marker(
7979
location: CGPoint(x: annotation.position.x, y: annotation.position.z),
8080
title: annotation.label,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// CartographyClock.swift
3+
// MCMaps
4+
//
5+
// Created by Marquis Kurt on 29-07-2025.
6+
//
7+
8+
import Combine
9+
import Foundation
10+
import os
11+
12+
/// A clock used to house several timers.
13+
///
14+
/// Maps and other parts of the interface might require a constant stream of updates based on a timer. Rather than
15+
/// creating several timers to manage, views can create a clock and subscribe to the timers they need.
16+
class CartographyClock {
17+
/// A timer used to publish realtime data.
18+
var realtime: Publishers.Autoconnect<Timer.TimerPublisher>
19+
20+
/// A timer used to publish data when connecting to the Bluemap service.
21+
///
22+
/// If the service is disabled or hasn't been set up with ``setupTimer(for:with:)``, this will almost never fire.
23+
var bluemap: Publishers.Autoconnect<Timer.TimerPublisher>
24+
25+
private let realtimeTimer = Timer.publish(every: 0.250, on: .main, in: .common)
26+
private var bluemapTimer: Timer.TimerPublisher?
27+
private let stillFrame = Timer.publish(every: .greatestFiniteMagnitude, on: .main, in: .default)
28+
29+
private let logger: Logger
30+
31+
/// Create a clock service.
32+
init() {
33+
logger = Logger(subsystem: "net.marquiskurt.mcmaps", category: "\(CartographyClock.self)")
34+
realtime = realtimeTimer.autoconnect()
35+
bluemap = stillFrame.autoconnect()
36+
}
37+
38+
/// Set up a timer for a corresponding service integration.
39+
/// - Parameter integration: The integration to set up a timer for.
40+
/// - Parameter timeInterval: The duration of the timer before a new event is published.
41+
func setupTimer(for integration: CartographyIntegrationService.ServiceType, with timeInterval: TimeInterval) {
42+
switch integration {
43+
case .bluemap:
44+
bluemapTimer = Timer.publish(every: timeInterval, on: .main, in: .common)
45+
}
46+
}
47+
48+
/// Restart all the current timers.
49+
func restartTimers() {
50+
let still = stillFrame.autoconnect()
51+
realtime = realtimeTimer.autoconnect()
52+
bluemap = bluemapTimer?.autoconnect() ?? still
53+
logger.debug("⏰ Timers have been restarted.")
54+
}
55+
56+
/// Cancel all currently running timers.
57+
func cancelTimers() {
58+
realtimeTimer.connect().cancel()
59+
bluemapTimer?.connect().cancel()
60+
stillFrame.connect().cancel()
61+
logger.debug("⏰ Timers have been cancelled.")
62+
}
63+
}

MCMaps/Shared/Services/CartographyIntegrationService.swift

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ actor CartographyIntegrationService {
7373
case bluemap
7474
}
7575

76+
/// An enumeration of the types of sync requests.
77+
enum SyncType {
78+
/// Fetch data used for a regular context.
79+
case regular
80+
81+
/// Fetch data used for realtime updates.
82+
case realtime
83+
84+
fileprivate var logValue: String {
85+
switch self {
86+
case .regular: "Regular"
87+
case .realtime: "Realtime"
88+
}
89+
}
90+
}
91+
7692
/// An enumeration of the errors that the service can throw when attempting to make requests.
7793
enum ServiceError: Error {
7894
/// The integration is disabled.
@@ -112,7 +128,8 @@ actor CartographyIntegrationService {
112128
/// Synchronize the current dataset from the integrations that the service supports, decoding the response.
113129
/// - Parameter dimension: The Minecraft world dimension to perform the synchronization operation under.
114130
func sync<Response: CartographyIntegrationServiceData>(
115-
dimension: MinecraftWorld.Dimension
131+
dimension: MinecraftWorld.Dimension,
132+
syncType: SyncType = .regular
116133
) async throws(ServiceError) -> Response? {
117134
guard integrationSettings.enabled else {
118135
logger.debug("☁️ Integrations are not enabled. Skipping fetch.")
@@ -121,27 +138,30 @@ actor CartographyIntegrationService {
121138

122139
switch serviceType {
123140
case .bluemap:
124-
self.logger.debug("☁️ Fetching data from Bluemap.")
125-
let response = try await fetchBluemapData(dimension: dimension)
141+
self.logger.debug("☁️ (\(syncType.logValue)) Fetching data from Bluemap.")
142+
let response = try await fetchBluemapData(dimension: dimension, syncType: syncType)
126143
return response as? Response
127144
}
128145
}
129146

130-
private func fetchBluemapData(dimension: MinecraftWorld.Dimension) async throws(ServiceError) -> BluemapResults {
147+
private func fetchBluemapData(
148+
dimension: MinecraftWorld.Dimension,
149+
syncType: SyncType = .regular
150+
) async throws(ServiceError) -> BluemapResults {
131151
let bluemapSettings = integrationSettings.bluemap
132152
guard bluemapSettings.enabled else {
133-
logger.debug("☁️ Bluemap integration is not enabled. Skipping fetch.")
153+
logger.debug("☁️ (\(syncType.logValue)) Bluemap integration is not enabled. Skipping fetch.")
134154
throw .integrationDisabled
135155
}
136156

137-
let itemsToFetch = bluemapSettings.displayOptions
157+
let itemsToFetch = syncType == .realtime ? [.players] : bluemapSettings.displayOptions
138158
do {
139159
let response = await makeBluemapRequests(itemsToFetch: itemsToFetch, dimension: dimension)
140160
switch response {
141161
case .success(let results):
142-
self.logger.debug("☁️ Results were fetched. Returning to sender.")
162+
self.logger.debug("☁️ (\(syncType.logValue)) Results were fetched. Returning to sender.")
143163
if results.isEmpty {
144-
self.logger.warning("☁️ Bluemap results don't contain anything.")
164+
self.logger.warning("☁️ (\(syncType.logValue)) Bluemap results don't contain anything.")
145165
}
146166
return results
147167
case .failure(let error):

0 commit comments

Comments
 (0)