mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-08-05 16:29:07 +00:00
Add Apple headless effect coordinator
This commit is contained in:
parent
5cc93c4d9f
commit
7a3a7db34a
1 changed files with 129 additions and 0 deletions
129
appleApp/AppleCore/FluxaAppleHeadlessCoordinator.swift
Normal file
129
appleApp/AppleCore/FluxaAppleHeadlessCoordinator.swift
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import Foundation
|
||||
|
||||
indirect enum FluxaAppleJsonValue: Codable, Sendable {
|
||||
case null
|
||||
case boolean(Bool)
|
||||
case number(Double)
|
||||
case string(String)
|
||||
case array([FluxaAppleJsonValue])
|
||||
case object([String: FluxaAppleJsonValue])
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if container.decodeNil() {
|
||||
self = .null
|
||||
} else if let value = try? container.decode(Bool.self) {
|
||||
self = .boolean(value)
|
||||
} else if let value = try? container.decode(Double.self) {
|
||||
self = .number(value)
|
||||
} else if let value = try? container.decode(String.self) {
|
||||
self = .string(value)
|
||||
} else if let value = try? container.decode([FluxaAppleJsonValue].self) {
|
||||
self = .array(value)
|
||||
} else {
|
||||
self = .object(try container.decode([String: FluxaAppleJsonValue].self))
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .null:
|
||||
try container.encodeNil()
|
||||
case .boolean(let value):
|
||||
try container.encode(value)
|
||||
case .number(let value):
|
||||
try container.encode(value)
|
||||
case .string(let value):
|
||||
try container.encode(value)
|
||||
case .array(let value):
|
||||
try container.encode(value)
|
||||
case .object(let value):
|
||||
try container.encode(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FluxaAppleHeadlessEffect: Decodable, Sendable {
|
||||
let id: String
|
||||
let type: String
|
||||
let generation: UInt64
|
||||
let payload: FluxaAppleJsonValue
|
||||
}
|
||||
|
||||
struct FluxaAppleHeadlessCompletion: Encodable, Sendable {
|
||||
let effectId: String
|
||||
let status: String
|
||||
let value: FluxaAppleJsonValue
|
||||
let error: FluxaAppleJsonValue
|
||||
|
||||
static func success(effectId: String, value: FluxaAppleJsonValue = .null) -> Self {
|
||||
Self(effectId: effectId, status: "ok", value: value, error: .null)
|
||||
}
|
||||
|
||||
static func failure(effectId: String, code: String) -> Self {
|
||||
Self(
|
||||
effectId: effectId,
|
||||
status: "error",
|
||||
value: .null,
|
||||
error: .object(["code": .string(code)])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct FluxaAppleHeadlessResult: Decodable, Sendable {
|
||||
let state: [String: FluxaAppleJsonValue]
|
||||
let effects: [FluxaAppleHeadlessEffect]
|
||||
}
|
||||
|
||||
protocol FluxaAppleHeadlessEffectExecutor: AnyObject {
|
||||
func execute(effect: FluxaAppleHeadlessEffect) async -> FluxaAppleHeadlessCompletion
|
||||
}
|
||||
|
||||
final class FluxaAppleHeadlessCoordinator {
|
||||
private let runtime: FluxaAppleHeadlessRuntime
|
||||
private weak var executor: (any FluxaAppleHeadlessEffectExecutor)?
|
||||
private let maxEffectsPerDispatch: Int
|
||||
private let decoder = JSONDecoder()
|
||||
private let encoder = JSONEncoder()
|
||||
|
||||
init(
|
||||
runtime: FluxaAppleHeadlessRuntime,
|
||||
executor: any FluxaAppleHeadlessEffectExecutor,
|
||||
maxEffectsPerDispatch: Int = 64
|
||||
) {
|
||||
self.runtime = runtime
|
||||
self.executor = executor
|
||||
self.maxEffectsPerDispatch = maxEffectsPerDispatch
|
||||
}
|
||||
|
||||
func dispatch(actionJson: String) async throws -> FluxaAppleHeadlessResult {
|
||||
try await drain(runtime.dispatch(actionJson: actionJson))
|
||||
}
|
||||
|
||||
func complete(completion: FluxaAppleHeadlessCompletion) async throws -> FluxaAppleHeadlessResult {
|
||||
try await drain(runtime.completeEffect(resultJson: try encode(completion)))
|
||||
}
|
||||
|
||||
private func drain(_ initialJson: String) async throws -> FluxaAppleHeadlessResult {
|
||||
var current = try decode(initialJson)
|
||||
var remaining = maxEffectsPerDispatch
|
||||
while let effect = current.effects.first, remaining > 0 {
|
||||
guard let executor else {
|
||||
return current
|
||||
}
|
||||
let completion = await executor.execute(effect: effect)
|
||||
current = try decode(runtime.completeEffect(resultJson: try encode(completion)))
|
||||
remaining -= 1
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
private func decode(_ json: String) throws -> FluxaAppleHeadlessResult {
|
||||
try decoder.decode(FluxaAppleHeadlessResult.self, from: Data(json.utf8))
|
||||
}
|
||||
|
||||
private func encode(_ completion: FluxaAppleHeadlessCompletion) throws -> String {
|
||||
String(decoding: try encoder.encode(completion), as: UTF8.self)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue