Skip to content

Commit

Permalink
fix(collection): Fixed assign not working with custom collection (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris committed May 19, 2023
1 parent 71cf0df commit 8ec5720
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 51 deletions.
4 changes: 2 additions & 2 deletions Sources/CohesionKit/KeyPath/PartialIdentifiableKeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 45,7 @@ public struct PartialIdentifiableKeyPath<Root> {
}
}

public init<C: MutableCollection>(_ keyPath: KeyPath<Root, C>) where C.Element: Identifiable, C.Index: Hashable {
public init<C: BufferedCollection>(_ keyPath: KeyPath<Root, C>) where C.Element: Identifiable, C.Index: Hashable {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
visitor.visit(
Expand All @@ -55,7 55,7 @@ public struct PartialIdentifiableKeyPath<Root> {
}
}

public init<C: MutableCollection>(_ keyPath: KeyPath<Root, C>) where C.Element: Aggregate, C.Index: Hashable {
public init<C: BufferedCollection>(_ keyPath: KeyPath<Root, C>) where C.Element: Aggregate, C.Index: Hashable {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
visitor.visit(
Expand Down
2 changes: 1 addition & 1 deletion Sources/CohesionKit/Storage/EntityNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 60,7 @@ class EntityNode<T>: AnyEntityNode {
}

/// observe one of the node child whose type is a collection
func observeChild<C: MutableCollection>(_ childNode: EntityNode<C.Element>, for keyPath: KeyPath<T, C>, index: C.Index)
func observeChild<C: BufferedCollection>(_ childNode: EntityNode<C.Element>, for keyPath: KeyPath<T, C>, index: C.Index)
where C.Index: Hashable {
observeChild(childNode, identity: keyPath.appending(path: \C[index])) { pointer, newValue in
pointer.assign(newValue, to: keyPath, index: index)
Expand Down
14 changes: 14 additions & 0 deletions Sources/CohesionKit/UnsafePointer/BufferedCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 1,14 @@
import Foundation

/// A collection providing an access to its buffer.
///
/// Idea was to directly use `BufferedCollection.withContiguousStorageIfAvailable`` **but default implementation returns nil**
/// (see https://github.com/apple/swift-evolution/blob/main/proposals/0237-contiguous-collection.md)
public protocol BufferedCollection: Collection {
/// Provides an access to collection inner buffer.
///
/// Don't forward to `withContiguousStorageIfAvailable` which default implementation returns nil 🤦‍♂️
mutating func withUnsafeMutableBufferPointer<R>(_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R) rethrows -> R
}

extension Array: BufferedCollection { }
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 11,7 @@ extension UnsafeMutablePointer {
unsafeValuePointer.pointee = value
}

func assign<C: MutableCollection>(_ value: C.Element, to keyPath: KeyPath<Pointee, C>, index: C.Index) {
func assign<C: BufferedCollection>(_ value: C.Element, to keyPath: KeyPath<Pointee, C>, index: C.Index) {

guard let unsafeCollectionPointer = UnsafeMutablePointer<C>(mutating: pointer(to: keyPath)) else {
fatalError("cannot update value for KeyPath<\(Pointee.self), \(C.self)>: failed to access memory pointer.")
Expand All @@ -22,6 22,6 @@ extension UnsafeMutablePointer {
.pointee
.distance(from: unsafeCollectionPointer.pointee.startIndex, to: index)

unsafeCollectionPointer.pointee.withContiguousMutableStorageIfAvailable { $0[distance] = value }
unsafeCollectionPointer.pointee.withUnsafeMutableBufferPointer { $0[distance] = value }
}
}
4 changes: 2 additions & 2 deletions Sources/CohesionKit/Visitor/IdentityMapStoreVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 32,7 @@ struct IdentityMapStoreVisitor: NestedEntitiesVisitor {
}
}

func visit<Root, C: MutableCollection>(context: EntityContext<Root, C>, entities: C)
func visit<Root, C: BufferedCollection>(context: EntityContext<Root, C>, entities: C)
where C.Element: Identifiable, C.Index: Hashable {

for index in entities.indices {
Expand All @@ -44,7 44,7 @@ struct IdentityMapStoreVisitor: NestedEntitiesVisitor {
}
}

func visit<Root, C: MutableCollection>(context: EntityContext<Root, C>, entities: C)
func visit<Root, C: BufferedCollection>(context: EntityContext<Root, C>, entities: C)
where C.Element: Aggregate, C.Index: Hashable {

for index in entities.indices {
Expand Down
4 changes: 2 additions & 2 deletions Sources/CohesionKit/Visitor/NestedEntitiesVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 8,9 @@ protocol NestedEntitiesVisitor {
func visit<Root, T: Identifiable>(context: EntityContext<Root, T?>, entity: T?)
func visit<Root, T: Aggregate>(context: EntityContext<Root, T?>, entity: T?)

func visit<Root, C: MutableCollection>(context: EntityContext<Root, C>, entities: C)
func visit<Root, C: BufferedCollection>(context: EntityContext<Root, C>, entities: C)
where C.Element: Identifiable, C.Index: Hashable

func visit<Root, C: MutableCollection>(context: EntityContext<Root, C>, entities: C)
func visit<Root, C: BufferedCollection>(context: EntityContext<Root, C>, entities: C)
where C.Element: Aggregate, C.Index: Hashable
}
41 changes: 0 additions & 41 deletions Tests/CohesionKitTests/KeyPath/UnsafeMutablePointerTests.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 1,87 @@
import XCTest
@testable import CohesionKit

class UnsafeMutablePointerTests: XCTestCase {
func test_assign_propertyIsImmutable_propertyIsChanged() {
var hello = Hello(collection: [], singleValue: "hello")

withUnsafeMutablePointer(to: &hello) {
$0.assign("world", to: \Hello.singleValue)
}

XCTAssertEqual(hello.singleValue, "world")
}

func test_assign_keyPathIsArray_propertyIsImmutable_arrayIsChanged() {
var hello = Hello(collection: [1, 2, 3, 4], singleValue: "")

withUnsafeMutablePointer(to: &hello) {
$0.assign(5, to: \Hello.collection, index: 3)
}

XCTAssertEqual(hello.collection, [1, 2, 3, 5])
}

func test_assign_keyPathIsArray_mutipleAssignments_arrayIsChanged() {
var hello = Hello(collection: [1, 2, 3, 4], singleValue: "")

withUnsafeMutablePointer(to: &hello) {
$0.assign(4, to: \Hello.collection, index: 0)
$0.assign(3, to: \Hello.collection, index: 0)
$0.assign(2, to: \Hello.collection, index: 0)
}

XCTAssertEqual(hello.collection, [2, 2, 3, 4])
}

func test_assign_keyPathIsCustomCollection_colectionIsChanged() {
var hello = Hello(collection: [], singleValue: "")

hello.myCollection = ["1", "2"]

withUnsafeMutablePointer(to: &hello) {
$0.assign("3", to: \.myCollection, index: 0)
$0.assign("4", to: \.myCollection, index: 1)
}

XCTAssertEqual(hello.myCollection, ["3", "4"])
}
}

private struct Hello {
let collection: [Int]
let singleValue: String
var myCollection: MyCollection = []
}

struct MyCollection: BufferedCollection, Equatable, ExpressibleByArrayLiteral {
typealias Element = Array<String>.Element
typealias Index = Array<String>.Index

var elements: [String] = []

var startIndex: Index { elements.startIndex }

var endIndex: Index { elements.endIndex }

init(arrayLiteral elements: String...) {
self.elements = elements
}

subscript(position: Index) -> Element {
get {
elements[position]
}
set(newValue) {
elements[position] = newValue
}
}

func index(after i: Index) -> Index {
elements.index(after: i)
}

mutating func withUnsafeMutableBufferPointer<R>(_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R) rethrows -> R {
try elements.withUnsafeMutableBufferPointer(body)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 60,7 @@ private class EntityNodeStub<T>: EntityNode<T> {
var observeChildKeyPathIndexCalled: (AnyEntityNode, PartialKeyPath<T>, Any) -> Void = { _, _, _ in }
var observeChildKeyPathOptionalCalled: (AnyEntityNode, PartialKeyPath<T>) -> Void = { _, _ in }

override func observeChild<C: MutableCollection>(
override func observeChild<C: BufferedCollection>(
_ childNode: EntityNode<C.Element>,
for keyPath: KeyPath<T, C>,
index: C.Index
Expand Down

0 comments on commit 8ec5720

Please sign in to comment.