Refactoring.

This commit is contained in:
Erik Moqvist
2026-06-28 06:16:16 +02:00
parent 6c63ad9e3a
commit 8c502e7d9a
37 changed files with 17 additions and 213 deletions
-1
View File
@@ -1,4 +1,3 @@
project: Moblin.xcodeproj
retain_objc_accessible: true
schemes:
- Moblin
-1
View File
@@ -13,7 +13,6 @@ PYTHON_DIRS = \
BLACK_ARGS = $(PYTHON_DIRS)
PERIPHERY_ARGS = \
--index-exclude "Moblin/Integrations/Tesla/Protobuf/*" \
--index-exclude "**/PrepareLicenseList/**" \
--disable-update-check
CODESPELL_ARGS = \
--skip "*.xcstrings,libsrt.xcframework,VoicesView.swift,TextAlignerSuite.swift,Web,node_modules,package-lock.json,*.log" \
-4
View File
@@ -135,10 +135,6 @@ enum WatchProtocolWorkoutType: Codable {
case cycling
}
struct WatchProtocolStartWorkout: Codable {
var type: WatchProtocolWorkoutType
}
struct WatchProtocolWorkoutStats: Codable {
var heartRate: Int?
var activeEnergyBurned: Int?
-2
View File
@@ -506,7 +506,6 @@ private class Rtp {
private var reorderBuffer: [UInt16: Data] = [:]
private let reorderBufferMaxSize = 64
var processor: RtpProcessor?
weak var client: RtspClient?
private let wrappingTimestamp = WrappingTimestamp(
name: "RTP",
maximumTimestamp: CMTime(seconds: 0x1_0000_0000)
@@ -664,7 +663,6 @@ class RtspClient: @unchecked Sendable {
transport?.delegate = self
transport?.start(host: host, port: port)
rtpVideo = Rtp()
rtpVideo.client = self
setState(newState: .connecting)
connectTimer.startSingleShot(timeout: 5) { [weak self] in
self?.reconnectSoon()
-10
View File
@@ -218,12 +218,6 @@ extension Model {
}
}
func selectMicById(id: String) {
if let mic = getAvailableMicById(id: id) {
selectMic(mic: mic)
}
}
func selectMicDefault(mic: SettingsMicsMic) {
media.attachBufferedAudio(cameraId: nil)
let preferStereoMic = database.debug.preferStereoMic
@@ -307,10 +301,6 @@ extension Model {
mic.inputGain = session.inputGain
}
@objc func handleAvailableInputsChangeNotification(notification _: NSNotification) {
updateMicsListAsync()
}
private func handleSystemVolumeDidChange(volume: Float, reason: String, sequenceNumber: Int) {
// For some reason two similar notifications are received. Not sure how to distinguish
// them from each other.
@@ -17,8 +17,4 @@ extension Model {
}
faceBackgroundImage = CIImage(cgImage: cgImage)
}
func deleteFaceBackgroundImage() {
try? FileManager.default.removeItem(at: faceBackgroundImagePath)
}
}
@@ -4,18 +4,6 @@ import Spatial
private let gimbalAngularVelocity: Double = 0.3
private let thumbStickDeadZone: Float = 0.1
class GimbalPresetJob {
let timer: SimpleTimer?
init() {
timer = SimpleTimer(queue: .main)
}
func wasLongPress() -> Bool {
timer == nil
}
}
extension Model {
func handleControllerFunction(buttonId: String,
function: SettingsControllerFunction,
@@ -119,9 +119,6 @@ extension Model: @preconcurrency MediaPlayerDelegate {
let latency = mediaPlayerLatency
media.addBufferedVideo(cameraId: playerId, name: name, latency: latency)
media.addBufferedAudio(cameraId: playerId, name: name, latency: latency)
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// self.selectMicById(id: "\(playerId) 0")
// }
}
func mediaPlayerFileUnloaded(playerId: UUID) {
-8
View File
@@ -89,10 +89,6 @@ extension Model {
bingoCardEffects[id]
}
func getPomodoroTimerEffect(id: UUID) -> PomodoroTimerEffect? {
pomodoroTimerEffects[id]
}
func getScoreboardEffect(id: UUID) -> ScoreboardEffect? {
scoreboardEffects[id]
}
@@ -245,10 +241,6 @@ extension Model {
database.scenes.first { $0.id == id }?.name
}
func getScene(id: UUID?) -> SettingsScene? {
database.scenes.first(where: { $0.id == id })
}
func getWidgetName(id: UUID?) -> String? {
database.widgets.first { $0.id == id }?.name
}
@@ -13,10 +13,6 @@ class VideoPreviewFeed: Identifiable, ObservableObject {
previewView = PreviewView()
previewView.videoGravity = .resizeAspect
}
func enqueue(_ sampleBuffer: CMSampleBuffer, isFirstAfterAttach: Bool) {
previewView.enqueue(sampleBuffer, isFirstAfterAttach: isFirstAfterAttach)
}
}
class VideoPreviewProvider: ObservableObject {
-1
View File
@@ -6,7 +6,6 @@ private struct HttpRequestParseResult {
let path: String
let version: String
let headers: [SettingsHttpHeader]
// periphery:ignore
let data: Data
}
@@ -178,8 +178,6 @@ class SettingsSrtlaServer: Codable, ObservableObject {
}
}
private let defaultSrtClientLatency: Int32 = 2000
class SettingsSrtClientStream: Codable, Identifiable, ObservableObject, Named {
static let baseName = String(localized: "My stream")
var id: UUID = .init()
@@ -2147,15 +2147,6 @@ class SettingsWidgetBingoCard: Codable, ObservableObject {
enum PomodoroPhase: String, Codable {
case focus = "Focus"
case shortBreak = "Break"
func toString() -> String {
switch self {
case .focus:
String(localized: "Focus")
case .shortBreak:
String(localized: "Break")
}
}
}
enum PomodoroFocusIcon: String, Codable, CaseIterable {
@@ -9,7 +9,6 @@ class LogsStorage: @unchecked Sendable {
private let fileManager: FileManager
private var logsUrl: URL
private var currentFileHandle: FileHandle?
private var currentFileUrl: URL?
private var currentFileSize: UInt64 = 0
init() {
@@ -94,14 +93,12 @@ class LogsStorage: @unchecked Sendable {
private func closeCurrentFile() {
try? currentFileHandle?.close()
currentFileHandle = nil
currentFileUrl = nil
currentFileSize = 0
}
private func setCurrentFile(url: URL) {
currentFileHandle = try? FileHandle(forWritingTo: url)
_ = try? currentFileHandle?.seekToEnd()
currentFileUrl = url
currentFileSize = url.fileSize
}
}
@@ -50,11 +50,9 @@ class StreamingHistoryStream: Identifiable, Codable {
var highestThermalState: ThermalState? = .nominal
var lowestBatteryLevel: Double? = 1.0
var highestBitrate: Int64? = Int64.min
var logId: UUID?
init(settings: SettingsStream) {
self.settings = settings
logId = UUID()
}
func updateBitrate(bitrate: Int64) {
+1
View File
@@ -36,6 +36,7 @@ private class PngTuberImage: Decodable {
let offset: PngCoordinate
// periphery:ignore
let parentId: Int?
// periphery:ignore
let pos: PngCoordinate
let showBlink: BlinkTalkState?
let showTalk: BlinkTalkState?
@@ -10,6 +10,7 @@ struct ReplayImage {
}
class ReplayEffectReplayReader: @unchecked Sendable {
// periphery: ignore
private let video: ReplayBufferFile
private let startTime: Double
private var reader: AVAssetReader?
-4
View File
@@ -21,10 +21,6 @@ struct ShapeEffectSettings {
private struct MaskImage {
var extent: CGRect?
var cornerRadius: Float?
var cornerRadiusTopLeft: Bool?
var cornerRadiusTopRight: Bool?
var cornerRadiusBottomLeft: Bool?
var cornerRadiusBottomRight: Bool?
var image: CIImage?
func get(extent: CGRect, settings: ShapeEffectSettings) -> CIImage? {
@@ -44,7 +44,7 @@ private struct WidgetView: View {
widget: widget,
scoreboard: widget.scoreboard)
case .pomodoroTimer:
WidgetPomodoroTimerQuickButtonControlsView(model: model, pomodoroTimer: widget.pomodoroTimer)
WidgetPomodoroTimerQuickButtonControlsView(pomodoroTimer: widget.pomodoroTimer)
default:
EmptyView()
}
@@ -53,7 +53,6 @@ private struct WidgetView: View {
struct QuickButtonSceneWidgetsView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var sceneSelector: SceneSelector
var body: some View {
@@ -77,7 +77,7 @@ private struct DjiDeviceWiFiSettingsView: View {
var body: some View {
Section {
NavigationLink {
DjiDeviceWiFiSettingsInnerView(model: model, database: model.database, device: device)
DjiDeviceWiFiSettingsInnerView(database: model.database, device: device)
} label: {
TextItemLocalizedView(name: "Network", value: device.wifiSsid)
}
@@ -94,7 +94,6 @@ private struct DjiDeviceWiFiSettingsView: View {
}
private struct DjiDeviceWiFiSettingsInnerView: View {
let model: Model
@ObservedObject var database: Database
@ObservedObject var device: SettingsDjiDevice
@@ -13,13 +13,11 @@ struct GameControllersControllerSettingsView: View {
}
Section("Thumb sticks") {
GameControllersControllerThumbStickSettingsView(
model: model,
image: "l.joystick",
name: "Left",
function: $gameController.leftThumbStickFunction
)
GameControllersControllerThumbStickSettingsView(
model: model,
image: "r.joystick",
name: "Right",
function: $gameController.rightThumbStickFunction
@@ -1,7 +1,6 @@
import SwiftUI
struct ControllerThumbStickView: View {
let model: Model
@Binding var function: SettingsControllerThumbStickFunction
var body: some View {
@@ -16,7 +15,6 @@ struct ControllerThumbStickView: View {
}
struct GameControllersControllerThumbStickSettingsView: View {
let model: Model
let image: String
let name: LocalizedStringKey
@Binding var function: SettingsControllerThumbStickFunction
@@ -25,7 +23,7 @@ struct GameControllersControllerThumbStickSettingsView: View {
NavigationLink {
Form {
Section {
ControllerThumbStickView(model: model, function: $function)
ControllerThumbStickView(function: $function)
}
}
.navigationTitle("Thumb stick")
@@ -1,6 +1,7 @@
import SwiftUI
struct IngestsSettingsView: View {
// periphery: ignore
let model: Model
let database: Database
@@ -2,7 +2,6 @@ import Network
import SwiftUI
struct UrlSettingsView: View {
let model: Model
let disabled: Bool
@Binding var url: String
@State var value: String
@@ -98,8 +97,7 @@ struct RtspClientStreamSettingsView: View {
}
Section {
NavigationLink {
UrlSettingsView(model: model,
disabled: false,
UrlSettingsView(disabled: false,
url: $stream.url,
value: stream.url,
placeholder: "rtsp://192.168.1.83/stream1",
@@ -14,7 +14,6 @@ struct SrtClientStreamSettingsView: View {
Section {
NavigationLink {
UrlSettingsView(
model: model,
disabled: false,
url: $stream.url,
value: stream.url,
@@ -14,8 +14,7 @@ struct WhepClientStreamSettingsView: View {
}
Section {
NavigationLink {
UrlSettingsView(model: model,
disabled: stream.enabled,
UrlSettingsView(disabled: stream.enabled,
url: $stream.url,
value: stream.url,
placeholder: "http://foo.com/whep",
@@ -44,7 +44,6 @@ private struct PomodoroSoundSelectorView: View {
}
struct WidgetPomodoroTimerQuickButtonControlsView: View {
let model: Model
@ObservedObject var pomodoroTimer: SettingsWidgetPomodoroTimer
var body: some View {
@@ -80,12 +79,11 @@ struct WidgetPomodoroTimerQuickButtonControlsView: View {
struct WidgetPomodoroTimerSettingsView: View {
let model: Model
let widget: SettingsWidget
@ObservedObject var pomodoroTimer: SettingsWidgetPomodoroTimer
var body: some View {
Section {
WidgetPomodoroTimerQuickButtonControlsView(model: model, pomodoroTimer: pomodoroTimer)
WidgetPomodoroTimerQuickButtonControlsView(pomodoroTimer: pomodoroTimer)
} header: {
Text("Controls")
}
@@ -15,81 +15,3 @@ struct WidgetScoreboardGolfFullScorecardGeneralSettingsView: View {
}
}
}
struct WidgetScoreboardGolfFullScorecardSettingsView: View {
@ObservedObject var golf: SettingsWidgetGolfScoreboard
let updated: () -> Void
var body: some View {
Section {
TextEditNavigationView(title: String(localized: "Title"), value: golf.title) {
golf.title = $0
updated()
}
Picker("Holes", selection: $golf.numberOfHoles) {
ForEach([9, 18], id: \.self) {
Text(String($0))
}
}
.onChange(of: golf.numberOfHoles) { _ in
golf.currentHole = 0
updated()
}
NavigationLink {
Form {
ForEach(0 ..< golf.numberOfHoles, id: \.self) { i in
Picker("Hole \(i + 1)", selection: $golf.pars[i]) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9], id: \.self) {
Text(String($0))
}
}
.onChange(of: golf.pars) { _ in
updated()
}
}
}
.navigationTitle("Pars")
} label: {
HStack {
Text("Par")
Spacer()
Text(String(golf.pars.prefix(golf.numberOfHoles).reduce(0, +)))
.foregroundStyle(.gray)
}
}
} header: {
Text("Round")
}
Section {
List {
ForEach(golf.players) { player in
TextEditNavigationView(title: String(localized: "Name"), value: player.name) {
player.name = $0
updated()
}
.contextMenuDeleteButton {
if let offsets = makeOffsets(golf.players, player.id) {
golf.players.remove(atOffsets: offsets)
updated()
}
}
}
.onDelete { offsets in
golf.players.remove(atOffsets: offsets)
updated()
}
}
if golf.players.count < 4 {
CreateButtonView {
let n = golf.players.count + 1
golf.players.append(SettingsWidgetGolfScoreboardPlayer(name: "Player \(n)"))
updated()
}
}
} header: {
Text("Players")
} footer: {
SwipeLeftToDeleteHelpView(kind: String(localized: "a player"))
}
}
}
@@ -245,7 +245,6 @@ private struct VariableWithLengthUnitView: View {
let description: String
let variable: String
@Binding var text: String
@State private var presentingPicker: Bool = false
private func units() -> [(String, String)] {
TextFormatLengthUnit.allCases.filter { $0 != .system }.map { ($0.toString(), $0.symbol()) }
@@ -265,7 +264,6 @@ private struct VariableWithSpeedUnitView: View {
let description: String
let variable: String
@Binding var text: String
@State private var presentingPicker: Bool = false
private func units() -> [(String, String)] {
TextFormatSpeedUnit.allCases.filter { $0 != .system }.map { ($0.toString(), $0.symbol()) }
@@ -285,7 +283,6 @@ private struct VariableWithTemperatureUnitView: View {
let description: String
let variable: String
@Binding var text: String
@State private var presentingPicker: Bool = false
private func units() -> [(String, String)] {
TextFormatTemperatureUnit.allCases.filter { $0 != .system }.map { ($0.toString(), $0.symbol()) }
@@ -296,11 +296,7 @@ struct WidgetSettingsView: View {
case .bingoCard:
WidgetBingoCardSettingsView(model: model, widget: widget, bingoCard: widget.bingoCard)
case .pomodoroTimer:
WidgetPomodoroTimerSettingsView(
model: model,
widget: widget,
pomodoroTimer: widget.pomodoroTimer
)
WidgetPomodoroTimerSettingsView(model: model, pomodoroTimer: widget.pomodoroTimer)
}
}
.navigationTitle("\(widget.type.toString()) widget")
@@ -1,6 +1,8 @@
import StreamDeckKit
import SwiftUI
#if !targetEnvironment(macCatalyst)
private struct StreamDeckSettingsKeyView: View {
let model: Model
@ObservedObject var key: SettingsStreamDeckKey
@@ -291,3 +293,5 @@ struct StreamDecksSettingsView: View {
.navigationTitle("Stream deck")
}
}
#endif
@@ -9,7 +9,6 @@ private func videoBitrates() -> [UInt32] {
}
struct StreamPreviewStreamSettingsView: View {
let model: Model
@ObservedObject var previewStream: SettingsStreamPreviewStream
var body: some View {
@@ -23,7 +22,6 @@ struct StreamPreviewStreamSettingsView: View {
Section {
NavigationLink {
UrlSettingsView(
model: model,
disabled: false,
url: $previewStream.url,
value: previewStream.url,
@@ -271,8 +271,7 @@ struct StreamSettingsView: View {
IconAndTextSettingView(image: "camera.aperture", text: "Snapshot")
}
NavigationLink {
StreamPreviewStreamSettingsView(model: model,
previewStream: stream.previewStream)
StreamPreviewStreamSettingsView(previewStream: stream.previewStream)
} label: {
IconAndTextSettingView(image: "video.circle", text: "Preview stream")
}
@@ -29,8 +29,7 @@ struct StreamUrlSettingsView: View {
@ObservedObject var stream: SettingsStream
var body: some View {
UrlSettingsView(model: model,
disabled: model.isLive || model.isRecording,
UrlSettingsView(disabled: model.isLive || model.isRecording,
url: $stream.url,
value: stream.url,
placeholder: "srtla://foobar.org:4432",
@@ -48,8 +47,7 @@ struct StreamMultiStreamingUrlView: View {
@ObservedObject var destination: SettingsStreamMultiStreamingDestination
var body: some View {
UrlSettingsView(model: model,
disabled: model.isLive || model.isRecording,
UrlSettingsView(disabled: model.isLive || model.isRecording,
url: $destination.url,
value: destination.url,
placeholder: "rtmp://foobar.org:3321/app/5678",
@@ -140,7 +140,6 @@ private struct ZoomView: View {
private struct StatusesView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopLeft
@ObservedObject var mic: Mic
@@ -35,7 +35,6 @@ private struct CollapsedBondingView: View {
private struct BondingStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var bonding: Bonding
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -69,9 +68,7 @@ private struct BondingStatusView: View {
private struct ReplayStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
// periphery:ignore
@ObservedObject var replay: SettingsStreamReplay
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -187,7 +184,6 @@ private struct CollapsedBitrateView: View {
private struct BitrateStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var bitrate: Bitrate
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -227,7 +223,6 @@ private func netStreamColor(model: Model) -> Color {
private struct StreamUptimeStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var streamUptime: StreamUptimeProvider
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -246,7 +241,6 @@ private struct StreamUptimeStatusView: View {
private struct CpuStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var systemMonitor: SystemMonitor
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -302,12 +296,9 @@ private struct HypeTrainStatusView: View {
private struct MoblinkStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var moblink: Moblink
// periphery:ignore
@ObservedObject var streamer: SettingsMoblinkStreamer
// periphery:ignore
@ObservedObject var relay: SettingsMoblinkRelay
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -335,12 +326,9 @@ private struct MoblinkStatusView: View {
private struct RemoteControlStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
// periphery:ignore
@ObservedObject var streamer: SettingsRemoteControlStreamer
// periphery:ignore
@ObservedObject var assistant: SettingsRemoteControlAssistant
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -367,7 +355,6 @@ private struct RemoteControlStatusView: View {
private struct DjiDevicesStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -386,7 +373,6 @@ private struct DjiDevicesStatusView: View {
private struct GameControllersStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -404,12 +390,9 @@ private struct GameControllersStatusView: View {
private struct IngestsStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var ingests: Ingests
// periphery:ignore
@ObservedObject var rtmpServer: SettingsRtmpServer
// periphery:ignore
@ObservedObject var srtlaServer: SettingsSrtlaServer
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -426,9 +409,7 @@ private struct IngestsStatusView: View {
private struct LocationStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
// periphery:ignore
@ObservedObject var location: SettingsLocation
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -446,7 +427,6 @@ private struct LocationStatusView: View {
private struct RecordingStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var recording: RecordingProvider
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -464,7 +444,6 @@ private struct RecordingStatusView: View {
private struct BrowserWidgetsStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -482,7 +461,6 @@ private struct BrowserWidgetsStatusView: View {
private struct CatPrinterStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -508,7 +486,6 @@ private struct CatPrinterStatusView: View {
private struct WorkoutDeviceStatusView: View {
@EnvironmentObject var model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -534,7 +511,6 @@ private struct WorkoutDeviceStatusView: View {
private struct FixedHorizonStatusView: View {
let model: Model
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -551,7 +527,6 @@ private struct FixedHorizonStatusView: View {
}
private struct BlackSharkCoolerDeviceStatusView: View {
// periphery:ignore
@ObservedObject var show: SettingsShow
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
@@ -602,7 +577,6 @@ private struct AutoSceneSwitcherStatusView: View {
private struct StatusesView: View {
@EnvironmentObject var model: Model
@ObservedObject var show: SettingsShow
// periphery:ignore
@ObservedObject var status: StatusTopRight
let textPlacement: StreamOverlayIconAndTextPlacement
-4
View File
@@ -2,10 +2,6 @@ import Foundation
private final class BundleToken {}
func isEqual<T: FloatingPoint>(_ actual: T, _ expected: T, epsilon: T) -> Bool {
abs(actual - expected) < epsilon
}
class MessageQueue<Message> {
private var buffer: [Message] = []
private var continuations: [CheckedContinuation<Message, Never>] = []