Skip to content

Commit 499da60

Browse files
authored
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
1 parent b3ce729 commit 499da60

File tree

4 files changed

+383
-105
lines changed

4 files changed

+383
-105
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
//
2+
// DynamicPropertyBuffer.swift
3+
// OpenSwiftUI
4+
//
5+
// Created by Kyle on 2023/9/24.
6+
// Lastest Version: iOS 15.5
7+
// Status: Complete
8+
// ID: 68550FF604D39F05971FE35A26EE75B0
9+
10+
internal import OpenGraphShims
11+
12+
private let nullPtr: UnsafeMutableRawPointer = Unmanaged.passUnretained(unsafeBitCast(0, to: AnyObject.self)).toOpaque()
13+
14+
public struct _DynamicPropertyBuffer {
15+
private(set) var buf: UnsafeMutableRawPointer
16+
private(set) var size: Int32
17+
private(set) var _count: Int32
18+
19+
init() {
20+
buf = nullPtr
21+
size = 0
22+
_count = 0
23+
}
24+
25+
init<Value>(
26+
fields: DynamicPropertyCache.Fields,
27+
container: _GraphValue<Value>,
28+
inputs: inout _GraphInputs,
29+
baseOffset: Int
30+
) {
31+
self.init()
32+
addFields(fields, container: container, inputs: &inputs, baseOffset: baseOffset)
33+
}
34+
35+
mutating func addFields<Value>(
36+
_ fields: DynamicPropertyCache.Fields,
37+
container: _GraphValue<Value>,
38+
inputs: inout _GraphInputs,
39+
baseOffset: Int
40+
) {
41+
switch fields.layout {
42+
case let .product(fieldArray):
43+
for field in fieldArray {
44+
field.type._makeProperty(
45+
in: &self,
46+
container: container,
47+
fieldOffset: field.offset &+ baseOffset,
48+
inputs: &inputs
49+
)
50+
}
51+
case let .sum(type, taggedFields):
52+
guard !taggedFields.isEmpty else {
53+
return
54+
}
55+
let size = MemoryLayout<(Item, EnumBox)>.stride
56+
let pointer = allocate(bytes: size)
57+
func project<Enum>(type _: Enum.Type) {
58+
pointer
59+
.assumingMemoryBound(to: Item.self)
60+
.initialize(to: Item(vtable: EnumVTable<Enum>.self, size: size, fieldOffset: baseOffset))
61+
}
62+
_openExistential(type, do: project)
63+
pointer
64+
.advanced(by: MemoryLayout<Item>.size)
65+
.assumingMemoryBound(to: EnumBox.self)
66+
.initialize(to: EnumBox(
67+
cases: taggedFields.map { taggedField in
68+
(
69+
taggedField.tag,
70+
_DynamicPropertyBuffer(
71+
fields: DynamicPropertyCache.Fields(layout: .product(taggedField.fields)),
72+
container: container,
73+
inputs: &inputs,
74+
baseOffset: 0
75+
)
76+
)
77+
},
78+
active: nil
79+
))
80+
_count &+= 1
81+
}
82+
}
83+
84+
mutating func append<Box: DynamicPropertyBox>(_ box: Box, fieldOffset: Int) {
85+
let size = MemoryLayout<(Item, Box)>.stride
86+
let pointer = allocate(bytes: size)
87+
let item = Item(vtable: BoxVTable<Box>.self, size: size, fieldOffset: fieldOffset)
88+
pointer
89+
.assumingMemoryBound(to: Item.self)
90+
.initialize(to: item)
91+
pointer
92+
.advanced(by: MemoryLayout<Item>.size)
93+
.assumingMemoryBound(to: Box.self)
94+
.initialize(to: box)
95+
_count &+= 1
96+
}
97+
98+
func destroy() {
99+
precondition(_count >= 0)
100+
var count = _count
101+
var pointer = buf
102+
while count > 0 {
103+
let itemPointer = pointer.assumingMemoryBound(to: Item.self)
104+
let boxPointer = pointer.advanced(by: MemoryLayout<Item>.size)
105+
itemPointer.pointee.vtable.deinitialize(ptr: boxPointer)
106+
// TODO: OSSignpost
107+
pointer += Int(itemPointer.pointee.size)
108+
count &-= 1
109+
}
110+
if size > 0 {
111+
buf.deallocate()
112+
}
113+
}
114+
115+
func reset() {
116+
precondition(_count >= 0)
117+
var count = _count
118+
var pointer = buf
119+
while count > 0 {
120+
let itemPointer = pointer.assumingMemoryBound(to: Item.self)
121+
let boxPointer = pointer.advanced(by: MemoryLayout<Item>.size)
122+
itemPointer.pointee.vtable.reset(ptr: boxPointer)
123+
pointer += Int(itemPointer.pointee.size)
124+
count &-= 1
125+
}
126+
}
127+
128+
func getState<Value>(type: Value.Type) -> Binding<Value>? {
129+
precondition(_count >= 0)
130+
var count = _count
131+
var pointer = buf
132+
while count > 0 {
133+
let itemPointer = pointer.assumingMemoryBound(to: Item.self)
134+
let boxPointer = pointer.advanced(by: MemoryLayout<Item>.size)
135+
if let binding = itemPointer.pointee.vtable.getState(ptr: boxPointer, type: type) {
136+
return binding
137+
}
138+
pointer += Int(itemPointer.pointee.size)
139+
count &-= 1
140+
}
141+
return nil
142+
}
143+
144+
func update(container: UnsafeMutableRawPointer, phase: _GraphInputs.Phase) -> Bool {
145+
precondition(_count >= 0)
146+
var isUpdated = false
147+
var count = _count
148+
var pointer = buf
149+
while count > 0 {
150+
let itemPointer = pointer.assumingMemoryBound(to: Item.self)
151+
let boxPointer = pointer.advanced(by: MemoryLayout<Item>.size)
152+
let propertyPointer = container.advanced(by: Int(itemPointer.pointee.fieldOffset))
153+
let updateResult = itemPointer.pointee.vtable.update(
154+
ptr: boxPointer,
155+
property: propertyPointer,
156+
phase: phase
157+
)
158+
itemPointer.pointee.lastChanged = updateResult
159+
isUpdated = isUpdated || updateResult
160+
pointer += Int(itemPointer.pointee.size)
161+
count &-= 1
162+
}
163+
return isUpdated
164+
}
165+
166+
private mutating func allocate(bytes: Int) -> UnsafeMutableRawPointer {
167+
precondition(_count >= 0)
168+
var count = _count
169+
var pointer = buf
170+
while count > 0 {
171+
let itemPointer = pointer.assumingMemoryBound(to: Item.self)
172+
pointer += Int(itemPointer.pointee.size)
173+
count &-= 1
174+
}
175+
return if Int(size) - buf.distance(to: pointer) >= bytes {
176+
pointer
177+
} else {
178+
allocateSlow(bytes: bytes, ptr: pointer)
179+
}
180+
}
181+
182+
private mutating func allocateSlow(bytes: Int, ptr: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
183+
let oldSize = Int(size)
184+
var allocSize = max(oldSize &* 2, 64)
185+
let expectedSize = oldSize + bytes
186+
while allocSize < expectedSize {
187+
allocSize &*= 2
188+
}
189+
let allocatedBuffer = UnsafeMutableRawPointer.allocate(
190+
byteCount: allocSize,
191+
alignment: .zero
192+
)
193+
var count = UInt(_count)
194+
var newBuffer = allocatedBuffer
195+
var oldBuffer = buf
196+
while count > 0 {
197+
let newItemPointer = newBuffer.assumingMemoryBound(to: Item.self)
198+
let oldItemPointer = oldBuffer.assumingMemoryBound(to: Item.self)
199+
newItemPointer.initialize(to: oldItemPointer.pointee)
200+
oldItemPointer.pointee.vtable.moveInitialize(
201+
ptr: newBuffer.advanced(by: MemoryLayout<Item>.size),
202+
from: oldBuffer.advanced(by: MemoryLayout<Item>.size)
203+
)
204+
let itemSize = Int(oldItemPointer.pointee.size)
205+
newBuffer += itemSize
206+
oldBuffer += itemSize
207+
count &-= 1
208+
}
209+
oldBuffer = buf
210+
if size > 0 {
211+
oldBuffer.deallocate()
212+
}
213+
buf = allocatedBuffer
214+
size = Int32(allocSize)
215+
return allocatedBuffer.advanced(by: oldBuffer.distance(to: ptr))
216+
}
217+
}
218+
219+
extension _DynamicPropertyBuffer {
220+
private struct Item {
221+
init(vtable: BoxVTableBase.Type, size: Int, fieldOffset: Int) {
222+
self.vtable = vtable
223+
self.size = Int32(size)
224+
self._fieldOffsetAndLastChanged = UInt32(Int32(fieldOffset))
225+
}
226+
227+
private(set) var vtable: BoxVTableBase.Type
228+
private(set) var size: Int32
229+
private var _fieldOffsetAndLastChanged: UInt32
230+
231+
@inline(__always)
232+
private static var fieldOffsetMask: UInt32 { 0x7FFF_FFFF }
233+
var fieldOffset: Int32 {
234+
Int32(bitPattern: _fieldOffsetAndLastChanged & Item.fieldOffsetMask)
235+
}
236+
237+
@inline(__always)
238+
private static var lastChangedMask: UInt32 { 0x8000_0000 }
239+
var lastChanged: Bool {
240+
get { (_fieldOffsetAndLastChanged & Item.fieldOffsetMask) == Item.fieldOffsetMask }
241+
set {
242+
if newValue {
243+
_fieldOffsetAndLastChanged |= Item.lastChangedMask
244+
} else {
245+
_fieldOffsetAndLastChanged &= ~Item.lastChangedMask
246+
}
247+
}
248+
}
249+
}
250+
}
251+
252+
// MARK: - BoxVTableBase
253+
254+
private class BoxVTableBase {
255+
class func moveInitialize(
256+
ptr _: UnsafeMutableRawPointer,
257+
from _: UnsafeMutableRawPointer
258+
) {
259+
fatalError()
260+
}
261+
262+
class func deinitialize(ptr _: UnsafeMutableRawPointer) {}
263+
264+
class func reset(ptr _: UnsafeMutableRawPointer) {}
265+
266+
class func update(
267+
ptr _: UnsafeMutableRawPointer,
268+
property _: UnsafeMutableRawPointer,
269+
phase _: _GraphInputs.Phase
270+
) -> Bool {
271+
false
272+
}
273+
274+
class func getState<Value>(
275+
ptr _: UnsafeMutableRawPointer,
276+
type _: Value.Type
277+
) -> Binding<Value>? {
278+
nil
279+
}
280+
}
281+
282+
// MARK: - BoxVTable
283+
284+
private class BoxVTable<Box: DynamicPropertyBox>: BoxVTableBase {
285+
override class func moveInitialize(ptr destination: UnsafeMutableRawPointer, from: UnsafeMutableRawPointer) {
286+
let fromBoxPointer = from.assumingMemoryBound(to: Box.self)
287+
let destinationBoxPointer = destination.assumingMemoryBound(to: Box.self)
288+
destinationBoxPointer.initialize(to: fromBoxPointer.move())
289+
}
290+
291+
override class func deinitialize(ptr: UnsafeMutableRawPointer) {
292+
let boxPointer = ptr.assumingMemoryBound(to: Box.self)
293+
boxPointer.pointee.destroy()
294+
boxPointer.deinitialize(count: 1)
295+
}
296+
297+
override class func reset(ptr: UnsafeMutableRawPointer) {
298+
let boxPointer = ptr.assumingMemoryBound(to: Box.self)
299+
boxPointer.pointee.reset()
300+
}
301+
302+
override class func update(
303+
ptr: UnsafeMutableRawPointer,
304+
property: UnsafeMutableRawPointer,
305+
phase: _GraphInputs.Phase
306+
) -> Bool {
307+
let boxPointer = ptr.assumingMemoryBound(to: Box.self)
308+
let propertyPointer = property.assumingMemoryBound(to: Box.Property.self)
309+
let isUpdated = boxPointer.pointee.update(property: &propertyPointer.pointee, phase: phase)
310+
if isUpdated {
311+
// TODO: OSSignpost
312+
}
313+
return isUpdated
314+
}
315+
316+
override class func getState<Value>(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding<Value>? {
317+
let boxPointer = ptr.assumingMemoryBound(to: Box.self)
318+
return boxPointer.pointee.getState(type: type)
319+
}
320+
}
321+
322+
// MARK: - EnumVTable
323+
324+
private struct EnumBox {
325+
var cases: [(tag: Int, links: _DynamicPropertyBuffer)]
326+
var active: (tag: Swift.Int, index: Swift.Int)?
327+
}
328+
329+
private class EnumVTable<Enum>: BoxVTableBase {
330+
override class func moveInitialize(ptr destination: UnsafeMutableRawPointer, from: UnsafeMutableRawPointer) {
331+
let fromBoxPointer = from.assumingMemoryBound(to: EnumBox.self)
332+
let destinationBoxPointer = destination.assumingMemoryBound(to: EnumBox.self)
333+
destinationBoxPointer.initialize(to: fromBoxPointer.move())
334+
}
335+
336+
override class func deinitialize(ptr: UnsafeMutableRawPointer) {
337+
let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self)
338+
for (_, links) in boxPointer.pointee.cases {
339+
links.destroy()
340+
}
341+
}
342+
343+
override class func reset(ptr: UnsafeMutableRawPointer) {
344+
let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self)
345+
guard let (_, index) = boxPointer.pointee.active else {
346+
return
347+
}
348+
boxPointer.pointee.cases[index].links.reset()
349+
boxPointer.pointee.active = nil
350+
}
351+
352+
override class func update(ptr: UnsafeMutableRawPointer, property: UnsafeMutableRawPointer, phase: _GraphInputs.Phase) -> Bool {
353+
var isUpdated = false
354+
withUnsafeMutablePointerToEnumCase(of: property.assumingMemoryBound(to: Enum.self)) { tag, _, pointer in
355+
let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self)
356+
if let (activeTag, index) = boxPointer.pointee.active, activeTag != tag {
357+
boxPointer.pointee.cases[index].links.reset()
358+
boxPointer.pointee.active = nil
359+
isUpdated = true
360+
}
361+
if boxPointer.pointee.active == nil {
362+
guard let matchedIndex = boxPointer.pointee.cases.firstIndex(where: { $0.tag == tag }) else {
363+
return
364+
}
365+
boxPointer.pointee.active = (tag, matchedIndex)
366+
isUpdated = true
367+
}
368+
if let (_, index) = boxPointer.pointee.active {
369+
isUpdated = boxPointer.pointee.cases[index].links.update(container: pointer, phase: phase)
370+
}
371+
}
372+
return isUpdated
373+
}
374+
375+
override class func getState<Value>(ptr: UnsafeMutableRawPointer, type: Value.Type) -> Binding<Value>? {
376+
let boxPointer = ptr.assumingMemoryBound(to: EnumBox.self)
377+
guard let (_, index) = boxPointer.pointee.active else {
378+
return nil
379+
}
380+
return boxPointer.pointee.cases[index].links.getState(type: type)
381+
}
382+
}

Sources/OpenSwiftUI/DataAndStorage/ModelData/DynamicProperty/DynamicPropertyCache.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ struct DynamicPropertyCache {
5454
fields = Fields(layout: .product(fieldArray))
5555
default:
5656
fields = Fields(layout: .product([]))
57-
break
5857
}
5958
if fields.behaviors.contains(.init(rawValue: 3)) {
6059
Log.runtimeIssues("%s is marked async, but contains properties that require the main thread.", [_typeName(type)])

0 commit comments

Comments
 (0)