Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4c8d36b
chore(conductor): Add new track 'Refactor the ECS to exclusively use …
ctreffs Feb 13, 2026
6291877
feat(phase): Complete API Design & Prototyping
ctreffs Feb 13, 2026
3d59f31
feat(phase): Complete Core Refactoring (Family & Nexus) including Cod…
ctreffs Feb 13, 2026
7836aec
feat(phase): Complete API Adoption & Cleanup
ctreffs Feb 13, 2026
7dd4317
chore(conductor): Mark track 'Refactor the ECS to exclusively use par…
ctreffs Feb 13, 2026
0bb44ab
feat(tests): Add comprehensive component tests (1-8) and fix encoding
ctreffs Feb 13, 2026
114f8ff
chore(conductor): Mark track 'Refactor the ECS to exclusively use par…
ctreffs Feb 13, 2026
923c85e
No conductor files
ctreffs Feb 13, 2026
c507b3d
Cleanups
ctreffs Feb 13, 2026
4783ea7
fix(review): Address PR feedback (restore Sequence, Coding, optimize …
ctreffs Feb 13, 2026
9fc888b
fix(docs): Update documentation and address remaining review comments
ctreffs Feb 13, 2026
76114b7
docs(conductor): Update plan with review tasks
ctreffs Feb 13, 2026
ce2b5d5
fix(coding): Restore Coding Feature Parity and Fix Tests
ctreffs Feb 13, 2026
b5126c6
docs(conductor): Update plan with coding parity task
ctreffs Feb 13, 2026
d4147c1
Restore family
ctreffs Feb 13, 2026
2b925a3
Restore Family+Coding [broken]
ctreffs Feb 13, 2026
6702b6a
Valid updates
ctreffs Feb 13, 2026
c863cd1
Coding
ctreffs Feb 13, 2026
b233795
Reset original benchmark code
ctreffs Feb 13, 2026
284e992
Remove conductor files
ctreffs Feb 13, 2026
689f29e
Pre-commit lint-fix
ctreffs Feb 13, 2026
7113bf9
Restore api.
ctreffs Feb 13, 2026
cde2c27
Fix FamilyMemberContainer
ctreffs Feb 13, 2026
2840d92
Finalize
ctreffs Feb 13, 2026
62a8046
Re-add require one component
ctreffs Feb 13, 2026
0876af9
Restore require API
ctreffs Feb 13, 2026
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
6 changes: 0 additions & 6 deletions .sourcery.yml

This file was deleted.

6 changes: 0 additions & 6 deletions .sourceryTests.yml

This file was deleted.

15 changes: 3 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ DOCS_VERSION_PATH ?= main
# The full base path for hosting
HOSTING_BASE_PATH ?= $(REPO_NAME)/$(DOCS_VERSION_PATH)

.PHONY: setup generate-code lint lint-fix test test-coverage testReadme build-debug build-release docs docs-preview docs-generate docs-coverage docs-check-coverage docs-check-links preview-analysis-docs generate-docs-githubpages pre-commit clean clean-sourcery
.PHONY: setup lint lint-fix test test-coverage testReadme build-debug build-release docs docs-preview docs-generate docs-coverage docs-check-coverage docs-check-links preview-analysis-docs generate-docs-githubpages pre-commit clean

# --- Setup ---

Expand All @@ -26,12 +26,6 @@ setup:
mint bootstrap
swift package resolve $(SWIFT_FLAGS)

# --- Codegen ---

generate-code:
mint run sourcery --quiet --config ./.sourcery.yml
mint run sourcery --quiet --config ./.sourceryTests.yml

# --- Quality Assurance ---

lint:
Expand Down Expand Up @@ -130,10 +124,7 @@ pre-commit: lint-fix test

# --- Cleanup ---

clean: clean-sourcery
clean:
swift package clean
rm -rdf .build
rm -rdf .swiftpm

clean-sourcery:
rm -rdf ${HOME}/Library/Caches/Sourcery
rm -rdf .swiftpm
1 change: 0 additions & 1 deletion Mintfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
realm/SwiftLint@0.63.2
nicklockwood/SwiftFormat@0.59.1
krzysztofzablocki/Sourcery@2.3.0
ldomaradzki/xcsift@v1.1.3
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "FirebladeECS",
platforms: [
Comment thread
ctreffs marked this conversation as resolved.
.macOS(.v14),
.iOS(.v17),
.tvOS(.v17),
.watchOS(.v10)
],
products: [
.library(name: "FirebladeECS",
targets: ["FirebladeECS"])
Expand Down
29 changes: 5 additions & 24 deletions Sources/FirebladeECS/Entity+Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,14 @@ extension Entity {
nexus.get(safe: identifier)
}

/// Retrieves two components of the specified types assigned to this entity.
/// Retrieves components of the specified types assigned to this entity.
/// - Parameters:
/// - _: The first component type.
/// - _: The second component type.
/// - Returns: A tuple containing the component instances (or `nil` if not found).
/// - _: The component types to retrieve.
/// - Returns: A tuple containing the optional component instances.
/// - Complexity: O(1)
@inlinable
public func get<A, B>(components _: A.Type, _: B.Type) -> (A?, B?) where A: Component, B: Component {
let compA: A? = get(component: A.self)
let compB: B? = get(component: B.self)
return (compA, compB)
}

// swiftlint:disable large_tuple
/// Retrieves three components of the specified types assigned to this entity.
/// - Parameters:
/// - _: The first component type.
/// - _: The second component type.
/// - _: The third component type.
/// - Returns: A tuple containing the component instances (or `nil` if not found).
/// - Complexity: O(1)
@inlinable
public func get<A, B, C>(components _: A.Type, _: B.Type, _: C.Type) -> (A?, B?, C?) where A: Component, B: Component, C: Component {
let compA: A? = get(component: A.self)
let compB: B? = get(component: B.self)
let compC: C? = get(component: C.self)
return (compA, compB, compC)
public func get<each C: Component>(components _: repeat (each C).Type) -> (repeat (each C)?) {
Comment thread
ctreffs marked this conversation as resolved.
Outdated
(repeat get(component: (each C).self))
}

/// Get or set component instance by type via subscript.
Expand Down
10 changes: 5 additions & 5 deletions Sources/FirebladeECS/Entity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public struct Entity {
/// - Returns: The created entity.
/// - Complexity: O(C + M) where C is the number of components and M is the number of families.
@discardableResult
public func createEntity(with components: Component...) -> Entity {
createEntity(with: components)
public func createEntity<each C: Component>(with components: repeat each C) -> Entity {
nexus.createEntity(with: repeat each components)
}

/// Creates a new entity with the provided components.
Expand Down Expand Up @@ -86,12 +86,12 @@ public struct Entity {
/// - Parameter components: one or more components.
/// - Complexity: O(M) where M is the number of families.
@discardableResult
public func assign(_ components: Component...) -> Entity {
assign(components)
public func assign<each C: Component>(_ components: repeat each C) -> Entity {
nexus.assign(components: repeat each components, to: self)
Comment thread
ctreffs marked this conversation as resolved.
return self
}

/// Add a component to this entity.
/// Add a single component to this entity.
/// - Parameter component: a component.
/// - Complexity: O(M) where M is the number of families.
@discardableResult
Expand Down
137 changes: 27 additions & 110 deletions Sources/FirebladeECS/Family+Coding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,124 +2,41 @@
// Family+Coding.swift
// FirebladeECS
//
// Created by Christian Treffs on 22.07.20.
// Created by Conductor on 2026-02-13.
//

#if canImport(Darwin) || swift(>=6.2)
public typealias UserInfoValue = any Sendable
#else
public typealias UserInfoValue = Any
#endif

/// A container for family members (components) used for encoding and decoding.
public struct FamilyMemberContainer<R: FamilyRequirementsManaging> {
/// The components of the family members.
public let components: [R.Components]

/// Creates a new family member container.
/// - Parameter components: The components to contain.
public init(components: [R.Components]) {
self.components = components
}
}

extension CodingUserInfoKey {
/// A user info key for accessing the nexus coding strategy during encoding and decoding.
///
/// This key is used to pass the `CodingStrategy` from the `Nexus` to the `FamilyMemberContainer`
/// so that it knows how to encode or decode component types.
static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy").unsafelyUnwrapped
}
#if canImport(Foundation)
import Foundation

// MARK: - encoding

extension FamilyMemberContainer: Encodable where R: FamilyEncoding {
/// Encodes the family members into the given encoder.
/// - Parameter encoder: The encoder to write data to.
/// - Throws: An error if encoding fails.
public func encode(to encoder: Encoder) throws {
let strategy = encoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy()
var familyContainer = encoder.unkeyedContainer()
try R.encode(componentsArray: components, into: &familyContainer, using: strategy)
}
}

/// A type that can encode values into a native format.
public protocol TopLevelEncoder {
/// The type this encoder produces.
associatedtype Output

/// Encodes an instance of the indicated type.
///
/// - Parameter value: The instance to encode.
/// - Returns: The encoded data.
extension Family where repeat each C: Encodable {
Comment thread
ctreffs marked this conversation as resolved.
/// Encodes the components of a single entity into a keyed container.
/// - Parameters:
/// - components: The components to encode.
/// - container: The encoding container to write to.
/// - strategy: The coding strategy to determine keys.
/// - Throws: An error if encoding fails.
func encode<T: Encodable>(_ value: T) throws -> Self.Output

/// Contextual user-provided information for use during decoding.
var userInfo: [CodingUserInfoKey: UserInfoValue] { get set }
}

extension Family where R: FamilyEncoding {
/// Encode family members (entities) to data using a given encoder.
///
/// The encoded members will *NOT* be removed from the nexus and will also stay present in this family.
/// - Parameter encoder: The data encoder. Data encoder respects the coding strategy set at `nexus.codingStrategy`.
/// - Returns: The encoded data.
/// - Complexity: O(N) where N is the number of family members.
public func encodeMembers<Encoder: TopLevelEncoder>(using encoder: inout Encoder) throws -> Encoder.Output {
encoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
let components = [R.Components](self)
let container = FamilyMemberContainer<R>(components: components)
return try encoder.encode(container)
}
}

// MARK: - decoding

extension FamilyMemberContainer: Decodable where R: FamilyDecoding {
/// Creates a new family member container by decoding from the given decoder.
/// - Parameter decoder: The decoder to read data from.
/// - Throws: An error if decoding fails.
/// - Complexity: O(N) where N is the number of components in the container.
public init(from decoder: Decoder) throws {
var familyContainer = try decoder.unkeyedContainer()
let strategy = decoder.userInfo[.nexusCodingStrategy] as? CodingStrategy ?? DefaultCodingStrategy()
components = try R.decode(componentsIn: &familyContainer, using: strategy)
public static func encode(
components: (repeat each C),
into container: inout KeyedEncodingContainer<DynamicCodingKey>,
using strategy: CodingStrategy
) throws {
// Encode each component using the strategy for keys
_ = (repeat try container.encode(each components, forKey: strategy.codingKey(for: (each C).self)))
}
}

/// A type that can decode values from a native format.
public protocol TopLevelDecoder {
/// The type this decoder accepts.
associatedtype Input

/// Decodes an instance of the indicated type.
extension Family where repeat each C: Decodable {
/// Decodes components for a family member from a keyed container.
/// - Parameters:
/// - type: The type of the value to decode.
/// - from: The data to decode from.
/// - Returns: The decoded value.
/// - container: The decoding container to read from.
/// - strategy: The coding strategy to determine keys.
/// - Returns: A tuple of decoded components.
/// - Throws: An error if decoding fails.
func decode<T: Decodable>(_ type: T.Type, from: Self.Input) throws -> T

/// Contextual user-provided information for use during decoding.
var userInfo: [CodingUserInfoKey: UserInfoValue] { get set }
}

extension Family where R: FamilyDecoding {
/// Decode family members (entities) from given data using a decoder.
///
/// The decoded members will be added to the nexus and will be present in this family.
/// - Parameters:
/// - data: The data decoded by decoder. An unkeyed container of family members (keyed component containers) is expected.
/// - decoder: The decoder to use for decoding family member data. Decoder respects the coding strategy set at `nexus.codingStrategy`.
/// - Returns: returns the newly added entities.
/// - Complexity: O(N) where N is the number of family members in the data.
@discardableResult
public func decodeMembers<Decoder: TopLevelDecoder>(from data: Decoder.Input, using decoder: inout Decoder) throws -> [Entity] {
decoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
let familyMembers = try decoder.decode(FamilyMemberContainer<R>.self, from: data)
return familyMembers.components
.map { createMember(with: $0) }
public static func decode(
from container: KeyedDecodingContainer<DynamicCodingKey>,
using strategy: CodingStrategy
) throws -> (repeat each C) {
return (repeat try container.decode((each C).self, forKey: strategy.codingKey(for: (each C).self)))
}
}
#endif
Loading
Loading