Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hashmap support #12

Merged
merged 3 commits into from
Feb 1, 2023
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
1 change: 1 addition & 0 deletions Sources/Beet/Beet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ public var BeetSupportedTypeMap: Dictionary<String, SupportedTypeDefinition> {
enumsTypeMap.forEach { supported[$0.0.rawValue] = $0.1 }
numbersTypeMap.forEach { supported[$0.0.rawValue] = $0.1 }
aliasesTypeMap.forEach { supported[$0.0.rawValue] = $0.1 }
mapsTypeMap.forEach { supported[$0.0.rawValue] = $0.1 }
return supported
}
274 changes: 274 additions & 0 deletions Sources/Beet/Beets/Maps.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import Foundation

struct fixedSizeMap: ElementCollectionFixedSizeBeet {

let keyElement: Beet
let valElement: Beet
let fixedElements: [AnyHashable: (FixedSizeBeet, FixedSizeBeet)]
let len: UInt32

var description: String
var length: UInt32
var lenPrefixByteSize: UInt
var byteSize: UInt
var elementByteSize: UInt

private var keyElementFixed: Bool {
return isFixedSizeBeet(x: keyElement)
}

private var valElementFixed: Bool {
return isFixedSizeBeet(x: valElement)
}

public init(
keyElement: Beet,
valElement: Beet,
fixedElements: [AnyHashable : (FixedSizeBeet, FixedSizeBeet)],
len: UInt32
) {
self.keyElement = keyElement
self.valElement = valElement
self.fixedElements = fixedElements
self.len = len

self.description = "Map<\(keyElement.description), \(valElement.description)>"
self.length = self.len
self.lenPrefixByteSize = 4

self.byteSize = 0
self.elementByteSize = 0

let size = determineSizes()
self.byteSize = size.byteSize
self.elementByteSize = size.elementByteSize
}

func determineSizes() -> (elementByteSize: UInt, byteSize: UInt) {
if case let .fixedBeet(fixedKeyElement) = keyElement,
case let .fixedBeet(fixedValElement) = valElement {
let elementByteSize = fixedKeyElement.byteSize + fixedValElement.byteSize
return (elementByteSize, 4 + UInt(len) * elementByteSize)

} else if case let .fixedBeet(fixedKeyElement) = keyElement {
var valsByteSize: UInt = 0
for (_, v) in fixedElements.values {
valsByteSize += v.byteSize
}

// If any element has a dynamic size all we can do here is take an average
let elementByteSize = fixedKeyElement.byteSize + UInt(Double(valsByteSize) / Double(len).rounded(.up) )
return (elementByteSize, 4 + fixedKeyElement.byteSize * UInt(len) + valsByteSize)
} else if case let .fixedBeet(fixedValElement) = valElement {
var keysByteSize: UInt = 0
for (k,_) in fixedElements.values {
keysByteSize += k.byteSize
}
let elementByteSize = UInt(Double(keysByteSize) / Double(len).rounded(.up)) + fixedValElement.byteSize
return (elementByteSize, 4 + keysByteSize + fixedValElement.byteSize * UInt(len))
} else {
var keysByteSize: UInt = 0
var valsByteSize: UInt = 0
for (k,v) in fixedElements.values {
keysByteSize += k.byteSize
valsByteSize += v.byteSize
}

let elementByteSize = UInt((Double(keysByteSize) / Double(len) + Double(valsByteSize) / Double(len)).rounded(.up))
return (elementByteSize, 4 + keysByteSize + valsByteSize)
}
}



func write<T>(buf: inout Data, offset: Int, value: T) {
let map = value as! [AnyHashable: Any]

// Write the values first and then the size as it comes clear while we do the former
var cursor = offset + 4
var size: UInt32 = 0

map.forEach { (k,v) in
var fixedKey: FixedSizeBeet? = nil
if case let .fixedBeet(fixedKeyElement) = keyElement {
fixedKey = fixedKeyElement
}

var fixedVal: FixedSizeBeet? = nil
if case let .fixedBeet(x: fixedValElement) = valElement {
fixedVal = fixedValElement
}

if fixedKey == nil || fixedVal == nil {
// When we write the value we know the key and an just pull the
// matching fixed beet for key/val from the provided map which is
// faster than fixing it by value
guard let els = fixedElements[k] else {
fatalError("Should be able to find beet els for \(k), but could not")
}
fixedKey = els.0
fixedVal = els.1

}

fixedKey!.write(buf: &buf, offset: cursor, value: k)
cursor += Int(fixedKey!.byteSize)

fixedVal!.write(buf: &buf, offset: cursor, value: v)
cursor += Int(fixedVal!.byteSize)
size += 1
}

u32().write(buf: &buf, offset: offset, value: size)
if len != size { fatalError("Expected map to have size \(len), but has \(size)") }
}

func read<T>(buf: Data, offset: Int) -> T {
let size: UInt32 = u32().read(buf: buf, offset: offset)
if len != size { fatalError("Expected map to have size \(len), but has \(size)") }
var cursor = offset + 4

var map: [AnyHashable: Any] = [:]

for _ in 0..<size{
// When we read the value from a buffer we don't know the key we're
// reading yet and thus cannot use the provided map of fixed
// de/serializers.
// Therefore we obtain it by fixing it by data instead.
let fixedKey: FixedSizeBeet
switch keyElement{
case .fixedBeet(let fixed):
fixedKey = fixed
case .fixableBeat(let fixable):
fixedKey = fixable.toFixedFromData(buf: buf, offset: cursor)
}

let k: AnyHashable = fixedKey.read(buf: buf, offset: cursor)
cursor += Int(fixedKey.byteSize)

let fixedVal: FixedSizeBeet
switch valElement{
case .fixedBeet(let fixed):
fixedVal = fixed
case .fixableBeat(let fixable):
fixedVal = fixable.toFixedFromData(buf: buf, offset: cursor)
}

let v: Any = fixedVal.read(buf: buf, offset: cursor)
cursor += Int(fixedVal.byteSize)

map[k] = v
}

return map as! T
}
}

