diff --git a/.swiftformat b/.swiftformat index 6a52dfc9c..eef2a89ec 100644 --- a/.swiftformat +++ b/.swiftformat @@ -5,13 +5,19 @@ --asynccapturing --beforemarks --binarygrouping 4,8 +--callsiteparen default --categorymark "MARK: %c" --classthreshold 0 --closingparen balanced --closurevoid remove --commas always +--complexattrs preserve +--computedvarattrs preserve +--condassignment after-property --conflictmarkers reject +--dateformat system --decimalgrouping 3,6 +--doccomments before-declarations --elseposition same-line --emptybraces no-space --enumnamespaces always @@ -30,11 +36,12 @@ --header ignore --hexgrouping 4,8 --hexliteralcase uppercase ---ifdef no-indent +--ifdef indent --importgrouping alpha --indent 4 --indentcase false --indentstrings false +--initcodernil false --lifecycle --lineaftermarks true --linebreaks lf @@ -42,34 +49,38 @@ --markextensions always --marktypes always --maxwidth none ---modifierorder +--modifierorder nonisolated,public --nevertrailing +--nilinit remove +--noncomplexattrs --nospaceoperators --nowrapoperators --octalgrouping 4,8 --onelineforeach ignore --operatorfunc spaced +--organizationmode visibility --organizetypes actor,class,enum,struct --patternlet hoist --ranges spaced ---redundanttype inferred +--redundanttype infer-locals-only --self remove --selfrequired --semicolons inline ---shortoptionals always +--shortoptionals except-properties --smarttabs enabled ---someany false +--someany true +--storedvarattrs preserve --stripunusedargs always --structthreshold 0 ---swiftversion 5.10 --tabwidth unspecified --throwcapturing +--timezone system --trailingclosures ---trimwhitespace nonblank-lines +--trimwhitespace always --typeattributes preserve --typeblanklines remove +--typedelimiter space-after --typemark "MARK: - %t" ---varattributes preserve --voidtype void --wraparguments preserve --wrapcollections preserve @@ -82,5 +93,4 @@ --wraptypealiases preserve --xcodeindentation disabled --yodaswap always ---disable blankLineAfterImports,wrapMultilineStatementBraces ---enable acronyms,blankLinesBetweenImports +--disable unusedArguments diff --git a/Sources/OpenSwiftUICore/Data/Transaction/TransitionTraitKey.swift b/Sources/OpenSwiftUICore/Data/Transaction/TransitionTraitKey.swift new file mode 100644 index 000000000..4118102c5 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/Transaction/TransitionTraitKey.swift @@ -0,0 +1,22 @@ +// +// TransitionTraitKey.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: WIP + +@usableFromInline +struct CanTransitionTraitKey: _ViewTraitKey { + @inlinable + static var defaultValue: Bool { false } +} + +@available(*, unavailable) +extension CanTransitionTraitKey: Sendable {} + +extension ViewTraitCollection { + package var canTransition: Bool { + get { self[CanTransitionTraitKey.self] } + set { self[CanTransitionTraitKey.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift index 25f11c9e1..a9926734f 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift @@ -199,6 +199,13 @@ public struct _GraphInputs { package struct Phase: Equatable { var value: UInt32 + @inline(__always) + static var isBeingRemovedBitCount: Int { 1 } + @inline(__always) + static var isBeingRemovedMask: UInt32 { (1 << isBeingRemovedBitCount) - 1} + @inline(__always) + static var resetSeedMask: UInt32 { ~isBeingRemovedMask } + @inlinable package init(value: UInt32) { self.value = value @@ -211,19 +218,17 @@ public struct _GraphInputs { @inlinable package var resetSeed: UInt32 { - get { value >> 1 } - set { value = (newValue << 1) | (value & 1) } + get { value >> Self.isBeingRemovedBitCount } + set { value = (newValue << Self.isBeingRemovedBitCount) | (value & Self.isBeingRemovedMask) } } package var isBeingRemoved: Bool { - get { value & 1 != 0 } - set { value = (newValue ? 1 : 0) | (value & 0xFFFF_FFFE) } + get { (value & Self.isBeingRemovedMask) != 0 } + set { value = (newValue ? 1 : 0) | (value & Self.resetSeedMask) } } @inlinable - package var isInserted: Bool { - value & 1 == 0 - } + package var isInserted: Bool { !isBeingRemoved } @inlinable package mutating func merge(_ other: _GraphInputs.Phase) { diff --git a/Sources/OpenSwiftUICore/Graph/GraphReuse.swift b/Sources/OpenSwiftUICore/Graph/GraphReuse.swift index d9c9ec0e1..0c989fcd2 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphReuse.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphReuse.swift @@ -12,7 +12,7 @@ package import OpenGraphShims package typealias Subgraph = OGSubgraph package typealias Graph = OGGraph -package final class IndirectAttributeMap { +public final class IndirectAttributeMap { #if canImport(Darwin) package final let subgraph: Subgraph package final var map: [AnyAttribute: AnyAttribute] diff --git a/Sources/OpenSwiftUICore/Graph/ReuseTrace.swift b/Sources/OpenSwiftUICore/Graph/ReuseTrace.swift index 72f9cf7d0..c63eb0025 100644 --- a/Sources/OpenSwiftUICore/Graph/ReuseTrace.swift +++ b/Sources/OpenSwiftUICore/Graph/ReuseTrace.swift @@ -34,7 +34,23 @@ package struct ReuseTrace { package static func traceReuseViewInputsDifferentFailure() { traceReuseFailure("reuse_inputsDifferent") } - + + @inline(__always) + package static func traceReuseUnaryElementExpectedFailure(_ elementType: any Any.Type) { + traceReuseFailure("reuse_unaryElement") + } + + @inline(__always) + package static func traceReuseInvalidSubgraphFailure(_ typeFoundInvalid: any Any.Type) { + // FIXME: ReuseTraceInternal.InvalidSubgraphFailure + traceReuseFailure("reuse_invalidSubgraph") + } + + @inline(__always) + package static func traceReuseBodyMismatchedFailure() { + traceReuseFailure("reuse_bodyMismatched") + } + // TODO final package class Recorder { diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift index 45d470148..01d38e856 100644 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/ViewModifier/CustomViewModifier.swift @@ -139,3 +139,56 @@ extension _ViewModifier_Content { } } } + +// MARK: - BodyInput + +// FIXME +private struct BodyInput {} + +private enum BodyInputElement: GraphReusable, Equatable { + typealias MakeViewBody = (_Graph, _ViewInputs) -> _ViewOutputs + typealias MakeViewListBody = (_Graph, _ViewListInputs) -> _ViewListOutputs + + case view(MakeViewBody) + case list(MakeViewListBody) + + static func == (lhs: BodyInputElement, rhs: BodyInputElement) -> Bool { + if case let .view(lhsBody) = lhs, case let .view(rhsBody) = rhs { + compareValues(lhsBody, rhsBody, options: .init(rawValue: 0x103)) + } else if case let .list(lhsBody) = lhs, case let .list(rhsBody) = rhs{ + compareValues(lhsBody, rhsBody, options: .init(rawValue: 0x103)) + } else { + false + } + } + + static var isTriviallyReusable: Bool { + _SemanticFeature_v5.isEnabled + } + + func makeReusable(indirectMap: IndirectAttributeMap) { + return + } + + func tryToReuse(by other: BodyInputElement, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool { + switch self { + case let .view(makeViewBody): + guard case let .view(otherMakeViewBody) = other else { + ReuseTrace.traceReuseInternalFailure() + return false + } + return Self.isTriviallyReusable || compareValues(makeViewBody, otherMakeViewBody, options: .init(rawValue: 0x103)) + case let .list(makeViewListBody): + guard case let .list(otherMakeViewListBody) = other else { + ReuseTrace.traceReuseInternalFailure() + return false + } + return Self.isTriviallyReusable || compareValues(makeViewListBody, otherMakeViewListBody, options: .init(rawValue: 0x103)) + } + } +} + + +private struct BodyCountInput: ViewInput { + static var defaultValue: Stack<_ViewListCountInputs> { .init() } +} diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift index ca04f9f05..7732a0151 100644 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/ViewModifier/ViewModifier.swift @@ -120,9 +120,7 @@ extension ViewModifier where Self: _GraphInputsModifier, Body == Never { body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs ) -> _ViewListOutputs { var inputs = inputs - inputs.withMutateGraphInputs { inputs in - _makeInputs(modifier: modifier, inputs: &inputs) - } + _makeInputs(modifier: modifier, inputs: &inputs.base) let outputs = body(_Graph(), inputs) return outputs } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift index bab243615..e9fef0fa1 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList_StableIdentity.swift @@ -134,7 +134,7 @@ extension _GraphInputs { pushScope(id: makeStableTypeData(type)) } - package var stableIDScope: WeakAttribute<_DisplayList_StableIdentityScope>? { + package var stableIDScope: WeakAttribute? { guard !options.contains(.needsStableDisplayListIDs) else { return nil } diff --git a/Sources/OpenSwiftUICore/View/AnyView.swift b/Sources/OpenSwiftUICore/View/AnyView.swift index f240353e5..3d6666e03 100644 --- a/Sources/OpenSwiftUICore/View/AnyView.swift +++ b/Sources/OpenSwiftUICore/View/AnyView.swift @@ -323,6 +323,10 @@ private struct AnyViewList: StatefulRule, AsyncAttribute { } struct Transform: _ViewList_SublistTransform_Item { + func bindID(_ id: inout _ViewList_ID) { + // TODO + } + func apply(sublist: inout _ViewList_Sublist) { item.bindID(&sublist.id) sublist.elements = item.wrapping(sublist.elements) diff --git a/Sources/OpenSwiftUICore/View/CustomView.swift b/Sources/OpenSwiftUICore/View/CustomView.swift index 6fc660ef6..1d49ab8be 100644 --- a/Sources/OpenSwiftUICore/View/CustomView.swift +++ b/Sources/OpenSwiftUICore/View/CustomView.swift @@ -44,9 +44,7 @@ extension View { nonisolated package static func makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { let fields = DynamicPropertyCache.fields(of: Self.self) var inputs = inputs - let (body, buffer) = inputs.withMutateGraphInputs { inputs in - makeBody(view: view, inputs: &inputs, fields: fields) - } + let (body, buffer) = makeBody(view: view, inputs: &inputs.base, fields: fields) let outputs = Body.makeDebuggableViewList(view: body, inputs: inputs) if let buffer { buffer.traceMountedProperties(to: body, fields: fields) diff --git a/Sources/OpenSwiftUICore/View/EmptyView.swift b/Sources/OpenSwiftUICore/View/EmptyView.swift index c154c13e5..111059a43 100644 --- a/Sources/OpenSwiftUICore/View/EmptyView.swift +++ b/Sources/OpenSwiftUICore/View/EmptyView.swift @@ -36,10 +36,7 @@ public struct EmptyView: PrimitiveView { } public static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { - guard inputs.options.contains(.isNonEmptyParent) else { - return _ViewListOutputs.emptyParentViewList(inputs: inputs) - } - return _ViewListOutputs.nonEmptyParentViewList(inputs: inputs) + .emptyViewList(inputs: inputs) } public static func _viewListCount(inputs: _ViewListCountInputs) -> Int? { diff --git a/Sources/OpenSwiftUICore/View/Input/ViewList.swift b/Sources/OpenSwiftUICore/View/Input/ViewList.swift new file mode 100644 index 000000000..67ef122ac --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Input/ViewList.swift @@ -0,0 +1,1961 @@ +// +// ViewList.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: WIP +// ID: 70E71091E926A1B09B75AAEB38F5AA3F (SwiftUI) +// ID: E479C0E92CDD045BAF2EF653123E2E0B (SwiftUICore) + +import Foundation +package import OpenGraphShims + +// MARK: - _ViewListInputs + +/// Input values to `View._makeViewList()`. +public struct _ViewListInputs { + package var base: _GraphInputs + package var implicitID: Int + + package struct Options: OptionSet { + package let rawValue: Int + + package init(rawValue: Int) { + self.rawValue = rawValue + } + + package static let canTransition: Options = .init(rawValue: 1 << 0) + package static let disableTransitions: Options = .init(rawValue: 1 << 1) + package static let requiresDepthAndSections: Options = .init(rawValue: 1 << 2) + package static let requiresNonEmptyGroupParent: Options = .init(rawValue: 1 << 3) + package static let isNonEmptyParent: Options = .init(rawValue: 1 << 4) + package static let resetHeaderStyleContext: Options = .init(rawValue: 1 << 5) + package static let resetFooterStyleContext: Options = .init(rawValue: 1 << 6) + package static let layoutPriorityIsTrait: Options = .init(rawValue: 1 << 7) + package static let requiresSections: Options = .init(rawValue: 1 << 8) + package static let tupleViewCreatesUnaryElements: Options = .init(rawValue: 1 << 9) + package static let previewContext: Options = .init(rawValue: 1 << 10) + package static let needsDynamicTraits: Options = .init(rawValue: 1 << 11) + package static let allowsNestedSections: Options = .init(rawValue: 1 << 12) + package static let sectionsConcatenateFooter: Options = .init(rawValue: 1 << 13) + package static let needsArchivedAnimationTraits: Options = .init(rawValue: 1 << 14) + package static let sectionsAreHierarchical: Options = .init(rawValue: 1 << 15) + } + + package var options: _ViewListInputs.Options + + private var _traits: OptionalAttribute + + package var traits: Attribute? { + get { _traits.attribute } + set { _traits.attribute = newValue } + } + + package var traitKeys: ViewTraitKeys? + + package init(_ base: _GraphInputs, implicitID: Int = 0, options: _ViewListInputs.Options = .init()) { + self.base = base + self.implicitID = implicitID + self.options = options + self._traits = .init() + self.traitKeys = .init() + } + + package init(_ base: _GraphInputs, implicitID: Int) { + self.base = base + self.implicitID = implicitID + self.options = [] + self._traits = .init() + self.traitKeys = .init() + } + + package init(_ base: _GraphInputs, options: _ViewListInputs.Options) { + self.base = base + self.implicitID = 0 + self.options = options + self._traits = .init() + self.traitKeys = .init() + } + + package init(_ base: _GraphInputs) { + self.base = base + self.implicitID = 0 + self.options = [] + self._traits = .init() + self.traitKeys = .init() + } + + package subscript(input: T.Type) -> T.Value where T: ViewInput { + get { base[input] } + set { base[input] = newValue } + } + + package subscript(input: T.Type) -> T.Value where T: ViewInput, T.Value: GraphReusable { + get { base[input] } + set { base[input] = newValue } + } + + package var canTransition: Bool { + options.contains(.canTransition) && !options.contains(.disableTransitions) + } + + package mutating func addTraitKey(_ key: K.Type) where K: _ViewTraitKey { + traitKeys?.insert(key) + } +} + +// MARK: - _ViewListCountInputs + +/// Input values to `View._viewListCount()`. +public struct _ViewListCountInputs { + package var customInputs: PropertyList + package var options: _ViewListInputs.Options + package var baseOptions: _GraphInputs.Options + package var customModifierTypes: [ObjectIdentifier] + + package init(_ inputs: _ViewListInputs) { + customInputs = inputs.base.customInputs + options = inputs.options + baseOptions = inputs.base.options + customModifierTypes = [] + } + + package subscript(input: T.Type) -> T.Value where T: GraphInput { + get { customInputs[input] } + set { customInputs[input] = newValue } + } + + package mutating func append(_ newValue: U, to key: T.Type) where T: GraphInput, T.Value == Stack { + var stack = self[key] + defer { self[key] = stack } + stack.push(newValue) + } + + package mutating func popLast(_ key: T.Type) -> U? where T: GraphInput, T.Value == Stack { + var stack = self[key] + defer { self[key] = stack } + return stack.pop() + } + + package var base: _GraphInputs { + var inputs = _GraphInputs.invalid + inputs.customInputs = customInputs + inputs.options = baseOptions + return inputs + } +} + +@available(*, unavailable) +extension _ViewListCountInputs: Sendable {} + +// MARK: - ViewListOutputs + +/// Output values from `View._makeViewList()`. +public struct _ViewListOutputs { + package enum Views { + case staticList(any ViewList.Elements) + case dynamicList(Attribute, ListModifier?) + } + + package var views: Views + package var nextImplicitID: Int + package var staticCount: Int? + + package init(_ views: _ViewListOutputs.Views, nextImplicitID: Int, staticCount: Int?) { + self.views = views + self.nextImplicitID = nextImplicitID + self.staticCount = staticCount + } + + package init(_ views: _ViewListOutputs.Views, nextImplicitID: Int) { + self.views = views + self.nextImplicitID = nextImplicitID + self.staticCount = nil + } + + package class ListModifier { + init() {} + + package func apply(to list: inout ViewList) {} + } +} + +@available(*, unavailable) +extension _ViewListOutputs: Sendable {} + +// MARK: - ViewList + +package protocol ViewList { + typealias ID = _ViewList_ID + typealias Elements = _ViewList_Elements + typealias Traits = ViewTraitCollection + typealias Node = _ViewList_Node + typealias Group = _ViewList_Group + typealias IteratorStyle = _ViewList_IteratorStyle + typealias Section = _ViewList_Section + typealias Sublist = _ViewList_Sublist + typealias SublistTransform = _ViewList_SublistTransform + typealias Subgraph = _ViewList_Subgraph + typealias Edit = _ViewList_Edit + + func count(style: IteratorStyle) -> Int + func estimatedCount(style: IteratorStyle) -> Int + var traitKeys: ViewTraitKeys? { get } + var viewIDs: ID.Views? { get } + var traits: ViewTraitCollection { get } + + typealias ApplyBody = (inout Int, IteratorStyle, Node, inout SublistTransform) -> Bool + + @discardableResult + func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool + func edit(forID id: ID, since transaction: TransactionID) -> Edit? + func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable +} + +// MARK: - ViewList.IteratorStyle + +package struct _ViewList_IteratorStyle: Equatable { + var value: UInt + + @inline(__always) + static var applyGranularityBitCount: Int { 1 } + @inline(__always) + static var applyGranularityMask: UInt { (1 << applyGranularityBitCount) - 1 } + @inline(__always) + static var granularityMask: UInt { ~applyGranularityMask } + + package var applyGranularity: Bool { + get { (value & Self.applyGranularityMask) != 0 } + set { value = (newValue ? 1 : 0) | (value & Self.granularityMask) } + } + + package var granularity: Int { + get { Int(bitPattern: value >> Self.applyGranularityBitCount) } + set { value = (UInt(bitPattern: newValue) << Self.applyGranularityBitCount) | (value & Self.applyGranularityMask) } + } + + package init(granularity: Int) { + value = UInt(bitPattern: granularity) << Self.applyGranularityBitCount + } + + package init() { + self.init(granularity: 1) + } + + package func applyGranularity(to count: Int) -> Int { + guard value != .zero else { return count } + return granularity * count + } + + package func alignToPreviousGranularityMultiple(_ value: inout Int) { + guard value != .zero else { return } + let granularity = granularity + guard granularity != 1 else { return } + let diff = value - value / granularity * granularity + value -= diff + } + + package func alignToNextGranularityMultiple(_ value: inout Int) { + let granularity = granularity + guard granularity != 1 else { return } + let diff = value - value / granularity * granularity + guard diff != .zero else { return } + value += (granularity - diff) + } +} + +// MARK: - ViewList.Edit + +package enum _ViewList_Edit { + case inserted + case removed +} + +// MARK: - ViewList.Sublist + +package struct _ViewList_Sublist { + package var start: Int + package var count: Int + package var id: ViewList.ID + package var elements: any ViewList.Elements + package var traits: ViewTraitCollection + package var list: Attribute? + + package init(start: Int, count: Int, id: _ViewList_ID, elements: any ViewList.Elements, traits: ViewList.Traits, list: Attribute?) { + self.start = start + self.count = count + self.id = id + self.elements = elements + self.traits = traits + self.list = list + } +} + +// MARK: - ViewList.SublistTransform + +package struct _ViewList_SublistTransform { + package typealias Item = _ViewList_SublistTransform_Item + + package var items: [any Item] + + package init() { items = [] } + + package var isEmpty: Bool { items.isEmpty } + + package mutating func push(_ item: T) where T: Item { + items.append(item) + } + + package mutating func pop() { + items.removeLast() + } + + package func apply(sublist: inout ViewList.Sublist) { + for item in items.reversed() { + item.apply(sublist: &sublist) + } + } + + package func bindID(_ id: inout ViewList.ID) { + for item in items.reversed() { + item.bindID(&id) + } + } +} + +// MARK: - ViewList.SublistTransform.Item + +package protocol _ViewList_SublistTransform_Item { + func apply(sublist: inout ViewList.Sublist) + func bindID(_ id: inout ViewList.ID) +} + +// MARK: - ViewList.Node + +package enum _ViewList_Node { + case list(any ViewList, Attribute?) + case sublist(ViewList.Sublist) + case group(ViewList.Group) + case section(ViewList.Section) + + package func count(style: ViewList.IteratorStyle) -> Int { + switch self { + case let .list(list, _): + list.count(style: style) + case let .sublist(sublist): + style.applyGranularity(to: sublist.count) + case let .group(group): + group.count(style: style) + case let .section(section): + section.count(style: style) + } + } + + package func estimatedCount(style: ViewList.IteratorStyle) -> Int { + switch self { + case let .list(list, _): + list.estimatedCount(style: style) + case let .sublist(sublist): + style.applyGranularity(to: sublist.count) + case let .group(group): + group.estimatedCount(style: style) + case let .section(section): + section.estimatedCount(style: style) + } + } + + @discardableResult + package func applyNodes( + from start: inout Int, + style: ViewList.IteratorStyle, + transform: inout ViewList.SublistTransform, + to body: ViewList.ApplyBody + ) -> Bool { + switch self { + case let .list(list, attribute): + return list.applyNodes( + from: &start, + style: style, + list: attribute, + transform: &transform, + to: body + ) + case let .sublist(sublist): + let count = style.applyGranularity(to: sublist.count) + if start >= count { + start &-= count + return true + } else { + defer { start = 0 } + return body(&start, style, self, &transform) + } + case let .group(group): + return group.applyNodes( + from: &start, + style: style, + transform: &transform, + to: body + ) + case let .section(section): + if section.isHierarchical { + let list = section.base.lists[0] + return list.list.applyNodes( + from: &start, + style: style, + list: list.attribute, + transform: &transform, + to: body + ) + } else { + return section.base.applyNodes( + from: &start, + style: style, + transform: &transform, + to: body + ) + } + } + } + + @discardableResult + package func applyNodes( + from start: inout Int, + transform: inout _ViewList_SublistTransform, + to body: ViewList.ApplyBody + ) -> Bool { + applyNodes( + from: &start, + style: .init(), + transform: &transform, + to: body + ) + } + + @discardableResult + package func applySublists( + from start: inout Int, + style: ViewList.IteratorStyle, + transform: inout ViewList.SublistTransform, + to body: (ViewList.Sublist) -> Bool + ) -> Bool { + switch self { + case let .list(list, attribute): + return list.applySublists( + from: &start, + style: style, + list: attribute, + transform: &transform, + to: body + ) + case let .sublist(sublist): + var sublist = sublist + let count = style.applyGranularity(to: sublist.count) + if start >= count { + start &-= count + return true + } else { + transform.apply(sublist: &sublist) + defer { start = 0 } + return body(sublist) + } + case let .group(group): + return group.applyNodes( + from: &start, + style: style, + transform: &transform + ) { start, style, node, transform in + node.applySublists( + from: &start, + style: style, + transform: &transform, + to: body + ) + } + case let .section(section): + return section.applyNodes( + from: &start, + style: style, + transform: &transform + ) { start, style, node, info, transform in + node.applySublists( + from: &start, + style: style, + transform: &transform, + to: body + ) + } + } + } + + @discardableResult + package func applySublists( + from start: inout Int, + transform: inout ViewList.SublistTransform, + to body: (ViewList.Sublist) -> Bool + ) -> Bool { + applySublists( + from: &start, + style: .init(), + transform: &transform, + to: body + ) + } + + package func firstOffset(forID id: OtherID, style: ViewList.IteratorStyle) -> Int? where OtherID: Hashable { + switch self { + case let .list(list, _): + list.firstOffset(forID: id, style: style) + case .sublist: + nil + case let .group(group): + group.firstOffset(forID: id, style: style) + case let .section(section): + section.firstOffset(forID: id, style: style) + } + } +} + +// MARK: - ViewList + Extension [Blocked by ID.Views] + +extension ViewList { + package var isEmpty: Bool { count == 0 } + + package var count: Int { + count(style: .init()) + } + + package var estimatedCount: Int { + estimatedCount(style: .init()) + } + + @discardableResult + package func applySublists( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: (Sublist) -> Bool + ) -> Bool { + applyNodes(from: &start, style: style, list: list, transform: &transform) { start, style, node, transform in + node.applySublists(from: &start, style: style, transform: &transform, to: body) + } + } + + @discardableResult + package func applySublists( + from start: inout Int, + list: Attribute?, + transform: inout SublistTransform, + to body: (Sublist) -> Bool + ) -> Bool { + applySublists(from: &start, style: .init(), list: list, transform: &transform, to: body) + } + + @discardableResult + package func applySublists( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + to body: (Sublist) -> Bool + ) -> Bool { + var transform = SublistTransform() + return applySublists(from: &start, style: style, list: list, transform: &transform, to: body) + } + + @discardableResult + package func applySublists( + from start: inout Int, + list: Attribute?, + to body: (Sublist) -> Bool + ) -> Bool { + applySublists(from: &start, style: .init(), list: list, to: body) + } + + @discardableResult + package func applySublists( + from start: inout Int, + style: IteratorStyle, + to body: (Sublist) -> Bool + ) -> Bool { + applySublists(from: &start, style: style, list: nil, to: body) + } + + @discardableResult + package func applySublists( + from start: inout Int, + to body: (Sublist) -> Bool + ) -> Bool { + applySublists(from: &start, style: .init(), list: nil, to: body) + } + + package var allViewIDs: ID.Views { + if let viewIDs { + return viewIDs + } else { + var start = 0 + let result = applySublists(from: &start, style: .init(), list: nil) { sublist in + // sublist.elements append + true + } + preconditionFailure("TODO") + } + } + + package func applyIDs( + from start: inout Int, + style: IteratorStyle, + listAttribute: Attribute?, + transform: inout ViewList.SublistTransform, + to body: (ViewList.ID) -> Bool + ) -> Bool { + preconditionFailure("TODO") + } + + package func applyIDs( + from start: inout Int, + listAttribute: Attribute?, + transform t: inout ViewList.SublistTransform, + to body: (ViewList.ID) -> Bool + ) -> Bool { + preconditionFailure("TODO") + } + + package func applyIDs( + from start: inout Int, + listAttribute: Attribute?, + to body: (ViewList.ID) -> Bool + ) -> Bool { + preconditionFailure("TODO") + } + + package func applyIDs( + from start: inout Int, + transform t: inout ViewList.SublistTransform, + to body: (ViewList.ID) -> Bool + ) -> Bool { + preconditionFailure("TODO") + } + + package func firstOffset(of id: ViewList.ID.Canonical, style: IteratorStyle) -> Int? { + preconditionFailure("TODO") + } + + package func firstOffset(of id: ViewList.ID.Canonical) -> Int? { + firstOffset(of: id, style: .init()) + } +} + +// MARK: - ViewList.Elements + +package protocol _ViewList_Elements { + typealias Body = (_ViewInputs, @escaping MakeElement) -> (_ViewOutputs?, Bool) + typealias MakeElement = (_ViewInputs) -> _ViewOutputs + typealias Release = _ViewList_ReleaseElements + + var count: Int { get } + + func makeElements( + from start: inout Int, + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: Body + ) -> (_ViewOutputs?, Bool) + + func tryToReuseElement( + at index: Int, + by other: any ViewList.Elements, + at otherIndex: Int, + indirectMap: IndirectAttributeMap, + testOnly: Bool + ) -> Bool + + func retain() -> Release? +} + +extension ViewList.Elements { + @inline(__always) + package func makeAllElements( + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: (_ViewInputs, @escaping MakeElement) -> _ViewOutputs? + ) -> _ViewOutputs? { + withoutActuallyEscaping(body) { escapingBody in + let wrapper: Body = { inputs, makeElement in + (escapingBody(inputs, makeElement), true) + } + var start = 0 + return makeElements(from: &start, inputs: inputs, indirectMap: indirectMap, body: wrapper).0 + } + } + + @inline(__always) + package func makeAllElements( + inputs: _ViewInputs, + body: (_ViewInputs, @escaping MakeElement) -> _ViewOutputs? + ) -> _ViewOutputs? { + makeAllElements(inputs: inputs, indirectMap: nil, body: body) + } + + @inline(__always) + package func makeOneElement( + at index: Int, + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: (_ViewInputs, @escaping MakeElement) -> _ViewOutputs? + ) -> _ViewOutputs? { + withoutActuallyEscaping(body) { escapingBody in + let wrapper: Body = { inputs, makeElement in + (escapingBody(inputs, makeElement), false) + } + var start = index + return makeElements(from: &start, inputs: inputs, indirectMap: indirectMap, body: wrapper).0 + } + } + + @inline(__always) + package func makeOneElement( + at index: Int, + inputs: _ViewInputs, + body: (_ViewInputs, @escaping MakeElement) -> _ViewOutputs? + ) -> _ViewOutputs? { + makeOneElement(at: index, inputs: inputs, indirectMap: nil, body: body) + } + + package func retain() -> Release? { + nil + } +} + +// MARK: - View.List.ID + +@_spi(ForOpenSwiftUIOnly) +public struct _ViewList_ID: Hashable { + package typealias Views = _ViewList_ID_Views + + private var _index: Int32 + + package var index: Int { + get { Int(_index) } + set { _index = Int32(newValue) } + } + + private var implicitID: Int32 + + private var explicitIDs: [Explicit] + + package init(implicitID: Int) { + self._index = 0 + self.implicitID = Int32(implicitID) + self.explicitIDs = [] + } + + package init() { + self._index = 0 + self.implicitID = 0 + self.explicitIDs = [] + } + + private struct Explicit: Equatable { + let id: AnyHashable2 + let reuseID: Int + #if canImport(Darwin) + let owner: AnyAttribute + #endif + let isUnary: Bool + } + + #if canImport(Darwin) + package static func explicit(_ id: ID, owner: AnyAttribute) -> ViewList.ID where ID: Hashable { + var viewListID = ViewList.ID() + viewListID.bind(explicitID: id, owner: owner, isUnary: true, reuseID: .zero) + return viewListID + } + + package static func explicit(_ id: ID) -> ViewList.ID where ID: Hashable { + explicit(id, owner: .nil) + } + #endif + + package func elementID(at index: Int) -> ViewList.ID { + var id = self + id.index = index + return id + } + + package struct Canonical: Hashable, CustomStringConvertible { + private var _index: Int32 + + package var index: Int { + get { Int(_index) } + set { _index = Int32(newValue) } + } + + private var implicitID: Int32 + + package var explicitID: AnyHashable2? + + init(_index: Int32, implicitID: Int32, explicitID: AnyHashable2?) { + self._index = _index + self.implicitID = implicitID + self.explicitID = explicitID + } + + package var requiresImplicitID: Bool { implicitID >= 0 } + + package var description: String { + if let explicitID { + explicitID.description + } else { + "@\(_index)" + } + } + } + + package var canonicalID: Canonical { + guard let explicitID = explicitIDs.first else { + return Canonical(_index: _index, implicitID: implicitID, explicitID: nil) + } + return Canonical(_index: _index, implicitID: explicitID.isUnary ? -1 : implicitID, explicitID: explicitID.id) + } + + package struct ElementCollection: RandomAccessCollection, Equatable { + package var id: ViewList.ID + package var count: Int + + package init(id: ViewList.ID, count: Int) { + self.id = id + self.count = count + } + + package var startIndex: Int { .zero } + package var endIndex: Int { count } + + package subscript(index: Int) -> ViewList.ID { + id.elementID(at: index) + } + } + + package func elementIDs(count: Int) -> ElementCollection { + ElementCollection(id: self, count: count) + } + + #if canImport(Darwin) + package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool, reuseID: Int) where ID: Hashable { + explicitIDs.append(Explicit(id: AnyHashable2(explicitID), reuseID: reuseID, owner: owner, isUnary: isUnary)) + } + + package mutating func bind(explicitID: ID, owner: AnyAttribute, reuseID: Int) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: reuseID) + } + + package mutating func bind(explicitID: ID, owner: AnyAttribute, isUnary: Bool) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: isUnary, reuseID: .zero) + } + + package mutating func bind(explicitID: ID, owner: AnyAttribute) where ID: Hashable { + bind(explicitID: explicitID, owner: owner, isUnary: false, reuseID: .zero) + } + #endif + + package var primaryExplicitID: AnyHashable2? { explicitIDs.first?.id } + + package var allExplicitIDs: [AnyHashable2] { explicitIDs.map(\.id) } + + #if canImport(Darwin) + package func explicitID(owner: AnyAttribute) -> ID? where ID: Hashable { + for explicitID in explicitIDs { + guard explicitID.owner == owner, + let id = explicitID.id.as(type: ID.self) + else { continue } + return id + } + return nil + } + #endif + + package func explicitID(for idType: ID.Type) -> ID? where ID: Hashable { + for explicitID in explicitIDs { + guard let id = explicitID.id.as(type: ID.self) + else { continue } + return id + } + return nil + } + + package func containsID(_ id: ID) -> Bool where ID: Hashable { + for explicitID in explicitIDs { + guard explicitID.id.as(type: ID.self) == id + else { continue } + return true + } + return false + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(_index) + hasher.combine(implicitID) + for explicitID in explicitIDs { + hasher.combine(explicitID.id) + #if canImport(Darwin) + hasher.combine(explicitID.owner) + #endif + } + } + + package var reuseIdentifier: Int { + var hasher = Hasher() + hasher.combine(_index) + hasher.combine(implicitID) + for explicitID in explicitIDs { + hasher.combine(explicitID.reuseID) + } + return hasher.finalize() + } + + final package class _Views: Views where Base: Equatable, Base: RandomAccessCollection, Base.Element == ViewList.ID, Base.Index == Int { + package let base: Base + + package init(_ base: Base, isDataDependent: Bool) { + self.base = base + super.init(isDataDependent: isDataDependent) + } + + override package var endIndex: Int { + base.endIndex + } + + override package subscript(index: Int) -> ViewList.ID { + base[index] + } + + override package func isEqual(to other: _ViewList_ID.Views) -> Bool { + guard let other = other as? Self else { return false } + return base == other.base + } + } + + final package class JoinedViews: Views { + package let views: [(views: Views, endOffset: Int)] + package let count: Int + + package init(_ views: [Views], isDataDependent: Bool) { + var offset = 0 + var result: [(views: Views, endOffset: Int)] = [] + for view in views { + offset += views.distance(from: 0, to: view.endIndex) + result.append((view, offset)) + } + self.views = result + self.count = offset + super.init(isDataDependent: isDataDependent) + } + + override package var endIndex: Int { count } + + override package subscript(index: Int) -> ViewList.ID { + var index = index + // Copied from Swift Standard Library's _partitioningIndex(where:) implementation + var n = views.count + var l = 0 + while n > 0 { + let half = n / 2 + let mid = l + half + if views[mid].endOffset > index { + n = half + } else { + l = mid + 1 + n -= half + 1 + } + } + + let targetIndex = l + if targetIndex != 0 { + index &-= views[targetIndex - 1].endOffset + } + + let view = views[targetIndex] + // Copied from Swift Standard Library's _checkIndex(_:) implementation + Swift.precondition(index >= startIndex, "Negative Array index is out of range") + Swift.precondition(index <= endIndex, "Array index is out of range") + return view.views[index] + } + + override package func isEqual(to other: ViewList.ID.Views) -> Bool { + guard let other = other as? JoinedViews, + count == other.count + else { + return false + } + guard !views.isEmpty else { + return true + } + for index in views.indices { + guard views[index].views.isEqual(to: other.views[index].views) else { + return false + } + } + return true + } + } +} + +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension _ViewList_ID: Sendable {} + +// MARK: - ViewList.ID.Views + +@_spi(ForOpenSwiftUIOnly) +open class _ViewList_ID_Views: RandomAccessCollection, Equatable { + final public let isDataDependent: Bool + + final public var startIndex: Int { 0 } + + open var endIndex: Int { preconditionFailure("") } + + open subscript(index: Int) -> _ViewList_ID { preconditionFailure("") } + + open func isEqual(to other: _ViewList_ID_Views) -> Bool { preconditionFailure("") } + + package init(isDataDependent: Bool) { + self.isDataDependent = isDataDependent + } + + package func withDataDependency() -> ViewList.ID.Views { + if isDataDependent { + self + } else { + ViewList.ID._Views(self, isDataDependent: true) + } + } + + public static func == (lhs: _ViewList_ID_Views, rhs: _ViewList_ID_Views) -> Bool { + lhs.isEqual(to: rhs) + } +} + +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension ViewList.ID.Views: Sendable {} + +// MARK: - UnaryViewGenerator + +public protocol UnaryViewGenerator { + func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs + func tryToReuse(by other: Self, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool +} + +public struct BodyUnaryViewGenerator: UnaryViewGenerator { + let body: ViewList.Elements.MakeElement + + public func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs { + body(inputs) + } + + public func tryToReuse(by other: BodyUnaryViewGenerator, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool { + // FIXME + // 1. pass body to OGCompareValues directly instaed of withUnsafePointer + // 2. Add 0x103 case instead of rawValue + guard compareValues(body, other.body, mode: .init(rawValue: 0x103)) else { + ReuseTrace.traceReuseBodyMismatchedFailure() + Log.graphReuse("Reuse failed: \(Self.self) failed comparison") + return false + } + return true + } +} + +public struct TypedUnaryViewGenerator: UnaryViewGenerator where V: View { + let view: WeakAttribute + + public func makeView(inputs: _ViewInputs, indirectMap: IndirectAttributeMap?) -> _ViewOutputs { + guard var view = view.attribute else { + return .init() + } + if let indirectMap { + view.makeReusable(indirectMap: indirectMap) + } + return V.makeDebuggableView(view: _GraphValue(view), inputs: inputs) + } + + public func tryToReuse(by other: TypedUnaryViewGenerator, indirectMap: IndirectAttributeMap, testOnly: Bool) -> Bool { + guard let view = view.attribute, let otherView = other.view.attribute else { + Log.graphReuse("Reuse failed: missing attribute for \(V.self)") + return false + } + return view.tryToReuse(by: otherView, indirectMap: indirectMap, testOnly: testOnly) + } +} + +// MARK: - UnaryElements [WIP] + +private struct UnaryElements: ViewList.Elements where Generator: UnaryViewGenerator { + var body: Generator + var baseInputs: _GraphInputs + + init(body: Generator, baseInputs: _GraphInputs) { + self.body = body + self.baseInputs = baseInputs + } + + var count: Int { 1 } + + func makeElements( + from start: inout Int, + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: Body + ) -> (_ViewOutputs?, Bool) { + preconditionFailure("TODO") + } + + func tryToReuseElement( + at index: Int, + by other: any ViewList.Elements, + at otherIndex: Int, + indirectMap: IndirectAttributeMap, + testOnly: Bool + ) -> Bool { + guard let other = other as? UnaryElements else { + Log.graphReuse("Reuse failed: other is not Unary") + ReuseTrace.traceReuseUnaryElementExpectedFailure(type(of: other)) + return false + } + // BodyInput + preconditionFailure("TODO") + } +} + +// MARK: - ViewListOutputs + Extension [WIP] + +extension _ViewListOutputs { + private struct ApplyModifiers: Rule, AsyncAttribute { + @Attribute var base: any ViewList + let modifier: ListModifier + + var value: any ViewList { + var value = base + modifier.apply(to: &value) + return value + } + } + + private static func staticList( + _ elements: any ViewList.Elements, + inputs: _ViewListInputs, + staticCount: Int + ) -> _ViewListOutputs { + let implicitID = inputs.implicitID + let scope = inputs.base.stableIDScope + let traits = inputs.traits + let canTransition = inputs.canTransition + let views: Views + if scope != nil || traits != nil || canTransition { + views = .dynamicList( + Attribute(BaseViewList.Init( + elements: elements, + implicitID: implicitID, + canTransition: canTransition, + stableIDScope: scope, + traitKeys: inputs.traitKeys, + traits: .init(traits) + )), + nil + ) + } else { + views = .staticList(elements) + } + return _ViewListOutputs( + views, + nextImplicitID: implicitID &+ staticCount, + staticCount: staticCount + ) + } + + // FIXME: Group + package static func nonEmptyParentViewList(inputs: _ViewListInputs) -> _ViewListOutputs { + preconditionFailure("TODO") + } + + package static func unaryViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs where V: View { + let generator = TypedUnaryViewGenerator(view: .init(view.value)) + let elements = UnaryElements(body: generator, baseInputs: inputs.base) + return staticList(elements, inputs: inputs, staticCount: 1) + } + + package static func unaryViewList(viewType: T.Type = T.self, inputs: _ViewListInputs, body: @escaping ViewList.Elements.MakeElement) -> _ViewListOutputs { + let generator = BodyUnaryViewGenerator(body: body) + let elements = UnaryElements(body: generator, baseInputs: inputs.base) + return staticList(elements, inputs: inputs, staticCount: 1) + } + + package static func emptyViewList(inputs: _ViewListInputs) -> _ViewListOutputs { + if inputs.options.contains(.isNonEmptyParent) { + nonEmptyParentViewList(inputs: inputs) + } else { + staticList(EmptyViewListElements(), inputs: inputs, staticCount: 0) + } + } + + package func makeAttribute(inputs: _ViewListInputs) -> Attribute { + switch views { + case let .staticList(elements): + Attribute(value: BaseViewList( + elements: elements, + implicitID: nextImplicitID, + canTransition: inputs.canTransition, + stableIDScope: inputs.base.stableIDScope, + traitKeys: .init(), + traits: .init() + )) + case let .dynamicList(attribute, modifier): + if let modifier { + Attribute(ApplyModifiers(base: attribute, modifier: modifier)) + } else { + attribute + } + } + } + + package func makeAttribute(viewInputs: _ViewInputs) -> Attribute { + switch views { + case let .staticList(elements): + Attribute(value: BaseViewList( + elements: elements, + implicitID: nextImplicitID, + canTransition: false, + stableIDScope: viewInputs.base.stableIDScope, + traitKeys: .init(), + traits: .init() + )) + case let .dynamicList(attribute, modifier): + if let modifier { + Attribute(ApplyModifiers(base: attribute, modifier: modifier)) + } else { + attribute + } + } + } + + package static func makeModifiedList(list: Attribute, modifier: ListModifier?) -> Attribute { + if let modifier { + Attribute(ApplyModifiers(base: list, modifier: modifier)) + } else { + list + } + } + + package mutating func multiModifier(_ modifier: _GraphValue, inputs: _ViewListInputs) where T: ViewModifier { + preconditionFailure("TODO") + } + + package static func concat(_ outputs: [_ViewListOutputs], inputs: _ViewListInputs) -> _ViewListOutputs { + preconditionFailure("TODO") + } +} + +// MARK: - BaseViewList + +private struct BaseViewList: ViewList { + var elements: any Elements + var implicitID: Int + var traitKeys: ViewTraitKeys? + var traits: ViewTraitCollection + + init( + elements: any Elements, + implicitID: Int, + canTransition: Bool, + stableIDScope: WeakAttribute?, + traitKeys: ViewTraitKeys?, + traits: ViewTraitCollection + ) { + self.elements = elements + self.implicitID = implicitID + self.traitKeys = traitKeys + self.traits = traits + if canTransition { + self.traits.canTransition = true + } + if let stableIDScope { + self.traits[DisplayList.StableIdentityScope.self] = stableIDScope + } + } + + func count(style: IteratorStyle) -> Int { + style.applyGranularity(to: elements.count) + } + + func estimatedCount(style: IteratorStyle) -> Int { + style.applyGranularity(to: elements.count) + } + + var viewIDs: ID.Views? { + ID._Views( + ID.ElementCollection( + id: ID(implicitID: implicitID), + count: elements.count), + isDataDependent: false + ) + } + + func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + let count = count(style: style) + guard start < count else { + start -= count + return true + } + let sublist = Sublist( + start: start, + count: count, + id: ViewList.ID(implicitID: implicitID), + elements: elements, + traits: traits, + list: list + ) + defer { start = 0 } + return body(&start, style, .sublist(sublist), &transform) + } + + func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + nil + } + + func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID : Hashable { + nil + } + + struct Init: Rule, AsyncAttribute, CustomStringConvertible { + let elements: any Elements + let implicitID: Int + let canTransition: Bool + let stableIDScope: WeakAttribute? + let traitKeys: ViewTraitKeys? + @OptionalAttribute var traits: ViewTraitCollection? + + var value: any ViewList { + BaseViewList( + elements: elements, + implicitID: implicitID, + canTransition: canTransition, + stableIDScope: stableIDScope, + traitKeys: traitKeys, + traits: traits ?? .init() + ) + } + + var description: String { + "Elements [\(elements.count)]" + } + } +} + +// MARK: - EmptyViewList + +package struct EmptyViewList: ViewList { + package init() {} + + package func count(style: IteratorStyle) -> Int { .zero } + + package func estimatedCount(style: IteratorStyle) -> Int { .zero } + + package var traitKeys: ViewTraitKeys? { .init() } + + package var viewIDs: ID.Views? { .init(isDataDependent: false) } + + package var traits: Traits { .init() } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + true + } + + package func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + nil + } + + package func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + nil + } +} + +// MARK: - EmptyViewListElements + +package struct EmptyViewListElements: ViewList.Elements { + package init() {} + + package var count: Int { 0 } + + package func makeElements( + from start: inout Int, + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: Body + ) -> (_ViewOutputs?, Bool) { + return (nil, true) + } + + package func tryToReuseElement( + at index: Int, + by other: any ViewList.Elements, + at otherIndex: Int, + indirectMap: IndirectAttributeMap, + testOnly: Bool + ) -> Bool { + guard other is EmptyViewListElements else { + Log.graphReuse("Reuse failed: other is not Empty") + return false + } + return true + } +} + +// MARK: - ViewListSlice + +package struct ViewListSlice: ViewList { + let base: any ViewList + let bounds: Range + + package var traitKeys: ViewTraitKeys? { nil } + + package var traits: Traits { .init() } + + final class ViewIDsSlice: ID.Views { + let base: ID.Views + let bounds: Range + + init?(base: ID.Views?, bounds: Range) { + guard let base else { + return nil + } + self.base = base + self.bounds = bounds + super.init(isDataDependent: base.isDataDependent) + } + + override var endIndex: Int { bounds.count } + + override subscript(index: Int) -> ID { + base[index + bounds.lowerBound] + } + } + + package var viewIDs: ID.Views? { + ViewIDsSlice(base: base.viewIDs, bounds: bounds) + } + + package init(base: any ViewList, bounds: Range) { + self.base = base + self.bounds = bounds + } + + package func count(style: IteratorStyle) -> Int { + bounds.count + } + + package func estimatedCount(style: IteratorStyle) -> Int { + bounds.count + } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + var start = bounds.lowerBound + start + return base.applyNodes( + from: &start, + style: style, + list: list, + transform: &transform + ) { start, style, node, transform in + guard start < bounds.upperBound else { + return false + } + return body(&start, style, node, &transform) + } + } + + package func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + base.edit(forID: id, since: transaction) + } + + package func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + guard let offset = base.firstOffset(forID: id, style: style) else { + return nil + } + return offset - bounds.lowerBound + } +} + +// MARK: - ViewList.Group + +package struct _ViewList_Group: ViewList { + package typealias AttributedList = (list: any ViewList, attribute: Attribute) + + package var lists: [AttributedList] + + package func count(style: IteratorStyle) -> Int { + lists.reduce(0) { $0 + $1.list.count(style: style) } + } + + package func estimatedCount(style: IteratorStyle) -> Int { + lists.reduce(0) { $0 + $1.list.estimatedCount(style: style) } + } + + package var traits: Traits { .init() } + + package var traitKeys: ViewTraitKeys? { + var traitKeys = ViewTraitKeys() + for (list, _) in lists { + guard let keys = list.traitKeys else { + return nil + } + traitKeys.formUnion(keys) + } + return traitKeys + } + + package var viewIDs: ID.Views? { + var views: [ID.Views] = [] + var isDataDependent = false + for (list, _) in lists { + guard let ids = list.viewIDs else { + return nil + } + views.append(ids) + isDataDependent = isDataDependent || ids.isDataDependent + } + return ViewList.ID.JoinedViews(views, isDataDependent: isDataDependent) + + } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + body(&start, style, .group(self), &transform) + } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + for (list, attribute) in lists { + guard list.applyNodes( + from: &start, + style: style, + list: attribute, + transform: &transform, + to: body + ) else { + return false + } + } + return true + } + + package func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + for (list, _) in lists { + guard let edit = list.edit(forID: id, since: transaction) else { + continue + } + return edit + } + return nil + } + + package func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + var previousCount = 0 + for (list, _) in lists { + guard let offset = list.firstOffset(forID: id, style: style) else { + previousCount += list.count(style: style) + continue + } + return previousCount + offset + } + return nil + } + + package struct Init: Rule, AsyncAttribute, CustomStringConvertible { + var lists: [Attribute] + + package var value: any ViewList { + _ViewList_Group(lists: lists.map { ($0.value, $0) }) + } + + package var description: String { "∪" } + } +} + +// MARK: - ViewList.Section + +package struct _ViewList_Section: ViewList { + package var id: UInt32 + package var base: ViewList.Group + package var traits: ViewList.Traits + package var isHierarchical: Bool + + package var header: ViewList.Group.AttributedList { + base.lists[0] + } + + package var content: ViewList.Group.AttributedList { + base.lists[1] + } + + package var footer: ViewList.Group.AttributedList { + base.lists[2] + } + + package var traitKeys: ViewTraitKeys? { + base.traitKeys + } + + package var viewIDs: ID.Views? { + if isHierarchical { + header.list.viewIDs + } else { + base.viewIDs + } + } + + @inline(__always) + package func headerFooterStyle(for style: IteratorStyle) -> IteratorStyle { + var style = style + style.applyGranularity = (style.granularity != 1) + return style + } + + + package func count(style: IteratorStyle) -> Int { + if isHierarchical { + var style = style + style.applyGranularity = (style.granularity != 1) + return header.list.count(style: style) + } else { + var contentCount = content.list.count(style: style) + style.alignToNextGranularityMultiple(&contentCount) + let headerFooterStyle = headerFooterStyle(for: style) + let headerCount = header.list.count(style: headerFooterStyle) + let footerCount = footer.list.count(style: headerFooterStyle) + return contentCount + headerCount + footerCount + } + } + + package func estimatedCount(style: IteratorStyle) -> Int { + if isHierarchical { + var style = style + style.applyGranularity = (style.granularity != 1) + return header.list.estimatedCount(style: style) + } else { + var contentEstimatedCount = content.list.estimatedCount(style: style) + style.alignToNextGranularityMultiple(&contentEstimatedCount) + let headerFooterStyle = headerFooterStyle(for: style) + let headerEstimatedCount = header.list.estimatedCount(style: headerFooterStyle) + let footerEstimatedCount = footer.list.estimatedCount(style: headerFooterStyle) + return contentEstimatedCount + headerEstimatedCount + footerEstimatedCount + } + } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + body(&start, style, .section(self), &transform) + } + + package struct Info { + package var id: UInt32 + package var isHeader: Bool + package var isFooter: Bool + } + + package func applyNodes( + from start: inout Int, + style: IteratorStyle, + transform: inout SublistTransform, + to body: (inout Int, IteratorStyle, Node, Info, inout SublistTransform) -> Bool + ) -> Bool { + style.alignToPreviousGranularityMultiple(&start) + let range = (0 ..< base.lists.count).prefix(isHierarchical ? 1 : .max) + let headerFooterStyle = headerFooterStyle(for: style) + for index in range { + let (list, attribute) = base.lists[index] + let result = list.applyNodes( + from: &start, + style: index == 1 ? style : headerFooterStyle, + list: attribute, + transform: &transform + ) { start, style, node, transform in + body( + &start, + style, + node, + Info(id: id, isHeader: index == 0, isFooter: index == 2), + &transform + ) + } + guard result else { + return false + } + style.alignToNextGranularityMultiple(&start) + } + return true + } + + package func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + base.edit(forID: id, since: transaction) + } + + package func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + let range = (0 ..< base.lists.count).prefix(isHierarchical ? 1 : .max) + let headerFooterStyle = headerFooterStyle(for: style) + var previousCount = 0 + for index in range { + let (list, _) = base.lists[index] + let currentStyle = index == 1 ? style : headerFooterStyle + guard let offset = list.firstOffset( + forID: id, + style: currentStyle + ) else { + var count = list.count(style: currentStyle) + style.alignToNextGranularityMultiple(&count) + previousCount += count + continue + } + return previousCount + offset + } + return nil + } +} + +// MARK: - ViewList.Subgraph + +@_spi(ForOpenSwiftUIOnly) +open class _ViewList_Subgraph { + final package let subgraph: Subgraph + private(set) final package var refcount: UInt32 + + package init(subgraph: Subgraph) { + self.subgraph = subgraph + self.refcount = 1 + } + + final package func wrapping(_ base: any ViewList.Elements) -> any ViewList.Elements { + SubgraphElements(base: base, subgraph: self) + } + + final package func wrapping(_ list: any ViewList) -> any ViewList { + SubgraphList(base: list, subgraph: self) + } + + open func invalidate() {} + + @inline(__always) + final var isValid: Bool { + guard refcount != 0 else { + return false + } + return subgraph.isValid + } + + @inline(__always) + final func retain() { + refcount &+= 1 + } + + @inline(__always) + final func release(isInserted: Bool) { + refcount &-= 1 + guard refcount == 0 else { + return + } + invalidate() + guard subgraph.isValid else { + return + } + subgraph.willInvalidate(isInserted: isInserted) + subgraph.invalidate() + } +} + +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension ViewList.Subgraph: Sendable {} + +private struct SubgraphElements: ViewList.Elements { + let base: any ViewList.Elements + let subgraph: ViewList.Subgraph + + var count: Int { base.count } + + func makeElements( + from start: inout Int, + inputs: _ViewInputs, + indirectMap: IndirectAttributeMap?, + body: Body + ) -> (_ViewOutputs?, Bool) { + guard subgraph.isValid else { + return (nil, true) + } + return base.makeElements(from: &start, inputs: inputs, indirectMap: indirectMap, body: body) + } + + func tryToReuseElement( + at index: Int, + by other: any ViewList.Elements, + at otherIndex: Int, + indirectMap: IndirectAttributeMap, + testOnly: Bool + ) -> Bool { + guard let otherSubgraphElement = other as? SubgraphElements, + otherSubgraphElement.subgraph.isValid + else { + ReuseTrace.traceReuseInvalidSubgraphFailure(type(of: other)) + return false + } + return base.tryToReuseElement( + at: index, + by: otherSubgraphElement.base, + at: otherIndex, + indirectMap: indirectMap, + testOnly: testOnly + ) + } + + func retain() -> Release? { + guard subgraph.isValid else { + return nil + } + return Release(base: base.retain(), subgraph: subgraph) + } +} + +private struct SubgraphList: ViewList { + var base: any ViewList + var subgraph: ViewList.Subgraph + + func count(style: IteratorStyle) -> Int { + base.count(style: style) + } + + func estimatedCount(style: IteratorStyle) -> Int { + base.estimatedCount(style: style) + } + + var traitKeys: ViewTraitKeys? { + base.traitKeys + } + + var viewIDs: ID.Views? { + base.viewIDs + } + + var traits: ViewTraitCollection { + base.traits + } + + func applyNodes( + from start: inout Int, + style: IteratorStyle, + list: Attribute?, + transform: inout SublistTransform, + to body: ApplyBody + ) -> Bool { + transform.push(Transform(subgraph: subgraph)) + defer { transform.pop() } + return base.applyNodes(from: &start, style: style, list: list, transform: &transform, to: body) + } + + func edit(forID id: ID, since transaction: TransactionID) -> Edit? { + base.edit(forID: id, since: transaction) + } + + func firstOffset(forID id: OtherID, style: IteratorStyle) -> Int? where OtherID: Hashable { + base.firstOffset(forID: id, style: style) + } + + struct Transform: ViewList.SublistTransform.Item { + var subgraph: ViewList.Subgraph + + func apply(sublist: inout _ViewList_Sublist) { + sublist.elements = SubgraphElements(base: sublist.elements, subgraph: subgraph) + } + + func bindID(_ id: inout _ViewList_ID) {} + } +} + +// MARK: - ViewList.Elements.Release + +final package class _ViewList_ReleaseElements: Equatable { + var base: ViewList.Elements.Release? + var subgraph: ViewList.Subgraph + + init(base: ViewList.Elements.Release?, subgraph: ViewList.Subgraph) { + self.base = base + self.subgraph = subgraph + } + + deinit { + Update.ensure { + subgraph.release(isInserted: true) + } + } + + package static func == (lhs: ViewList.Elements.Release, rhs: ViewList.Elements.Release) -> Bool { + guard lhs.subgraph === rhs.subgraph else { + return false + } + guard let lhsBase = lhs.base, let rhsBase = rhs.base else { + return lhs.base == nil && rhs.base == nil + } + return lhsBase == rhsBase + } +} + +// FIXME: VariadicView Part + +// MARK: - _ViewList_View + +package struct _ViewList_View { + var elements: any ViewList.Elements + var id: _ViewList_ID + var index: Int + var count: Int + var contentSubgraph: Subgraph +} + +// MARK: - ViewListVisitor + +protocol ViewListVisitor { + mutating func visit(view: _ViewList_View, traits: ViewTraitCollection) -> Bool +} diff --git a/Sources/OpenSwiftUICore/View/ViewList.swift b/Sources/OpenSwiftUICore/View/ViewList.swift deleted file mode 100644 index c3e9ccaec..000000000 --- a/Sources/OpenSwiftUICore/View/ViewList.swift +++ /dev/null @@ -1,488 +0,0 @@ -// -// ViewList.swift -// OpenSwiftUI -// -// Audited for iOS 15.5 -// Status: WIP -// ID: 70E71091E926A1B09B75AAEB38F5AA3F - -import Foundation -package import OpenGraphShims - -// MARK: - _ViewListInputs - -/// Input values to `View._makeViewList()`. -public struct _ViewListInputs { - package var base: _GraphInputs - package var implicitID: Int - - package struct Options: OptionSet { - package let rawValue: Int - - package init(rawValue: Int) { - self.rawValue = rawValue - } - - package static let canTransition: Options = Options(rawValue: 1 << 0) - package static let disableTransitions: Options = Options(rawValue: 1 << 1) - package static let requiresDepthAndSections: Options = Options(rawValue: 1 << 2) - package static let requiresNonEmptyGroupParent: Options = Options(rawValue: 1 << 3) - package static let isNonEmptyParent: Options = Options(rawValue: 1 << 4) - package static let resetHeaderStyleContext: Options = Options(rawValue: 1 << 5) - package static let resetFooterStyleContext: Options = Options(rawValue: 1 << 6) - package static let layoutPriorityIsTrait: Options = Options(rawValue: 1 << 7) - package static let requiresSections: Options = Options(rawValue: 1 << 8) - package static let tupleViewCreatesUnaryElements: Options = Options(rawValue: 1 << 9) - package static let previewContext: Options = Options(rawValue: 1 << 10) - package static let needsDynamicTraits: Options = Options(rawValue: 1 << 11) - package static let allowsNestedSections: Options = Options(rawValue: 1 << 12) - package static let sectionsConcatenateFooter: Options = Options(rawValue: 1 << 13) - package static let needsArchivedAnimationTraits: Options = Options(rawValue: 1 << 14) - package static let sectionsAreHierarchical: Options = Options(rawValue: 1 << 15) - } - - package var options: _ViewListInputs.Options - @OptionalAttribute var traits: ViewTraitCollection? - package var traitKeys: ViewTraitKeys? - - - // MARK: - base - - @inline(__always) - mutating func withMutateGraphInputs(_ body: (inout _GraphInputs) -> R) -> R { - body(&base) - } -} - -// MARK: - ViewListOutputs - -/// Output values from `View._makeViewList()`. -public struct _ViewListOutputs { - var views: Views - var nextImplicitID: Int - var staticCount: Int? - - enum Views { - case staticList(_ViewList_Elements) - case dynamicList(Attribute, ListModifier?) - } - - class ListModifier { - init() {} - - func apply(to: inout ViewList) { - // TODO - } - } - - private static func staticList(_ elements: _ViewList_Elements, inputs: _ViewListInputs, staticCount: Int) -> _ViewListOutputs { - preconditionFailure("TODO") - } -} - -extension _ViewListOutputs { - package static func unaryViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs where V: View { - preconditionFailure("TODO") - } - - - @inline(__always) - static func emptyParentViewList(inputs: _ViewListInputs) -> _ViewListOutputs { - staticList(EmptyElements(), inputs: inputs, staticCount: 0) - } - - package static func nonEmptyParentViewList(inputs: _ViewListInputs) -> _ViewListOutputs { - preconditionFailure("TODO") - } -} - -// MARK: - _ViewListCountInputs - -/// Input values to `View._viewListCount()`. -public struct _ViewListCountInputs { - var customInputs: PropertyList - var options: _ViewListInputs.Options - var baseOptions: _GraphInputs.Options - - subscript(_ type: Input.Type) -> Input.Value { - get { customInputs[type] } - set { customInputs[type] = newValue } - } - - mutating func append(_ value: Value, to type: Input.Type) where Input.Value == [Value] { - var values = self[type] - values.append(value) - self[type] = values - } - - mutating func popLast(_ type: Input.Type) -> Value? where Input.Value == [Value] { - var values = self[type] - guard let value = values.popLast() else { - return nil - } - self[type] = values - return value - } -} - -// MARK: - _ViewList_ID - -package struct _ViewList_ID { - var _index: Int32 - var implicitID: Int32 - private var explicitIDs: [Explicit] - - package class Views { - let isDataDependent: Bool - var endIndex: Int { preconditionFailure("") } - subscript(index: Int) -> _ViewList_ID { preconditionFailure("") } - func isEqual(to other: Views) -> Bool { preconditionFailure("") } - init(isDataDependent: Bool) { - self.isDataDependent = isDataDependent - } - // func withDataDependency() -> Views {} - } - - final class _Views: Views where Base: Equatable, Base: RandomAccessCollection, Base.Element == _ViewList_ID, Base.Index == Int { - let base: Base - - init(_ base: Base, isDataDependent: Bool) { - self.base = base - super.init(isDataDependent: isDataDependent) - } - override var endIndex: Int { - base.endIndex - } - - override subscript(index: Int) -> _ViewList_ID { - base[index] - } - - override func isEqual(to other: _ViewList_ID.Views) -> Bool { - guard let other = other as? Self else { return false } - return base == other.base - } - } - - final class JoinedViews: Views { - let views: [(views: Views, endOffset: Int)] - let count: Int - - init(_ views: [Views], isDataDependent: Bool) { - var offset = 0 - var result: [(views: Views, endOffset: Int)] = [] - for view in views { - offset += views.distance(from: 0, to: view.endIndex) - result.append((view, offset)) - } - self.views = result - count = offset - super.init(isDataDependent: isDataDependent) - } - - override var endIndex: Int { - views.endIndex - } - - override subscript(index: Int) -> _ViewList_ID { - preconditionFailure("TODO") - } - - override func isEqual(to other: _ViewList_ID.Views) -> Bool { - preconditionFailure("TODO") - } - } - - private struct Explicit: Equatable { - let id: AnyHashable - #if canImport(Darwin) - let owner: AnyAttribute - #endif - let isUnary: Bool - } - - struct Canonical { - var _index: Int32 - var implicitID: Int32 - var explicitID: AnyHashable? - } - - #if canImport(Darwin) - mutating func bind(explicitID: AnyHashable, owner: AnyAttribute, isUnary: Bool) { - explicitIDs.append(.init(id: explicitID, owner: owner, isUnary: isUnary)) - } - #endif -} - -// MARK: - IndirectMap - -//#if OPENSWIFTUI_RELEASE_2024 -//final package class IndirectAttributeMap { -// final package let subgraph: OGSubgraph -// // final package var map: [AnyAttribute: AnyAttribute] -// -// package init(subgraph: OGSubgraph) { -// self.subgraph = subgraph -// // self.map = [:] -// } -//} -//#elseif OPENSWIFTUI_RELEASE_2021 -final package class _ViewList_IndirectMap { - final package let subgraph: OGSubgraph - - #if canImport(Darwin) - final package var map: [AnyAttribute: AnyAttribute] - #endif - - init(subgraph: OGSubgraph) { - self.subgraph = subgraph - #if canImport(Darwin) - self.map = [:] - #endif - } -} -//#endif - -// MARK: - _ViewList_Elements - -package protocol _ViewList_Elements { - typealias Body = (_ViewInputs, @escaping Self.MakeElement) -> (_ViewOutputs?, Swift.Bool) - typealias MakeElement = (_ViewInputs) -> _ViewOutputs -// #if OPENSWIFTUI_RELEASE_2024 -// typealias Release = _ViewList_ReleaseElements -// #elseif OPENSWIFTUI_RELEASE_2021 - typealias Release = () -> Void -// #endif - - var count: Int { get } - -// #if OPENSWIFTUI_RELEASE_2024 -// func makeElements( -// from start: inout Int, -// inputs: _ViewInputs, -// indirectMap: IndirectAttributeMap?, -// body: Body -// ) -> (_ViewOutputs?, Bool) -// -// func tryToReuseElement( -// at index: Int, -// by other: any _ViewList_Elements, -// at otherIndex: Int, -// indirectMap: IndirectAttributeMap, -// testOnly: Bool -// ) -> Bool -// #elseif OPENSWIFTUI_RELEASE_2021 - func makeElements( - from start: inout Int, - inputs: _ViewInputs, - indirectMap: _ViewList_IndirectMap?, - body: Body - ) -> (_ViewOutputs?, Bool) - - func tryToReuseElement( - at index: Int, - by other: any _ViewList_Elements, - at otherIndex: Int, - indirectMap: _ViewList_IndirectMap, - testOnly: Bool - ) -> Bool -// #endif - - func retain() -> Release -} - -extension _ViewList_Elements { - func retain() -> Release { - {} - } -} - -private struct EmptyElements: _ViewList_Elements { - var count: Int { 0 } - - func makeElements( - from start: inout Int, - inputs: _ViewInputs, - indirectMap: _ViewList_IndirectMap?, - body: Body - ) -> (_ViewOutputs?, Bool) { - return (nil, true) - } - - func tryToReuseElement( - at index: Int, - by other: any _ViewList_Elements, - at otherIndex: Int, - indirectMap: _ViewList_IndirectMap, - testOnly: Bool - ) -> Bool { - other is EmptyElements - } -} - -// TODO -private struct UnaryElements: _ViewList_Elements { - var body: Value - var baseInputs: _GraphInputs - - init(body: Value, baseInputs: _GraphInputs) { - self.body = body - self.baseInputs = baseInputs - } - - var count: Int { 1 } - - func makeElements( - from start: inout Int, - inputs: _ViewInputs, - indirectMap: _ViewList_IndirectMap?, - body: Body - ) -> (_ViewOutputs?, Bool) { - preconditionFailure("TODO") - } - - func tryToReuseElement( - at index: Int, - by other: any _ViewList_Elements, - at otherIndex: Int, - indirectMap: _ViewList_IndirectMap, - testOnly: Bool - ) -> Bool { - preconditionFailure("TODO") - } -} - -// MARK: - _ViewList_Subgraph - -class _ViewList_Subgraph { - let subgraph: OGSubgraph - private var refcount : UInt32 - - init(subgraph: OGSubgraph) { - self.subgraph = subgraph - self.refcount = 1 - } - - func invalidate() {} -} - -extension _ViewList_Subgraph { - var isValid: Bool { - guard refcount > 0 else { - return false - } - return subgraph.isValid - } - - func retain() { - refcount &+= 1 - } - - func release(isInserted: Bool) { - refcount &-= 1 - guard refcount == 0 else { - return - } - invalidate() - guard subgraph.isValid else { - return - } - subgraph.willInvalidate(isInserted: isInserted) - subgraph.invalidate() - } - - @inlinable - func wrapping(_ elements: _ViewList_Elements) -> _ViewList_Elements { - SubgraphElements(base: elements, subgraph: self) - } -} - -// TODO -private struct SubgraphElements: _ViewList_Elements { - let base: _ViewList_Elements - let subgraph: _ViewList_Subgraph - - var count: Int { - preconditionFailure("TODO") - } - - func makeElements(from start: inout Int, inputs: _ViewInputs, indirectMap: _ViewList_IndirectMap?, body: (_ViewInputs, @escaping MakeElement) -> (_ViewOutputs?, Bool)) -> (_ViewOutputs?, Bool) { - preconditionFailure("TODO") - } - - func tryToReuseElement(at index: Int, by other: any _ViewList_Elements, at otherIndex: Int, indirectMap: _ViewList_IndirectMap, testOnly: Bool) -> Bool { - preconditionFailure("TODO") - } -} - -// MARK: - _ViewList_View - -package struct _ViewList_View { - var elements: _ViewList_Elements - var id: _ViewList_ID - var index: Int - var count: Int - var contentSubgraph: OGSubgraph -} - -// MARK: - _ViewList_Sublist - -struct _ViewList_Sublist { - var start: Int - var count: Int - var id: _ViewList_ID - var elements: _ViewList_Elements - var traits: ViewTraitCollection - var list: Attribute? -} - -struct _ViewList_SublistTransform { - var items: [any _ViewList_SublistTransform_Item] -} - - -protocol _ViewList_SublistTransform_Item { - func apply(sublist: inout _ViewList_Sublist) -} - -// MARK: - ViewList - -protocol ViewList { - func count(style: _ViewList_IteratorStyle) -> Int - func estimatedCount(style: _ViewList_IteratorStyle) -> Int - var traitKeys: ViewTraitKeys? { get } - var viewIDs: _ViewList_ID.Views? { get } - var traits: ViewTraitCollection { get } - func applyNodes(from index: inout Int, style: _ViewList_IteratorStyle, list: _GraphValue?, transform: inout _ViewList_SublistTransform, to body: (inout Int, _ViewList_IteratorStyle, _ViewList_Node, inout _ViewList_SublistTransform) -> Bool) -> Bool - func edit(forID id: _ViewList_ID, since transaction: TransactionID) -> _ViewList_Edit? - func firstOffset(forID id: OtherID, style: _ViewList_IteratorStyle) -> Int? where OtherID: Hashable -} - -// MARK: - ViewListVisitor - -protocol ViewListVisitor { - mutating func visit(view: _ViewList_View, traits: ViewTraitCollection) -> Bool -} - -// MARK: - _ViewList_IteratorStyle - -// TODO -struct _ViewList_IteratorStyle: Equatable { - var value: UInt - - func alignToPreviousGranularityMultiple(_ value: inout Int) { - preconditionFailure("TODO") - } -} - -enum _ViewList_Edit: Equatable { - case inserted - case removed -} - -enum _ViewList_Node { - case list(any ViewList, Attribute?) - case sublist(_ViewList_Sublist) - // case group(_ViewList_Group) - // case section(_ViewList_Section) -} diff --git a/Tests/OpenSwiftUICoreTests/View/Input/ViewListTests.swift b/Tests/OpenSwiftUICoreTests/View/Input/ViewListTests.swift new file mode 100644 index 000000000..6c459b05c --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/View/Input/ViewListTests.swift @@ -0,0 +1,149 @@ +// +// ViewListTests.swift +// OpenSwiftUICoreTests + +@_spi(ForOpenSwiftUIOnly) +import OpenSwiftUICore +import Testing + +struct ViewListIteratorStyleTests { + @Test + func granularityAndApplyGranularity() { + var iteratorStyle = ViewList.IteratorStyle() + #expect(iteratorStyle.granularity == 1) + #expect(iteratorStyle.applyGranularity == false) + + iteratorStyle.granularity = 2 + iteratorStyle.applyGranularity = true + + #expect(iteratorStyle.granularity == 2) + #expect(iteratorStyle.applyGranularity == true) + } + + @Test(arguments: [ + (ViewList.IteratorStyle(granularity: 1), 0, 0, 0), + (ViewList.IteratorStyle(granularity: 1), 1, 1, 1), + (ViewList.IteratorStyle(granularity: 1), 2, 2, 2), + (ViewList.IteratorStyle(granularity: 2), 0, 0, 0), + (ViewList.IteratorStyle(granularity: 2), 1, 0, 2), + (ViewList.IteratorStyle(granularity: 2), 2, 2, 2), + (ViewList.IteratorStyle(granularity: 2), 3, 2, 4), + (ViewList.IteratorStyle(granularity: 3), 0, 0, 0), + (ViewList.IteratorStyle(granularity: 3), 1, 0, 3), + (ViewList.IteratorStyle(granularity: 3), 2, 0, 3), + (ViewList.IteratorStyle(granularity: 3), 3, 3, 3), + (ViewList.IteratorStyle(granularity: 3), 4, 3, 6), + ]) + func alignment( + _ iteratorStyle: ViewList.IteratorStyle, + _ initialValue: Int, + _ expectedPrevious: Int, + _ expectedNext: Int + ) { + var previous = initialValue + iteratorStyle.alignToPreviousGranularityMultiple(&previous) + #expect(previous == expectedPrevious) + + var next = initialValue + iteratorStyle.alignToNextGranularityMultiple(&next) + #expect(next == expectedNext) + } +} + +struct ViewListIDTests { + @Test + func initialization() { + let id1 = ViewList.ID() + #expect(id1.index == 0) + #expect(id1.primaryExplicitID == nil) + + let id2 = ViewList.ID(implicitID: 42) + #expect(id2.index == 0) + #expect(id2.primaryExplicitID == nil) + } + + @Test + func elementIDGeneration() { + let baseID = ViewList.ID(implicitID: 1) + let element1 = baseID.elementID(at: 0) + let element2 = baseID.elementID(at: 5) + + #expect(element1.index == 0) + #expect(element2.index == 5) + } + + @Test + func elementCollection() { + let baseID = ViewList.ID(implicitID: 1) + let collection = baseID.elementIDs(count: 3) + + #expect(collection.count == 3) + #expect(collection.startIndex == 0) + #expect(collection.endIndex == 3) + + let elements = Array(collection) + #expect(elements.count == 3) + #expect(elements[0].index == 0) + #expect(elements[1].index == 1) + #expect(elements[2].index == 2) + } + + @Test + func canonicalID() { + var id = ViewList.ID(implicitID: 42) + let canonical1 = id.canonicalID + #expect(canonical1.index == 0) + #expect(canonical1.requiresImplicitID == true) + #expect(canonical1.explicitID == nil) + #expect(canonical1.description == "@0") + + #if canImport(Darwin) + id.bind(explicitID: "test", owner: .nil) + let canonical2 = id.canonicalID + #expect(canonical2.index == 0) + #expect(canonical2.requiresImplicitID == true) + #expect(canonical2.explicitID?.description == "test") + #expect(canonical2.description == "test") + #endif + } + + @Test + func explicitIDOperations() { + #if canImport(Darwin) + var id = ViewList.ID() + + // Test binding single explicit ID + id.bind(explicitID: "test1", owner: .nil) + #expect(id.primaryExplicitID?.description == "test1") + #expect(id.allExplicitIDs.count == 1) + + // Test binding multiple explicit IDs + id.bind(explicitID: "test2", owner: .nil) + #expect(id.allExplicitIDs.count == 2) + #expect(id.primaryExplicitID?.description == "test1") + + // Test explicit ID lookup + let stringID: String? = id.explicitID(for: String.self) + #expect(stringID == "test1") + + // Test static explicit ID creation + let explicitID = ViewList.ID.explicit("test3") + #expect(explicitID.primaryExplicitID?.description == "test3") + #endif + } + + @Test + func hashableConformance() { + let id1 = ViewList.ID(implicitID: 1) + let id2 = ViewList.ID(implicitID: 1) + let id3 = ViewList.ID(implicitID: 2) + + #expect(id1 == id2) + #expect(id1 != id3) + + var hasher = Hasher() + id1.hash(into: &hasher) + id2.hash(into: &hasher) + // Just verifying that hashing doesn't crash + } +}