Skip to content
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
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
trim_trailing_whitespace = true

# 4 space indentation
[*.swift]
indent_style = space
indent_size = 4
12 changes: 6 additions & 6 deletions Sources/CohesionKit/EntityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ extension EntityStore {
@discardableResult
public func update<T: Identifiable>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
transaction {
guard var entity = storage[T.self, id: id]?.ref.value else {
guard var entity = storage[T.self, id: id]?.value else {
return false
}

Expand All @@ -275,7 +275,7 @@ extension EntityStore {
@discardableResult
public func update<T: Aggregate>(_ type: T.Type, id: T.ID, modifiedAt: Stamp? = nil, _ update: Update<T>) -> Bool {
transaction {
guard var entity = storage[T.self, id: id]?.ref.value else {
guard var entity = storage[T.self, id: id]?.value else {
return false
}

Expand All @@ -294,7 +294,7 @@ extension EntityStore {
@discardableResult
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
transaction {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
guard let aliasNode = refAliases[named], var content = aliasNode.value.content else {
return false
}

Expand All @@ -313,7 +313,7 @@ extension EntityStore {
@discardableResult
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
transaction {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
guard let aliasNode = refAliases[named], var content = aliasNode.value.content else {
return false
}

Expand All @@ -333,7 +333,7 @@ extension EntityStore {
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<C>)
-> Bool where C.Element: Identifiable {
transaction {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
guard let aliasNode = refAliases[named], var content = aliasNode.value.content else {
return false
}

Expand All @@ -353,7 +353,7 @@ extension EntityStore {
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<C>)
-> Bool where C.Element: Aggregate {
transaction {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
guard let aliasNode = refAliases[named], var content = aliasNode.value.content else {
return false
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/CohesionKit/Observer/EntityObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ public struct EntityObserver<T> {
let createObserver: (@escaping OnChange) -> Subscription

init(node: EntityNode<T>, registry: ObserverRegistry) {
self.value = node.ref.value
self.value = node.value
self.createObserver = { onChange in
registry.addObserver(node: node, initial: true, onChange: onChange)
}
}

init<Element>(nodes: [EntityNode<Element>], registry: ObserverRegistry) where T == [Element] {
self.value = nodes.map(\.ref.value)
self.value = nodes.map(\.value)
self.createObserver = { onChange in
registry.addObserver(nodes: nodes, initial: true, onChange: onChange)
}
}

init<Wrapped>(alias node: EntityNode<AliasContainer<Wrapped>>, registry: ObserverRegistry)
where T == Optional<Wrapped> {
self.init(value: node.ref.value.content) { onChange in
self.init(value: node.value.content) { onChange in
registry.addObserver(node: node, initial: true, onChange: { container in
onChange(container.content)
})
Expand Down
50 changes: 0 additions & 50 deletions Sources/CohesionKit/Observer/Observable.swift

This file was deleted.

12 changes: 6 additions & 6 deletions Sources/CohesionKit/Observer/ObserverRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class ObserverRegistry {
/// register an observer to observe changes on an entity node. Everytime `ObserverRegistry` is notified about changes
/// to this node `onChange` will be called.
func addObserver<T>(node: EntityNode<T>, key: ObjectKey, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription {
let handler = Handler { onChange($0.ref.value) }
let handler = Handler { onChange($0.value) }

if initial {
if queue == DispatchQueue.main && Thread.isMainThread {
onChange(node.ref.value)
onChange(node.value)
}
else {
queue.sync {
onChange(node.ref.value)
onChange(node.value)
}
}
}
Expand All @@ -57,16 +57,16 @@ class ObserverRegistry {
func addObserver<T>(nodes: [EntityNode<T>], initial: Bool = false, onChange: @escaping ([T]) -> Void) -> Subscription {
let handler = Handler { (_: EntityNode<T>) in
// use last value from nodes
onChange(nodes.map(\.ref.value))
onChange(nodes.map(\.value))
}

if initial {
if queue == DispatchQueue.main && Thread.isMainThread {
onChange(nodes.map(\.ref.value))
onChange(nodes.map(\.value))
}
else {
queue.sync {
onChange(nodes.map(\.ref.value))
onChange(nodes.map(\.value))
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions Sources/CohesionKit/Observer/Subscription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

public class Subscription {
public let unsubscribe: () -> Void

init(unsubscribe: @escaping () -> Void) {
var unsubscribed = false

self.unsubscribe = {
if !unsubscribed {
unsubscribe()
}

unsubscribed = true
}
}

deinit {
unsubscribe()
}
}
25 changes: 9 additions & 16 deletions Sources/CohesionKit/Storage/EntityNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ struct EntityMetadata {
protocol AnyEntityNode: AnyObject {
associatedtype Value

var ref: Observable<Value> { get }
var value: Any { get }
var value: Value { get }
var metadata: EntityMetadata { get }
var storageKey: String { get }

Expand All @@ -47,34 +46,28 @@ class EntityNode<T>: AnyEntityNode {
let node: any AnyEntityNode
}

var value: Any { ref.value }
private(set) var value: Value

var metadata = EntityMetadata()
// FIXME: to delete, it's "just" to have a strong ref and avoid nodes to be deleted. Need a better memory management
private var childrenNodes: [any AnyEntityNode] = []

var applyChildrenChanges = true
/// An observable entity reference
let ref: Observable<T>

let storageKey: String

/// last time the ref.value was changed. Any subsequent change must have a higher value to be applied
/// last time `value` was changed. Any subsequent change must have a higher value to be applied
/// if nil ref has no stamp and any change will be accepted
private var modifiedAt: Stamp?
/// entity children
private(set) var children: [PartialKeyPath<T>: SubscribedChild] = [:]

init(ref: Observable<T>, key: String, modifiedAt: Stamp?) {
self.ref = ref
init(_ entity: T, key: String, modifiedAt: Stamp?) {
self.value = entity
self.modifiedAt = modifiedAt
self.storageKey = key
}

convenience init(_ entity: T, key: String, modifiedAt: Stamp?) {
self.init(ref: Observable(value: entity), key: key, modifiedAt: modifiedAt)
}

convenience init(_ entity: T, modifiedAt: Stamp?) where T: Identifiable {
let key = "\(T.self)-\(entity.id)"
self.init(entity, key: key, modifiedAt: modifiedAt)
Expand All @@ -89,11 +82,11 @@ class EntityNode<T>: AnyEntityNode {
}

modifiedAt = newModifiedAt ?? modifiedAt
ref.value = newEntity
value = newEntity
}

func nullify() -> Bool {
if let value = ref.value as? Nullable {
if let value = value as? Nullable {
do {
try updateEntity(value.nullified() as! T, modifiedAt: nil)
return true
Expand Down Expand Up @@ -126,12 +119,12 @@ class EntityNode<T>: AnyEntityNode {
}

if let writableKeyPath = keyPath as? WritableKeyPath<T, U.Value> {
ref.value[keyPath: writableKeyPath] = child.ref.value
value[keyPath: writableKeyPath] = child.value
return
}

if let optionalWritableKeyPath = keyPath as? WritableKeyPath<T, U.Value?> {
ref.value[keyPath: optionalWritableKeyPath] = child.ref.value
value[keyPath: optionalWritableKeyPath] = child.value
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ObserverRegistryStub: ObserverRegistry {
}

func hasPendingChange<T: Equatable>(for entity: T) -> Bool {
pendingChangesStub.contains { ($0 as? EntityNode<T>)?.ref.value == entity }
pendingChangesStub.contains { ($0 as? EntityNode<T>)?.value == entity }
}

func hasPendingChange<T>(for _: T.Type) -> Bool {
Expand All @@ -22,7 +22,7 @@ class ObserverRegistryStub: ObserverRegistry {

/// number of times change has been inserted for this entity
func pendingChangeCount<T: Equatable>(for entity: T) -> Int {
pendingChangesStub.filter { ($0 as? EntityNode<T>)?.ref.value == entity }.count
pendingChangesStub.filter { ($0 as? EntityNode<T>)?.value == entity }.count
}

func clearPendingChangesStub() {
Expand Down
40 changes: 0 additions & 40 deletions Tests/CohesionKitTests/RefTests.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Tests/CohesionKitTests/Storage/AliasStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class AliasStorageTests: XCTestCase {
var storage: AliasStorage = [:]

XCTAssertNotNil(storage[safe: .testCollection])
XCTAssertNil(storage[safe: .testCollection].ref.value.content)
XCTAssertNil(storage[safe: .testCollection].value.content)
}

func test_subscriptGet_aliasHasSameNameThanAnotherType_itReturnsAliasContainer() {
Expand Down
10 changes: 5 additions & 5 deletions Tests/CohesionKitTests/Storage/EntityNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,27 @@ class EntityNodeTests: XCTestCase {
try node.updateEntity(newEntity, modifiedAt: startTimestamp)
)

XCTAssertEqual(node.value as? RootFixture, startEntity)
XCTAssertEqual(node.value, startEntity)
}

func test_updateEntity_stampIsSup_entityIsUpdated() throws {
try node.updateEntity(newEntity, modifiedAt: startTimestamp + 1)

XCTAssertEqual(node.value as? RootFixture, newEntity)
XCTAssertEqual(node.value, newEntity)
}

func test_updateEntity_stampIsInf_entityIsNotUpdated() throws {
XCTAssertThrowsError(
try node.updateEntity(newEntity, modifiedAt: startTimestamp - 1)
)

XCTAssertEqual(node.value as? RootFixture, startEntity)
XCTAssertEqual(node.value, startEntity)
}

func test_updateEntity_stampIsNil_entityIsUpdated() throws {
try node.updateEntity(newEntity, modifiedAt: nil)

XCTAssertEqual(node.value as? RootFixture, newEntity)
XCTAssertEqual(node.value, newEntity)
}

func test_updateEntity_stampIsNil_stampIsNotUpdated() throws {
Expand Down Expand Up @@ -94,7 +94,7 @@ class EntityNodeTests: XCTestCase {

node.updateEntityRelationship(childNode)

XCTAssertEqual(node.ref.value.singleNode, newChild)
XCTAssertEqual(node.value.singleNode, newChild)
}

func test_observeChild_childIsCollection_eachChildIsAdded() {
Expand Down