Skip to content

Eliminate the allocation of single element arrays #33

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

Merged
merged 2 commits into from
Nov 17, 2023
Merged
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
93 changes: 59 additions & 34 deletions Sources/HTTPTypes/HTTPFields.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ public struct HTTPFields: Sendable, Hashable {
/// semicolon.
public subscript(name: HTTPField.Name) -> String? {
get {
let values = self[values: name]
if !values.isEmpty {
let fields = self.fields(for: name)
if fields.first(where: { _ in true }) != nil {
let separator = name == .cookie ? "; " : ", "
return values.joined(separator: separator)
return fields.lazy.map(\.value).joined(separator: separator)
} else {
return nil
}
Expand All @@ -184,62 +184,87 @@ public struct HTTPFields: Sendable, Hashable {
if let newValue {
if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *),
name == .cookie {
self[fields: name] = newValue.split(separator: "; ", omittingEmptySubsequences: false).map {
self.setFields(newValue.split(separator: "; ", omittingEmptySubsequences: false).lazy.map {
HTTPField(name: name, value: String($0))
}
}, for: name)
} else {
self[values: name] = [newValue]
self.setFields(CollectionOfOne(HTTPField(name: name, value: newValue)), for: name)
}
} else {
self[values: name] = []
self.setFields(EmptyCollection(), for: name)
}
}
}

/// Access the field values by name as an array of strings. The order of fields is preserved.
public subscript(values name: HTTPField.Name) -> [String] {
get {
self[fields: name].map(\.value)
self.fields(for: name).map(\.value)
}
set {
self[fields: name] = newValue.map { HTTPField(name: name, value: $0) }
self.setFields(newValue.lazy.map { HTTPField(name: name, value: $0) }, for: name)
}
}

/// Access the fields by name as an array. The order of fields is preserved.
public subscript(fields name: HTTPField.Name) -> [HTTPField] {
get {
var fields = [HTTPField]()
var index = self._storage.ensureIndex[name.canonicalName]?.first ?? .max
while index != .max {
let (field, next) = self._storage.fields[Int(index)]
fields.append(field)
index = next
}
return fields
Array(self.fields(for: name))
}
set {
if !isKnownUniquelyReferenced(&self._storage) {
self._storage = self._storage.copy()
}
var existingIndex = self._storage.ensureIndex[name.canonicalName]?.first ?? .max
var newFieldIterator = newValue.makeIterator()
var toDelete = [Int]()
while existingIndex != .max {
if let field = newFieldIterator.next() {
self._storage.fields[Int(existingIndex)].field = field
} else {
toDelete.append(Int(existingIndex))
self.setFields(newValue, for: name)
}
}

private func fields(for name: HTTPField.Name) -> some Sequence<HTTPField> {
struct HTTPFieldSequence: Sequence {
let fields: [(field: HTTPField, next: UInt16)]
let index: UInt16

struct Iterator: IteratorProtocol {
let fields: [(field: HTTPField, next: UInt16)]
var index: UInt16

mutating func next() -> HTTPField? {
if self.index == .max {
return nil
}
let (field, next) = self.fields[Int(self.index)]
self.index = next
return field
}
existingIndex = self._storage.fields[Int(existingIndex)].next
}
if !toDelete.isEmpty {
self._storage.fields.remove(at: toDelete)
self._storage.index = nil

func makeIterator() -> Iterator {
Iterator(fields: self.fields, index: self.index)
}
while let field = newFieldIterator.next() {
self._storage.append(field: field)
}

let index = self._storage.ensureIndex[name.canonicalName]?.first ?? .max
return HTTPFieldSequence(fields: self._storage.fields, index: index)
}

private mutating func setFields(_ fieldSequence: some Sequence<HTTPField>, for name: HTTPField.Name) {
if !isKnownUniquelyReferenced(&self._storage) {
self._storage = self._storage.copy()
}
var existingIndex = self._storage.ensureIndex[name.canonicalName]?.first ?? .max
var newFieldIterator = fieldSequence.makeIterator()
var toDelete = [Int]()
while existingIndex != .max {
if let field = newFieldIterator.next() {
self._storage.fields[Int(existingIndex)].field = field
} else {
toDelete.append(Int(existingIndex))
}
existingIndex = self._storage.fields[Int(existingIndex)].next
}
if !toDelete.isEmpty {
self._storage.fields.remove(at: toDelete)
self._storage.index = nil
}
while let field = newFieldIterator.next() {
self._storage.append(field: field)
}
}

Expand Down