From 499da6008370dc86f717c48ec85411a4ed3780aa Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 31 Jan 2024 01:43:20 +0800 Subject: [PATCH] Update DynamicProperty implementation (#30) * Update DynamicPropertyBuffer file name * Add BoxVTable * Add EnumVTable implementation * Update _DynamicPropertyBuffer.addFields implementation * Add allocate implementation * Update allocateSlow implementation * Update DynamicPropertyBuffer implementation * Format code of DynamicPropertyBuffer * Update OpenGraph dependency version --- Package.resolved | 2 +- .../DynamicPropertyBuffer.swift | 382 ++++++++++++++++++ .../DynamicPropertyCache.swift | 1 - .../_DynamicPropertyBuffer.swift | 103 ----- 4 files changed, 383 insertions(+), 105 deletions(-) create mode 100644 Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyBuffer.swift delete mode 100644 Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/_DynamicPropertyBuffer.swift diff --git a/Package.resolved b/Package.resolved index 4a232132..ac93c93d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,7 +15,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "ca3f8991a8a9564d581a08d9ce43b216c9afe01b" + "revision" : "d4f467d9780fcca4c268a1242a8ae90f61377c3d" } }, { diff --git a/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyBuffer.swift b/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyBuffer.swift new file mode 100644 index 00000000..7c194f7e --- /dev/null +++ b/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyBuffer.swift @@ -0,0 +1,382 @@ +// +// DynamicPropertyBuffer.swift +// OpenSwiftUI +// +// Created by Kyle on 2023/9/24. +// Lastest Version: iOS 15.5 +// Status: Complete +// ID: 68550FF604D39F05971FE35A26EE75B0 + +internal import OpenGraphShims + +private let nullPtr: UnsafeMutableRawPointer = Unmanaged.passUnretained(unsafeBitCast(0, to: AnyObject.self)).toOpaque() + +public struct _DynamicPropertyBuffer { + private(set) var buf: UnsafeMutableRawPointer + private(set) var size: Int32 + private(set) var _count: Int32 + + init() { + buf = nullPtr + size = 0 + _count = 0 + } + + init( + fields: DynamicPropertyCache.Fields, + container: _GraphValue, + inputs: inout _GraphInputs, + baseOffset: Int + ) { + self.init() + addFields(fields, container: container, inputs: &inputs, baseOffset: baseOffset) + } + + mutating func addFields( + _ fields: DynamicPropertyCache.Fields, + container: _GraphValue, + inputs: inout _GraphInputs, + baseOffset: Int + ) { + switch fields.layout { + case let .product(fieldArray): + for field in fieldArray { + field.type._makeProperty( + in: &self, + container: container, + fieldOffset: field.offset &+ baseOffset, + inputs: &inputs + ) + } + case let .sum(type, taggedFields): + guard !taggedFields.isEmpty else { + return + } + let size = MemoryLayout<(Item, EnumBox)>.stride + let pointer = allocate(bytes: size) + func project(type _: Enum.Type) { + pointer + .assumingMemoryBound(to: Item.self) + .initialize(to: Item(vtable: EnumVTable.self, size: size, fieldOffset: baseOffset)) + } + _openExistential(type, do: project) + pointer + .advanced(by: MemoryLayout.size) + .assumingMemoryBound(to: EnumBox.self) + .initialize(to: EnumBox( + cases: taggedFields.map { taggedField in + ( + taggedField.tag, + _DynamicPropertyBuffer( + fields: DynamicPropertyCache.Fields(layout: .product(taggedField.fields)), + container: container, + inputs: &inputs, + baseOffset: 0 + ) + ) + }, + active: nil + )) + _count &+= 1 + } + } + + mutating func append(_ box: Box, fieldOffset: Int) { + let size = MemoryLayout<(Item, Box)>.stride + let pointer = allocate(bytes: size) + let item = Item(vtable: BoxVTable.self, size: size, fieldOffset: fieldOffset) + pointer + .assumingMemoryBound(to: Item.self) + .initialize(to: item) + pointer + .advanced(by: MemoryLayout.size) + .assumingMemoryBound(to: Box.self) + .initialize(to: box) + _count &+= 1 + } + + func destroy() { + precondition(_count >= 0) + var count = _count + var pointer = buf + while count > 0 { + let itemPointer = pointer.assumingMemoryBound(to: Item.self) + let boxPointer = pointer.advanced(by: MemoryLayout.size) + itemPointer.pointee.vtable.deinitialize(ptr: boxPointer) + // TODO: OSSignpost + pointer += Int(itemPointer.pointee.size) + count &-= 1 + } + if size > 0 { + buf.deallocate() + } + } + + func reset() { + precondition(_count >= 0) + var count = _count + var pointer = buf + while count > 0 { + let itemPointer = pointer.assumingMemoryBound(to: Item.self) + let boxPointer = pointer.advanced(by: MemoryLayout.size) + itemPointer.pointee.vtable.reset(ptr: boxPointer) + pointer += Int(itemPointer.pointee.size) + count &-= 1 + } + } + + func getState(type: Value.Type) -> Binding? { + precondition(_count >= 0) + var count = _count + var pointer = buf + while count > 0 { + let itemPointer = pointer.assumingMemoryBound(to: Item.self) + let boxPointer = pointer.advanced(by: MemoryLayout.size) + if let binding = itemPointer.pointee.vtable.getState(ptr: boxPointer, type: type) { + return binding + } + pointer += Int(itemPointer.pointee.size) + count &-= 1 + } + return nil + } + + func update(container: UnsafeMutableRawPointer, phase: _GraphInputs.Phase) -> Bool { + precondition(_count >= 0) + var isUpdated = false + var count = _count + var pointer = buf + while count > 0 { + let itemPointer = pointer.assumingMemoryBound(to: Item.self) + let boxPointer = pointer.advanced(by: MemoryLayout.size) + let propertyPointer = container.advanced(by: Int(itemPointer.pointee.fieldOffset)) + let updateResult = itemPointer.pointee.vtable.update( + ptr: boxPointer, + property: propertyPointer, + phase: phase + ) + itemPointer.pointee.lastChanged = updateResult + isUpdated = isUpdated || updateResult + pointer += Int(itemPointer.pointee.size) + count &-= 1 + } + return isUpdated + } + + private mutating func allocate(bytes: Int) -> UnsafeMutableRawPointer { + precondition(_count >= 0) + var count = _count + var pointer = buf + while count > 0 { + let itemPointer = pointer.assumingMemoryBound(to: Item.self) + pointer += Int(itemPointer.pointee.size) + count &-= 1 + } + return if Int(size) - buf.distance(to: pointer) >= bytes { + pointer + } else { + allocateSlow(bytes: bytes, ptr: pointer) + } + } + + private mutating func allocateSlow(bytes: Int, ptr: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + let oldSize = Int(size) + var allocSize = max(oldSize &* 2, 64) + let expectedSize = oldSize + bytes + while allocSize < expectedSize { + allocSize &*= 2 + } + let allocatedBuffer = UnsafeMutableRawPointer.allocate( + byteCount: allocSize, + alignment: .zero + ) + var count = UInt(_count) + var newBuffer = allocatedBuffer + var oldBuffer = buf + while count > 0 { + let newItemPointer = newBuffer.assumingMemoryBound(to: Item.self) + let oldItemPointer = oldBuffer.assumingMemoryBound(to: Item.self) + newItemPointer.initialize(to: oldItemPointer.pointee) + oldItemPointer.pointee.vtable.moveInitialize( + ptr: newBuffer.advanced(by: MemoryLayout.size), + from: oldBuffer.advanced(by: MemoryLayout.size) + ) + let itemSize = Int(oldItemPointer.pointee.size) + newBuffer += itemSize + oldBuffer += itemSize + count &-= 1 + } + oldBuffer = buf + if size > 0 { + oldBuffer.deallocate() + } + buf = allocatedBuffer + size = Int32(allocSize) + return allocatedBuffer.advanced(by: oldBuffer.distance(to: ptr)) + } +} + +extension _DynamicPropertyBuffer { + private struct Item { + init(vtable: BoxVTableBase.Type, size: Int, fieldOffset: Int) { + self.vtable = vtable + self.size = Int32(size) + self._fieldOffsetAndLastChanged = UInt32(Int32(fieldOffset)) + } + + private(set) var vtable: BoxVTableBase.Type + private(set) var size: Int32 + private var _fieldOffsetAndLastChanged: UInt32 + + @inline(__always) + private static var fieldOffsetMask: UInt32 { 0x7FFF_FFFF } + var fieldOffset: Int32 { + Int32(bitPattern: _fieldOffsetAndLastChanged & Item.fieldOffsetMask) + } + + @inline(__always) + private static var lastChangedMask: UInt32 { 0x8000_0000 } + var lastChanged: Bool { + get { (_fieldOffsetAndLastChanged & Item.fieldOffsetMask) == Item.fieldOffsetMask } + set { + if newValue { + _fieldOffsetAndLastChanged |= Item.lastChangedMask + } else { + _fieldOffsetAndLastChanged &= ~Item.lastChangedMask + } + } + } + } +} + +// MARK: - BoxVTableBase + +private class BoxVTableBase { + class func moveInitialize( + ptr _: UnsafeMutableRawPointer, + from _: UnsafeMutableRawPointer + ) { + fatalError() + } + + class func deinitialize(ptr _: UnsafeMutableRawPointer) {} + + class func reset(ptr _: UnsafeMutableRawPointer) {} + + class func update( + ptr _: UnsafeMutableRawPointer, + property _: UnsafeMutableRawPointer, + phase _: _GraphInputs.Phase + ) -> Bool { + false + } + + class func getState( + ptr _: UnsafeMutableRawPointer, + type _: Value.Type + ) -> Binding? { + nil + } +} + +// MARK: - BoxVTable + +private class BoxVTable: BoxVTableBase { + override class func moveInitialize(ptr destination: UnsafeMutableRawPointer, from: UnsafeMutableRawPointer) { + let fromBoxPointer = from.assumingMemoryBound(to: Box.self) + let destinationBoxPointer = destination.assumingMemoryBound(to: Box.self) + destinationBoxPointer.initialize(to: fromBoxPointer.move()) + } + + override class func deinitialize(ptr: UnsafeMutableRawPointer) { + let boxPointer = ptr.assumingMemoryBound(to: Box.self) + boxPointer.pointee.destroy() + boxPointer.deinitialize(count: 1) + } + + override class func reset(ptr: UnsafeMutableRawPointer) { + let boxPointer = ptr.assumingMemoryBound(to: Box.self) + boxPointer.pointee.reset() + } + + override class func update( + ptr: UnsafeMutableRawPointer, + property: UnsafeMutableRawPointer, + phase: _GraphInputs.Phase + ) -> Bool { + let boxPointer = ptr.assumingMemoryBound(to: Box.self) + let propertyPointer = property.assumingMemoryBound(to: Box.Property.self) + let isUpdated = boxPointer.pointee.update(property: &propertyPointer.pointee, phase: phase) + if isUpdated { + // TODO: OSSignpost + } + return isUpdated + } + + override class func getState(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding? { + let boxPointer = ptr.assumingMemoryBound(to: Box.self) + return boxPointer.pointee.getState(type: type) + } +} + +// MARK: - EnumVTable + +private struct EnumBox { + var cases: [(tag: Int, links: _DynamicPropertyBuffer)] + var active: (tag: Swift.Int, index: Swift.Int)? +} + +private class EnumVTable: BoxVTableBase { + override class func moveInitialize(ptr destination: UnsafeMutableRawPointer, from: UnsafeMutableRawPointer) { + let fromBoxPointer = from.assumingMemoryBound(to: EnumBox.self) + let destinationBoxPointer = destination.assumingMemoryBound(to: EnumBox.self) + destinationBoxPointer.initialize(to: fromBoxPointer.move()) + } + + override class func deinitialize(ptr: UnsafeMutableRawPointer) { + let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self) + for (_, links) in boxPointer.pointee.cases { + links.destroy() + } + } + + override class func reset(ptr: UnsafeMutableRawPointer) { + let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self) + guard let (_, index) = boxPointer.pointee.active else { + return + } + boxPointer.pointee.cases[index].links.reset() + boxPointer.pointee.active = nil + } + + override class func update(ptr: UnsafeMutableRawPointer, property: UnsafeMutableRawPointer, phase: _GraphInputs.Phase) -> Bool { + var isUpdated = false + withUnsafeMutablePointerToEnumCase(of: property.assumingMemoryBound(to: Enum.self)) { tag, _, pointer in + let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self) + if let (activeTag, index) = boxPointer.pointee.active, activeTag != tag { + boxPointer.pointee.cases[index].links.reset() + boxPointer.pointee.active = nil + isUpdated = true + } + if boxPointer.pointee.active == nil { + guard let matchedIndex = boxPointer.pointee.cases.firstIndex(where: { $0.tag == tag }) else { + return + } + boxPointer.pointee.active = (tag, matchedIndex) + isUpdated = true + } + if let (_, index) = boxPointer.pointee.active { + isUpdated = boxPointer.pointee.cases[index].links.update(container: pointer, phase: phase) + } + } + return isUpdated + } + + override class func getState(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding? { + let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self) + guard let (_, index) = boxPointer.pointee.active else { + return nil + } + return boxPointer.pointee.cases[index].links.getState(type: type) + } +} diff --git a/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyCache.swift b/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyCache.swift index a151d03c..68fa0022 100644 --- a/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyCache.swift +++ b/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyCache.swift @@ -54,7 +54,6 @@ struct DynamicPropertyCache { fields = Fields(layout: .product(fieldArray)) default: fields = Fields(layout: .product([])) - break } if fields.behaviors.contains(.init(rawValue: 3)) { Log.runtimeIssues("%s is marked async, but contains properties that require the main thread.", [_typeName(type)]) diff --git a/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/_DynamicPropertyBuffer.swift b/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/_DynamicPropertyBuffer.swift deleted file mode 100644 index 352cae42..00000000 --- a/Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/_DynamicPropertyBuffer.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// _DynamicPropertyBuffer.swift -// OpenSwiftUI -// -// Created by Kyle on 2023/9/24. -// Lastest Version: iOS 15.5 -// Status: WIP -// ID: 68550FF604D39F05971FE35A26EE75B0 - -private let nullPtr: UnsafeMutableRawPointer = Unmanaged.passUnretained(unsafeBitCast(0, to: AnyObject.self)).toOpaque() - -public struct _DynamicPropertyBuffer { - var buf: UnsafeMutableRawPointer - var size: Int32 - var _count: Int32 - - init() { - buf = nullPtr - size = 0 - _count = 0 - } - - init( - fields: DynamicPropertyCache.Fields, - container: _GraphValue, - inputs: inout _GraphInputs, - baseOffset: Int - ) { - self.init() - addFields(fields, container: container, inputs: &inputs, baseOffset: baseOffset) - } - - - mutating func addFields( - _ fields: DynamicPropertyCache.Fields, - container: _GraphValue, - inputs: inout _GraphInputs, - baseOffset: Int - ) { - switch fields.layout { - case let .product(fieldArray): - for field in fieldArray { - field.type._makeProperty( - in: &self, - container: container, - fieldOffset: field.offset + baseOffset, - inputs: &inputs - ) - } - case let .sum(_, taggedFields): - guard !taggedFields.isEmpty else { - return - } - // TODO - } - } - - func append(_: some DynamicPropertyBox, fieldOffset _: Int) { - // TODO - } -} - -extension _DynamicPropertyBuffer { - private struct Item { - var vtable: BoxVTableBase.Type - var size: Int32 - var _fieldOffsetAndLastChanged: UInt32 - - init(vtable: BoxVTableBase, size: Int, fieldOffset: Int) { - - fatalError("TODO") - -// self.vtable = vtable -// self.size = size -// self._fieldOffsetAndLastChanged = _fieldOffsetAndLastChanged -// if size < 0 { -// -// } else { -// -// } - } - } -} - -// MARK: - BoxVTableBase - -private class BoxVTableBase { - static func moveInitialize(ptr _: UnsafeMutableRawPointer, from _: UnsafeMutableRawPointer) { - fatalError() - } - - static func deinitialize(ptr _: UnsafeMutableRawPointer) {} - - static func reset(ptr _: UnsafeMutableRawPointer) {} - - static func update(ptr _: UnsafeMutableRawPointer, property _: UnsafeMutableRawPointer, phase _: _GraphInputs.Phase) -> Bool { - false - } - - static func getState(ptr _: UnsafeMutableRawPointer, type _: Value.Type) -> Binding? { - nil - } -}