Skip to content

Commit df50d32

Browse files
authored
Parameter Packs for ECS (#108)
* chore(conductor): Add new track 'Refactor the ECS to exclusively use parameter packs' * feat(phase): Complete API Design & Prototyping * feat(phase): Complete Core Refactoring (Family & Nexus) including Codable * feat(phase): Complete API Adoption & Cleanup * chore(conductor): Mark track 'Refactor the ECS to exclusively use parameter packs for a unified and sleek API' as complete * feat(tests): Add comprehensive component tests (1-8) and fix encoding * chore(conductor): Mark track 'Refactor the ECS to exclusively use parameter packs for a unified and sleek API' as complete * No conductor files * Cleanups * fix(review): Address PR feedback (restore Sequence, Coding, optimize init) * fix(docs): Update documentation and address remaining review comments * docs(conductor): Update plan with review tasks * fix(coding): Restore Coding Feature Parity and Fix Tests * docs(conductor): Update plan with coding parity task * Restore family * Restore Family+Coding [broken] * Valid updates * Coding * Reset original benchmark code * Remove conductor files * Pre-commit lint-fix * Restore api. * Fix FamilyMemberContainer * Finalize * Re-add require one component * Restore require API
1 parent eafa019 commit df50d32

40 files changed

+744
-3696
lines changed

.sourcery.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

.sourceryTests.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

Benchmarks/Benchmarks/ECSBenchmark/Base.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Color: Component {
4646
}
4747

4848
class ExampleSystem {
49-
private let family: Family2<Position, Velocity>
49+
private let family: Family<Position, Velocity>
5050

5151
init(nexus: Nexus) {
5252
family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self)

Makefile

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ DOCS_VERSION_PATH ?= main
1414
# The full base path for hosting
1515
HOSTING_BASE_PATH ?= $(REPO_NAME)/$(DOCS_VERSION_PATH)
1616

17-
.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
17+
.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
1818

1919
# --- Setup ---
2020

@@ -26,12 +26,6 @@ setup:
2626
mint bootstrap
2727
swift package resolve $(SWIFT_FLAGS)
2828

29-
# --- Codegen ---
30-
31-
generate-code:
32-
mint run sourcery --quiet --config ./.sourcery.yml
33-
mint run sourcery --quiet --config ./.sourceryTests.yml
34-
3529
# --- Quality Assurance ---
3630

3731
lint:
@@ -130,10 +124,7 @@ pre-commit: lint-fix test
130124

131125
# --- Cleanup ---
132126

133-
clean: clean-sourcery
127+
clean:
134128
swift package clean
135129
rm -rdf .build
136-
rm -rdf .swiftpm
137-
138-
clean-sourcery:
139-
rm -rdf ${HOME}/Library/Caches/Sourcery
130+
rm -rdf .swiftpm

Mintfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
realm/SwiftLint@0.63.2
22
nicklockwood/SwiftFormat@0.59.1
3-
krzysztofzablocki/Sourcery@2.3.0
43
ldomaradzki/xcsift@v1.1.3

Package.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import PackageDescription
33

44
let package = Package(
55
name: "FirebladeECS",
6+
platforms: [
7+
.macOS(.v14),
8+
.iOS(.v17),
9+
.tvOS(.v17),
10+
.watchOS(.v10)
11+
],
612
products: [
713
.library(name: "FirebladeECS",
814
targets: ["FirebladeECS"])
@@ -12,11 +18,9 @@ let package = Package(
1218
],
1319
targets: [
1420
.target(name: "FirebladeECS",
15-
exclude: ["Stencils/Family.stencil"],
1621
swiftSettings: [.enableUpcomingFeature("StrictConcurrency")]),
1722
.testTarget(name: "FirebladeECSTests",
1823
dependencies: ["FirebladeECS"],
19-
exclude: ["Stencils/FamilyTests.stencil"],
2024
swiftSettings: [.enableUpcomingFeature("StrictConcurrency")]),
2125
.testTarget(name: "FirebladeECSPerformanceTests",
2226
dependencies: ["FirebladeECS"],

Sources/FirebladeECS/Documentation.docc/Documentation.md

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,45 +52,16 @@ For a more detailed example of FirebladeECS in action, see the [Fireblade ECS De
5252
- ``EntityComponentHash``
5353
- ``StateComponentMapping``
5454
- ``DynamicComponentProvider``
55-
- ``RequiringComponents1``
56-
- ``RequiringComponents2``
57-
- ``RequiringComponents3``
58-
- ``RequiringComponents4``
59-
- ``RequiringComponents5``
60-
- ``RequiringComponents6``
61-
- ``RequiringComponents7``
62-
- ``RequiringComponents8``
6355
- ``DefaultInitializable``
6456
- ``SingleComponent``
6557

6658
### Systems
6759

6860
- ``Family``
69-
- ``FamilyEncoding``
70-
- ``FamilyDecoding``
7161
- ``FamilyMemberAdded``
7262
- ``FamilyMemberRemoved``
73-
- ``FamilyMemberBuilder-3f2i6``
74-
- ``FamilyMemberBuilder``
7563
- ``FamilyTraitSet``
76-
- ``Requires1``
77-
- ``Requires2``
78-
- ``Requires3``
79-
- ``Requires4``
80-
- ``Requires5``
81-
- ``Requires6``
82-
- ``Requires7``
83-
- ``Requires8``
8464
- ``Single``
85-
- ``Family1``
86-
- ``Family2``
87-
- ``Family3``
88-
- ``Family4``
89-
- ``Family5``
90-
- ``Family6``
91-
- ``Family7``
92-
- ``Family8``
93-
- ``FamilyRequirementsManaging``
9465

9566
### Coding Strategies
9667

Sources/FirebladeECS/Entity+Component.swift

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,14 @@ extension Entity {
2323
nexus.get(safe: identifier)
2424
}
2525

26-
/// Retrieves two components of the specified types assigned to this entity.
26+
/// Retrieves components of the specified types assigned to this entity.
2727
/// - Parameters:
28-
/// - _: The first component type.
29-
/// - _: The second component type.
30-
/// - Returns: A tuple containing the component instances (or `nil` if not found).
28+
/// - components: The component types to retrieve.
29+
/// - Returns: A tuple containing the optional component instances.
3130
/// - Complexity: O(1)
3231
@inlinable
33-
public func get<A, B>(components _: A.Type, _: B.Type) -> (A?, B?) where A: Component, B: Component {
34-
let compA: A? = get(component: A.self)
35-
let compB: B? = get(component: B.self)
36-
return (compA, compB)
37-
}
38-
39-
// swiftlint:disable large_tuple
40-
/// Retrieves three components of the specified types assigned to this entity.
41-
/// - Parameters:
42-
/// - _: The first component type.
43-
/// - _: The second component type.
44-
/// - _: The third component type.
45-
/// - Returns: A tuple containing the component instances (or `nil` if not found).
46-
/// - Complexity: O(1)
47-
@inlinable
48-
public func get<A, B, C>(components _: A.Type, _: B.Type, _: C.Type) -> (A?, B?, C?) where A: Component, B: Component, C: Component {
49-
let compA: A? = get(component: A.self)
50-
let compB: B? = get(component: B.self)
51-
let compC: C? = get(component: C.self)
52-
return (compA, compB, compC)
32+
public func get<each C: Component>(components: repeat (each C).Type) -> (repeat (each C)?) {
33+
(repeat get(component: (each C).self))
5334
}
5435

5536
/// Get or set component instance by type via subscript.

Sources/FirebladeECS/Entity.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public struct Entity {
4949
/// - Returns: The created entity.
5050
/// - Complexity: O(C + M) where C is the number of components and M is the number of families.
5151
@discardableResult
52-
public func createEntity(with components: Component...) -> Entity {
53-
createEntity(with: components)
52+
public func createEntity<each C: Component>(with components: repeat each C) -> Entity {
53+
nexus.createEntity(with: repeat each components)
5454
}
5555

5656
/// Creates a new entity with the provided components.
@@ -86,12 +86,12 @@ public struct Entity {
8686
/// - Parameter components: one or more components.
8787
/// - Complexity: O(M) where M is the number of families.
8888
@discardableResult
89-
public func assign(_ components: Component...) -> Entity {
90-
assign(components)
89+
public func assign<each C: Component>(_ components: repeat each C) -> Entity {
90+
nexus.assign(components: repeat each components, to: self)
9191
return self
9292
}
9393

94-
/// Add a component to this entity.
94+
/// Add a single component to this entity.
9595
/// - Parameter component: a component.
9696
/// - Complexity: O(M) where M is the number of families.
9797
@discardableResult
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Family+Coding+Foundation.swift
3+
// FirebladeECS
4+
//
5+
// Created by Christian Treffs on 13.02.26.
6+
//
7+
8+
#if canImport(Foundation)
9+
import Foundation
10+
11+
extension Family where repeat each C: Encodable {
12+
/// Encode family members (entities) to data using a given encoder.
13+
///
14+
/// The encoded members will *NOT* be removed from the nexus and will also stay present in this family.
15+
/// - Parameter encoder: The data encoder. Data encoder respects the coding strategy set at `nexus.codingStrategy`.
16+
/// - Returns: The encoded data.
17+
/// - Complexity: O(N) where N is the number of family members.
18+
public func encodeMembers(using encoder: inout JSONEncoder) throws -> Data {
19+
encoder.userInfo[CodingUserInfoKey.nexusCodingStrategy] = nexus.codingStrategy
20+
let container = FamilyMemberContainer<repeat each C>(components: makeIterator())
21+
return try encoder.encode(container)
22+
}
23+
}
24+
25+
extension Family where repeat each C: Decodable {
26+
/// Decode family members (entities) from given data using a decoder.
27+
///
28+
/// The decoded members will be added to the nexus and will be present in this family.
29+
/// - Parameters:
30+
/// - data: The data decoded by decoder. An unkeyed container of family members (keyed component containers) is expected.
31+
/// - decoder: The decoder to use for decoding family member data. Decoder respects the coding strategy set at `nexus.codingStrategy`.
32+
/// - Returns: returns the newly added entities.
33+
/// - Complexity: O(N) where N is the number of family members in the data.
34+
@discardableResult
35+
public func decodeMembers(from data: Data, using decoder: inout JSONDecoder) throws -> [Entity] {
36+
decoder.userInfo[CodingUserInfoKey.nexusCodingStrategy] = nexus.codingStrategy
37+
let familyMembers = try decoder.decode(FamilyMemberContainer<repeat each C>.self, from: data)
38+
return familyMembers.components
39+
.map { (memberComponents: (repeat each C)) in
40+
createMember(with: repeat each memberComponents)
41+
}
42+
}
43+
}
44+
45+
#endif

0 commit comments

Comments
 (0)