struct map: FixableBeet {
let keyElement: Beet
let valElement: Beet

private var keyIsFixed: Bool {
return isFixedSizeBeet(x: keyElement)
}

private var valIsFixed: Bool {
return isFixedSizeBeet(x: valElement)
}

func toFixedFromData(buf: Data, offset: Int) -> FixedSizeBeet {
let len: UInt32 = u32().read(buf: buf, offset: offset)
var cursor = offset + 4
// Shortcut for the case that both key and value are fixed size beets
if (keyIsFixed && valIsFixed) {
return FixedSizeBeet(
value: FixedSizeBeetType.collection(
fixedSizeMap(keyElement: keyElement, valElement: valElement, fixedElements: [:], len: len)
)
)
}
// If either key or val are not fixed size beets we need to determine the
// fixed versions and add them to a map by key
var fixedBeets: [AnyHashable: (FixedSizeBeet, FixedSizeBeet)] = [:]
for _ in 0..<len {
let keyFixed: FixedSizeBeet
switch keyElement {
case .fixedBeet(let fixed):
keyFixed = fixed
case .fixableBeat(let fixable):
keyFixed = fixable.toFixedFromData(buf: buf, offset: cursor)
}
let key: AnyHashable = keyFixed.read(buf: buf, offset: cursor)
cursor += Int(keyFixed.byteSize)

let valFixed: FixedSizeBeet
switch valElement {
case .fixedBeet(let fixed):
valFixed = fixed
case .fixableBeat(let fixable):
valFixed = fixable.toFixedFromData(buf: buf, offset: cursor)
}

fixedBeets[key] = (keyFixed, valFixed)
cursor += Int(valFixed.byteSize)
}
return FixedSizeBeet(
value: FixedSizeBeetType.collection(
fixedSizeMap(keyElement: keyElement, valElement: valElement, fixedElements: fixedBeets, len: len)
)
)
}

func toFixedFromValue(val: Any) -> FixedSizeBeet {
let mapVal = val as! [AnyHashable: Any]
let len = mapVal.count
if (keyIsFixed && valIsFixed) {
return FixedSizeBeet(
value: FixedSizeBeetType.collection(
fixedSizeMap(
keyElement: keyElement, valElement: valElement, fixedElements: [:], len: UInt32(len)
)
)
)
}
var fixedBeets: [AnyHashable: (FixedSizeBeet, FixedSizeBeet)] = [:]
mapVal.forEach { (k, v) in
let keyFixed: FixedSizeBeet
switch keyElement {
case .fixedBeet(let fixed):
keyFixed = fixed
case .fixableBeat(let fixable):
keyFixed = fixable.toFixedFromValue(val: k)
}

let valFixed: FixedSizeBeet
switch valElement {
case .fixedBeet(let fixed):
valFixed = fixed
case .fixableBeat(let fixable):
valFixed = fixable.toFixedFromValue(val: v)
}

fixedBeets[k] = (keyFixed, valFixed)
}
return FixedSizeBeet(
value: FixedSizeBeetType.collection(
fixedSizeMap(keyElement: keyElement, valElement: valElement, fixedElements: fixedBeets, len: UInt32(len))
)
)
}

var description: String {
"FixableMap<\(keyElement.description), $\(valElement.description)>"
}
}

public enum MapsTypeMapKey: String {
case map
}

public typealias MapsTypeMap = (MapsTypeMapKey, SupportedTypeDefinition)

public let mapsTypeMap: [MapsTypeMap] = [
(MapsTypeMapKey.map, SupportedTypeDefinition(beet: "map(keyElement: {{innerK}}, valElement: {{innerV}})", isFixable: true, sourcePack: BEET_PACKAGE, swift: "Dictionary"))
]
10 changes: 10 additions & 0 deletions Sources/Beet/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,18 @@ public protocol FixableBeet: BeetBase {
* @category beet
*/
public enum Beet {

case fixedBeet(FixedSizeBeet)
case fixableBeat(FixableBeet)

var description: String {
switch(self){
case .fixedBeet(let beet):
return beet.description
case .fixableBeat(let beet):
return beet.description
}
}
}

/**
Expand Down
Loading