COW help

Hi,

I am playing around with the idea for a web framework. But I can't seem to figure out how to trigger a copy with the following code correctly. Is this even possible with the nonmutating set? I would like to have one of the WWW structs per connection. Not 100% if this would even scale right if possible.

struct WW {
    @Wrapper var v: Int = 0
    var contents: Button {
        Button("\(v)") {
            v += 1
        }
    }
}

struct Button {
    var action: () -> Void
    var label: String
    init(_ label: String, action: @escaping () -> Void) {
        self.label = label
        self.action = action
    }
}

struct WWW {
    var v: WW = WW()
}

final class Test<Value> {
    init(_ value: Value) {
        self._value = Inner(value)
    }
    var _value: Inner<Value>
    var value: Value {
        get {
            return _value.value
        }
        set {
            _value.value = newValue
        }
    }
}

final class Inner<Value> {
    init(_ value: Value) {
        self.value = value
    }
    var value: Value
}

@propertyWrapper
struct Wrapper<Value> {
    init(wrappedValue: Value) {
        t = Test(wrappedValue)
    }
    var t: Test<Value>
    var wrappedValue: Value {
        get {
            t.value
        }
        nonmutating set {

            if !isKnownUniquelyReferenced(&t._value) {
                t._value = Inner(newValue)
                print("COW")
            } else {
                t.value = newValue
            }
        }
    }
}
let w = WWW()
let w2 = w
print(w2.v.v)
print(w.v.v)
print("action1")
w2.v.contents.action()
print("action2")
w2.v.contents.action()
print(w2.v.v)
print(w.v.v)

Thanks,
Zane

Draw the diagram. Is it like this?

WW    ===>
             Test ---> Inner
WW2   ===>

Non mutating set means WW2 == WW, and the only way to change the contents is by changing the Test contents. But so long as WW and WW2 have the same "Test" there is no "copy"...

I'd recommend you to simplify the code (get rid of a property wrapper, remove one of the structs, etc.

1 Like

Thanks, that’s what I was hoping wasn’t true. Looks like the SwiftUI design won’t work sever side. :confused:

I feel like you're making some odd leaps here. This just isn't the pattern you would normally use for copy-on-write; generally a value type that's using copy-on-write internally still presents externally as a normal value type, including things like mutations being mutating methods.

4 Likes

Thank you so much. I admit I'm still very new to Swift.

I was playing around with the idea of doing a SwiftUI inspired web DSL for webpages over web sockets, inspired by Phoenix live view. The basic idea worked pretty easily, except every connection was sharing the same state. So I was trying to figure out how to do do a "deep copy" of this structure and thought maybe the COW mechanic could work.

From my limited understanding the nonmutating set is needed to get the Button and @State API to work. Is there possibly another way to make a unique copy of this structure?

Thanks,
Zane

SwiftUI's @State is something of an oddity because of the way that it interacts with the view hierarchy: the framework conspires to make @State work like a consistent value for a particular place in the hierarchy despite the views not just being a normal tree of values. Its mutators are nonmutating because they're really modifying a value held in the framework's state storage, which is more like reference semantics than an ordinary value. It's a hard pattern to grasp even for experts, and I would not recommend trying to reproduce it.

12 Likes

A bit off topic, but people already trying to crack it LiveView Native · GitHub
I know it's a bit different from what you wanted to achieve, but maybe worth checking for inspiration. :slight_smile:

1 Like

You may start with having explicit "copy" operations:

class C {
    var value: Int
    init(value: Int) { self.value = value }
}

struct S {
    private let c: C
    init(value: Int) { c = C(value: value) }
    var value: Int { c.value }
    /* non-mutating */func mutate(_ value: Int) {
        c.value = value
    }
    func copy() -> Self {
        S(value: value)
    }
}

var a = S(value: 1)
var b = a.copy()
a.mutate(2)
print(a.value) // 2
print(b.value) // 1

Swift (compared to C++) doesn't have a copy constructor to customise so having an explicit copy is a must to get different a & b values in the above example.


Then you may introduce additional level (C & D are classes):

Before copy:

a ==> Ca
            \
              -->  Da
            /
b ==> Cb

After copy:

a ==> Ca  --> Da

b ==> Cb  --> Db

Given that each struct now has its own C those C objects could be changed independently to point to different D's (to implement the copy in COW). A bit unusual setup but would achieve the goal:

Yes, semi-possible as per above, but too much hassle I'd say.