1
0

DynamicKeyPath.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #if canImport(Darwin)
  2. import func Darwin.memcmp
  3. #elseif canImport(Glibc)
  4. import func Glibc.memcmp
  5. #elseif canImport(WinSDK)
  6. import func WinSDK.memcmp
  7. #elseif canImport(Android)
  8. import func Android.memcmp
  9. #endif
  10. /// A type similar to KeyPath, but that can be constructed at run time given
  11. /// an instance of a struct, and the value of the desired property.
  12. ///
  13. /// Construction fails if the property's in-memory representation is not unique within the
  14. /// struct. SwiftCrossUI only uses `DynamicKeyPath` in situations where it is
  15. /// highly likely for properties to have unique in-memory representations, such
  16. /// as when properties have internal storage pointers.
  17. struct DynamicKeyPath<Base, Value> {
  18. /// The property's offset within instances of `Base`.
  19. var offset: Int
  20. /// Constructs a key path given an instance of the base type, and the
  21. /// value of the desired property.
  22. ///
  23. /// The initializer will search through the base instance's in-memory
  24. /// representation to find the unique offset that matches the representation
  25. /// of the given property value. If such an offset can't be found or isn't
  26. /// unique, then the initialiser returns `nil`.
  27. ///
  28. /// - Parameters:
  29. /// - value: The value to construct a key path to.
  30. /// - base: The value to construct a key path from.
  31. /// - label: The property's label, if any. Only used for debugging.
  32. init?(
  33. forProperty value: Value,
  34. of base: Base,
  35. label: String? = nil
  36. ) {
  37. let propertyAlignment = MemoryLayout<Value>.alignment
  38. let propertySize = MemoryLayout<Value>.size
  39. let baseStructSize = MemoryLayout<Base>.size
  40. var index = 0
  41. var matches: [Int] = []
  42. while index + propertySize <= baseStructSize {
  43. let isMatch =
  44. withUnsafeBytes(of: base) { viewPointer in
  45. withUnsafeBytes(of: value) { valuePointer in
  46. let viewBytes = UnsafeRawBufferPointer(start: viewPointer.baseAddress!.advanced(by: index), count: propertySize)
  47. return viewBytes.elementsEqual(valuePointer)
  48. }
  49. }
  50. if isMatch {
  51. matches.append(index)
  52. }
  53. index += propertyAlignment
  54. }
  55. guard let offset = matches.first else {
  56. logger.warning(
  57. "no offset found for dynamic property",
  58. metadata: ["property": "\(label ?? "<unknown>")"]
  59. )
  60. return nil
  61. }
  62. guard matches.count == 1 else {
  63. logger.warning(
  64. "multiple offsets found for dynamic property",
  65. metadata: ["property": "\(label ?? "<unknown>")"]
  66. )
  67. return nil
  68. }
  69. self.offset = offset
  70. }
  71. /// Gets the property's value on the given instance.
  72. ///
  73. /// - Parameter base: The instance to get the property on.
  74. /// - Returns: This property's value on `base`.
  75. func get(_ base: Base) -> Value {
  76. withUnsafeBytes(of: base) { buffer in
  77. buffer.baseAddress!.advanced(by: offset)
  78. .assumingMemoryBound(to: Value.self)
  79. .pointee
  80. }
  81. }
  82. /// Sets the property's value to a new value on the given instance.
  83. ///
  84. /// - Parameters:
  85. /// - base: The instance to set the property on.
  86. /// - newValue: The new value of the property.
  87. func set(_ base: inout Base, _ newValue: Value) {
  88. withUnsafeMutableBytes(of: &base) { buffer in
  89. buffer.baseAddress!.advanced(by: offset)
  90. .assumingMemoryBound(to: Value.self)
  91. .pointee = newValue
  92. }
  93. }
  94. }