diff --git a/Sources/OpenSwiftUICore/Render/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList.swift index 70e320e6..59dced7e 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList.swift @@ -1,13 +1,388 @@ -// FIXME -struct DisplayList {} +// +// DisplayList.swift +// OpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: WIP +// ID: F37E3733E490AA5E3BDC045E3D34D9F8 + +import Foundation + +// MARK: - _DisplayList_Identity + +private var lastIdentity: UInt32 = 0 + +package struct _DisplayList_Identity: Hashable, Codable, CustomStringConvertible { + package private(set) var value: UInt32 + + init(value: UInt32) { + self.value = value + } + + package init() { + lastIdentity += 1 + self.init(value: lastIdentity) + } + + package init(decodedValue value: UInt32) { + self.init(value: value) + } + + package static let none = _DisplayList_Identity(value: 0) + package var description: String { "#\(value)" } +} + +// MARK: - DisplayList + +package struct DisplayList: Equatable { + package private(set) var items: [Item] + + package struct Features: OptionSet { + package let rawValue: UInt16 + package init(rawValue: UInt16) { + self.rawValue = rawValue + } + package static let required = Features(rawValue: 1 << 0) + package static let views = Features(rawValue: 1 << 1) + package static let animations = Features(rawValue: 1 << 2) + package static let dynamicContent = Features(rawValue: 1 << 3) + package static let interpolatorLayers = Features(rawValue: 1 << 4) + package static let interpolatorRoots = Features(rawValue: 1 << 5) + package static let stateEffects = Features(rawValue: 1 << 6) + package static let states = Features(rawValue: 1 << 7) + package static let flattened = Features(rawValue: 1 << 9) + } + + package private(set) var features: Features + package private(set) var properties: Properties + + package init() { + items = [] + features = [] + properties = [] + } + + package init(_ item: Item) { + fatalError("TODO") + } + + package init(_ items: [Item]) { + guard !items.isEmpty else { + self.init() + return + } + fatalError("TODO") + } + + package mutating func append(_ item: Item) { + fatalError("TODO") + } + + package mutating func append(contentsOf other: DisplayList) { + fatalError("TODO") + } +} + +@available(*, unavailable) +extension DisplayList: Sendable {} + +@available(*, unavailable) +extension DisplayList.Version: Sendable {} -// FIXME extension DisplayList { - struct Key: PreferenceKey { - static var defaultValue: Void = () + package typealias Identity = _DisplayList_Identity + package typealias StableIdentity = _DisplayList_StableIdentity + package typealias StableIdentityMap = _DisplayList_StableIdentityMap + package typealias StableIdentityRoot = _DisplayList_StableIdentityRoot + package typealias StableIdentityScope = _DisplayList_StableIdentityScope + + package struct Item: Equatable { + package var frame: CGRect + package var version: Version + package var value: Item.Value + package var identity: Identity + package enum Value { + case empty + case content(Content) + case effect(Effect, DisplayList) + case states([(StrongHash, DisplayList)]) + } + package init(_ value: Item.Value, frame: CGRect, identity: Identity, version: Version) { + self.frame = frame + self.version = version + self.value = value + self.identity = identity + } + + package static func == (lhs: Item, rhs: Item) -> Bool { + lhs.identity == rhs.identity && lhs.version == rhs.version + } + + package var position: CGPoint { frame.origin } + package var size: CGSize { frame.size } + } + + package struct Content { + package var value: Content.Value + package var seed: Seed + package enum Value { +// indirect case backdrop(BackdropEffect) + indirect case color(Color.Resolved) +// indirect case chameleonColor(fallback: Color.Resolved, filters: [GraphicsFilter]) +// indirect case image(GraphicsImage) +// indirect case shape(Path, AnyResolvedPaint, FillStyle) +// indirect case shadow(Path, ResolvedShadowStyle) +// indirect case platformView(any PlatformViewFactory) +// indirect case platformLayer(any PlatformLayerFactory) +// indirect case text(StyledTextContentView, CGSize) +// indirect case flattened(DisplayList, CGPoint, RasterizationOptions) +// indirect case drawing(any RenderBox.RBDisplayListContents, CGPoint, RasterizationOptions) +// indirect case view(any _DisplayList_ViewFactory) + case placeholder(id: Identity) + } + package init(_ value: Content.Value, seed: Seed) { + self.value = value + self.seed = seed + } + } + +// package typealias ViewFactory = _DisplayList_ViewFactory + + package enum Effect { + case identity + case geometryGroup + case compositingGroup + case backdropGroup(Bool) + indirect case archive(ArchiveIDs?) + case properties(Properties) +// indirect case platformGroup(any PlatformGroupFactory) + case opacity(Float) +// case blendMode(GraphicsBlendMode) +// indirect case clip(Path, FillStyle, _: GraphicsContext.ClipOptions = .init()) +// indirect case mask(DisplayList, _: GraphicsContext.ClipOptions = .init()) +// indirect case transform(Transform) +// indirect case filter(GraphicsFilter) +// indirect case animation(any _DisplayList_AnyEffectAnimation) +// indirect case contentTransition(ContentTransition.State) +// indirect case view(any _DisplayList_ViewFactory) +// indirect case accessibility([AccessibilityNodeAttachment]) +// indirect case platform(PlatformEffect) + indirect case state(StrongHash) +// indirect case interpolatorRoot(InterpolatorGroup, contentOrigin: CGPoint, contentOffset: CGSize) +// case interpolatorLayer(InterpolatorGroup, serial: UInt32) +// indirect case interpolatorAnimation(InterpolatorAnimation) + } + + package enum Transform { + #if canImport(Darwin) + case affine(CGAffineTransform) + #endif + case projection(ProjectionTransform) + // case rotation(_RotationEffect.Data) + // case rotation3D(_Rotation3DEffect.Data) + } + +// package typealias AnyEffectAnimation = _DisplayList_AnyEffectAnimation +// package typealias AnyEffectAnimator = _DisplayList_AnyEffectAnimator + + package struct ArchiveIDs { + package var uuid: UUID + package var stableIDs: StableIdentityMap + package init(uuid: UUID, stableIDs: StableIdentityMap) { + self.uuid = uuid + self.stableIDs = stableIDs + } + } + +// package struct InterpolatorAnimation { +// package var value: StrongHash? +// package var animation: Animation? +// } + + package struct Version: Comparable, Hashable { + package private(set) var value: Int + + package init() { value = .zero } + package init(decodedValue value: Int) { self.value = value } + + private static var lastValue: Int = .zero + + package init(forUpdate: Void) { + Version.lastValue &+= 1 + value = Version.lastValue + } + + package mutating func combine(with other: Version) { + value = max(value, other.value) + } + + package static func < (lhs: Version, rhs: Version) -> Bool { + lhs.value < rhs.value + } + } - static func reduce(value _: inout Void, nextValue _: () -> Void) {} + package struct Seed: Hashable { + package private(set) var value: UInt16 + + init(value: UInt16) { + self.value = value + } + + package init() { self.init(value: .zero) } + package init(decodedValue value: UInt16) { self.init(value: value) } + package init(_ version: Version) { + if version.value == .zero { + self.init(value: .zero) + } else { + var rawValue = UInt32(bitPattern: Int32(truncatingIfNeeded: version.value >> 16)) + rawValue += (rawValue << 5) + rawValue ^= UInt32(bitPattern: Int32(truncatingIfNeeded: version.value)) + rawValue = 1 | (rawValue << 1) + self.init(value: UInt16(truncatingIfNeeded: rawValue)) + } + } + + package mutating func invalidate() { + guard value != .zero else { return } + value = (~value | 1) + } + + package static let undefined: Seed = Seed(value: 2) + } + package struct Properties: OptionSet { + package let rawValue: UInt8 + package init(rawValue: UInt8) { + self.rawValue = rawValue + } + package static let foregroundLayer = Properties(rawValue: 1 << 0) + package static let ignoresEvents = Properties(rawValue: 1 << 1) + package static let privacySensitive = Properties(rawValue: 1 << 2) + package static let archivesInteractiveControls = Properties(rawValue: 1 << 3) + package static let secondaryForegroundLayer = Properties(rawValue: 1 << 4) + package static let tertiaryForegroundLayer = Properties(rawValue: 1 << 5) + package static let quaternaryForegroundLayer = Properties(rawValue: 1 << 6) + package static let screencaptureProhibited = Properties(rawValue: 1 << 7) + } + + package struct Key : PreferenceKey { + package static let _includesRemovedValues: Bool = true + package static let defaultValue = DisplayList() + package static func reduce(value: inout DisplayList, nextValue: () -> DisplayList) { + value.append(contentsOf: nextValue()) + } + } + + package struct Options: OptionSet, ViewInput { + package let rawValue: UInt8 + package init(rawValue: UInt8) { + self.rawValue = rawValue + } - typealias Value = Void + package static let disableCanonicalization = Options(rawValue: 1 << 0) + package static let defaultValue: Options = [] + } + + package struct Index { + package private(set) var identity: _DisplayList_Identity = .none + package private(set) var serial: UInt32 = .zero + package private(set) var archiveIdentity: _DisplayList_Identity = .none + package private(set) var archiveSerial: UInt32 = .zero + private var restored: RestoreOptions = [] + + package init() {} + package mutating func enter(identity: Identity) -> Index { + if identity == .none { + self.serial &+= 1 + let copy = self + self.restored = [] + return copy + } else { + let copy = self + self.identity = identity + self.serial = 0 + self.restored = ._1 + return copy + } + } + + package mutating func leave(index saved: Index) { + if restored.contains(._4) || saved.restored.contains(._8) { + let oldIdentity = identity + let oldSerial = serial + if restored.contains(._4) { + identity = archiveIdentity + serial = archiveSerial + } + if restored.contains(._8) { + archiveIdentity = oldIdentity + archiveSerial = oldSerial + } + } + if restored.contains(._1) { + identity = saved.identity + serial = saved.serial + } + if restored.contains(._2) { + archiveIdentity = saved.archiveIdentity + archiveSerial = saved.archiveSerial + } + restored = saved.restored + } + + package mutating func updateArchive(entering: Bool) { + if entering { + archiveIdentity = identity + archiveSerial = serial + identity = .none + serial = .zero + if !restored.contains([._2, ._4]) { + restored = restored.union([._2, ._4]) + } + } else { + // false + identity = archiveIdentity + serial = archiveSerial + archiveIdentity = .none + archiveSerial = .zero + if !restored.contains([._1, ._8]) { + restored = restored.union([._1, ._8]) + } + } + } + + package mutating func skip(list: DisplayList) { + fatalError("TODO") + } + + package mutating func skip(item: Item) { + fatalError("TODO") + } + + package mutating func skip(effect: Effect) { + + fatalError("TODO") + } + + package func assertItem(_ item: Item) {} + + package var id: ID { + ID(identity: identity, serial: serial, archiveIdentity: archiveIdentity, archiveSerial: archiveSerial) + } + + package struct ID: Hashable { + var identity: _DisplayList_Identity + var serial: UInt32 + var archiveIdentity: _DisplayList_Identity + var archiveSerial: UInt32 + } + + private struct RestoreOptions: OptionSet { + let rawValue: UInt8 + + static let _1 = RestoreOptions(rawValue: 1 << 0) + static let _2 = RestoreOptions(rawValue: 1 << 1) + static let _4 = RestoreOptions(rawValue: 1 << 2) + static let _8 = RestoreOptions(rawValue: 1 << 3) + } } } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList_StableIdentity.swift b/Sources/OpenSwiftUICore/Render/DisplayList_StableIdentity.swift new file mode 100644 index 00000000..47f62475 --- /dev/null +++ b/Sources/OpenSwiftUICore/Render/DisplayList_StableIdentity.swift @@ -0,0 +1,152 @@ +// +// DisplayList_StableIdentity.swift +// OpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: WIP +// ID: 6C8682BE0755616E63B02969BA08C92E + +package import OpenGraphShims + +package struct _DisplayList_StableIdentity: Hashable, Codable { + package var hash: StrongHash + package var serial: UInt32 + + package init(hash: StrongHash, serial: UInt32) { + self.hash = hash + self.serial = serial + } +} + +package struct _DisplayList_StableIdentityMap { + package var map: [_DisplayList_Identity : _DisplayList_StableIdentity] = [:] + package init() {} + package var isEmpty: Bool { map.isEmpty } + package subscript(index: _DisplayList_Identity) -> _DisplayList_StableIdentity? { + get { map[index] } + set { map[index] = newValue } + } + package mutating func formUnion(_ other: _DisplayList_StableIdentityMap) { + map.merge(other.map) { old, _ in old } + } +} + +final package class _DisplayList_StableIdentityRoot { + var scopes: [WeakAttribute<_DisplayList_StableIdentityScope>] = [] + var map: _DisplayList_StableIdentityMap? + + package init() {} + + package final subscript(index: _DisplayList_Identity) -> _DisplayList_StableIdentity? { + if let map { + return map[index] + } else { + fatalError("TODO") + } + } +} + +package struct _DisplayList_StableIdentityScope: ViewInput, _ViewTraitKey { + package static let defaultValue: WeakAttribute<_DisplayList_StableIdentityScope> = WeakAttribute() + + package let root: _DisplayList_StableIdentityRoot + package let hash: StrongHash + package var map: _DisplayList_StableIdentityMap = .init() + package var serial: UInt32 = .zero + + package init(root: _DisplayList_StableIdentityRoot) { + self.root = root + self.hash = StrongHash(of: "root") + } + + package init(id: ID, parent: _DisplayList_StableIdentityScope) where ID: StronglyHashable { + self.root = parent.root + self.hash = StrongHash(of: id) + } + + package mutating func makeIdentity() -> _DisplayList_StableIdentity { + serial &+= 1 + return _DisplayList_StableIdentity(hash: hash, serial: serial) + } + + package mutating func pushIdentity(_ identity: _DisplayList_Identity) { + map[identity] = makeIdentity() + } +} + +// TODO: Blocked by ProtobufMessage +//extension _DisplayList_StableIdentity: ProtobufMessage { +// package func encode(to encoder: inout ProtobufEncoder) throws +// package init(from decoder: inout ProtobufDecoder) throws +//} +//extension _DisplayList_StableIdentityMap: ProtobufMessage { +// package func encode(to encoder: inout ProtobufEncoder) throws +// package init(from decoder: inout ProtobufDecoder) throws +//} + +// TODO: Blocked by _ViewInputs +//extension _ViewInputs { +// package mutating func configureStableIDs(root: _DisplayList_StableIdentityRoot) { +// package func pushIdentity(_ identity: _DisplayList_Identity) +// package func makeStableIdentity() -> _DisplayList_StableIdentity +//} + +extension _GraphInputs { + private func pushScope(id: ID) where ID: StronglyHashable { + let stableIDScope = self[_DisplayList_StableIdentityScope.self].value! + let newStableScope = _DisplayList_StableIdentityScope(id: id, parent: stableIDScope) + stableIDScope.root.scopes.append(WeakAttribute(Attribute(value: newStableScope))) + } + + package mutating func pushStableID(_ id: ID) where ID: Hashable { + guard options.contains(.needsStableDisplayListIDs) else { + return + } + if let stronglyHashable = id as? StronglyHashable { + pushScope(id: stronglyHashable) + } else { + pushScope(id: makeStableIDData(from: id) ?? .random()) + } + } + + package mutating func pushStableIndex(_ index: Int) { + guard options.contains(.needsStableDisplayListIDs) else { + return + } + pushScope(id: index) + } + + package mutating func pushStableType(_ type: any Any.Type) { + guard options.contains(.needsStableDisplayListIDs) else { + return + } + pushScope(id: makeStableTypeData(type)) + } + + package var stableIDScope: WeakAttribute<_DisplayList_StableIdentityScope>? { + guard !options.contains(.needsStableDisplayListIDs) else { + return nil + } + let result = self[_DisplayList_StableIdentityScope.self] + return result.attribute == nil ? nil : result + } +} + +package func makeStableTypeData(_ type: any Any.Type) -> StrongHash { + // OGTypeGetSignature + fatalError("TODO") +} + +package func makeStableIDData(from id: ID) -> StrongHash? { + guard let encodable = id as? Encodable else { + Log.externalWarning("ID type is not Encodable: \(ID.self)") + return nil + } + do { + let hash = try StrongHash(encodable: encodable) + return hash + } catch { + Log.externalWarning("ID failed to encode: \(ID.self), \(error.localizedDescription)") + return nil + } +} diff --git a/Sources/OpenSwiftUICore/View/ViewInput.swift b/Sources/OpenSwiftUICore/View/ViewInput.swift deleted file mode 100644 index 84271aa0..00000000 --- a/Sources/OpenSwiftUICore/View/ViewInput.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// ViewInput.swift -// OpenSwiftUI -// -// Audited for RELEASE_2021 -// Status: Complete - -protocol ViewInput: GraphInput {} diff --git a/Sources/OpenSwiftUICore/View/ViewInputs.swift b/Sources/OpenSwiftUICore/View/ViewInputs.swift index d58d6ebf..0af323de 100644 --- a/Sources/OpenSwiftUICore/View/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/ViewInputs.swift @@ -9,6 +9,8 @@ internal import OpenGraphShims package typealias ViewPhase = _GraphInputs.Phase +package protocol ViewInput: GraphInput {} + /// The input (aka inherited) attributes supplied to each view. Most /// view types will only actually wire a small number of these into /// their node. Doesn't include the view itself, which is passed diff --git a/Tests/OpenSwiftUICoreTests/Render/DisplayList_StableIdentityTests.swift b/Tests/OpenSwiftUICoreTests/Render/DisplayList_StableIdentityTests.swift new file mode 100644 index 00000000..7be601da --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Render/DisplayList_StableIdentityTests.swift @@ -0,0 +1,24 @@ +// +// DisplayList_StableIdentityTests.swift +// OpenSwiftUICoreTests + +import OpenSwiftUICore +import Testing + +@_spi(Debug) import OpenGraphShims + +struct DisplayList_StableIdentityTests { + @Test + func formUnion() { + var m1 = _DisplayList_StableIdentityMap() + var m2 = _DisplayList_StableIdentityMap() + + let identity = _DisplayList_Identity(decodedValue: 1) + m1[identity] = .init(hash: .init(of: 1), serial: 1) + m2[identity] = .init(hash: .init(of: 2), serial: 2) + + m1.formUnion(m2) + + #expect(m1[identity] == .init(hash: .init(of: 1), serial: 1)) + } +}