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
12 changes: 9 additions & 3 deletions Sources/PoieticCore/Design/Design.swift
Original file line number Diff line number Diff line change
Expand Up @@ -347,16 +347,22 @@ public class Design {
/// Discards the mutable frame that is associated with the design.
///
public func discard(_ frame: TransientFrame) {
precondition(frame.design === self)
precondition(frame.state == .transient)
precondition(_transientFrames[frame.id] != nil)
precondition(isPending(frame))

identityManager.freeReservation(frame.id)
identityManager.freeReservations(Array(frame._reservations))
_transientFrames[frame.id] = nil
frame.discard()
}

/// Return `true` if the transient frame is owned by the design and is in transient state.
///
public func isPending(_ trans: TransientFrame) -> Bool {
return trans.design === self
&& trans.state == .transient
&& _transientFrames[trans.id] != nil
}

/// Remove a frame from the design.
///
/// The frame will also be removed from named frames, undoable frame list and redo-able frame
Expand Down
2 changes: 0 additions & 2 deletions Sources/PoieticCore/Design/DesignFrame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
/// - SeeAlso: ``TransientFrame``
///
public final class DesignFrame: Frame, Identifiable {
public typealias Snapshot = ObjectSnapshot

/// Design to which the frame belongs.
public unowned let design: Design

Expand Down
18 changes: 9 additions & 9 deletions Sources/PoieticCore/Design/Frame.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ public protocol Frame:
func filter(type: ObjectType) -> [ObjectSnapshot]

/// Get distinct values of an attribute.
func distinctAttribute(_ attributeName: String, ids: [ObjectID]) -> Set<Variant>
func distinctAttribute(_ attributeName: String, ids: some Collection<ObjectID>) -> Set<Variant>

/// Get distinct object types of a list of objects.
func distinctTypes(_ ids: [ObjectID]) -> [ObjectType]
func distinctTypes(_ ids: some Collection<ObjectID>) -> [ObjectType]

/// Get shared traits of a list of objects.
func sharedTraits(_ ids: [ObjectID]) -> [Trait]
func sharedTraits(_ ids: some Collection<ObjectID>) -> [Trait]

/// Filter IDs and keep only those that are contained in the frame.
///
Expand Down Expand Up @@ -223,7 +223,7 @@ extension Frame {
// MARK: Distinct queries

extension Frame {
public func distinctAttribute(_ attributeName: String, ids: [ObjectID]) -> Set<Variant> {
public func distinctAttribute(_ attributeName: String, ids: some Collection<ObjectID>) -> Set<Variant> {
// TODO: Use ordered set here
var values: Set<Variant> = Set()
for id in ids {
Expand All @@ -239,7 +239,7 @@ extension Frame {
///
/// IDs that do not have corresponding objects in the frame are ignored.
///
public func distinctTypes(_ ids: [ObjectID]) -> [ObjectType] {
public func distinctTypes(_ ids: some Collection<ObjectID>) -> [ObjectType] {
var types: [ObjectType] = []
for id in ids {
guard let object = self[id] else { continue }
Expand All @@ -254,14 +254,14 @@ extension Frame {
}

/// Get shared traits of a list of objects.
public func sharedTraits(_ ids: [ObjectID]) -> [Trait] {
public func sharedTraits(_ ids: some Collection<ObjectID>) -> [Trait] {
// TODO: Move this method to metamodel as sharedTraits(_ types: [ObjectType])
guard ids.count > 0 else {
return []
}
guard ids.count > 0 else { return [] }

let types = self.distinctTypes(ids)

guard !types.isEmpty else { return [] }

var traits = types.first!.traits

for type in types.suffix(from: 1) {
Expand Down
126 changes: 65 additions & 61 deletions Sources/PoieticCore/Design/Selection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@
// Created by Stefan Urbanek on 18/01/2025.
//

import Collections

/// Component denoting a selection change.
///
/// Example use-cases of this component:
///
/// - Selection tool in an application on a mouse interaction.
/// - Search feature in an application
///
/// - SeeAlso: ``Selection/apply(_:)``
///
public enum SelectionChange: Component {
case appendOne(ObjectID)
case append([ObjectID])
case replaceAllWithOne(ObjectID)
case replaceAll([ObjectID])
case removeAll
case toggle(ObjectID)
}

/// Collection of selected objects.
///
/// An ordered set of object identifiers with convenience methods to support typical user
Expand All @@ -26,57 +46,79 @@ public final class Selection: Collection, Component {
/// When a selection is preserved between changes, it is recommended to sanitise the objects
/// in the selection using the ``Frame/existing(from:)`` function.
///
public private(set) var ids: [ObjectID] = []
public private(set) var ids: OrderedSet<ObjectID> = []

public var startIndex: Index { ids.startIndex }
public var endIndex: Index { ids.endIndex }
public func index(after i: Index) -> Index { ids.index(after: i) }
public subscript(i: Index) -> ObjectID { return ids[i] }


/// Create an empty selection.
public init() {
self.ids = []
}

/// Create a selection of given IDs
///
public init(_ ids:[ObjectID]) {
self.ids = OrderedSet(ids)
}
public init(_ ids:OrderedSet<ObjectID>) {
self.ids = ids
}



/// Returns `true` if the selection contains a given ID.
public func contains(_ id: ObjectID) -> Bool {
guard !ids.isEmpty else { return false }
return ids.contains(id)
}

public var isEmpty: Bool {
return ids.isEmpty
}

/// Returns an object ID if it is the only object in the selection, otherwise `nil`.
public func selectionOfOne() -> ObjectID? {
if ids.count == 1 {
return ids.first!
}
else {
return nil
}
}

/// Apply a selection change.
///
/// Use this function in a selection system that is typically triggered by an user interaction
/// such as tool use.
///
public func apply(_ change: SelectionChange) {
switch change {
case .appendOne(let id): self.append([id])
case .append(let ids): self.append(ids)
case .replaceAllWithOne(let id): self.replaceAll([id])
case .replaceAll(let ids): self.replaceAll(ids)
case .removeAll: self.removeAll()
case .toggle(let id): self.toggle(id)
}
}

/// Append the ID if it is not already present in the selection.
public func append(_ id: ObjectID) {
guard !contains(id) else {
return
}
ids.append(id)
}

/// Append IDs to the selection, if they are not already present in the selection.
///
public func append(_ ids: [ObjectID]) {
for id in ids {
guard !contains(id) else {
return
}
self.ids.append(id)
}
self.ids.append(contentsOf: ids)
}

/// Replace all objects in the selection.
///
public func replaceAll(_ ids: [ObjectID]) {
self.ids.removeAll()
self.ids += ids
self.ids.append(contentsOf: ids)
}


Expand All @@ -90,76 +132,38 @@ public final class Selection: Collection, Component {
/// selection.
///
public func toggle(_ id: ObjectID) {
if let index = ids.firstIndex(of: id) {
ids.remove(at: index)
if ids.contains(id) {
ids.remove(id)
}
else {
ids.append(id)
}
}
}

extension Selection: SetAlgebra {
extension Selection /* : SetAlgebra */ {
public func union(_ other: __owned Selection) -> Self {
var result: [ObjectID] = []
for item in other {
if !result.contains(item) {
result.append(item)
}
}
return Self(result)
return Self(self.ids.union(other.ids))
}

public func intersection(_ other: Selection) -> Self {
var result: [ObjectID] = []
for item in other {
if ids.contains(item) {
result.append(item)
}
}
return Self(result)
return Self(self.ids.intersection(other.ids))
}

public func symmetricDifference(_ other: __owned Selection) -> Self {
fatalError("NOT IMPLEMENTED")
}

public func insert(_ newMember: __owned ObjectID) -> (inserted: Bool, memberAfterInsert: ObjectID) {
if !ids.contains(newMember) {
ids.append(newMember)
return (true, newMember)
}
else {
return (false, newMember)
}
}

public func remove(_ member: ObjectID) -> ObjectID? {
if let index = ids.firstIndex(of: member) {
let obj = ids[index]
ids.remove(at: index)
return obj
}
else {
return nil
}
}

public func update(with newMember: __owned ObjectID) -> ObjectID? {
// do nothing
return newMember
return Self(self.ids.symmetricDifference(other.ids))
}

public func formUnion(_ other: __owned Selection) {
fatalError("NOT IMPLEMENTED")
self.ids.formUnion(other.ids)
}

public func formIntersection(_ other: Selection) {
fatalError("NOT IMPLEMENTED")
self.ids.formIntersection(other.ids)
}

public func formSymmetricDifference(_ other: __owned Selection) {
fatalError("NOT IMPLEMENTED")
self.ids.formSymmetricDifference(other.ids)
}

public static func == (lhs: Selection, rhs: Selection) -> Bool {
Expand Down
6 changes: 2 additions & 4 deletions Sources/PoieticCore/Design/TransientObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Collections

// FIXME: Remove id and Identifiable (historical remnant that causes confusion)
@usableFromInline
class _TransientSnapshotBox: Identifiable {
// IMPORTANT: Make sure that the self.id is _always_ object ID, not a snapshot ID here.
Expand Down Expand Up @@ -120,7 +121,6 @@ public class TransientObject: ObjectProtocol {

/// Flag to denote whether the object's parent-child hierarchy has been modified,
public private(set) var hierarchyChanged: Bool
public private(set) var componentsChanged: Bool

/// Set of changed attributes.
///
Expand All @@ -129,7 +129,7 @@ public class TransientObject: ObjectProtocol {
///
public private(set) var changedAttributes: Set<String>

var hasChanges: Bool { !changedAttributes.isEmpty || hierarchyChanged || componentsChanged }
var hasChanges: Bool { !changedAttributes.isEmpty || hierarchyChanged }

public init(type: ObjectType,
snapshotID: ObjectSnapshotID,
Expand All @@ -148,15 +148,13 @@ public class TransientObject: ObjectProtocol {
attributes: attributes)
self.changedAttributes = Set()
self.hierarchyChanged = false
self.componentsChanged = false
}

init(original: ObjectSnapshot, snapshotID: ObjectSnapshotID) {
self.snapshotID = snapshotID
self._body = original._body
self.changedAttributes = Set()
self.hierarchyChanged = false
self.componentsChanged = false
}

@inlinable public var objectID: ObjectID { _body.id }
Expand Down
36 changes: 30 additions & 6 deletions Sources/PoieticCore/Expression/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,16 @@ public struct FunctionArgument: Sendable {
///
public let type: VariableType

/// Flag whether the argument is a constant.
///
public let isConstant: Bool

/// Create a new function argument.
///
/// - Parameters:
/// - name: Name of the argument.
/// - type: Argument type. Default is ``VariableType/any``.
/// - isConstant: Flag whether the function argument is a constant.
///
public init(_ name: String, type: VariableType = .any, isConstant: Bool = false) {
public init(_ name: String, type: VariableType = .any) {
self.name = name
self.type = type
self.isConstant = isConstant
}
}

Expand Down Expand Up @@ -112,6 +107,35 @@ public final class Signature: CustomStringConvertible, Sendable {
],
returns: .double
)
public static let NumericBinaryOperator = Signature(
[
FunctionArgument("left", type: .union([.int, .double])),
FunctionArgument("right", type: .union([.int, .double]))
],
returns: .double
)
public static let EquatableOperator = Signature(
[
FunctionArgument("left", type: .any),
FunctionArgument("right", type: .any)
],
returns: .bool
)
public static let ComparisonOperator = Signature(
[
FunctionArgument("left", type: .union([.int, .double])),
FunctionArgument("right", type: .union([.int, .double]))
],
returns: .bool
)
public static let LogicalBinaryOperator = Signature(
[
FunctionArgument("left", type: .union([.bool])),
FunctionArgument("right", type: .union([.bool]))
],
returns: .bool
)

/// Convenience signature representing a numeric function with many
/// numeric arguments.
///
Expand Down
Loading
Loading