Skip to content

Commit

Permalink
improved tuple performance (#51)
Browse files Browse the repository at this point in the history
* use hand-made tuples for normal rendering

* use bespoke tuple types and inlinable rendering

* inlinable HTMLBuilder functions
  • Loading branch information
sliemeobn authored Oct 14, 2024
1 parent c1fdbe3 commit 4ab9270
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 126 deletions.
5 changes: 3 additions & 2 deletions Sources/Elementary/Core/AsyncContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// The this element can only be rendered in an async context (ie: by calling ``HTML/render(into:chunkSize:)`` or ``HTML/renderAsync()``).
/// All HTML tag types (``HTMLElement``) support async content closures in their initializers, so you don't need to use this element directly in most cases.
public struct AsyncContent<Content: HTML>: HTML, Sendable {
@usableFromInline
var content: @Sendable () async throws -> Content
public typealias Tag = Content.Tag

Expand All @@ -22,12 +23,12 @@ public struct AsyncContent<Content: HTML>: HTML, Sendable {
self.content = content
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.assertionFailureNoAsyncContext(self)
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
try await Content._render(await html.content(), into: &renderer, with: context)
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/Elementary/Core/AsyncForEach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
/// }
/// ```
public struct AsyncForEach<Source: AsyncSequence, Content: HTML>: HTML {
@usableFromInline
var sequence: Source
@usableFromInline
var contentBuilder: (Source.Element) -> Content

/// Creates a new async HTML element that renders the specified content for each element of the sequence.
Expand All @@ -25,12 +27,12 @@ public struct AsyncForEach<Source: AsyncSequence, Content: HTML>: HTML {
self.contentBuilder = contentBuilder
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.assertionFailureNoAsyncContext(self)
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
context.assertNoAttributes(self)

Expand Down
11 changes: 7 additions & 4 deletions Sources/Elementary/Core/CoreModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public protocol HTMLTagDefinition: Sendable {
/// The name of the HTML tag as it is rendered in an HTML document.
static var name: String { get }

@_spi(Rendering)
/// Internal property that controls formatted rendering of the element (inline or block).
///
/// A default implementation is provided that returns `false`.
static var _rendersInline: Bool { get }
}

Expand All @@ -48,6 +50,7 @@ extension Never: HTMLTagDefinition {
}

public struct _RenderingContext {
@usableFromInline
var attributes: _AttributeStorage

public static var emptyContext: Self { Self(attributes: .none) }
Expand Down Expand Up @@ -76,18 +79,18 @@ public protocol _AsyncHTMLRendering {
}

public extension HTML {
@_transparent
@inlinable @inline(__always)
static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
Content._render(html.content, into: &renderer, with: context)
}

@_transparent
@inlinable @inline(__always)
static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
try await Content._render(html.content, into: &renderer, with: context)
}
}

public extension HTMLTagDefinition {
@_spi(Rendering)
@inlinable @inline(__always)
static var _rendersInline: Bool { false }
}
4 changes: 2 additions & 2 deletions Sources/Elementary/Core/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public struct _ModifiedTaskLocal<T: Sendable, Content: HTML>: HTML {
var taskLocal: TaskLocal<T>
var value: T

@_spi(Rendering)
@inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
#if compiler(>=6.0)
// https://github.com/swiftlang/swift/issues/76474
Expand All @@ -93,7 +93,7 @@ public struct _ModifiedTaskLocal<T: Sendable, Content: HTML>: HTML {
}
}

@_spi(Rendering)
@inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
#if compiler(>=6.0)
// https://github.com/swiftlang/swift/issues/76474
Expand Down
6 changes: 4 additions & 2 deletions Sources/Elementary/Core/ForEach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
public struct ForEach<Data, Content>: HTML
where Data: Sequence, Content: HTML
{
@usableFromInline
var sequence: Data
// TODO: Swift 6 - @Sendable is not ideal here, but currently the response generators for hummingbird/vapor require sendable HTML types
// also, currently there is no good way to conditionally apply Sendable conformance based on closure type
@usableFromInline
var contentBuilder: @Sendable (Data.Element) -> Content

/// Creates a new `ForEach` element with the given sequence and content builder closure.
Expand All @@ -26,7 +28,7 @@ public struct ForEach<Data, Content>: HTML
self.contentBuilder = contentBuilder
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.assertNoAttributes(self)

Expand All @@ -35,7 +37,7 @@ public struct ForEach<Data, Content>: HTML
}
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
context.assertNoAttributes(self)

Expand Down
7 changes: 4 additions & 3 deletions Sources/Elementary/Core/Html+Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ public struct _AttributedElement<Content: HTML>: HTML {
self.attributes = attributes
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.prependAttributes(html.attributes)
Content._render(html.content, into: &renderer, with: context)
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
context.prependAttributes(html.attributes)
try await Content._render(html.content, into: &renderer, with: context)
Expand Down Expand Up @@ -110,7 +110,8 @@ public extension HTML where Tag: HTMLTrait.Attributes.Global {
}
}

private extension _RenderingContext {
extension _RenderingContext {
@usableFromInline
mutating func prependAttributes(_ attributes: consuming _AttributeStorage) {
attributes.append(self.attributes)
self.attributes = attributes
Expand Down
20 changes: 11 additions & 9 deletions Sources/Elementary/Core/Html+Elements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag

/// Creates a new HTML element with the specified content.
/// - Parameter content: The content of the element.
@inlinable
public init(@HTMLBuilder content: () -> Content) {
_attributes = .init()
self.content = content()
Expand Down Expand Up @@ -44,7 +45,7 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
self.content = content()
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
html._attributes.append(context.attributes)

Expand All @@ -53,7 +54,7 @@ public struct HTMLElement<Tag: HTMLTagDefinition, Content: HTML>: HTML where Tag
renderer.appendToken(.endTag(Tag.name, type: Tag.renderingType))
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
html._attributes.append(context.attributes)

Expand Down Expand Up @@ -96,13 +97,13 @@ public struct HTMLVoidElement<Tag: HTMLTagDefinition>: HTML where Tag: HTMLTrait
_attributes = .init(attributes)
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
html._attributes.append(context.attributes)
renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
html._attributes.append(context.attributes)
try await renderer.appendToken(.startTag(Tag.name, attributes: html._attributes.flattened(), isUnpaired: true, type: Tag.renderingType))
Expand All @@ -121,13 +122,13 @@ public struct HTMLComment: HTML {
self.text = text
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.assertNoAttributes(self)
renderer.appendToken(.comment(html.text))
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
context.assertNoAttributes(self)
try await renderer.appendToken(.comment(html.text))
Expand All @@ -146,13 +147,13 @@ public struct HTMLRaw: HTML {
self.text = text
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _HTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) {
context.assertNoAttributes(self)
renderer.appendToken(.raw(html.text))
}

@_spi(Rendering)
@inlinable @inline(__always)
public static func _render<Renderer: _AsyncHTMLRendering>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws {
context.assertNoAttributes(self)
try await renderer.appendToken(.raw(html.text))
Expand All @@ -164,7 +165,8 @@ extension HTMLVoidElement: Sendable {}
extension HTMLComment: Sendable {}
extension HTMLRaw: Sendable {}

private extension HTMLTagDefinition {
extension HTMLTagDefinition {
@usableFromInline
static var renderingType: _HTMLRenderToken.RenderingType {
_rendersInline ? .inline : .block
}
Expand Down
47 changes: 0 additions & 47 deletions Sources/Elementary/Core/HtmlBuilder+Embedded.swift

This file was deleted.

Loading

0 comments on commit 4ab9270

Please sign in to comment.