Skip to content

Update DynamicProperty implementation #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"location" : "https://github.com/OpenSwiftUIProject/OpenGraph",
"state" : {
"branch" : "main",
"revision" : "ca3f8991a8a9564d581a08d9ce43b216c9afe01b"
"revision" : "d4f467d9780fcca4c268a1242a8ae90f61377c3d"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Value>(
fields: DynamicPropertyCache.Fields,
container: _GraphValue<Value>,
inputs: inout _GraphInputs,
baseOffset: Int
) {
self.init()
addFields(fields, container: container, inputs: &inputs, baseOffset: baseOffset)
}

mutating func addFields<Value>(
_ fields: DynamicPropertyCache.Fields,
container: _GraphValue<Value>,
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<Enum>(type _: Enum.Type) {
pointer
.assumingMemoryBound(to: Item.self)
.initialize(to: Item(vtable: EnumVTable<Enum>.self, size: size, fieldOffset: baseOffset))
}
_openExistential(type, do: project)
pointer
.advanced(by: MemoryLayout<Item>.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: DynamicPropertyBox>(_ box: Box, fieldOffset: Int) {
let size = MemoryLayout<(Item, Box)>.stride
let pointer = allocate(bytes: size)
let item = Item(vtable: BoxVTable<Box>.self, size: size, fieldOffset: fieldOffset)
pointer
.assumingMemoryBound(to: Item.self)
.initialize(to: item)
pointer
.advanced(by: MemoryLayout<Item>.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<Item>.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<Item>.size)
itemPointer.pointee.vtable.reset(ptr: boxPointer)
pointer += Int(itemPointer.pointee.size)
count &-= 1
}
}

func getState<Value>(type: Value.Type) -> Binding<Value>? {
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<Item>.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<Item>.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<Item>.size),
from: oldBuffer.advanced(by: MemoryLayout<Item>.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<Value>(
ptr _: UnsafeMutableRawPointer,
type _: Value.Type
) -> Binding<Value>? {
nil
}
}

// MARK: - BoxVTable

private class BoxVTable<Box: DynamicPropertyBox>: 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<Value>(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding<Value>? {
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<Enum>: 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<Value>(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding<Value>? {
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)])
Expand Down
Loading