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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ TODO.md
.ag_template/
build/
.ag_repo/
.og_repo/
.oag_repo/
.claude
3 changes: 1 addition & 2 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
version: 1
builder:
configs:
- swift_version: 6.0
documentation_targets: [OpenAttributeGraph]
- documentation_targets: [OpenAttributeGraph]
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

[![codecov](https://codecov.io/gh/OpenSwiftUIProject/OpenAttributeGraph/graph/badge.svg?token=W1KDSUMWJW)](https://codecov.io/gh/OpenSwiftUIProject/OpenAttributeGraph)

OpenAttributeGraph is an open source implementation of Apple's Private framework - AttributeGraph
OpenAttributeGraph is an open source implementation of Apple's Private framework - AttributeGraph which is a high performance computing engine written in C++ and Swift.

AttributeGraph is a high performance computing engine written in C++ and Swift.

And it powers the underlying computing and diffing of SwiftUI.
And it powers the underlying computing and diffing of [OpenSwiftUI](https://github.com/OpenSwiftUIProject/OpenSwiftUI).

| **CI Status** |
|---|
Expand All @@ -25,6 +23,8 @@ The project is for the following purposes:

Currently, this project is in early development.

Please refer to the [documentation](https://swiftpackageindex.com/OpenSwiftUIProject/OpenAttributeGraph/main/documentation/openattributegraph) for more information on it.

## Usage

### Via Swift Package Manager
Expand Down
66 changes: 66 additions & 0 deletions Sources/OpenAttributeGraph/Attribute/Attribute/Attribute.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,60 @@
public import OpenAttributeGraphCxx

/// A reactive property wrapper that automatically tracks dependencies and manages value updates.
///
/// `Attribute` is the core building block of the OpenAttributeGraph reactive system. When you wrap a
/// property with `@Attribute`, it becomes reactive and can automatically track dependencies and
/// propagate changes.
///
/// @Attribute var count: Int = 0
/// @Attribute var doubledCount: Int = count * 2
///
/// count = 5 // doubledCount automatically becomes 10
///
/// ## Key Features
///
/// - Automatic dependency tracking: Attributes automatically discover their dependencies
/// - Efficient updates: Only affected attributes are recomputed when changes occur
/// - Type safety: Full Swift type safety with compile-time checking
/// - Dynamic member lookup: Access nested properties as reactive attributes
/// - Property wrapper syntax: Clean, declarative syntax using `@Attribute`
///
/// ## Property Wrapper Usage
///
/// Use `@Attribute` to make any Swift value reactive:
///
/// struct CounterView {
/// @Attribute var count: Int = 0
///
/// var body: some View {
/// Button("Count: \(count)") {
/// count += 1
/// }
/// }
/// }
///
/// ## Dynamic Member Lookup
///
/// Access nested properties as separate attributes:
///
/// @Attribute var person: Person = Person(name: "Alice", age: 30)
/// let nameAttribute: Attribute<String> = person.name
/// let ageAttribute: Attribute<Int> = person.age
///
/// ## Integration with Rules
///
/// Create computed attributes using ``Rule`` or ``StatefulRule``:
///
/// struct DoubledRule: Rule {
/// typealias Value = Int
/// let source: Attribute<Int>
///
/// func value() -> Int {
/// source.wrappedValue * 2
/// }
/// }
///
/// let doubled = Attribute(DoubledRule(source: count))
@frozen
@propertyWrapper
@dynamicMemberLookup
Expand All @@ -8,14 +63,23 @@ public struct Attribute<Value> {

// MARK: - Initializer

/// Creates an attribute from a type-erased identifier.
///
/// - Parameter identifier: The type-erased attribute identifier
public init(identifier: AnyAttribute) {
self.identifier = identifier
}

/// Creates an attribute by copying another attribute.
///
/// - Parameter attribute: The attribute to copy
public init(_ attribute: Attribute<Value>) {
self = attribute
}

/// Creates an attribute with an initial value.
///
/// - Parameter value: The initial value for the attribute
public init(value: Value) {
self = withUnsafePointer(to: value) { valuePointer in
withUnsafePointer(to: External<Value>()) { bodyPointer in
Expand Down Expand Up @@ -61,6 +125,7 @@ public struct Attribute<Value> {

// MARK: - propertyWrapper

/// The current value of the attribute.
public var wrappedValue: Value {
unsafeAddress {
OAGGraphGetValue(identifier, type: Value.self)
Expand All @@ -70,6 +135,7 @@ public struct Attribute<Value> {
nonmutating set { _ = setValue(newValue) }
}

/// The attribute itself when accessed with the `$` prefix.
public var projectedValue: Attribute<Value> {
get { self }
set { self = newValue }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Audited for RELEASE_2021
// Status: Complete

/// An optional attribute wrapper that may or may not contain a value.
@frozen
@propertyWrapper
@dynamicMemberLookup
Expand Down
29 changes: 29 additions & 0 deletions Sources/OpenAttributeGraph/Attribute/Rule/Focus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@
// Audited for RELEASE_2021
// Status: Complete

/// A rule that focuses on a specific property of another attribute using KeyPath.
///
/// `Focus` provides a way to create attributes that automatically track a specific property of another attribute. It uses Swift's KeyPath system to create a focused view of part of a larger data structure.
///
/// struct Person {
/// let name: String
/// let age: Int
/// }
///
/// @Attribute var person = Person(name: "Alice", age: 30)
/// let nameAttribute = Attribute(Focus(root: $person, keyPath: \.name))
/// // nameAttribute automatically updates when person.name changes
///
/// Focus is commonly used internally by OpenAttributeGraph's dynamic member lookup system to create property-specific attributes.
///
/// ## Key Features
///
/// - KeyPath-based focusing: Use any Swift KeyPath to focus on specific properties
/// - Automatic updates: Changes to the focused property automatically propagate
/// - Type safety: Full compile-time type checking for focused properties
/// - Efficient tracking: Only tracks changes to the specific focused property
///
/// ## Usage Pattern
///
/// Focus is ideal for:
/// - Creating focused views of complex data structures
/// - Implementing dynamic member lookup for attributes
/// - Breaking down large objects into smaller, more manageable attribute pieces
/// - Selective observation of specific properties
@frozen
public struct Focus<Root, Value>: Rule, CustomStringConvertible {
public var root: Attribute<Root>
Expand Down
44 changes: 44 additions & 0 deletions Sources/OpenAttributeGraph/Attribute/Rule/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,53 @@

public import OpenAttributeGraphCxx

/// A protocol for defining computed attributes that automatically update when dependencies change.
///
/// Rules provide a way to create derived attributes that compute their values based on other attributes.
/// When any dependency changes, the rule will automatically recompute its value.
///
/// struct DoubledRule: Rule {
/// typealias Value = Int
/// let source: Attribute<Int>
///
/// var value: Int {
/// source.wrappedValue * 2
/// }
/// }
///
/// @Attribute var count: Int = 5
/// let doubled = Attribute(DoubledRule(source: $count))
/// // doubled.wrappedValue == 10
///
/// count = 10
/// // doubled.wrappedValue automatically becomes 20
///
/// ## Key Features
///
/// - Automatic dependency tracking: Dependencies are discovered automatically when accessed
/// - Lazy evaluation: Values are only computed when needed
/// - Caching: Results are cached until dependencies change
/// - Efficient updates: Only recomputes when dependencies actually change
///
/// ## Implementation Requirements
///
/// Types conforming to `Rule` must provide:
/// - `Value`: The type of value produced by the rule
/// - `value`: A computed property that returns the current value
/// - `initialValue`: An optional initial value (defaults to `nil`)
///
/// ## Advanced Usage
///
/// For rules that need to maintain state between evaluations, see ``StatefulRule``.
/// For rules that can be cached based on their content, make your rule type conform to `Hashable`.
public protocol Rule: _AttributeBody {
/// The type of value produced by this rule.
associatedtype Value

/// An optional initial value to use before the rule is first evaluated.
static var initialValue: Value? { get }

/// Computes and returns the current value of the rule.
var value: Value { get }
}

Expand Down
30 changes: 30 additions & 0 deletions Sources/OpenAttributeGraph/Attribute/Rule/StatefulRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@

public import OpenAttributeGraphCxx

/// A protocol for defining computed attributes that maintain state between evaluations.
///
/// `StatefulRule` extends the basic `Rule` concept by allowing rules to maintain mutable state between updates. This is useful for rules that need to track changes over time or maintain internal state.
///
/// struct CounterRule: StatefulRule {
/// typealias Value = Int
/// private var counter = 0
///
/// mutating func updateValue() {
/// counter += 1
/// value = counter
/// }
/// }
///
/// Unlike ``Rule``, `StatefulRule` allows mutation through the `updateValue()` method and provides direct access to the current value through the `value` property.
///
/// ## Key Features
///
/// - Mutable state: Rules can maintain and modify internal state
/// - Direct value access: Read and write the current value directly
/// - Context access: Access to rule evaluation context and attribute
/// - Lifecycle control: Manual control over when and how values update
///
/// ## Usage Pattern
///
/// StatefulRule is ideal for scenarios where you need to:
/// - Accumulate values over time
/// - Maintain counters or timers
/// - Implement complex state machines
/// - Cache expensive computations with custom invalidation
public protocol StatefulRule: _AttributeBody {
associatedtype Value
static var initialValue: Value? { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

public import OpenAttributeGraphCxx

/// Context object providing access to rule evaluation state and input values.
@frozen
public struct RuleContext<Value>: Equatable {
public var attribute: Attribute<Value>
Expand Down
27 changes: 27 additions & 0 deletions Sources/OpenAttributeGraph/Attribute/Weak/WeakAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@

public import OpenAttributeGraphCxx

/// A weak reference property wrapper for attributes that prevents retain cycles.
///
/// `WeakAttribute` provides a way to hold weak references to attributes, preventing strong reference cycles in the attribute graph while still allowing access to reactive values.
///
/// @WeakAttribute var parentAttribute: SomeType?
///
/// // Safe access to potentially deallocated attribute
/// if let value = parentAttribute {
/// print("Parent value: \(value)")
/// }
///
/// The weak attribute automatically becomes `nil` when the referenced attribute is deallocated, providing memory-safe access to optional attribute references.
///
/// ## Key Features
///
/// - Weak references: Prevents retain cycles in attribute relationships
/// - Automatic nil assignment: Referenced attributes become nil when deallocated
/// - Dynamic member lookup: Access nested properties through weak references
/// - Optional semantics: All values are optional since references may be deallocated
///
/// ## Usage Pattern
///
/// WeakAttribute is essential for:
/// - Parent-child attribute relationships
/// - Observer patterns that don't own the observed attribute
/// - Breaking potential retain cycles in complex attribute graphs
/// - Optional attribute references in data structures
@frozen
@propertyWrapper
@dynamicMemberLookup
Expand Down
Loading