Skip to content

Commit b0e8ffb

Browse files
authored
Merge pull request #5 from alloverse/feature/custom-components
Support custom components
2 parents d80ba95 + bcba8d1 commit b0e8ffb

11 files changed

+119
-119
lines changed

Package.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,12 @@ let package = Package(
3636
)
3737
],
3838
dependencies: [
39-
.package(url: "https://github.com/christophhagen/BinaryCodable", from: "3.0.0"),
40-
//.package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.25.1")),
39+
.package(url: "https://github.com/outfoxx/PotentCodables.git", from: "3.5.3"),
40+
4141
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.07"),
4242
.package(url: "https://github.com/swhitty/FlyingFox.git", .upToNextMajor(from: "0.25.0")),
4343
.package(url: "https://github.com/swhitty/FlyingFoxMacros.git", .upToNextMajor(from: "0.2.0")),
4444

45-
.package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0"),
4645
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
4746
.package(url: "https://github.com/alloverse/OpenCombine.git", branch: "fix/vision-support"), // So we can use Combine on Linux.
4847
.package(url: "https://github.com/keyvariable/kvSIMD.swift.git", from: "1.1.0"), // So we can use simd on Linux
@@ -57,8 +56,7 @@ let package = Package(
5756
.target(
5857
name: "allonet2",
5958
dependencies: [
60-
"BinaryCodable",
61-
"AnyCodable",
59+
"PotentCodables",
6260
"FlyingFox",
6361
"FlyingFoxMacros",
6462
"Version",

Sources/allonet2/AlloSession.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
//
77

88
import Foundation
9-
import BinaryCodable
9+
import PotentCodables
10+
import PotentCBOR
1011
import OpenCombineShim
1112
import Logging
1213

@@ -91,7 +92,7 @@ public class AlloSession : NSObject, TransportDelegate
9192
// TODO: this unsafe is going to bite me... store it threadsafely so logging can use it?
9293
nonisolated(unsafe) public var clientId: ClientId? { transport.clientId }
9394

94-
let encoder = BinaryEncoder()
95+
let encoder = CBOREncoder()
9596

9697
public func send(interaction: Interaction)
9798
{
@@ -200,7 +201,7 @@ public class AlloSession : NSObject, TransportDelegate
200201
}
201202
}
202203

203-
let decoder = BinaryDecoder()
204+
let decoder = CBORDecoder()
204205
nonisolated public func transport(_ transport: Transport, didReceiveData data: Data, on channel: DataChannel)
205206
{
206207
switch channel.alloLabel {

Sources/allonet2/Interaction.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
import Foundation
9-
import AnyCodable
9+
import PotentCodables
1010

1111
@MainActor
1212
public struct Interaction : Codable
@@ -66,7 +66,7 @@ public enum InteractionBody : Codable
6666
case tap(at: SIMD3<Float>) // oneway
6767

6868
// - Other
69-
case custom(value: [String: AnyCodable])
69+
case custom(value: [String: AnyValue])
7070

7171
// - Generic responses
7272
case error(domain: String, code: Int, description: String)

Sources/allonet2/Place.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public struct ComponentSet: CustomStringConvertible
136136
{
137137
public subscript<T>(componentType: T.Type) -> T? where T : Component
138138
{
139-
return state.current.components[componentType.componentTypeId]?[id] as! T?
139+
return state.current.components[componentType.componentTypeId]?[id]?.decoded() as! T?
140140
}
141141
public func set<T>(_ newValue: T) async throws(AlloverseError) where T: Component
142142
{
@@ -145,7 +145,7 @@ public struct ComponentSet: CustomStringConvertible
145145
}
146146
public subscript(componentTypeID: ComponentTypeID) -> (any Component)?
147147
{
148-
return state.current.components[componentTypeID]?[id]
148+
return state.current.components[componentTypeID]?[id]?.decoded()
149149
}
150150

151151
private let state: PlaceState

Sources/allonet2/PlaceContents+Changes.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ extension PlaceContents
7878
{
7979
added.append(.componentAdded(entityId, component))
8080
}
81-
else if !component.isEqualTo(prev!)
81+
else if component != prev!
8282
{
8383
updated.append(.componentUpdated(entityId, component))
8484
}
@@ -135,15 +135,15 @@ extension PlaceContents
135135
case .entityRemoved(let e):
136136
entities[e.id] = nil
137137
case .componentAdded(let eid, let component):
138-
let key = type(of:component).componentTypeId
138+
let key = component.componentTypeId
139139
lists[key, default: [:]][eid] = component
140140
case .componentUpdated(let eid, let component):
141-
let key = type(of:component).componentTypeId
141+
let key = component.componentTypeId
142142
guard let _ = lists[key]?[eid] else { return nil }
143143
lists[key]![eid]! = component
144144
case .componentRemoved(let edata, let component):
145-
guard let _ = lists[type(of:component).componentTypeId] else { return nil }
146-
lists[type(of:component).componentTypeId]![edata.id] = nil
145+
guard let _ = lists[component.componentTypeId] else { return nil }
146+
lists[component.componentTypeId]![edata.id] = nil
147147
}
148148
}
149149
return PlaceContents(revision: changeSet.toRevision, entities: entities, components: ComponentLists(lists: lists), logger: self.logger)

Sources/allonet2/PlaceContents+Codable.swift

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Logging
9+
import PotentCodables
910

1011
extension PlaceContents: Equatable
1112
{
@@ -25,7 +26,7 @@ extension PlaceContents: Equatable
2526

2627
// Compare each component using the helper method.
2728
for (l, r) in zip(lhsComponents, rhsComponents) {
28-
if !l.value.isEqualTo(r.value) {
29+
if l.value != r.value {
2930
return false
3031
}
3132
}
@@ -59,50 +60,64 @@ public final class ComponentRegistry
5960
}
6061
}
6162

63+
/// `AnyComponent` lets AlloPlace only store and forward type-erased value trees of Components, while client code can use `decoded()` to receive the real concrete Component type.
6264
@MainActor
63-
public struct AnyComponent: Component {
64-
public static func == (lhs: AnyComponent, rhs: AnyComponent) -> Bool {
65-
return lhs.base.isEqualTo(rhs.base)
65+
public struct AnyComponent: Codable, Equatable
66+
{
67+
public static func == (lhs: AnyComponent, rhs: AnyComponent) -> Bool
68+
{
69+
return lhs.anyValue == rhs.anyValue
6670
}
6771

68-
public var base: any Component
72+
// The concrete Component type we use
73+
public func decoded() -> any Component
74+
{
75+
return decodedIfAvailable()!
76+
}
77+
// ... or nil, if the type is not compiled into this binary and registered with the ComponentRegistry.
78+
public func decodedIfAvailable() -> (any Component)?
79+
{
80+
guard let type = ComponentRegistry.shared.component(for: componentTypeId)
81+
else { return nil }
82+
return try! AnyValueDecoder.default.decode(type, from: anyValue) // if the type is registered, it should decode
83+
}
84+
public func decodeCustom() -> CustomComponent
85+
{
86+
return CustomComponent(typeId: componentTypeId, fields: anyValue)
87+
}
6988

70-
public var componentTypeId: String { type(of:base).componentTypeId }
89+
// The type-erased content, available whether the concrete type is available or not
90+
public var anyValue: AnyValue
91+
public var componentTypeId: String
7192

72-
public init(_ base: some Component) {
73-
self.base = base
93+
func indentedDescription(_ prefix: String) -> String
94+
{
95+
return decodedIfAvailable()?.indentedDescription(prefix) ?? "\(prefix)AnyComponent<\(componentTypeId)>: \(anyValue.description)"
7496
}
7597

76-
// MARK: - Codable
77-
private enum CodingKeys: String, CodingKey {
78-
case type
79-
case payload
98+
// MARK: Codable
99+
public init(_ base: some Component)
100+
{
101+
componentTypeId = type(of: base).componentTypeId
102+
anyValue = try! AnyValueEncoder().encode(base)
80103
}
81104

105+
// optimization idea: store CBOR treeValue instead of AnyValue to avoid two tree walks
106+
private enum CodingKeys: String, CodingKey
107+
{
108+
case componentTypeId
109+
case value
110+
}
82111
public init(from decoder: Decoder) throws {
83-
// First, decode the type discriminator.
84112
let container = try decoder.container(keyedBy: CodingKeys.self)
85-
let typeId = try container.decode(String.self, forKey: .type)
86-
87-
// Ask the registry for the correct concrete type.
88-
guard let componentType = ComponentRegistry.shared.component(for: typeId) else {
89-
throw DecodingError.dataCorruptedError(forKey: .type,
90-
in: container,
91-
debugDescription: "Unknown component type: \(typeId)")
92-
}
93-
94-
// Decode the actual component.
95-
self.base = try componentType.init(from: container.superDecoder(forKey: .payload))
113+
componentTypeId = try container.decode(String.self, forKey: .componentTypeId)
114+
anyValue = try container.decode(AnyValue.self, forKey: .value)
96115
}
97-
116+
98117
public func encode(to encoder: Encoder) throws {
99-
// Create a container for both the type discriminator and the payload.
100118
var container = encoder.container(keyedBy: CodingKeys.self)
101-
// Write out the type identifier. We use the static property from the concrete type.
102-
try container.encode(String(describing: type(of: base)), forKey: .type)
103-
104-
// Encode the underlying component.
105-
try base.encode(to: container.superEncoder(forKey: .payload))
119+
try container.encode(componentTypeId, forKey: .componentTypeId)
120+
try container.encode(anyValue, forKey: .value)
106121
}
107122
}
108123

@@ -134,15 +149,15 @@ extension PlaceChange: Codable
134149
try container.encode(ChangeKind.componentAdded, forKey: .kind)
135150
try container.encode(eid, forKey: .entityID)
136151
// Wrap the component so we can encode it generically.
137-
try container.encode(AnyComponent(component), forKey: .component)
152+
try container.encode(component, forKey: .component)
138153
case .componentUpdated(let eid, let component):
139154
try container.encode(ChangeKind.componentUpdated, forKey: .kind)
140155
try container.encode(eid, forKey: .entityID)
141-
try container.encode(AnyComponent(component), forKey: .component)
156+
try container.encode(component, forKey: .component)
142157
case .componentRemoved(let edata, let component):
143158
try container.encode(ChangeKind.componentRemoved, forKey: .kind)
144159
try container.encode(edata, forKey: .entity)
145-
try container.encode(AnyComponent(component), forKey: .component)
160+
try container.encode(component, forKey: .component)
146161
}
147162
}
148163

@@ -161,15 +176,15 @@ extension PlaceChange: Codable
161176
case .componentAdded:
162177
let eid = try container.decode(EntityID.self, forKey: .entityID)
163178
let anyComp = try container.decode(AnyComponent.self, forKey: .component)
164-
self = .componentAdded(eid, anyComp.base)
179+
self = .componentAdded(eid, anyComp)
165180
case .componentUpdated:
166181
let eid = try container.decode(EntityID.self, forKey: .entityID)
167182
let anyComp = try container.decode(AnyComponent.self, forKey: .component)
168-
self = .componentUpdated(eid, anyComp.base)
183+
self = .componentUpdated(eid, anyComp)
169184
case .componentRemoved:
170185
let edata = try container.decode(EntityData.self, forKey: .entity)
171186
let anyComp = try container.decode(AnyComponent.self, forKey: .component)
172-
self = .componentRemoved(edata, anyComp.base)
187+
self = .componentRemoved(edata, anyComp)
173188
}
174189
}
175190
}

0 commit comments

Comments
 (0)